Machine Learning for Everyone Else Python 機械学習プログラミング データ分析演習編 ver2.0 2016/11/15 中井悦司 (Twitter @enakai00)
Machine Learning for Everyone Else
Python 機械学習プログラミングデータ分析演習編
ver2.0 2016/11/15中井悦司 (Twitter @enakai00)
2
Python 機械学習プログラミング
目次■ 分析データの取り込みと確認
■ 分析データの可視化
■ scikit-learnによる機械学習処理
■ (オプション)カテゴリーデータの相関と仮説検定
■ (オプション)カイ二乗検定
3
Python機械学習プログラミング
ハンズオン環境の利用方法
■ 本講義のハンズオン環境は、クラウド上の仮想マシンで用意してあります。下記のBlog記事の手順にしたがって、同じ環境を自分で用意することもできます。
- GCPでJupyterを利用する方法(GCEのVMインスタンスを利用する場合)
- http://enakai00.hatenablog.com/entry/2016/07/03/201117
■ Jupyter Notebookの利用方法については、下記の資料を参照してください。
- Python機械学習プログラミング データ分析ライブラリー解説編
- http://www.slideshare.net/enakai/it-pandas
4
Python 機械学習プログラミング
分析データの取り込みと確認
5
Python 機械学習プログラミング
データの取り込み■ Webで公開されているcsvデータをpandasのデータフレームに取り込みます。
- 取り込んだデータの説明は下記に記載されています。
● http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3info.txt
- 数値自体に意味のないデータが数値で表現されている場合、誤った(意味のない)統計量を計算しないように、データ型を文字列型に変換しておきます。いまの場合、「pclass(社会的地位)」は数値で表現されていますが、この値の「平均値」を取っても特に意味はありません。
In [1]: import numpy as np import matplotlib.pyplot as plt import pandas as pd from pandas import Series, DataFrame
In [2]: data = pd.read_csv('http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3.csv') data['pclass'] = data['pclass'].map(str) # pclassの型を文字列型に変換
VARIABLE DESCRIPTIONS:pclass Passenger Class (1 = 1st; 2 = 2nd; 3 = 3rd)survived Survival (0 = No; 1 = Yes)name Namesex Sexage Agesibsp Number of Siblings/Spouses Aboardparch Number of Parents/Children Aboardticket Ticket Numberfare Passenger Farecabin Cabinembarked Port of Embarkation (C = Cherbourg; Q = Queenstown; S = Southampton)boat Lifeboatbody Body Identification Numberhome.dest Home/Destination
タイタニック号の乗船名簿の情報に、沈没による死亡情報を加えたものです。
6
Python 機械学習プログラミング
データの全体感の把握■ 取り込んだデータの全体像を眺めて特徴を把握します。
- 今回使用するデータはそれほど大きくない(全部で1309件)ので、まずはスプレッドシートで開いて、どのようなデータか眺めておきます。
- 項目によっては欠損値(データが埋まっていないセル)が多いなどのデータセットとしての特徴、あるいは、「1歳未満の乳児が乗船している」などの社会的観点での特徴が見えてきます。
7
Python 機械学習プログラミング
データのサマリー情報の確認■ 項目ごとのデータ数や平均値など、標準的な統計量を確認しておきます。
- 項目によってデータ数(count)が異なるのは、欠損値が存在するためです。
- 欠損値の影響で、パーセンタイルがうまく計算できいない部分があります。これらの項目は、次ページのように、欠損値を削除して計算します。
In [3]: data.columnsOut[3]: Index([u'pclass', u'survived', u'name', u'sex', u'age', u'sibsp', u'parch', u'ticket', u'fare', u'cabin', u'embarked', u'boat', u'body', u'home.dest'], dtype='object')
In [4]: data.describe()Out[4]:
8
Python 機械学習プログラミング
データのサマリー情報の確認■ dropna()メソッドで欠損値を含む行を削除した上で、統計情報を確認します。
- 平均値(mean)と中央値(50%)が乖離している場合、値の分布に歪み(skew)があると考えられます。
In [5]: data[['age']].dropna().describe()In [6]: data[['fare']].dropna().describe()In [7]: data[['body']].dropna().describe()
平均値と中央値が乖離した例
9
Python 機械学習プログラミング
分析データの可視化
10
Python 機械学習プログラミング
■ 数値データはヒストグラムによって可視化します。
- 例として、年齢(age)と料金(fare)の分布をヒストグラムで確認します。
- 15歳未満の子供の分布、もしくは、200ポンド以上の高額料金の乗客などに分布の特徴が見られます。
In [8]: data[['age']].dropna().plot(kind='hist', bins=16)
In [9]: data[['fare']].dropna().plot(kind='hist', bins=20)グラフ化する際は、dropna()で
欠損値を削除しておきます。
数値データの可視化
11
Python 機械学習プログラミング
数値データの相関の可視化■ 2つの数値データの関係性を見るときは、散布図で可視化します。
- 例として、年齢(age)と料金(fare)の関係を散布図で表示します。
In [10]: df = data[['age','fare']].dropna() df.plot(kind='scatter', x='age', y='fare')
- 特に目立った関係はありませんが、200ポンド以上の料金で乗船しているのは15歳以上に限られるなどが確認できます。
12
Python 機械学習プログラミング
カテゴリーデータと数値データの相関の可視化■ カテゴリーデータと数値データの関係性を見るときは、箱ひげ図で可視化します。
- 例として、社会的地位(pclass)と料金(fare)の関係を箱ひげ図で表示します。
In [11]: df = data[['fare','pclass']].dropna() df.boxplot(column='fare', by='pclass')
- 社会的地位が高い人(pclass:1)は高額料金で乗船していることがわかります。
外れ値
75パーセンタイル
50パーセンタイル(中央値)
25パーセンタイル
13
Python 機械学習プログラミング
3つ以上のデータの相関の可視化■ 3つ以上のデータの関係を表示する場合は、データの種類に応じて適切な可視化方法を選択する必要があります。
- たとえば、年齢(age)と料金(fare)の散布図を社会的地位(pclass)で色分けしてみます。
In [12]: df1 = data[data.pclass=='1'][['age','fare']].dropna() df2 = data[data.pclass=='2'][['age','fare']].dropna() df3 = data[data.pclass=='3'][['age','fare']].dropna()
plt.scatter(df1.age, df1.fare, facecolor='blue') plt.scatter(df2.age, df2.fare, facecolor='green') plt.scatter(df3.age, df3.fare, facecolor='red')
14
Python 機械学習プログラミング
カテゴリーデータの相関■ カテゴリーデータの例として、性別(sex)と生存(survival)の相関を確認します。
- 2種類のカテゴリーデータの相関は、クロス集計表で確認します。
- この結果を見ると、性別によって生存率が大きく変わることが分かります。
- 同様の分析を社会的地位(pclass)と生存(survival)について行ってみてください。
- また、その他のデータについても自分なりの可視化を行って、特徴を発見してみてください。
In [13]: df = data[['sex','survived']].dropna() pd.crosstab(df.sex, df.survived)Out[13]:
In [14]: pd.crosstab(data.sex ,data.survived).plot(kind='bar')
In [15]: df.mean()Out[15]: survived 0.381971 # 平均生存率dtype: float64
In [16]: 339.0/(127+339)Out[16]: 0.7274678111587983 # 女性の生存率
15
Python 機械学習プログラミング
scikit-learnによる機械学習処理
16
Python 機械学習プログラミング
ロジスティック回帰■ 「性別」「年齢」の2つの特徴を用いたロジスティック回帰で、生存確率を予測するモ
デルを構築します。
- この後の分析で使用するモジュールをインポートします。
- 欠損値を含む行を削除して、分析に使用する列のみを含むDataFrameを用意します。ここでは、特徴変数 X とラベル y を個別にDataFrameとして作成しています。
- scikit-learnでは、分析に使用するデータを数値データに変換する必要があります。ここでは、性別を 0, 1 に変換した gender 列を用意します。
In [17]: from PIL import Image from sklearn.cross_validation import train_test_split, cross_val_score, KFold from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score from sklearn.tree import DecisionTreeClassifier, export_graphviz
In [18]: tmp = data[['age', 'sex', 'survived']].dropna() X = tmp[['age', 'sex']] y = tmp['survived'] X.head()
In [19]: X['gender'] = X['sex'].map({'female': 0, 'male': 1}).astype(int) X = X.drop(['sex'], axis=1) X.head()
In [18]: tmp = data[['age', 'sex', 'survived']].dropna() X = tmp[['age', 'sex']] y = tmp['survived'] X.head()
17
Python 機械学習プログラミング
ロジスティック回帰- データセットをトレーニングセットとテストセットに分割した後、トレーニングセットを用いて
学習処理を実行します。
- 学習結果を用いて、トレーニングセットとテストセットに対する正解率を計算します。
- クロスバリデーションを実行する関数を用意します。
- クロスバリデーションを実施して、結果を表示します。
In [20]: X_train, X_val, y_train, y_val = train_test_split(X, y, train_size=0.8, random_state=1) clf = LogisticRegression() clf.fit(X_train, y_train)
In [22]: def cross_val(clf, X, y, K, random_state=0): cv = KFold(len(y), K, shuffle=True, random_state=random_state) scores = cross_val_score(clf, X, y, cv=cv) return scores
In [21]: y_train_pred = clf.predict(X_train) y_val_pred = clf.predict(X_val) print 'Accuracy on Training Set: %.3f' % accuracy_score(y_train, y_train_pred) print 'Accuracy on Validation Set: %.3f' % accuracy_score(y_val, y_val_pred)Out[21]:Accuracy on Training Set: 0.801Accuracy on Validation Set: 0.751
In [23]: clf = LogisticRegression() scores = cross_val(clf, X, y, 5) print 'Scores:', scores print 'Mean Score: %f ± %.3f' % (scores.mean(), scores.std())Out[23]:Scores: [ 0.81904762 0.80861244 0.75119617 0.77033493 0.74641148]Mean Score: 0.779121 ± 0.030
18
Python 機械学習プログラミング
決定木(Decision Tree)■ 決定木を用いて、先ほどと同じ分析を実施します。
- クロスバリデーションを実施して、結果を表示します。
- 決定木を画像化して表示します。
● value は、survived = 0, 1 それぞれの
カテゴリーのデータ数を示します。
In [24]: clf = DecisionTreeClassifier(criterion='entropy', max_depth=2, min_samples_leaf=2) scores = cross_val(clf, X, y, 5) print 'Scores:', scores print 'Mean Score: %f ± %.3f' % (scores.mean(), scores.std())Out[24]:Scores: [ 0.8047619 0.75119617 0.78947368 0.77990431 0.74162679]Mean Score: 0.773393 ± 0.024
In [25]: clf.fit(X, y) export_graphviz(clf, out_file='tree.dot')In [26]: !dot -Tpng tree.dot -o tree.pngIn [27]: Image.open("tree.png")
女性 男性
19
Python 機械学習プログラミング
(オプション)カテゴリーデータの相関と仮説検定
20
Python 機械学習プログラミング
カテゴリーデータの相関■ カテゴリーデータの例として、性別(sex)と生存(survival)の相関を確認します。
- 2種類のカテゴリーデータの相関は、クロス集計表で確認します。
- 女性の生存率(0.73)は、平均生存率(0.38)よりも大幅に高いことがわかります。これほど大きな差が発生した理由としては、女性が優先的に救助されたなどの社会的背景が想像できますが・・・
- 仮にもう少し微妙な差であった場合、具体的にどれだけの差があれば、「統計学的に確かに差があった」と自信を持って主張できるのでしょうか?
In [13]: df = data[['sex','survived']].dropna() pd.crosstab(df.sex, df.survived)Out[13]:
In [14]: pd.crosstab(data.sex ,data.survived).plot(kind='bar')
In [15]: df.mean()Out[15]: survived 0.381971 # 平均生存率dtype: float64
In [16]: 339.0/(127+339)Out[16]: 0.7274678111587983 # 女性の生存率
21
Python 機械学習プログラミング
仮説検定の考え方■ 「男女の生存率に差がある」と統計学的に自信を持って主張するには、次のような手続
きをとります。
- それが正しくないと仮定したモデル(帰無仮説)を用意します。
- そのモデルの下で「偶然」に同じ結果が得られる確率(p値)を計算します。
- 事前に決めておいた有意水準(例えば5%)よりp値が小さければ、「有意水準XX%で有意な差がある」と主張します。
■ このような手続きを「仮説検定」と呼びます。仮説検定においては、次の点に注意が必要です。
- 帰無仮説として利用するモデルによって、計算されるp値は異なります。「どのようなモデルの下に有意水準XX%なのか」という事まで説明しないと意味がありません。
- 仮説検定は、主張したい内容が正しいことを「証明する」手法ではありません。「統計的に偶然の差異とは考えづらいから、より詳細な調査を続ける価値がある」と判断するための手法です。
22
Python 機械学習プログラミング
女性の生存率についての仮説検定■ 「男女の生存率に差がない」とする帰無仮説として、次のモデルを用いた仮説検定をお
こないます。
- 帰無仮説のモデル:男性843人+女性466人が乗船した船が沈没すると、男女関係なく、それぞれの人は、確率 0.38 で生存する。
- このモデルで船を沈没させた時に女性全体の生存率が R を越える確率をp値とする。
- p値が5%以下であれば、「生存率は男女によって異なる」と自信を持って主張できる。(「このモデルにおいては、有意水準5%で生存率は男女で異なる」と主張できる。)
■ ここでは、次の2つの問題を考えます。
問1. R = 0.72 の場合、有意水準5%で生存率は異なると主張できるか?
問2. 有意水準5%で生存率は異なると主張できなくなるのは、R がいくら以下の時か?
23
Python 機械学習プログラミング
数値シュミレーションによる計算例■ このモデルにおけるp値は、理論的に計算することもできますが、ここでは、数値シュ
ミレーションによる計算を行います。
- モデルで設定した条件の船を10000回沈没させて、女性全体の生存率がどのように分布するかを観測します。
■ ただし、愚直に実装すると結構時間がかかります・・・。
In [17]: from numpy.random import rand def sink(n): result = [] df = data[['sex','survived']].dropna() for i in range(n): for index in range(0,len(df)): if rand() < 0.38: alive = True else: alive = False df.loc[index, 'survived'] = alive df_female = df[df.sex == 'female'] num_alive = len(df_female[df_female.survived == True]) result.append(float(num_alive) / len(df_female)) return result
In [18]: time result = sink(100)CPU times: user 35.8 s, sys: 34.8 ms, total: 35.9 sWall time: 35.8 s
100回沈没させるのに約36秒
24
Python 機械学習プログラミング
データのシャッフルによるシュミレーション■ 元データをシャッフルする手法で、同じシュミレーションが効率化できます。
- この手法でシュミレーションを実施すると次の結果が得られました。
In [19]: def sink2(n): df = data[['survived','sex']].dropna() num_female = len(df[df.sex == 'female']) # num = 466 result = [] for i in range(n): df_shuffled = df.reindex(np.random.permutation(df.index)) samples = df_shuffled[:num_female] survive_rate = float(len(samples[samples.survived == 1]))/num_female result.append(survive_rate) return result
In [20]: result = sink2(10000) plt.hist(result)
- 女性の生存率が R=0.72 を越えることはありませんでした。(つまり p値は 0 で、問1の答えは Yes)
■ それでは、実際の女性の生存率が何割以下であれば、有意な差があるとは言えなくなるのでしょうか?
- ヒストグラムから目視確認するのは困難なので、違う種類のグラフを利用します。
25
Python 機械学習プログラミング
CDF(累積分布関数)によるしきい値の確認■ シュミレーションで得られた10000個のサンプルを次の手順で整理します。
- 生存率 X = 0%, 1%, …, 100% の101個の値について、次を計算します。
● P(X) = 「生存率が X 以下のデータの個数の割合」
- P(X)が 95% に達する生存率 X を発見します。
⇒ これが、生存率が X 以上になるデータが全体の5%以下となるしきい値を与えます。 (これ以上 X が大きければ、「有意水準5%で有意な差がある」と言える。)
- この結果より、生存率 40% が有意水準5%のしきい値とわかります。
In [21]: count = np.array([0.0]*101) for i in result: i = int(i*100) for x in range(i ,101): count[x] += 1 cdf = count / len(result)
In [22]: plt.plot(range(30,46), cdf[30:46])
In [23]: cdf[40]Out[23]: 0.94230000000000003
この関数をCDF(累積分布関数)と呼びます。
26
Python 機械学習プログラミング
(オプション)カイ二乗検定
27
Python 機械学習プログラミング
3つ以上のカテゴリーのケース■ 先ほどと同じ方法で社会的地位(pclass)と生存(survival)の相関を確認します。
- 2種類のカテゴリーデータの相関は、クロス集計表で確認します。
In [24]: df = data[['pclass','survived']].dropna() pd.crosstab(df.pclass, df.survived)Out[24]:
In [25]: pd.crosstab(df.pclass, df.survived).plot(kind='bar')
- 社会的地位が高いほど生存率が高いことが読み取れます。この事実を検定するには、どのような確率を計算すればよいでしょうか?
28
Python 機械学習プログラミング
3つ以上のカテゴリーのケース■ 仮に社会的地位と生存率に関連性がないとすれば、理想的には、それぞれのクラスの
「生存者数の期待値」は次のようになります。
■ この3つの値について、「(観測値 – 期待値)^2 / 期待値」の合計を計算します。
- この値を「カイ二乗統計量」と呼び、データシャッフルによるシュミレーションでこの値がどのように分布するかを確認します。(77以上の値になるサンプルがどの程度あるかで、上記の結果が偶然かどうかを判断します。)
In [26]: df.survived.mean()Out[26]: 0.3819709702062643
In [27]: df.survived.mean() * len(df[df.pclass == '1'])Out[27]: 123.37662337662337
In [28]: df.survived.mean() * len(df[df.pclass == '2'])Out[28]: 105.80595874713521
In [29]: df.survived.mean() * len(df[df.pclass == '3'])Out[29]: 270.81741787624139
In [30]: e1 = df.survived.mean() * len(df[df.pclass == '1']) e2 = df.survived.mean() * len(df[df.pclass == '2']) e3 = df.survived.mean() * len(df[df.pclass == '3']) ((200-e1)**2 / e1) + ((110-e2)**2 / e2) + ((181-e3)**2 / e3)Out[30]: 77.541616252803522
29
Python 機械学習プログラミング
データシャッフルによるシュミレーション結果■ 実行結果は次のとおりです。
- 10000回のサンプリングで、カイ二乗統計量が77を越えることはありませんでしたので、有意水準5%で有意な差があると言えます。
※ ここではランダムサンプリングを用いて p 値を確認しましたが、カイ二乗統計量の確率分布は理論的に計算す ることが可能で、サンプリングを行わずに、理論計算だけで p 値を求めることも可能です。
In [31]: def sink3(n): df = data[['pclass','survived']].dropna() p1 = len(df[df.pclass == '1']) p2 = len(df[df.pclass == '2']) p3 = len(df[df.pclass == '3']) e1 = df.survived.mean() * p1 e2 = df.survived.mean() * p2 e3 = df.survived.mean() * p3
result = [] for i in range(n): df_suffled = df.reindex(np.random.permutation(df.index)) df1 = df_suffled[:p1] df2 = df_suffled[p1:p1+p2] df3 = df_suffled[p1+p2:] o1 = len(df1[df1.survived == 1]) o2 = len(df2[df2.survived == 1]) o3 = len(df3[df3.survived == 1]) result.append( ((o1-e1)**2/e1) + ((o2-e2)**2/e2) + ((o3-e3)**2/e3)) return result
In [32]: result = sink3(10000) max(result)Out[32]: 13.013916253312704
30
Python 機械学習プログラミング
練習問題■ サイコロを60回振ったところ、それぞれの目が出た回数は次のようになりました。
- 1 : 9回、2 : 13回、3 : 7回、4 : 5回、5 : 17回、6 : 9回
■ ランダムサンプリングを用いたカイ二乗検定を用いて、このサイコロは偏りがないかどうかを議論してください。
- 偏りのないサイコロであれば、それぞれの回数の期待値は、10回ずつになります。
- したがって、この結果のカイ二乗統計量は次の計算から 9.4 になります。
In [33]: ((9-10)**2/10.0)+((13-10)**2/10.0)+((7-10)**2/10.0)+((5-10)**2/10.0)+((17-10)**2/10.0)+((9-10)**2/10.0)Out[33]: 9.4
31
Python 機械学習プログラミング
解答用のコード実行例In [34]: dices = np.random.randint(1,7,(10000,60)) result = [] for sample in dices: chi2 = 0.0 for i in range(1,7): chi2 += (len([ 1 for c in sample if c == i ]) - 10.0)**2 / 10.0 result.append(chi2)
In [35]: max(result)Out[35]: 29.799999999999997
In [36]: plt.hist(result,bins=40)
In [37]: count = np.array([0.0]*36) for i in result: for x in range(int(i), 36): count[x] += 1 cdf = count / len(result) plt.plot(cdf[:21])
In [38]: cdf[9:12]Out[38]: array([ 0.9233, 0.9474, 0.965 ])
カイ2乗統計量が9.4を超える場合が存在します。
CDFのグラフを見ると、χ =9.4は、95%領域の境界あたりになります。
厳密に値を見ると、χ =10.0 は95%領域に含まれるので、サイコロに偏りがあるとは結論できません。
32
Python 機械学習プログラミング
メモとしてお使いください
Machine Learning for Everyone Else
Thank You!