年初に「今年は機械学習やっていきます」と抱負を立てた気がするんですが、もうそろそろ年の瀬だというのに 1 ミリも進んでいないので、強制的にインプット出来るもくもく会に参加して、Kaggle 参加者が一番最初にやるタイタニックの予測をやってみました。
正しくは「やってみた」というか「写経して理解した」ですね。
Kaggle はおろか、機械学習(ほぼ)初めましての状態だったので、完全に雰囲気で理解しました。
機械学習むずかしい。むずかしいけど楽しい。
参加したイベント
参加したイベントはこちらです。
福岡 Kaggle もくもく会 #24 - connpass
Kaggle のもくもく会をやっている事自体は知っていて、ずっと行こう行こうとは思っていましたが、ようやく参加しました。Fusic さんに来たのは何気に久しぶりです。
参考にした記事
上記イベントページ内にもあるこちらの記事を参考に進めていきました。
【Kaggle 超初心者向け】Titanic にチャレンジしてみた - Qiita
上位陣は正答率 100%なんですけど、どんな魔法を使ってるんでしょうか…。
やってること概略
やってること自体はすごく単純で、csv 読んで前処理して sklearn を使って予測するだけです。
- pandas で csv を読み込む
- 前処理(欠損値の補完、カテゴリ変数の変換、不要なカラム削除)
- sklearn を使って決定木で予測
- sklearn を使ってランダムフォレストで予測
- ランダムフォレストで結果を出力
決定木とかランダムフォレストとか知らねぇ!ってところですが、どういう手法のものかさえ知っていれば OK で、実装自体は sklearn にほぼ丸投げ可能。まぁ僕はどういう手法のものかさえ知らないわけですが。
※それぞれの手法については後述します。
使うモジュール
記事内で使われていたモジュールは以下です。僕は素の Python ではなくanaconda3-2019.10
を使ったので、全て内包されていました。
- pandas
- numpy
- matplotlib.pyplot
- seaborn
- sklearn
seaborn 聞いたことなかったんですが、グラフ用のモジュールですね。matplotlib だけじゃダメなの?って思いましたが、なんか matplotlib よりかっこいいグラフが作れるっぽい?
処理を追っていく
それでは記事内に書かれている処理を個別で追ってみます。なお、各種モジュールに関しては以下で読み込まれているものとします。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import (roc_curve, auc, accuracy_score)
from sklearn.ensemble import RandomForestClassifier
csv を読み込む
df = pd.read_csv('train.csv')
pandas でトレーニング用のデータセットであるtrain.csv
を読み込みます。DataFrame というなんかいい感じで便利なやつが戻り値になります。なお、ここでは使ってませんが、ドキュメント1によるとかなりの数のパラメータが渡せるようです。
かなり柔軟性ありそうなので、pandas がデファクトになっているのもうなずけます。
読み込んだ DataFrame を確認
予測の本筋とはあまり関係ありませんが、DataFrame の便利メソッドの確認も兼ねて、実際に読み込んだデータを見ていきます。
df.head()
head()
で先頭データを取得します。ドキュメント2を見ると、デフォルトは 5 件で、引数で n をとって n 行分のデータを取得することができます。
df.describe()
describe()
は合計値とか最大値最小値などの統計情報を取得できます。実際に取得できた値は以下でした。
- count: 要素数
- mean: 算術平均
- std: 標準偏差
- min: 最小値
- 25%: 1/4 分位数
- 50%: 中央値(=median)
- 75%: 3/4 分位数
- max: 最大値
標準偏差とか分位数とか忘れちゃったよーって方はググってください(僕もググりました)。このへんは今後学習を進めていくにあたってちゃんと使えるようになるのは必須な感じがします。
df.hist()
hist()
はヒストグラムを書けます。matplotlib
を通さなくても書けるんですね。知りませんでした。
データのビジュアライゼーション
matplotlib
とseaborn
を駆使して表示しています。ここでは表示しているだけで、本筋ではないので割愛します。
前処理をする前の確認
df.isnull().sum()
isnull()
で欠損値を抽出して、sum()
で合計値を集計できます。便利ですね。今回のタイタニックのデータだと、Age(年齢)と Cabin(キャビン番号)が結構欠損していることがわかります。
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 177
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 687
Embarked 2
以降の前処理で、これらの欠損値や、文字列のラベルを数値変換したりしていきます。
データの前処理
欠損値の補完
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Age'] = df['Age'].fillna(df['Age'].median())
df['Embarked'] = df['Embarked'].fillna('S')
Fare(運賃)に関しては欠損してないのになぜか処理が入ってますね…謎。
Age は中央値で埋めて、Embarked は S で埋めています。S で埋めている理由はちょっとよくわかりませんでした。欠損件数も 2 件だし、ぶっちゃけ何で埋めてもあんまり変わらないような気もします。
Cabin も欠損値多いですが、今回の予測には不要で捨てるので、ここでは前処理の対象にはなっていません。
ラベルの変換
df['Sex'] = df['Sex'].apply(lambda x: 1 if x == 'male' else 0)
df['Embarked'] = df['Embarked'].map({'S': 0, 'C':1, 'Q':2}).astype(int)
変換しないと具体的にどう影響するのかどうかはわかりませんが、直感的に文字列だと取り扱いづらい感じはします。ここでは Sex および Embarked をそれぞれ文字列ラベルから int に変換しています。
不要なカラムを削除
df = df.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1)
予測に影響しないカラムをdrop()
で落としています。
学習データとテストデータに分割
train_X = df.drop('Survived', axis=1)
train_y = df.Survived
(train_X, test_X, train_y, test_y) = train_test_split(train_X, train_y, test_size=0.3, random_state=0)
個人的にはここが第一の難関でした。
まず、train_X
には Survived をdrop()
したデータが入っています。train_y
は Survived のみのデータが入っています。これをtrain_test_split()
に渡してそれぞれを分割します。
test_size
に従って 30%の割合がテストデータになるので、もともとのtrain_X
が 7:3 で学習用データのtrain_X
とテスト用データのtest_X
に分割されます。train_y
に関しても同様です。
一見すると何してるんかわかりませんが、まぁドキュメント3を見ればなんのことはなかったです。
決定木で予測する
さて、ここからが本番です。ここでは決定木とランダムフォレストの 2 種類で予測しています。まずは決定木です。
#モデルを作成して予測を実行
clf = DecisionTreeClassifier(random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)
#評価
accuracy_score(pred, test_y)
コード自体は単純で、DecisionTreeClassifier()
にぶん投げて終わりみたいなイメージです。
ではそもそも決定木とはどういうものでしょうか。
決定木とは
Qiita の記事4がよくまとまっていたのでそちらから抜粋すると、以下のように書かれていました。
決定木とは木構造を用いて分類や回帰を行う機械学習の手法の一つです
イメージとしてはなんとなくわかります。細かいアルゴリズムについては、今回はスルーします。
やっていること
詳しくはドキュメント5に任せるとして、まずはfit()
で決定木分類機を作ります。で、predict()
で予測を実行します。
…以上です。
なんだが簡単すぎて不安になります…。
正答率はaccuracy_score()
に投げてるだけです。記事内ではroc_curve()
とかauc()
とかも見てますが、今回は関係なさそうだったのでスルーしました。
ランダムフォレストで予測する
ランダムフォレストも決定木とほぼ同じで、実装自体は簡単です。
#モデルを作成して予測を実行
clf = RandomForestClassifier(n_estimators=10, max_depth=5, random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)
#正解率の算出
accuracy_score(pred,test_y)
ランダムフォレストとは
SlideShare の記事6がとてもよかったので引用すると、以下のように書かれていました。
複数の決定木の結果をあわせて識別・回帰・クラスタリングを行う
これは完全に決定木より強そうです。まぁ一概には言えないのでしょうが、素人目で見ると、こちらの方がより高精度に学習できる気がします。
やっていること
こちらもまた詳しくはドキュメント7に任せるのですが、決定木と同様の I/F で、fit()
してpredict()
するだけです。
sklearn 優秀過ぎませんかね…。
テスト用データを予測して出力する
test.csv
を読み込んでここまでと同じ処理をするだけなので割愛します。今回はランダムフォレストの方が結果がよかったのでそちらの結果を使っています。
総括
とりあえず初 Kaggle、初機械学習だったわけですが、どういう感じで進めていけばいいかは掴めました。初めてでもちょっと時間をかければ雰囲気は理解できるものですね(ちなみにもくもく会の時間内では終わらなかったので、家に持ち帰ってます w)。
当面は(おそらく sklearn に組み込まれているであろう)既存の手法の理解を深めつつ引き出しを増やす感じで進めていくのがベターかなという印象です。
慣れてきたら TensorFlow とかも使って深層学習の方にも手を出して行きたいですね。
まず機械学習の第一歩を踏み出してみて、完全ブラックボックスだった部分が多少は見えてきたな、というのは大きな収穫になりました。
P.S.
機械学習系はそれこそ数年前からやりたいと思っていて、本も Udemy も Coursera も詰みまくっているので、ガンガン消化して副業とかで実践スキルをつけられたら今後のキャリアの幅も広がりそうです。
本業で出来れば一番いいんですが、なかなかそういう案件がないのでね…。仮にあったとしても社内には全く知見がないので、立ち上げるには相当苦労しそう。
とりあえず、今年の抱負を 1 ミリぐらいは進められたので優勝です。