Python
機械学習
データ分析
チュートリアル
Kaggle

[Kaggle]0から本当に機械学習を理解するために学ぶべきこと~一流のデータサイエンティストを例に~

「機械学習が出来るようになりたい」そう思いつつも、中々身についた感じがしない。

そんな方々に向けて、Kaggleで公開されているデータ分析の手順を追いかけながら、そこで必要とされている知識を解説したいと思います。全体像を把握することで、より理解が進むはずです。

1. データを分析するために必要な統計的知識

機械学習の目的は未知の事柄を推定することです。そのために既にあるデータから何らかの法則性を見つけ出す為に様々な手法が考えられてきました。

統計学はご存知でしょうか? 機械学習はデータを扱うという点で統計学と深い関係があります。平均値や標準偏差などは聞いたことがあると思います。統計学はそれらの情報をこねくり回すことによって、限られたデータから本当の全体像を推定します。例えば、選挙の結果を開示前に知りたいときに、投票者全員に聞ければ良いですが、そうもいきません。そこで、統計学は様々な方法を使ってそれらを推定します。
 
ここで、予測したいことは「誰が当選するのか」ということです。予測するのに使えるデータは、自分が聞いて回って知ることが出来た各候補者の得票数です。「データの中で一番得票数の多い人が当選!」と、考えるのは最もなのですが、あながちそうでも無いのです。なぜなら、あなたが聞いて回った投票所は候補者Aの支持者が多い場所かもしれません。あなたが聞いて回った時間は候補者Bの支持者が多い時間帯かもしれません。これらのようなデータの偏りをバイアスといいます。統計学ではバイアスに影響を受けないように様々な方法が編み出され、なるべく正しい予測をしようと頑張りました。

機械学習はデータから予測します。元々のデータがおかしなものだと全くうまくいきません。ですから、機械学習を行うにあたって、データの中身を分析することは何よりも大事です。よって、まず学ぶべきことは
データを分析するために必要な統計的知識です。

これを身につけるのにオススメの本はこれ↓

遠回りに感じるかもしれませんが、プログラミングをする前にキーボードの使い方を覚えなければならないようなものです。取り敢えず流し読みでもよいので、概観を把握しておきましょう。後々必ず基礎的な統計学の知識は必要になります。

2. 取り敢えずKaggleを見てみる

2.1 問題の把握

Titanicの乗客で誰が生き残るか?

という問題を予測してみたいと思います。まず、この段階で分かることは分類をするのが目的であるということ。生き残るグループとそうではないグループに乗客を分類する訳です。

機械学習には他にも回帰問題があります。これは数値を推定するということで、例えば地域や間取りなどから住宅の価格を予測する問題があります。

機械学習の概要を知るのにオススメの本はこれ↓

与えられたデータについて見てみると、train setとtest setが与えられていることが分かります。機械学習のモデルを作成するのに使うデータはtrain setです。出来上がったモデルの精度を最後に確かめる為にだけ使うのがtest setです。test set の生き残ったかどうかという「答え」は未知のデータとして置いておかないといけません。なぜなら、このtest setにのみ最適化したモデルを無意識に作ってしまいかねないからです。機械学習の本当の目的は未知のデータの予測ですから、ここは我慢して正しいモデル評価の為に置いておきましょう。今回は元々、test setには答えが無いので安心してデータを見ていきましょう。

また、ある程度のデータの集まりなので、機械学習ではこれらのデータのことを総称してデータセットと呼ぶことが多いです。

2.2 ソースコードを眺める

では、なんとなく掴めたところで、プロの御業を見ていきたいと思います。

Titanic Data Science Solutions

上のURLを開くとJupyter Notebookが開きます。Jupyter Notebookは慣れれば直感的に使えますが、ある程度概要について伝えておくと、普通のpythonプログラムはファイル単位で実行しなければならず、さらに途中でコードを書き換えると初めからやり直しですが、Jupyter Notebookは実行単位を好きに決められるので、一行ずつ試行錯誤しながら実行していくことができるのです。さらに、実行した内容は保持されるので、試行錯誤したい箇所のみ何度も書き換えるだけで済むので、とても楽です。
Notebookを見ていく際に、その区切りに注目すると、書き手の思考の切れ目を感じることができます。文章でいう段落のようなものですね。

