機械学習によるタイタニック号の生存者予測 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"))
機械学習ガチ勢目指して頑張りたいです。!!