2019年11月24日
QiitaとGitHubの2つで情報発信することにします。
技術ブログサービス 「Qrunch」に引っ越すことにするって書きましたが、結局あまり更新できていません。
もう、プログラミングに関する内容は、QiitaとGitHubの2つに絞ろうと思います。
https://qiita.com/gushwell
https://github.com/gushwell
Posted by gushwell at
20:11
│Comments(0)
2019年03月09日
ブログ、引っ越します。
いままで、約15年も続けてきたlivedoorブログですが、技術ブログサービス 「Qrunch」に引っ越すことにしました。
新しいブログへのリンク貼っておきます。
Gushwell's Dev Notes
基本、過去の記事はそのままここに残し、新しい記事を上記ブログで書いていきます。
今後ともよろしくお願いします。
Posted by gushwell at
12:29
│Comments(0)
2018年12月03日
言語処理100本ノックでPython入門 #79 - 機械学習、scikit-learnで適合率-再現率&グラフの描画
いよいよ言語処理100本ノック 2015の第8章・機械学習の最後の問題です。
■ 問題
79. 適合率-再現率グラフの描画
ロジスティック回帰モデルの分類の閾値を変化させることで,適合率-再現率グラフを描画せよ.
■ どう解いたか
久しぶりにmatplotlibを利用します。
標準では、閾値が0.5より大きいか、小さいかで+1にする-1にするのかを判断しているので、この値を変化させることで、適合率-再現率がどう変化するのかを調べる問題です。
scikit-learnには、sklearn.metrics.precision_recall_curveという関数が用意されていて、これを使うと、閾値を少しずつ変化させ、その閾値毎の適合率(precision)と再現率(recall)を計算することができます。
precision, recall, threshold = precision_recall_curve(y_test, pred_positive)thresholdはその閾値の値のリストになっています。
この値を使って、matplotlibでグラフを描画します。
これについては、
scikit-learnのロジスティック回帰で特定のクラスに分類されやすくする
を参考にさせていただきました。 ただ、僕はnumpyが全く分かってません。というかPythonがまだよくわかっていないことがわかりました。
np.argmin(np.abs(thresholds - (i * 0.05)))
この thresholds - (i * 0.05) って文法的に何を意味しているのだろう。 thresholdsというのがarrayだったら、型が違うからエラーになると思ったんだけど、エラーになりません。arrayに対して マイナス記号で引き算すると、各要素の値に対して減算されるって、規定されてるのかな?
それともscikit-learnのどこかで、- 記号が overloadされてるんだろうか? あるいは、numpyのクラスで、 - 記号が overloadされてるんだろうか?
まだまだ知らないことがたくさんありそうです。 numpyのことをもう少し勉強する必要がありそうです。
話を戻すと、ある値の絶対値をとって、その最小値のインデックスを求めているんだから、 たぶん、各リスト内容の各要素に対して、N - (i*0.05) を計算してるんだろうと思います。 そうすれば、閾値にもっとも近いものを取り出せるってことなんだと理解。
実行した結果のグラフ(最後に示します)を見てみると、再現率を上げれば、適合率が下がり、適合率を上げれば、再現率は下がるという関係になっています。 閾値をどこにするかは、とても難しい問題です。
■ Pythonのコード
import re from nltk import stem import numpy as np from sklearn.metrics import precision_recall_curve from sklearn.feature_extraction.text import CountVectorizer from sklearn.linear_model import LogisticRegression from sklearn.externals import joblib from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt class Stopwords: words = [ \ 'a', 'about', 'all', 'an', 'and', 'any', 'are', 'as', \ 'at', 'be', 'been', 'but', 'by', 'can', 'could', 'do', \ 'does', 'for', 'from', 'has', 'have', 'he', 'her', 'his', \ 'how', 'i', 'if', 'in', 'into', 'is', 'it', 'its', 'made', \ 'make', 'may', 'me', 'my', 'no', 'not', 'of', 'on', 'one', \ 'or', 'out', 'she', 'should', 'so', 'some', 'than', 'that', \ 'the', 'their', 'them', 'there', 'then', 'they', 'this', \ 'those', 'to', 'too', 'us', 'was', 'we', 'what', 'when',\ 'which', 'who', 'with', 'would', 'you', 'your', '' ] @staticmethod def exists(word): return word in Stopwords.words class SentimentFeatures: def __init__(self): self.stemmer = stem.PorterStemmer() self.validreg = re.compile(r'^[-=!@#$%^&*()_+|;";,.<>/?]+$') self.splitreg = re.compile(r'\s|,|\.|\(|\)|\'|/|\'|\[|\]|-') def isValid(self, word): if word == '' or len(word) <= 2: return False if self.validreg.match(word): return False return not Stopwords.exists(word) def getFromLine(self, line): array = self.splitreg.split(line) # こういう時はlambda キーワードいらないんですね。 words = filter(self.isValid, array) xs = map(self.stemmer.stem, words) return xs def enumerate(self, filename, encoding): with open(filename, 'r', encoding=encoding) as fin: for line in fin: sentiment = line[:3] yield sentiment, self.getFromLine(line[3:]) class SentimentAnalyser: def __init__(self): self.cv = CountVectorizer(encoding='utf-8') self.lr = LogisticRegression(solver='sag', max_iter=10000) # LogisticRegression を使い学習する def fit(self, X_train, y_train): X_train_cv = self.cv.fit_transform(X_train) self.lr.fit(X_train_cv, y_train) # LogisticRegression を使い予測する def predict(self, X_test): x = self.cv.transform(X_test) return self.lr.predict(x) # 予測し、分類毎に確率を得る def predict_proba(self, X_test): x = self.cv.transform(X_test) return self.lr.predict_proba(x) # 学習済みデータをロードする def load(self): self.cv = joblib.load('chapter08/cv73.learn') self.lr = joblib.load('chapter08/lr73.learn') # 学習済みデータを保存する def save(self): # 学習したデータを保存する joblib.dump(self.cv, 'chapter08/cv73.learn') joblib.dump(self.lr, 'chapter08/lr73.learn') # 学習に利用するデータを取り出す # y[] は、センチメント # X[] は、素性データ @staticmethod def getFeatureData(filename): X = [] y = [] sf = SentimentFeatures() for sentiment, features in sf.enumerate(filename, 'utf-8'): y.append(1.0 if sentiment[0] == '+' else 0.0) X.append(' '.join(features)) return X, y # 参考URL http://ohke.hateblo.jp/entry/2017/08/25/230000 def main(): sa = SentimentAnalyser() X, y = sa.getFeatureData('chapter08/sentiment.txt') # 5分割交差検定を行うためにデータを分割する X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # ベクターに変換 #X_train_cv = sa.trainToVector(X_train) #X_test_cv = sa.testToVector(X_test) # 学習 sa.fit(X_train, y_train) # 予測 pp = sa.predict_proba(X_test) print(pp[:, 1]) print() print(pp[:5, 1]) # +1の予測確率を取り出す [:, 1] は、1列目のすべてのデータ pred_positive = sa.predict_proba(X_test)[:, 1] # ある閾値の時の適合率、再現率, 閾値の値を取得 precisions, recalls, thresholds = precision_recall_curve(y_test, pred_positive) # 0から1まで0.05刻みで○をプロット for i in range(21): close_point = np.argmin(np.abs(thresholds - (i * 0.05))) plt.plot(precisions[close_point], recalls[close_point], 'o') # 適合率-再現率曲線 plt.plot(precisions, recalls) plt.xlabel('Precision') plt.ylabel('Recall') plt.show() input() if __name__ == '__main__': main()
■ 実行結果