KaggleにはJupyter Notebookを公開できるサービスがあり、上記は人気のものの内の一つです。有難い前置きはGoogle翻訳で読んで頂くとして、さっそくコードを見ていきます。

[1] ライブラリのimport

[1]
# data analysis and wrangling
import pandas as pd
import numpy as np
import random as rnd

# visualization
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

# machine learning
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier

はい、1つめから情報過多ですね。

2.2.1 pandasとnumpyとmatplotlib

まずは

#data analysis and wrangling
import pandas as pd
import numpy as np
import random as rnd

から見ていきます。

randomはpythonの標準ライブラリですが、その他2つは違います。
機械学習、データサイエンスの分野では必須のライブラリとして

  • pandas
  • numpy

があります。ここではそれらをimportしています。

酷使するので、pandasとnumpyについては一冊読むことをオススメします↓

# visualization
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

ここでは結果を図示するためのライブラリをインポートしています。matplotlibは非常によく使うので、知っておいてください。

%matplotlib inline

は Jupyter Notebook でのみ有効なもので、ノートブック内に図を描画するように指定しています。これも毎回といっていい程書きます。

2.2.2 scikit-learn

# machine learning
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier

沢山インポートしていますが、これはscikit-learnというこれまた酷使することになるライブラリから、予測するのに用いるアルゴリズムを幾つかインポートしているだけです。機械学習の概要についてある程度知っている人であれば、なんとなく分かっていただけるかと思います。英語なので分かりづらいですが、

  1. LogisticRegression == ロジスティック回帰
  2. SVC(SVM Classification) == SVMをクラス分類に用いる(SVMは回帰もできるので区別する。回帰はSVR: SVM Regression )
  3. RandomForestClassifier == ランダムフォレスト
  4. KneighborsClassifier == k近傍法
  5. GaussianNB == ナイーブベイズのガウス分布版
  6. Perceptron == パーセプトロン
  7. SGDClassifier == 確率的勾配降下(stochastic gradient descent)法を用いてSVMやロジスティック回帰を最適化する方法でクラス分類します。最適化については後で紹介する本に詳しく書かれていますので参照してください。
  8. DecisionTreeClassifier == 決定木

です。

ここまで紹介してきた本を読んで頂ければ、ざっくりとそれぞれの手法については理解していると思います。その状態からscikit-learnを使いこなす為にオススメなのが以下の本↓

[2] CSVファイルからデータセットの取り込み

[2]
train_df = pd.read_csv('../input/train.csv')
test_df = pd.read_csv('../input/test.csv')
combine = [train_df, test_df]

ここでは、csvファイルから読み込んでpandasのDataframeに放り込んでいます。Dataframeもほぼ毎回使うもので、簡単に説明するとエクセルのような表形式でデータセットを扱えるようにするもので、この存在により、Pythonはデータ分析に使われる言語として揺るぎない地位を得ることになりました。

一般的に機械学習で用いるデータセットはcsvファイルに保存されていることが多いです。表形式のテキストなので、扱いが容易いからです。実際にデータを集めてデータセットを作る際には、データベースやWeb上の数値や文字をかき集めて、機械学習で扱いやすいように整形する必要があるので、いろいろなソフトウェアやプログラミング言語で扱いやすいというのは大きな意味を持ちます。

[3] 特徴量を把握する

print(train_df.columns.values)
['PassengerId' 'Survived' 'Pclass' 'Name' 'Sex' 'Age' 'SibSp' 'Parch'
 'Ticket' 'Fare' 'Cabin' 'Embarked']

この段階でデータセットにどんな特徴量(予測するために使えるデータ。性別や客室番号など)があるのかを把握するために、列名を表示しています。

特徴量の性質
特徴量には大きく分けて、カテゴリカルなものと、数値のものがあります。

fig1.png統計WEB: https://bellcurve.jp/statistics/course/1562.html

ここでいう、質的データがカテゴリカルなもので、量的データが数値のものになります。
https://www.kaggle.com/c/titanic/data
で確かめると、今回は以下の通り。
- Categorical: Survived, Sex, and Embarked. Ordinal: Pclass.
- Continous: Age, Fare. Discrete: SibSp, Parch.

[4] データセットをざっと見て考える

# preview the data
train_df.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

