Pythonで関連記事を抽出する
この前、RSSのテキストデータを解析して関連記事を抽出するというのをやりました。そのことについての自分なりのまとめです。
自然言語処理とは?
自然言語処理(しぜんげんごしょり、英語: natural language processing、略称:NLP)は、人間が日常的に使っている自然言語をコンピュータに処理させる一連の技術であり、人工知能と言語学の一分野である。「計算言語学」(computational linguistics)との類似もあるが、自然言語処理は工学的な視点からの言語処理をさすのに対して、計算言語学は言語学的視点を重視する手法をさす事が多い[1]。データベース内の情報を自然言語に変換したり、自然言語の文章をより形式的な(コンピュータが理解しやすい)表現に変換するといった処理が含まれる。応用例としては予測変換、IMEなどの文字変換が挙げられる。(wikipedia)
fmfm
類似文書の探し方
詳しくはこちらkesin.hatenablog.com
かなりわかりやすかったです。いろいろぐぐりながらやったのですが、ダントツで分かりやすかったです。すごい。
こちらで紹介されていたのはベクトルを使いどれだけ文章が似ているのかを数値として計算するほうほうです。
例
AppleはMacというOSを作った
MicrosoftはWindowsというOSを作った
Googleは優秀な検索エンジンを開発
この3つの文書から意味のある言葉を抜き出すと、(今回は助詞、接尾、BOS、EOS、記号、名詞,数、助動詞、を除く。)
[Apple, Mac, OS, 作った]
[Microsoft, Windows, OS, 作った]
[Google, 優秀, 検索エンジン, 開発]
というように文書を単語で表すことができました。
同じ単語が何回も出ていますね?これを利用する。
ではこの3つの文書をベクトル化。まずは登場した全ての単語の集合を作ります。
[Apple, Mac, OS, 作った, Microsoft, Windows, Google, 優秀, 検索エンジン, 開発]
そしてこの集合の左から、それぞれの文書の中にこの単語が登場したら1, 登場しなければ0と置いていくとベクトルを作ることができます。
{1, 1, 1, 1, 0, 0, 0, 0, 0, 0}
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0}
{0, 0, 0, 0, 0, 0, 1, 1, 1, 1}
既にどの文書が似ているのか一目瞭然ですが、実際にどれだけ似ているかを計算します。ここではとても単純に2つのベクトルがなす角度を比べることにします。つまり、2つのベクトルの余弦を計算します。計算式はcos=ベクトルA,Bの内積/(ベクトルAのノルム*ベクトルBのノルム) です。これをcos類似度と呼びます。
cos類似について
コサイン類似度
ここでは10次元という大きな次元でベクトル計算する。
文書1と文書2の類似度: 0.5
文書1と文書3の類似度: 0
というような結果になりました。単語集合やベクトルのときから何となく分かっていた結果ですが、このように計算で数値的に文書がどれだけ似ているかを求めることができます。
次はどのように文書を品詞ごとに区別するのかをみます。
Pythonでは普通にMeCabが使えるのでこちらを使用します。
こんな感じで、品詞分解してくれます。すごい。。
という単語集合を得ることができます。単語さえ抜き出すことができれば後は簡単です。全ての文書の単語を抜き出してベクトルを作り、cos類似度を計算するだけです。
実際のコードがこちら
#coding: utf-8 import feedparser import MeCab import re import math def analysis_entrie(entrie_list, allnoun_set): #エントリーの単語を解析 t = MeCab.Tagger("") re_word = re.compile("助詞|接尾|BOS|EOS|記号|名詞,数|助動詞| ") #除外する品詞 feed=[] for entrie in entrie_list['entries']: if not "PR:" in entrie.title and not "AD:" in entrie.title: #広告除外 noun_set = set([]) dict = {} m = t.parseToNode(entrie.title.encode('utf-8')) #MeCabはUnicodeに対応していないのでutf-8に変換 while m: if not re_word.findall(m.feature) and not m.surface.isspace(): #品詞チェック noun_set.add(m.surface.decode('utf-8')) #単語集合に追加 allnoun_set.add(m.surface.decode('utf-8')) #全体単語集合に追加 m = m.next dict['title'] = entrie.title dict['noun'] = noun_set dict['date'] = entrie.updated dict['url'] = entrie.link feed.append(dict) print entrie.title print "["+",".join(noun_set)+"]" return feed def get_vector(allnoun_set, noun_set): #記事ごとの単語と全単語からベクトル生成 vector = [] for noun in allnoun_set: if noun in noun_set: vector.append(1) else: vector.append(0) return vector def cos_similar(vector_A, vector_B): #cos類似度計算 inner=0.0 for (a, b) in zip(vector_A, vector_B): inner += a*b #内積 for i in range(len(vector_A)): vector_A[i] = vector_A[i]**2 for i in range(len(vector_B)): vector_B[i] = vector_B[i]**2 norm_A = math.sqrt(sum(vector_A)) #ノルムA norm_B = math.sqrt(sum(vector_B)) #ノルムB return inner / (norm_A * norm_B) if __name__ == '__main__': hatena = feedparser.parse("http://b.hatena.ne.jp/hotentry/it.rss") mycom_apple = feedparser.parse("http://feeds.journal.mycom.co.jp/rss/mycom/pc/apple") ascii_apple = feedparser.parse("http://rss.rssad.jp/rss/ascii/mac/rss.xml") itmedia_iphone = feedparser.parse("http://rss.rssad.jp/rss/itm/1.0/kw_iphone.xml") site_list= [] site_list.append(hatena) site_list.append(mycom_apple) site_list.append(ascii_apple) site_list.append(itmedia_iphone) allnoun_set = set([]) feed_list = [] for site in site_list: feed_list += analysis_entrie(site, allnoun_set) for entrie in feed_list: entrie['vector'] = get_vector(allnoun_set, entrie['noun']) while True: #入力した記事番号と類似する記事を表示 print "記事番号を入力:" num = input() find_entrie = feed_list[num] for entrie in feed_list: #選択した記事とのcos類似度を計算 entrie['score'] = cos_similar(find_entrie['vector'], entrie['vector']) feed_list.sort(key=lambda x:x['score'], reverse=True) print '\n"'+find_entrie['title'] +"\t"+"["+",".join(find_entrie["noun"])+"]" print "に似た上位5記事は以下のものです:" for entrie in feed_list[:6]: if (entrie['score'] < 0.99 and entrie['score'] > 0): #score=1は元記事、=0は全く関係ないので除外 print entrie['title'], "\t"+"score:"+str(entrie['score'])+" ["+",".join(entrie["noun"])+"]"
実行結果
記事を指定すると、その記事を形態素解析して不要な品詞を除き、関連記事をスコアの高い順に出してくれます。
これをFlaskとかでjinja表示するとすぐにアプリケーションとかで使えそう。
あと、三回に一回ぐらい、コードは全く同じなのにUnicodeErrorがでます。かなり調べたのですが解決できずじまいです。。。。どなたか教えていただきたいです。
時間でrssのテキストは当然かわるので、そこが原因かとおもうのですが。。。
画像処理 with Visual Studio(C#)
Mac派なのですが、今回はC#の画像処理のまとめを書きたいので、Visual Studioを使ってWindowsから更新したいと思います。
フォントの違和感がすごい。。。。
画像処理の原理
画像処理は基本的には、ピクセル(コンピュータで画像処理を扱うときの色情報をもつ最小単位、最小要素)を数値としてとらえ、それを行列的に処理することで不要な要素を削除したり、重要な要素を抽出したりします。
今回は基本となる二値化と画像の合成ができるアプリケーションを作成したいと思います。
まずはBitmapで画像を読み込み、画像のデータを取得する。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Drawing.Imaging; class Program { static void Main(string[] args) { Bitmap src = new Bitmap("microsoft.jpg");//ファイル読み込み int width = src.Width; //ピクセル数 int height = src.Height; //ピクセル数 Bitmap dest = new Bitmap(src, width, height);//画像の作成 dest.Save("img02.jpg");//ファイルの書き出し } }
次に、取得したピクセル値等を使用してがぞうを加工してみる。
class Program { static void Main(string[] args) { Bitmap src = new Bitmap("microsoft.jpg"); Bitmap image = new Bitmap(src); int width = src.Width; int height = src.Height; Color col; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { col = src.GetPixel(i, j); int gray = col.R; image.SetPixel(i, j, Color.FromArgb(gray, gray, gray)); } } image.Save("img02.jpg"); } }
上のメソッドについて、GetPixelメソッドでピクセル値を獲得し、SetPixelメソッドでピクセル値を設定している。FromArgsメソッドはRGB成分からColor構造体を作成する。うえの例では白黒にしている。
処理後画像
処理前画像
次は二値化。ピクセル値0-255のあたいを150以上なら255、150以下なら0にし、画像として出力する。
class Program { static void Main(string[] args) { Bitmap src = new Bitmap("microsoft.jpg"); Bitmap image = new Bitmap(src); int width = src.Width; int height = src.Height; Color col; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { col = src.GetPixel(i, j); int gray = col.R; if (gray > 150) { gray = 255; } else { gray = 0; } image.SetPixel(i, j, Color.FromArgb(gray, gray, gray)); } } image.Save("img02.jpg"); } }
処理後画像
とりあえずこの処理をスクロールバーを用いて処理できるようなアプリにします。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace No1114 { public partial class Form1 : Form { OpenFileDialog openFileDialog1 = new OpenFileDialog(); Bitmap bitmap1; public Form1() { InitializeComponent(); } private void openToolStripMenuItem_Click(object sender, EventArgs e) { // ダイアログのタイトルを設定する openFileDialog1.Title = "画像選択"; // 初期表示するディレクトリを設定する openFileDialog1.InitialDirectory = @"C:¥"; // 初期表示するファイル名を設定する openFileDialog1.FileName = "img.jpg"; // ファイルのフィルタを設定する openFileDialog1.Filter = "テキスト ファイル|*.txt;*.log|すべてのファイル|*.*"; // ファイルの種類 の初期設定を 2 番目に設定する (初期値 1) openFileDialog1.FilterIndex = 2; // ダイアログボックスを閉じる前に現在のディレクトリを復元する(初期値 false) openFileDialog1.RestoreDirectory = true; // 複数のファイルを選択可能にする (初期値 false) openFileDialog1.Multiselect = true; // [ヘルプ] ボタンを表示する (初期値 false) openFileDialog1.ShowHelp = true; // [読み取り専用] チェックボックスを表示する (初期値 false) openFileDialog1.ShowReadOnly = true; // [読み取り専用] チェックボックスをオンにする (初期値false) openFileDialog1.ReadOnlyChecked = true; // 存在しないファイルを指定した場合は警告を表示する (初期値 true) openFileDialog1.CheckFileExists = true; // 存在しないパスを指定した場合は警告を表示する (初期値true) openFileDialog1.CheckPathExists = true; // 拡張子を指定しない場合は自動的に拡張子を付加する (初期値 true) openFileDialog1.AddExtension = true; // ダイアログを表示し、戻り値が [OK] の場合は、選択したファイルを表示する if (openFileDialog1.ShowDialog() == DialogResult.OK) { MessageBox.Show(openFileDialog1.FileName); // Multiselect が true の場合はこのように列挙する //foreach (string nFileName in openFileDialog1.FileNames) { // MessageBox.Show(nFileName); //}} // 不要になった時点で破棄する (正しくは オブジェクトの破棄を保証する を参照) openFileDialog1.Dispose(); } openFileDialog1.RestoreDirectory = true; if (openFileDialog1.ShowDialog() == DialogResult.OK) { bitmap1 = new Bitmap(openFileDialog1.FileName); pictureBox1.Image = bitmap1; } } private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) { hScrollBar1.Minimum = 0; hScrollBar1.Maximum = 254 + hScrollBar1.LargeChange; label1.Text = Convert.ToString(hScrollBar1.Value); Bitmap outBit = new Bitmap(bitmap1); int width = bitmap1.Width; int height = bitmap1.Height; Color col; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { col = bitmap1.GetPixel(i, j); int co = col.R; if (co < hScrollBar1.Value) { co = 0; } else { co = 255; } outBit.SetPixel(i, j, Color.FromArgb(co, co, co)); } } pictureBox2.Image = outBit; } } }
スクショ
バイオインフォマティクスぶって、実験で使用したMRI画像にしてみました。
スクロール値で二値化する閾値を決定し、それに伴い画像が変化します。
続きは次回!!
Swift入門
いろいろありまして、先月からiOS開発をやらしていただくことになりました!
今までモバイルはAndroidを基本的にやらしていただいておりましたので、わからないことも多いですが、頑張っていこうと思います!
Swiftについてザッとまとめたことを書きたいと思います。
Swift の特徴
SwiftはPythonやRubyのようなインタプリタ系の言語に近い文法で記述できるので、CやC++ライクなObjective-C に比べると簡単に記述できる。また、Appleの発表によるとPythonやObjective-Cに比べてSwiftはかなり早い。
Swiftには、複数の戻り値、クロージャ、ジェネリクス、タイプインターフェース、名前空間といったイマドキな機能がある。
Swiftは安全でないコードが使用されないような設計になってる。変数は使用前に必ず初期化され、配列や整数はオーバーフローがチェックされ、メモリは自動的に管理されるらしい。
現在のiOSアプリ開発言語の主流である「Objective-C」と共存できる。そのため、開発者は適切だと感じる場合にSwiftを使って、複数の言語で書かれたアプリを作成することができる。
Swiftの構文
コメント、定数、変数、型について
// 一行コメント /* 複数行コメント */ var myVar = 42 myVar = 50 let myConst = 42 myConst = 1 // => error: cannot assign to 'let' value 'myConst' let implicitDouble = 70.0 let explicitDouble: Double = 70 let errorCode = 94 "error code: " + errorCode // => error: could not find an overload for '+' that accepts the supplied arguments println("error code: " + String(errorCode))
コメントはJavaとおなじ。
変数はvar、再代入不可な定数はletを使用。
Swiftはその他おおくの言語と同様に静的型付け言語。プログラム実行前に変数定義が必要。変数定義では代入する値の型から型が推論される。よってもちろん、推論する値がなえればエラーになる。
var message //型を推論できないのでエラーになる。 var message:String //明示的に型をつける
また、キャストについて値が暗黙に他の型に変換されることはない。他の型に変換する必要がある場合、明示的に所望の型のインスタンスを作る。
文字列処理
var name = "Swift" + " " + "Programming" name += " Language" countElements(name) let boyCount = 3 let girlCount = 5 println("There are \(boyCount + girlCount) children.")
文字列オブジェクトに文字列を追加するには+=演算子を使う。文字列の長さはグローバル関数countElementsで分かる。
文字列の空判定にはisEmptyなどがあり、文字列の比較には==や!=を使用するのでかなり直感的に書くことが出来る。
Stringのおもなプロパティは以下のとおり。
- isEmpty (空判定)
- hasPrefix (前方一致)
- hasSuffix (後方一致)
- lowercaseString (小文字変換)
- uppercaseString (大文字変換)
配列
var colours = ["red", "black", "white", "blue"] let names = String[]() // 空配列 colours[0] = "pink" colours += "grey" colours.count for colour in colours { println(colour) }
配列はブラケットを使って定義する。空の配列は、初期化子構文で定義する。
基本的に配列は多数の型は宣言できない。letを使うと定数の再代入不可の配列ができる。
配列の要素にアクセスするには、インデックスをブラケット中に書く。
配列の要素数はcountプロパティー。要素追加は+=演算子で、
要素をイテレートするにはfor-inを使う。直感的。
また、型定義と配列定義を同時にする場合は以下のようになる。
var array[Int] = [1,2,3,4] array[0] //0の範囲にアクセス array[0...1] // 範囲を指定してアクセス
要素操作のためのプロパティ、メッソドは以下のとおり。
- appendメッソド (末尾に要素追加)
- countプロパティ (要素数)
- isEmpty プロパティ (空判定)
- insertメッソド (指定インデックスに要素を挿入)
- removeAtIndexメソッド (指定インデックスの要素を削除)
- removeLastメッソド (末尾の要素を削除)
辞書
var petCounts = ["dog": 2, "cat": 3] var wordCounts = Dictionary<String, Int>() // 空ハッシュ wordCounts["dog"] = petCounts["dog"] wordCounts.count wordCounts.keys wordCounts.values for (pet, count) in petCounts { println(pet + ":" + String(count)) }
ハッシュはブラケットを使って、キーとバリューをコロン区切りにして定義する。
空ハッシュは、初期化子構文で定義する。
ハッシュの要素にアクセスするには、キーをブラケット中に書く。
ハッシュの要素をイテレートするにはfor-inを使う。
配列や変数同様、letで定義すると再代入不可、定義したときに型宣言せず要素も無ければエラーがでる。
よく使うプロパティ、メッソドは以下のとおり。
- indexForKeyメッソド (末尾に要素追加)
- countプロパティ (要素数)
- isEmptyプロパティ (空判定)
- valuesプロパティ (値を取得)
- removeAtIndexメッソド (指定インデックスの要素を削除)
- keysプロパティ (keyを取得)
タプル
複数の要素を組みとして持てる機能。異なる型でも可能。複数のことなるオブジェクトを関連ずけて一組として扱うためのデータ構造。
var fruit = {"apple", 100} fruit.0 //apple fruit.1 //100
タプルのプロパティ名を指定する場合は
var fruit = (name:"apple",price:100) fruit.name //apple fruit.price //100
また一部の要素のみをプロパティ名をつけて残りを省略することも可能。必要な要素のみを取り出す場合は、
var (_, price) = fruit price //100
この例ではpriceに100を代入している。このとき不要な要素はアンダースコアで代入を省略できる。
Optional Value 型
Swiftにはnil値を変数の中に許容できる型としてOptional Value型というものがある。Swiftでは基本的に変数にnil値は代入することができない。
また、??演算子は変数値がnilかどうかを判定して代入を実行することができる。変数がnilの場合は右辺を、変数がnilではない場合は左辺を代入する。
var str = "Hello, " var messsage = "Tanaka Junya" var message:String? = "Hello" message = nil var message2:String = message ?? "Hello" //"Helloが代入される"
また、アンラップという機能があり、nilチェックがアンラップ行こう必要なくなる。
構造体
構造体の初期値は()でかこって宣言し、それぞれのプロパティにたいして:で値をいれる。アクセスするには.を使用。
関数ももてる。
struct Programming { var name:String var number:Int } var programming:Programming = Programming(name:Python, number:1) println(programming.name)
列挙型
enumを使用して列挙型をつくることができる。
rawValueでプロパティにアクセスできる。構造体同様、関数ももてる。
enum Company:String{ case one = "Google" case two = "Microsoft" case three = "Apple" } println(Company.one.rawValue)
制御文
//'for' and 'if' for i in 0...10{ for j in 0...10{ if (i+j)%3==0{ print("⬜︎ ") } else if (i+j)%3==1{ print(" ") } else{ print("⬛︎ ") } } println() } //while var i:Int=0 while i<10{ var j:Int=0 while j<10{ print(j) j++ } println() i++ } //swich var service:Int = 0 switch service{ case 0: println("Google") case 1: println("Microsoft") case 2: println("Apple") default: println("CookBiz") }
if文の分岐条件部分は()の省略ができる。
また、定型文のような形でifでnilをチェックするやる方がある。
var optionalValue:String? = "Tokyo" if let local = optionalValue { println(local) }
この変数optionalValueは、String?で定義しているため、nilが入る可能性がある。この時、if文の条件式で定数localを宣言することによってnilをチェックしている。もし変数optionalValueがnilの場合はlocalにnilが代入されるためprint文がスキップされる。
またSwift1.2からは1つのif letで複数の変数のnilチェックが可能になった。
関数
func out(text:String) -> Int { println(text) var length:Int=countElements(text) return length } var len =out("Hello") println(len)
また、Swiftも引数にデフォルト値を代入することができ、戻り値にタプルを使用することで、複数の戻り値をつけることができる。型もそれぞれ違うものを指定可能。
クロージャは変数や定数に代入することができる関数のオブジェクトである。
var add = {(a:Int, b:Int) -> Int in return a + b } println(add(1,3))
クロージャはオブジェクトとして振舞うため、変数に代入することができる。実行時は変数に引数をわたす。
クロージャは関数の引数にも指定することができ、関数の引数として渡す利点は、開発者が処理を自由に変更できる点です。関数内部のロジックの変更や終了時のコールバックなど様々な利用方法がある。Javaでは、無名クラスを使い似た挙動を表現していた。
let names = ["Chris", "Alex","Ewa","Barry","Daniella"] var backwards = {(s1 : String, s2:String) -> Bool in return s1 -> s2 } sorted(names) sorted(names, backwards)
こちらの例はクロージャを使って文字列をソートしている例。
sorted関数にnamesをわたしてソートしている。sorted関数は引数が1つの場合は昇順でソートし、2つ目の引数を渡すとこれをソートの評価方法とする仕様。
この例では、backwards(降順)というクロージャをつくって引数としてわたし、ソートの挙動を変更している。
クラス
プロパティとメソッドを定義できる。
class Book{ var title:String var price:Int init(title: String, price:Int){ self.title = title self.price = price } func print(){ printIn("書籍情報: \(title) 価格 : \(price)園") } } var book = Book(title:"ハリーポッター", price:3200) book.print()
クラスの継承を行う場合、クラスの後にクラス名を指定し、: につづいて元となる親クラスを指定する。
class Ebook: Book{ var fomat:String init(title: String, price:Int, format:String){ self.format = format super.init(title: title, price: price) } override func print(){ println("書籍情報:\(title) 価格:\(price)円 配布形態:\(format)") } }
Bookクラスを継承したEbookクラス。formatプロパティが追加されていて、メソッドの内容も上書きされている。
上書きするメッソドにはoverrideをつけて宣言する。Javaでいう@Override。
Any型とAnyObject型
型を特定せずに操作できる仕組み。Any型は値、参照、関数など、すべてを表せる。
var any:[Any] = ["text", 123, 4.0]
値を取り出す時は、Any型では取り出せないのでisやasを使う。
AnyObject型はクラスのインスタンスを表す。AnyObject型はAny型にくらべて、範囲が限定される。
Cocoa-APIを使うときはよく登場し、APIを呼び出したときAnyObject型の配列をうけとることがある。(objective-cでは明示的に型付けされた配列を持っていないため)
型の検証と変換
型変換にはasが、型の検証のためにはisがある。
var any:[Any] = ["text",123] for object in any{ if object is String{ println("String") } }
as演算子、as?演算子、as!演算子はダウンキャスト(型変換)に使用。
class Lang{} class Japanese:Lang{} var lang:Lang = Japanese() var jp:Japanese = lang as! Japanese;
Lang型からダウンキャストしてJapanese型に変換している。
必ず成功するときにはas!を使う。
as?を使うと、型変換できない時にnilをかえし、実行時えらーを防ぐ。
asによるキャストは保証された変換の場合にのみしようできる。整数型を浮動小数型に変換する場合。
var value = 1 as Float class Lang{} class Japanese:Lang{} var jp:Japanese = Japanese() var lang:Lang = jp as Lang
アップキャスト時は明治的にasを使用しなくても良い。
値型と参照型
値型は変数への代入の際に、データを一意にコピーして保持する。Swiftでは文字列、配列、辞書、構造体、列挙型、タプルが該当する。
struct SItem{ var name:String var price:Int } var a = SItem( name:"Apple", price:100) var b = a.price println("\(a.price), \(b.price)")
ここでは、SItem構造体aを用意してbに代入しています。代入のあとa.priceを操作してもb.priceには影響しない。出力した結果は"50, 100"となりa,bがそれぞれ独立している。
参照型は、変数への代入の際にデータをコピーして保持できない。どこにデータがあるか、という参照のみを保存し、データを共有している。参照型には、クラスのインスタンスが該当する。
class CItem{ var name:String = "Apple" var price:Int } var x = CItem() var y = x x.price = 50 println("\(x.price), \(y.price)")
この例ではCItemクラスのインスタンスxを生成して、yに代入している。そのあとx.priceを100から50にへんこうしている。出力結果をみると、x.priceだけでなく、y.priceも50になっている。
データを共有したい、先の処理でも変更可能な状態を保持したい場合は参照型を使う。マルチスレッド環境のように複数のスレッドから操作するプログラムの場合は、意図しないタイミングで操作してデータが壊れないように値型を使う。
鬼重要。
プロトコルは構造体とクラスで利用でき、名前の決まったメソッドやプロパティごとに関する約束をプロトコルとよぶ。
class ViewController: UIViewController, UIWebViewDelegate{ }
この例ではViewControllerクラスをつくっている。親クラスにUIViewControllerをもち、UIWebViewDelegateプロトコルに適合させている。開発者が独自のプロトコルを宣言することもできる。
protocol MyProtocol{ func sayHello() -> String }
この例では、MyProtocolというプロトコルを宣言しており、sayHelloの実装を要求していることがわかる。
プロトコルはJavaのインターフェイスににているが、メソッドの実装のみを要求できるjavaと違いプロパティの宣言を実装クラスに要求することができる。
protocol MyProtocol{ var mustBeSettable: Int{ get set} var doesNotNeedToBeSettable: Int{get} } class MyClass: MyProtocol2 { var mustBeSettable:Int init(){ mustBeSettable = 0 } var doesNotNeedToBeSettable : Int{ return mustBeSettable + 100 } }
mustBeSettableプロパティの宣言が要求されているため、MyClassでもプロパティを用意して、initメッソドをで初期化。
プロパティの他に、構造体もプロトコルへの適合を宣言するなど、プロトコルは多くの機能を提供している。
くわしくはこちらdeveloper.apple.com
iOSがんばります!
ところでApple Musicめっちゃいいとおもいます!
つかれた
機械学習によるタイタニック号の生存者予測 with Python(Part 2)
つづきです。
階級別年齢テーブルの作成
元のコードは可読性が悪いのでやや修正する。
median_ages = np.zeros((2,3)) # 2行3列のマトリックスを作成 print median_ages import itertools for gi, pi in itertools.product(range(0, 2), range(3)): # productで2x3の直積を計算 gender, pclass = gi, pi + 1 gender_mask = (df["Gender"] == gender) pclass_mask = (df["Pclass"] == pclass) mask = gender_mask & pclass_mask median_ages[gi, pi] = df[mask]["Age"].dropna().median() print median_ages
実行結果
[[ 0. 0. 0.]
[ 0. 0. 0.]]
[[ 35. 28. 21.5]
[ 40. 30. 25. ]]
Ageの欠損値を直接書き換える事も可能であるが慎重に処理するために元データは保持し、AgeFillという新しい列を作成
df["AgeFill"] = df["Age"] df.head()
実行結果
df[ df["Age"].isnull() ][ ["Gender", "Pclass", "Age", "AgeFill"] ].head(10)
実行結果
# 欠損値であるかを0/1で示すカラムも作っておく df["AgeIsNull"] = pd.isnull(df.Age).astype(int) df.describe()
実行結果
SibSp(兄弟と夫/妻の数)とParch(親と子供の数)を足してFamilySizeカラムを作成する。
階級が高くて若い方が生存しやすいためAgeFillとPclassを掛けあわせたAge*Classカラムを作る。
df["FamilySize"] = df["SibSp"] + df["Parch"] df["Age*Class"] = df["AgeFill"] * df["Pclass"] df.describe()
最後の準備
pandasのDataFrameはそのままではscikit-learnに渡せない。
数値データではないカラムへの対処、数値データのnumpy.ndarrayへの変換が必要
# 各カラムの型を見る
df.dtypes
実行結果
# 型名がobjectである項目のみをフィルタ # objectは文字列であることが分かる df.dtypes[df.dtypes.map(lambda x: x == "object")] # 不必要なカラムを捨てる df2 = df.drop(["Name", "Sex", "Ticket", "Cabin", "Embarked"], axis=1) # Ageもコピーしたカラムがあるので必要ない df2 = df2.drop(["Age"], axis=1) # dropnaでNaNを含むレコードを削除できる # しかしいずれかの項目一つがNaNである場合でも # そのレコードの他のカラムの値が全て捨てられることに注意 df3 = df2.dropna() df3.dtypes
実行結果
# ここまででクリーンできっちりしたデータを準備することが出来た # 最後にvaluesメソッドを使うことでnumpy.ndarrayに変換可能 train_data = df3.values
scikit-learnに食わせるデータが完成!
scikit-learnを用いたRandomForestによる生存予測
scikit-learnとは、pythonにおける機械学習ライブラリ。
回帰、識別、クラスタリング、次元削減などにおける諸々のアルゴリズムが実装されている。
RandomForestとは、決定木を用いたアンサンブル学習の手法である。識別や回帰に使える。
ここでは生存したか否かの識別に用いる。
学習させてみる
sklearn.ensembleモジュールのRandomForestClassifierを使う。
train_dataのdtypesを見て何を学習データとして何を正解ラベルデータとするかを見る。
1列目はIDなので要らない
2列目が生存ラベルなのでこれを正解ラベルとする
3列目以降を学習データにする
評価データを準備し、学習データを作成するのと同じ要領で作成する。
scikit-learnのモデル学習、予測のAPIはさまざまなアルゴリズムで共通
モデルを作成したいアルゴリズムクラスのコンストラクタを読んで作成
モデルインスタンスのfitメソッドで学習
学習データのshapeは常に(nsample, nfeature)の形をとる。
モデルインスタンスのpredictメソッドで予測する。
xs = train_data[:, 2:] # 3列目以降 y = train_data[:, 1] # 正解ラベル print xs.shape print y.shape
実行結果
(891, 9)
(891,)
from sklearn.ensemble import RandomForestClassifier # 決定木の数を100に設定してモデルインスタンスを作成 forest = RandomForestClassifier(n_estimators = 100) # 学習 forest = forest.fit(xs, y) # テストデータの読み込みおよび加工 # 学習データと同じ方法でテストデータを処理する必要有 def preprocessing_read(file_path): # csvファイルの読み込み df = pd.read_csv(file_path, header=0) # 0行目がヘッダー(defaultの動作、指定しない場合はNoneを指定) df["Gender"] = df["Sex"].map( {'female': 0, "male": 1} ).astype(int) # 辞書を渡すだけで変換してくれる df["AgeFill"] = df["Age"] # 階級、性別ごとに中央値テーブルを使って欠損値を置換 for gi, pi in itertools.product(range(2), range(3)): # productで2x3の直積を計算 gender = gi pclass = pi + 1 gender_mask = (df["Gender"] == gender) pclass_mask = (df["Pclass"] == pclass) age_mask = df["Age"].isnull() mask = gender_mask & pclass_mask & age_mask df.ix[mask, "AgeFill"] = median_ages[gi,pi] # 欠損値であるかを0/1で示すカラムも作っておく df["AgeIsNull"] = pd.isnull(df.Age).astype(int) df["FamilySize"] = df["SibSp"] + df["Parch"] df["Age*Class"] = df["AgeFill"] * df["Pclass"] df = df.drop(["Age", "Name", "Sex", "Ticket", "Cabin", "Embarked"], axis=1) df = df.dropna() return df test_df = preprocessing_read("./data/test.csv") test_df.dtypes
実行結果
PassengerId int64
Pclass int64
SibSp int64
Parch int64
Fare float64
Gender int64
AgeFill float64
AgeIsNull int64
FamilySize int64
Age*Class float64
dtype: object
# 予測 test_data = test_df.values xs_test = test_data[:, 1:] output = forest.predict(xs_test) print len(test_data[:,0]), len(output) print zip(test_data[:,0].astype(int), output.astype(int))
実行結果
# ファイル書き込み with open("tutorial/random_forest_model.csv", "wb") as f: writer = csv.writer(f) writer.writerow(["PassengerId", "Survived"]) for pid, survived in zip(test_data[:,0].astype(int), output.astype(int)): writer.writerow([pid, survived])
これでsubmitできると見せかけて、実はPassengerId 1044のテストデータに対する予測が欠けているためにこれでは通らない。
dropna()でレコードごと落ちたため?
Fareは階級と発着場から決まると仮定して調べる
PassengerId 1044の階級は3、発着場はS
mask = (df.Pclass == 3) & (df.Embarked == "S") df.ix[mask, ["Pclass", "Embarked", "Fare"]] df.ix[mask, "Fare"].describe()
実行結果
def preprocessing_read2(file_path): df = pd.read_csv(file_path, header=0) df["Gender"] = df["Sex"].map( {'female': 0, "male": 1} ).astype(int) df["AgeFill"] = df["Age"] for gi, pi in itertools.product(range(2), range(3)): gender = gi pclass = pi + 1 gender_mask = (df["Gender"] == gender) pclass_mask = (df["Pclass"] == pclass) age_mask = df["Age"].isnull() mask = gender_mask & pclass_mask & age_mask df.ix[mask, "AgeFill"] = median_ages[gi,pi] df["AgeIsNull"] = pd.isnull(df.Age).astype(int) df["FamilySize"] = df["SibSp"] + df["Parch"] df["Age*Class"] = df["AgeFill"] * df["Pclass"] df = df.drop(["Age", "Name", "Sex", "Ticket", "Cabin", "Embarked"], axis=1) # Fareが欠損している場合の処理 df.ix[df["Fare"].isnull(), "Fare"] = 8.05 df = df.dropna() return df test_df = preprocessing_read2("./data/test.csv") print len(test_df) test_df.dtypes
実行結果
# 無事に418件得られたので改めて予測 test_data = test_df.values xs_test = test_data[:, 1:] output = forest.predict(xs_test) # ファイル書き込み with open("tutorial/random_forest_model.csv", "wb") as f: writer = csv.writer(f) writer.writerow(["PassengerId", "Survived"]) for pid, survived in zip(test_data[:,0].astype(int), output.astype(int)): writer.writerow([pid, survived])
Submitする
これでやっとSubmitできるようになる。
from IPython.display import display, Image display(Image("./img/second_submit.png"))
機械学習ガチ勢目指して頑張りたいです。!!
機械学習によるタイタニック号の生存者予測 with Python
かなり前に勉強会でやった内容のKaggleチュートリアルの復習。
タイタニック(RMS Titanic)は、20世紀初頭に建造された豪華客船である。
処女航海中の1912年4月14日深夜、北大西洋上で氷山に接触、翌日未明にかけて沈没した。犠牲者数は乗員乗客合わせて1,513人(他に1,490人、1,517人、1,522~23人など様々な説がある)であり、当時世界最悪の海難事故であった。その後、映画化されるなどして世界的にその名を知られている。
乗客やスタッフに対する十分な数の救命ボートがなかったため、被害が拡大したと言われています。
他にも生存に必要なさまざまな要素が欠けていたのですが、女性や子供、上流階級といったあるグループの人は他の人よりも生存する傾向にあったので、今回はどのような性質を持った人々が生存する傾向にあるかについて機械学習のツールを使って分析を行います。
評価方法について
・データは"training set"と"test set"の2つに分かれています。
"training set"に対しては各々の乗客に対する結果(ground truth)を提供します。
この訓練セットはテストセットに対する予測を行うためのモデル作成に利用可能にする。
・テストセットの各々の乗客に対し、沈没に際し生存したかどうかを予測
0が死亡を表し,1が生存を表します。
正しく予測できた割合がスコア。
データセット
変数について
・survival 生存したか否か
・0 = No, 1 = Yes
・pclass 乗客の階級
・1 = 1st; 2 = 2nd; 3 = 3rd
・社会経済におけるステータス、1stが上流、2ndが中流、3rdが下流
・name 名前
・sex 性別
・age 年齢
・1歳未満の場合は小数
・推定年齢の場合はxx.5などという表記
・親族関係の変数
・sibsp タイタニック号に乗っていたSibling/Spouseの数
・Sibling: 兄弟、姉妹、義兄弟、義姉妹
・Spouse: 夫、妻
・愛人や婚約者については除外
・parch タイタニック号に乗っていたParent/Childrenの数
・Parent: 母親、父親
・Child: 息子、娘、まま息子、まま娘
・無視されている関係も存在
・いとこ、甥、姪、おじ、おば、舅、姑は除外
・ベビーシッターとだけ旅行していた何人かの子供はparch=0
・親友や近所の人と旅行に来ていた人に対しての属性はない
・ticket チケットナンバー
・fare 乗車料金
・cabin Cabin(船室の部屋番号?)
・embarked 乗船場
・C = Cherbourg, Q = Queenstown; S = Southampton
numpyとcsvライブラリを使って学習データを読む
import csv as csv import numpy as np csv_file_object = csv.reader(open('./data/train.csv', 'rb')) header = csv_file_object.next() data=[] for row in csv_file_object: data.append(row) data = np.array(data) print header print data print data.shape
実行結果
['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked']
[['1' '0' '3' ..., '7.25' '' 'S']
['2' '1' '1' ..., '71.2833' 'C85' 'C']
['3' '1' '3' ..., '7.925' '' 'S']
...,
['889' '0' '3' ..., '23.45' '' 'S']
['890' '1' '1' ..., '30' 'C148' 'C']
['891' '0' '3' ..., '7.75' '' 'Q']]
(891, 12)|
# Ageの列のデータを15個読む print header[5] print data[0:15, 5]
実行結果
Age
['22' '38' '26' '35' '35' '' '54' '2' '27' '14' '4' '58' '20' '39' '14']
# stringをfloatに変換する print data[0:5, 5].astype(np.float) print data[0::,5].astype(np.float) # 欠損値""のためにできない
実行結果
[ 22. 38. 26. 35. 35.]
pandasをつかって読み込む
import pandas as pd import numpy as np # pandasのバージョン print pd.version.version # csvファイルの読み込み df = pd.read_csv('./data/train.csv', header=0) # 0行目がヘッダー(defaultの動作、指定しない場合はNoneを指定) # 各カラムの型がpandasにはどのように解釈されているかを表示 # pandasは小数点がある場合は自動的に浮動小数点として解釈する df.dtypes
データの前処理
pandasのDataFrameではデータのフィルタリング, 操作, 欠損値の棄却, フィル, 変換および置換が簡単にできる
# 平均を算出、これはdf.describe()で表示された値と同じ df["Age"].mean() # 中央値を表示 df["Age"].median() # 複数の列にまとめてアクセスする方法 df[ ["Sex", "Pclass", "Age"]] # フィルターされたデータの内特定の列だけ見たい場合 df[df['Age'] > 60][['Sex', 'Pclass', 'Age', 'Survived']]
実行結果
この結果から60歳以上のほとんどが男性で、ほとんどが上流階級(Pclass==1)、ほとんどが死亡(survived==0)したことが分かる
# 階級ごとの男性の数を出す for i in range(1,4): print i, len(df[ (df['Sex'] == 'male') & (df['Pclass'] == i) ])
実行結果
1 122
2 108
3 347
結果を可視化
matplotlibを用いる。%matplotlib inlineはインラインプロットするためのmagic
付けないとブラウザ外のウインドウに描画される。
%matplotlib inline import matplotlib.pyplot as plt df["Age"].hist()
実行結果
# 欠損値を除いた上でビン数と透過度、範囲を指定 df["Age"].dropna().hist(bins=16, range=(0, 80), alpha=0.5)
データをクリーニングする
文法は大体分かったので機械学習に使える形にデータを整形することを考える。"male"と"female"の文字列に対して解析を掛けるのはややこしい。
異なる三つの方法でデータの変換を練習する。
二つは興味のために行い、一つは実用的なものを紹介します。
新しいカラムに結果は保存するので元のカラムは保持する。
一つ目の方法
# pandasでは新しいカラムを追加するのは新しいカラム名に値を代入するだけ df["Gender"] = 4 # 元はSex df.head(10) # Genderカラムが増えている
実行結果
二つ目の方法
# Genderの値をSexから何かしら作る df["Gender"] = df["Sex"].map( lambda x: x[0].upper() ) # 文字列の一文字目を大文字に変換 df.head(10)
実行結果
三つ目の方法
# 実際には整数が欲しいので女性を0、男性を1にする df["Gender"] = df["Sex"].map( {'female': 0, "male": 1} ).astype(int) # 辞書を渡すだけで変換してくれる df.head(10)
実行結果
Embarked(船着場)に関しても同様な操作が可能になる。
Ageの欠損値の取り扱い
機械学習では完全に値が埋まったサンプル集合が必要なので欠損値はどうにかしないといけない。
推測によって値を埋めることで予測モデルに何らかのノイズを加えることになるが、合理的な推測が可能であればそれは歴史的な真実に近づくはず。
どのような値で埋めるか?
年齢の分かっている全乗客の年齢の平均(29.6991176)?
数少ない70, 80代の影響を減らせるため、中央値の方が良い!!!
以前にプロットした年齢のヒストグラムは正の歪度を持つ
𝔼[(x−𝔼[x]σ)3]
歪度は正の場合ヒストグラムが左に偏る、負の場合は右に偏る
これらはkaggleにおいて自分自身のモデルを作る際に決定しないといけないことの一つ
結局、階級、性別ごとの年齢の中央値を利用する。
続きはPart2へ!
算術計算法の基礎 with Python
基本的な算術アルゴリズムのまとめ。
1.力まかせ探索
力まかせ探索(ちからまかせたんさく、英: Brute-force search)またはしらみつぶし探索(英: Exhaustive search)は、単純だが非常に汎用的な計算機科学の問題解決法であり、全ての可能性のある解の候補を体系的に数えあげ、それぞれの解候補が問題の解となるかをチェックする方法である。(wikipedia)
以下は入力された数字が立方根かどうかを判定するプログラムです。
x = int(raw_input('整数を入力してください: ')) number = 0 while number** 3 < abs(x): number = number + 1 if number**3 != abs(x): print x, 'は立方根ではありません' else: if x < 0: number = -number print '立方根は', x, 'です。' number
とりあえず、一番小さい数字から手当たり次第に調べよう!みたいな感じですね。昔のパソコンだと場合によっては時間がかなりかかってたらしいのですが、今のパソコンなら全く問題なく、最も実用的らしいです。
まず、入力した値と自然数の三乗を小さい順に比較していき、自然数の三乗の値が入力値より大きくなった時点で評価する。
ただし、入力値がゼロの場合は除外。
大きくなった時点で評価しているので、1を引く。
2. 近似解と力まかせ探索
負でない数の平方根をだすプログラム。以下は力まかせ探索を用いた平方根近似の例。対象とする数字は25。
x = 25 gosa = 0.01 step = gosa**2 y = 0 ans = 0.0 while abs(answer**2 - x) >= gosa and answer <= x: answer += step y += 1 print 'y =', y if abs(ans ** 2 - x) >= gosa: print x,'は失敗' else: print answer, 'が、', x,'の近似解に一番近い'
25は完全平方数であるが、こちらのプログラムを使用すると、5は答えとしてはでてこず、それに一番近い近似解、4.999がでてくる。しかし、このプラグラムだと、かなりおおきな数を調べると適切な数を見落としてしまう。これを解消するために、あるプログラムを考える。
xの近似平方根を探す違うアプローチのプログラム。
まず、0からmaxの間に解が存在すると仮定し、数字は規則ただしいという事実をうまく利用する。すなわち、異なった数のペア(n1, n2)がn1
正しい答えでない場合、正しい答えより大きいか小さいかを判断する。もし正しい答えより大きい場合、答えが左にならなければいけないことは既知である。小さい場合、答えは右になければならない。そしてさらに小さい間隔でこの処理を繰り返す。
x = 25 gosa = 0.01 y = 0 low = 0.0 high = max(1.0,x) answer = (high + low)/2.0 while abs(answer**2-x) >= gosa: print 'low=', low, 'high=', high, 'answer=',answer y += 1 if answer**2 < x: low = answer else: high = answer answer = (high + low)/2.0 print 'y = ', y print answer, 'が', x,'の近似解に一番近い'
探索される空間サイズが半分にカットされている。探索空間をこのように半分にしているこの方法をは2分法と呼ばれる。
ニュートンーラフソン法
数値解析の分野において、ニュートン法(ニュートンほう、Newton's method)またはニュートン・ラフソン法(Newton-Raphson method)は、方程式系を数値計算によって解くための反復法による求根アルゴリズムの1つである。対象とする方程式系に対する条件は、領域における微分可能性と2次微分に関する符号だけであり、線型性などは特に要求しない。収束の速さも2次収束なので古くから数値計算で使用されていた。名称はアイザック・ニュートンとジョゼフ・ラフソン(英語版)に由来する。(wikipedia)
ニュートンは、ある値(以降xとよぶ)が多項式(p)の根の近似解である場合、
は、よりよい近似解であるという定理を証明した。任意の定数kと係数cに対しての一次導関数は2cxである。またの一次導関数は2xである。ゆえに現在の現在の予測値(y)にもとずき、次の予測値としてを選ぶことで改良できる。これを逐次近似とよぶ。
以下はニュートンーラフソン法の例。
gosa = 0.01 k = 24.0 y = k/2.0 while abs(y**2 - k) >= gosa: y = y - (((y**2) - k)/(2*y)) print k,'の平方根は', y
こうゆうのもっと勉強していきたいですよね!
参考文献
Amazon.co.jp: Python言語によるプログラミングイントロダクション: 世界標準MIT教科書: ジョン・V. グッターグ, John V. Guttag, 久保幹雄: 本
ParseのAndroidStudioでの使い方(後編)
ParseのAndroidStudioでの使い方(前編) - hello_world.py
昨日の続きをまとめます。自分用のメモです。
Facebook連携のログイン
こことかを見ながらkeyやIDを発行。
Getting Started
Applicationクラスに以下を追加する。AppIDは発行されたランダム文字列
ParseFacebookUtils.initialize("AppID");
LoginActivityを以下のようにする。
public class LoginActivity extends Activity { private Button loginButton; private Dialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); loginButton = (Button) findViewById(R.id.facebook_log_in_button); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(Application.TAG, "Login button をクリックしました。"); onLoginButtonClicked(); } }); //現在facebookログイン中かどうかで条件分岐 ParseUser currentUser = ParseUser.getCurrentUser(); if ((currentUser != null) && ParseFacebookUtils.isLinked(currentUser)) { // 次のアクティビティへ NextActivity(); } ParseAnalytics.trackAppOpened(getIntent()); } private void onLoginButtonClicked() { LoginActivity.this.progressDialog = ProgressDialog.show( LoginActivity.this, "", "", true); List<String> permissions = Arrays.asList("","",""); ParseFacebookUtils.logIn(permissions, this, new LogInCallback() { @Override public void done(ParseUser user, ParseException err) { LoginActivity.this.progressDialog.dismiss(); if (user == null) { Log.i(Application.TAG, "Facebook ログインをキャンセルしました。"); } else if (user.isNew()) { Log.i(Application.TAG, "Facebookでログインしました!"); NextActivity(); } else { Log.i(Application.TAG, "Facebookでログインしました!"); NextActivity(); } } }); } /** *Facebookアプリをインストールしていないユーザーのために、「シングルサインオン」を提供 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); ParseFacebookUtils.finishAuthentication(requestCode, resultCode, data); } private void showHomeListActivity() { Intent intent = new Intent(this, NextActivity.class); startActivity(intent); //戻れないように、スクリーン閉じる finish(); }
あえて公式サイトに載ってないような感じで書いたのですが、もっと効率良く、使いやすくできそうなのでいろいろ今後試します。いまはとりあえずこんな感じ。
次はSNS使わず、emailとかでのログイン機能です。
ログイン機能
ログイン機能の実装について。
以下をLogInActivityに加えることでログイン。Exceptionまたはuser==nullの場合には失敗です。
ParseUser.logInInBackground(name, password, new LogInCallback() { public void done(ParseUser user, ParseException e) { if (user != null) { Log.d(TAG, "Log in !"); } else { Log.e(TAG, "Error!"); } } });
ちなみにログアウトは以下を使用。
ParseUser.logOut();
メールアドレス、パスワードを使用しないログインの場合は以下を使用。
ParseAnonymousUtils.logIn(new LogInCallback() { @Override public void done(ParseUser user, ParseException e) { if (e != null) { Log.e(TAG, "Error!"); } else { Log.d(TAG, "Log in!"); } } });
他にもTwitterでもログイン実装できます。
機械学習が楽しくなってきたので、swiftとC言語のUNIXとかの勉強がほったらかしになってます。三つとも密かにもっとできるようになって、会社のめっちゃ優秀な先輩方を驚かせたいです!!
次は機械学習かけるようにがんばる!!