■ 最後に
言語処理100本ノック 2015は、第9章、第10章が残っているんですが、すこし興味を持続するのが難しくなってきました。 ということで、この「言語処理100本ノック 2015」のシリーズはいったん、終了したいと思います。
まあ、最初はPython全くわからなかったけど、なんとかここまで来て、Pythonの知識も随分と増えたかなと思います。でも、使わないとすぐ忘れてしまうんだろうなとは思います。
以前やった Haskell とかは完全に忘却のかなたです(笑)
...
ところで、livedoorブログっていつになったら、https化するんでしょうね。ちょっと待ちくたびれた感があります。
markdown使えないのも不便だし...
14年間続けてきたけど、他に移ろうかな〜。Qiita一本でもいいかなとも感じてます。
2018年11月27日
言語処理100本ノックでPython入門 #78 - 機械学習、scikit-learnの5分割交差検定
今日は言語処理100本ノック 2015の第8章・機械学習の問題78に挑戦です。
■ 問題
78. 5分割交差検定
76-77の実験では,学習に用いた事例を評価にも用いたため,正当な評価とは言えない.すなわち,分類器が訓練事例を丸暗記する際の性能を評価しており,モデルの汎化性能を測定していない.そこで,5分割交差検定により,極性分類の正解率,適合率,再現率,F1スコアを求めよ.
■ Pythonのコード
import re from nltk import stem from sklearn.feature_extraction.text import CountVectorizer from sklearn.linear_model import LogisticRegression from sklearn.externals import joblib from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score class Stopwords: words = [ \ 'a', 'about', 'all', 'an', 'and', 'any', 'are', 'as', \ 'at', 'be', 'been', 'but', 'by', 'can', 'could', 'do', \ 'does', 'for', 'from', 'has', 'have', 'he', 'her', 'his', \ 'how', 'i', 'if', 'in', 'into', 'is', 'it', 'its', 'made', \ 'make', 'may', 'me', 'my', 'no', 'not', 'of', 'on', 'one', \ 'or', 'out', 'she', 'should', 'so', 'some', 'than', 'that', \ 'the', 'their', 'them', 'there', 'then', 'they', 'this', \ 'those', 'to', 'too', 'us', 'was', 'we', 'what', 'when',\ 'which', 'who', 'with', 'would', 'you', 'your', '' ] @staticmethod def exists(word): return word in Stopwords.words class SentimentFeatures: def __init__(self): self.stemmer = stem.PorterStemmer() self.validreg = re.compile(r'^[-=!@#$%^&*()_+|;";,.<>/?]+$') self.splitreg = re.compile(r'\s|,|\.|\(|\)|\'|/|\'|\[|\]|-') def isValid(self, word): if word == '' or len(word) <= 2: return False if self.validreg.match(word): return False return not Stopwords.exists(word) def getFromLine(self, line): array = self.splitreg.split(line) # こういう時はlambda キーワードいらないんですね。 words = filter(self.isValid, array) xs = map(self.stemmer.stem, words) return xs def enumerate(self, filename, encoding): with open(filename, 'r', encoding=encoding) as fin: for line in fin: sentiment = line[:3] yield sentiment, self.getFromLine(line[3:]) class SentimentAnalyser: def __init__(self): self.cv = CountVectorizer(encoding='utf-8') self.lr = LogisticRegression(solver='sag', max_iter=10000) # LogisticRegression を使い学習する def fit(self, X_train, y_train): X_train_cv = self.cv.fit_transform(X_train) self.lr.fit(X_train_cv, y_train) # LogisticRegression を使い予測する def predict(self, X_test): x = self.cv.transform(X_test) return self.lr.predict(x) # 予測し、分類毎に確率を得る def predict_proba(self, X_test): x = self.cv.transform(X_test) return self.lr.predict_proba(x) # 学習済みデータをロードする def load(self): self.cv = joblib.load('chapter08/cv73.learn') self.lr = joblib.load('chapter08/lr73.learn') # 学習済みデータを保存する def save(self): # 学習したデータを保存する joblib.dump(self.cv, 'chapter08/cv73.learn') joblib.dump(self.lr, 'chapter08/lr73.learn') # 学習に利用するデータを取り出す # y[] は、センチメント # X[] は、素性データ @staticmethod def getFeatureData(filename): X = [] y = [] sf = SentimentFeatures() for sentiment, features in sf.enumerate(filename, 'utf-8'): y.append(1.0 if sentiment[0] == '+' else 0.0) X.append(' '.join(features)) return X, y def main(): sa = SentimentAnalyser() X, y = sa.getFeatureData('chapter08/sentiment.txt') # 5分割交差検定を行うためにデータを分割する X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # 4/5のデータで学習する # X_train_cv = sa.trainToVector(X_train) sa.fit(X_train, y_train) # 1/5のデータで予測する y_test_pred = sa.predict(X_test) print('正解率 accuracy:', accuracy_score(y_test, y_test_pred)) print('適合率 precision:', precision_score(y_test, y_test_pred)) print('再現率 recall:', recall_score(y_test, y_test_pred)) print('F1スコア f1_score:', f1_score(y_test, y_test_pred)) if __name__ == '__main__': main()
scikit-Learnには、n分割交差検定を行うためのテストデータの分割機能が用意されています。 以下のコードで、データの4/5を学習用データに、1/5をテストデータに分割することができます。
# 5分割交差検定を行うためにデータを分割する X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
これができれば、あとは、fitとpredictを呼び出すだけです。
最後に、accuracy_score、precision_scoreなどのメソッドを使い、適合率などを求めています。
■ 結果
正解率 accuracy: 0.768870135959 適合率 precision: 0.770398481973 再現率 recall: 0.763875823142 F1スコア f1_score: 0.767123287671
学習データとテストデータが別れたので、当然ながらそれぞれに値が下がっていることを確認できます。
2018年11月23日
C#でパズルを解こう!の記事をすべてQiitaに移行しました。
姉妹サイトである「Gushwell's C# Programming Page」の「C#でパズルを解こう!」で公開していた記事全27本をすべて、加筆修正し、Qiitaに移行しました。
「Gushwell's C# Programming Page」では、多くのプログラムをC#+Silverlightで書いていましたが、C#+.NET Core(コンソールアプリ)に書き換えています。
記事の一覧を以下に示します。
協力最短詰めオセロ | 二人で協力しどちらかが完全勝利する最短手を見つける。 |
ハノイの塔を再帰で解く | ハノイの塔を再帰で解くための考え方も解説してます。 |
迷路を波状探索で解く | 迷路を波状探索(勝手に命名)で解いています。 |
迷路を幅優先探索で解く | 迷路を幅優先探索で解いています。 |
迷路を深さ優先探索で解く | 迷路を深さ優先探索で解いています。 |
MagicStarを解く | MagicStar(星陣)を深さ優先探索で解く |
8クィーンパズル | N-Queenに対応しています。 |
ハイパークィーン問題 | 8クィーンと似たパズル |
碁石拾い | 日本古来の碁石を使ったパズルを再帰で解く |
ナイト(騎士)巡回問題 | バックトラックでナイト巡回問題を解く |
ナイト(騎士)の最適配置 | チェス盤に騎士を配置し効き筋で埋めます |
ステインハウスの三角形 | 整数のビットパターンで組合せを求める |
Triangle15パズル | 順列が問題を解くカギ |
コイン15 | 10円玉と5円玉をつかったパズルを深さ優先探索で解く |
覆銭問題 | Eventを発行し途中経過を知らせる |
割り切れる4桁の逆転数 | 逆転した数で割り切れる4桁の数を得る |
巡回数 | n倍すると巡回する自然数を求める |
小町算 | 1-9の数字と+−で100にする:LINQで組合せを求める |
2乗した値が1-9で構成 | 2乗した値と求めた値が1-9で構成される3桁の数を得る |
小町数となる単位分数 | 約分して1/nとなる数の分母と分子が小町数 |
3つの式が等しくなる小町数 | ○○-○=○○/○=○+○*○が小町数 |
素因数分解小町 | 素因数分解した結果の値が1-9で構成される数を求める |
小町リング | 円の中の数の合計が等しくる小町数 |
センチュリーパズル | K + N / D = 100 のK,N,Dが小町数 |
リングナンバーの最大公約数 | 16個の数字の最大公約数を求める |
二乗して回文となる非回文数 | 例えば264は、264*264=69696 で結果が回文となる |
Less Than Tree | 規則に沿って2色の石を置いてゆく。 IObserver / IObservableを利用。 |
2018年11月18日
言語処理100本ノックでPython入門 #77 - 機械学習、scikit-learnでの正解率の計測
今日は言語処理100本ノック 2015の第8章・機械学習の問題77に挑戦です。
■ 問題
77. 正解率の計測
76の出力を受け取り,予測の正解率,正例に関する適合率,再現率,F1スコアを求めるプログラムを作成せよ.
■ Pythonのコード
import re from nltk import stem from sklearn.feature_extraction.text import CountVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, accuracy_score, \ f1_score, precision_score, recall_score from sklearn.externals import joblib class Stopwords: words = [ \ 'a', 'about', 'all', 'an', 'and', 'any', 'are', 'as', \ 'at', 'be', 'been', 'but', 'by', 'can', 'could', 'do', \ 'does', 'for', 'from', 'has', 'have', 'he', 'her', 'his', \ 'how', 'i', 'if', 'in', 'into', 'is', 'it', 'its', 'made', \ 'make', 'may', 'me', 'my', 'no', 'not', 'of', 'on', 'one', \ 'or', 'out', 'she', 'should', 'so', 'some', 'than', 'that', \ 'the', 'their', 'them', 'there', 'then', 'they', 'this', \ 'those', 'to', 'too', 'us', 'was', 'we', 'what', 'when',\ 'which', 'who', 'with', 'would', 'you', 'your', '' ] @staticmethod def exists(word): return word in Stopwords.words class SentimentFeatures: def __init__(self): self.stemmer = stem.PorterStemmer() self.validreg = re.compile(r'^[-=!@#$%^&*()_+|;";,.<>/?]+$') self.splitreg = re.compile(r'\s|,|\.|\(|\)|\'|/|\'|\[|\]|-') def isValid(self, word): if word == '' or len(word) <= 2: return False if self.validreg.match(word): return False return not Stopwords.exists(word) def getFromLine(self, line): array = self.splitreg.split(line) # こういう時はlambda キーワードいらないんですね。 words = filter(self.isValid, array) xs = map(self.stemmer.stem, words) return xs def enumerate(self, filename, encoding): with open(filename, 'r', encoding=encoding) as fin: for line in fin: sentiment = line[:3] yield sentiment, self.getFromLine(line[3:]) class SentimentAnalyser: def __init__(self): self.cv = CountVectorizer(encoding='utf-8') self.lr = LogisticRegression(solver='sag', max_iter=10000) # LogisticRegression を使い学習する def fit(self, X_train, y_train): X_train_cv = self.cv.fit_transform(X_train) self.lr.fit(X_train_cv, y_train) # LogisticRegression を使い予測する def predict(self, X_test): x = self.cv.transform(X_test) return self.lr.predict(x) # 予測し、分類毎に確率を得る def predict_proba(self, X_test): x = self.cv.transform(X_test) return self.lr.predict_proba(x) # 学習済みデータをロードする def load(self): self.cv = joblib.load('chapter08/cv73.learn') self.lr = joblib.load('chapter08/lr73.learn') # 学習済みデータを保存する def save(self): # 学習したデータを保存する joblib.dump(self.cv, 'chapter08/cv73.learn') joblib.dump(self.lr, 'chapter08/lr73.learn') # 学習に利用するデータを取り出す # y[] は、センチメント # X[] は、素性データ @staticmethod def getFeatureData(filename): X = [] y = [] sf = SentimentFeatures() for sentiment, features in sf.enumerate(filename,'utf-8'): y.append(1.0 if sentiment[0] == '+' else 0.0) X.append(' '.join(features)) return X, y def main(): sa = SentimentAnalyser() sa.load() X_test, y_test = sa.getFeatureData('chapter08/sentiment.txt') y_test_pred = sa.predict(X_test) print('正解率 accuracy:', accuracy_score(y_test, y_test_pred)) print('適合率 precision:', precision_score(y_test, y_test_pred)) print('再現率 recall:', recall_score(y_test, y_test_pred)) print('F1スコア f1_score:', f1_score(y_test, y_test_pred)) if __name__ == '__main__': main()
■ どう解いたか
今回も、3つのクラス(Stopwords,SentimentFeatures,SentimentAnalyser)は前回と同じです、mainメソッドだけを変更しています。
問題文は「76の出力を受け取り」とありますが、これをどう解釈したら良いか出題意図がわからなかったので、この部分は無視して、「予測の正解率,正例に関する適合率,再現率,F1スコアを求めるプログラムを作成」しています。
predictメソッドで予測した後に、accuracy_score、precision_scoreなどのメソッドを使い、求める値を得ています。
■ 結果
正解率 accuracy: 0.94363158882 適合率 precision: 0.946563444109 再現率 recall: 0.940348902645 F1スコア f1_score: 0.943445939588
ちなみに
print(classification_report(y_test, y_test_pred))とすると、以下の結果が得られます。
precision recall f1-score support 0.0 0.94 0.95 0.94 5331 1.0 0.95 0.94 0.94 5331 avg / total 0.94 0.94 0.94 10662