このように、一発でデータを見やすく整形して表示してくれます。Pandasの素晴らしさを感じませんか? 見やすいというのは何よりも大事です。実は精度が出るかどうかはこれから行うモデルを作る前の前処理で8割方決まっているといっても良いでしょう。ここがデータサイエンスの肝です。

[5] 欠損値がないか調べる

特徴量にデータタイプが混合してるものがないか?

同じ特徴量内で数値と英数字が混合しているものがあります。このままだと扱いづらいので後々処理をしようと考えます。

  • Ticketは、数字と英数字のデータタイプが混在しています。 Cabinは英数字です。

どの特徴量にエラーや誤植が含まれている可能性があるか?

大規模なデータセットを検討するのは難しいですが、小さなデータセットからいくつかのサンプルを見直すと、どの特徴量を修正する必要があるかがわかります。

  • nameには、Titleや丸括弧、代替名や略称に使用される引用符など、nameを記述するためのいくつかの方法があるため、エラーや誤字が含まれることがあるかもしれません。
train_df.tail()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.00 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.00 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.45 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.00 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.75 NaN Q

どの特徴量に空白やnullやNaNが含まれているか

空白やnullやNaNは欠損値と呼ばれ、データが欠けていることを表しています。これらはそのままだと予測精度に影響するので、何らかの形で訂正する必要があります。

適当に集められたデータを解析する際には、エグい程の欠損値と戦うこともあります。まずは疑ってかかりましょう。

  • train set を詳しく見ると Cabin, Age, Embarked に欠損値があります。
  • test set には Cabin, Age に欠損値があります。

[6] データセットの情報

train_df.info()
print('_'*40)
test_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
________________________________________
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId    418 non-null int64
Pclass         418 non-null int64
Name           418 non-null object
Sex            418 non-null object
Age            332 non-null float64
SibSp          418 non-null int64
Parch          418 non-null int64
Ticket         418 non-null object
Fare           417 non-null float64
Cabin          91 non-null object
Embarked       418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB

なんとpandasには各特徴量について、合計数、nullがあるかどうか、データ型、について教えてくれるメソッドがあります。もうExcelには戻れませんね。

[7] 統計量を確認

データセットでは数値タイプの特徴量の分布はどうなっているのか?

分布を調べることによって、トレーニングデータセットがどのように全体像を代表して表しているか分かります。

トレーニングデータセットはあくまで母集団から抽出してきた標本ですから、予測精度を上げる為にはトレーニングデータセットが母集団の性質を維持して居なければなりません。

もし、日本人(母集団)の平均年収を調べる為にアンケートを取るときに、港区在住の人(標本)のみに回答を依頼したらどうなるでしょうか? 高い値のみが集められ、それらの値の平均をとっても日本人の平均年収とは言えませんよね。
正しく予測するには色々な工夫をして母集団をうまく表現出来るような標本が大事なのです。

  • トレーニングデータセットの合計は891で、タイタニック号に搭乗した乗客の実際の数(2,224)の40%です。
  • Survived は0または1の値を持つカテゴリカル特徴量です。
  • トレーニングデータセットの約38%が実際のSurvived率の32%を代表して生き残りました。
  • ほとんどの乗客(> 75%)は親または子供と一緒に旅行しなかった。
  • 乗客のほぼ30%が兄弟姉妹や配偶者を乗せていました。
  • 運賃は、一部の乗客(< 1%)だけ異常に高く、512ドル払っていました。
  • 65歳から80歳までの高齢者はほとんどいませんでした(< 1%)。
train_df.describe()
# Review survived rate using `percentiles=[.61, .62]` knowing our problem description mentions 38% survival rate.
# Review Parch distribution using `percentiles=[.75, .8]`
# SibSp distribution `[.68, .69]`
# Age and Fare `[.1, .2, .3, .4, .5, .6, .7, .8, .9, .99]`
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200

カテゴリカル特徴量の分布は?

  • nameはデータセット全体で一意です(count = unique = 891)
  • 65%が男性(top=male、freq= 577 / count= 891)
  • 客室の値はいくつかのトレーニングデータで二重になっています。 あるいは、複数の乗客が客室を共有しました。
  • Embarkedは3つの値をとる可能性があるが、 ほとんどの乗客がS港をから乗船しました。(top= S)
  • Ticket特徴量では、重複した値(ユニーク= 681 )の割合(22%)が高い。
train_df.describe(include=['O'])
Name Sex Ticket Cabin Embarked
count 891 891 891 204 889
unique 891 2 681 147 3
top Novel, Mr. Mansouer male 1601 B96 B98 S
freq 1 577 7 4 644

データから考えられる仮説を立てる

これまでに行われたデータ分析に基づいて、以下の仮説を立てました。機械学習の手段を考える前に、これらの仮説をさらに検証する必要があるかもしれません。

相関関係

「Survived」を予測したい(0: 亡くなった, 1: 生存した)ので、各特徴量がどのように「Survived」に相関しているかを知りたいと思っています。プロジェクトの早い段階でこの作業を行い、プロジェクトの後半でモデル化された相関関係と仮説で考えた相関関係を一致させたいと考えています。

欠損値の補完

  1. Survived と明らかに相関があるため、age の欠損値はなんらかの手段を用いて補完する必要があるかもしれません。
  2. Survived または他の重要な特徴量と相関する可能性があるため、Embarked の欠損値も補完する必要があるかもしれません。

修正

  1. 重複率(22%)が高く、TicketとSurvivedの間に相関がない可能性があるため、Ticket特徴量は分析から除外される可能性があります。
  2. Cabin特徴量は、トレーニングデータとテストデータセットの両方で非常に不完全であるか、または多くのnull値を含んでいるため、削除される可能性があります。
  3. Survivedに寄与しないため、PassengerIdはトレーニングデータセットから削除される可能性があります。
  4. nameの特徴量は比較的非標準であり、Survivedに直接貢献しない可能性があります。

作成

  1. ParchとSibSpに基づいてFamilyという新しい特徴量を作成し、家族の総数を取得したい場合があります。
  2. name特徴量を使用して、Titleを新しい特徴量として抽出すると良いかも。
  3. Age 特徴量ではある程度の幅をで表した、新しい特徴量を作成したい場合があります。これは、連続的な数値的特徴量を順序尺度のカテゴリカル特徴量に変えることで扱いやすくするためです。
  4. 分析に役立つ場合は、Ageと同じようにFareの範囲を特徴量として作成することもできます。

予測するのに重要そうな特徴量

また、当時の状況を鑑みると、以下の事柄を仮説に追加するかもしれません。

  1. 女性(性別=女性)は生存していた可能性が高い。
  2. 子供(年齢<?)は生存していた可能性が高い。
  3. 上位クラスの乗客(Pclass = 1)は生き残った可能性が高い。

特徴量の相関を解析する

私たちの見立てと仮説の一部を確認するために、特徴量のSurvivedとの相関関係を分析します。この段階では空の値を持たない特徴量に対してのみ行うことができます。また、カテゴリ(性別)、順序(Pclass)または離散(SibSp、Parch)タイプの特徴量に対してのみ行うことが適切です。

  • Pclass
    Pclass =1 とSurvivedの間に有意な相関(> 0.5)が観察されました。予想した通りだったので、この特徴量をモデルに含めることにしました。

  • 性別 
    女性のSurvived率が74%で非常に高いという仮説が正しいことが確認できました。

  • SibSpとParch 
    これらの特徴は、特定の値では無相関です。これらの個々の特徴量から新しい特徴量または特徴量のセットを作ることが最善の策でしょう。

train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False)
Pclass Survived
0 1 0.629630
1 2 0.472826
2 3 0.242363
train_df[["Sex", "Survived"]].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False)
Sex Survived
0 female 0.742038
1 male 0.188908
train_df[["SibSp", "Survived"]].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived', ascending=False)
SibSp Survived
1 1 0.535885
2 2 0.464286
0 0 0.345395
3 3 0.250000
4 4 0.166667
5 5 0.000000
6 8 0.000000
train_df[["Parch", "Survived"]].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived', ascending=False)
Parch Survived
3 3 0.600000
1 1 0.550847
2 2 0.500000
0 0 0.343658
5 5 0.200000
4 4 0.000000
6 6 0.000000

長くなったので続きは別記事に分けます。

つづきできました!
[part2]0から本当に機械学習を理解するために学ぶべきこと~一流のデータサイエンティストを例に~

完結しました!
[part3]0から本当に機械学習を理解するために学ぶべきこと~0からscikit-learnを使いこなす~