「機械学習が出来るようになりたい」そう思いつつも、中々身についた感じがしない。
そんな方々に向けて、Kaggleで公開されているデータ分析の手順を追いかけながら、そこで必要とされている知識を解説したいと思います。全体像を把握することで、より理解が進むはずです。
1. データを分析するために必要な統計的知識
機械学習の目的は未知の事柄を推定することです。そのために既にあるデータから何らかの法則性を見つけ出す為に様々な手法が考えられてきました。
統計学はご存知でしょうか? 機械学習はデータを扱うという点で統計学と深い関係があります。平均値や標準偏差などは聞いたことがあると思います。統計学はそれらの情報をこねくり回すことによって、限られたデータから本当の全体像を推定します。例えば、選挙の結果を開示前に知りたいときに、投票者全員に聞ければ良いですが、そうもいきません。そこで、統計学は様々な方法を使ってそれらを推定します。
ここで、予測したいことは「誰が当選するのか」ということです。予測するのに使えるデータは、自分が聞いて回って知ることが出来た各候補者の得票数です。「データの中で一番得票数の多い人が当選!」と、考えるのは尤もなのですが、あながちそうでも無いのです。なぜなら、あなたが聞いて回った投票所は候補者Aの支持者が多い場所かもしれません。あなたが聞いて回った時間は候補者Bの支持者が多い時間帯かもしれません。これらのようなデータの偏りをバイアスといいます。統計学ではバイアスに影響を受けないように様々な方法が編み出され、なるべく正しい予測をしようと頑張りました。
機械学習はデータから予測します。元々のデータがおかしなものだと全くうまくいきません。ですから、機械学習を行うにあたって、データの中身を分析することは何よりも大事です。よって、まず学ぶべきことは
データを分析するために必要な統計的知識です。
これを身につけるのにオススメの本はこれ↓
機械学習を含むデータサイエンスで使う視点から、統計学を解説した本。
- データサイエンスのための統計学入門 ―予測、分類、統計モデリング、統計的機械学習とRプログラミング
かなり分かりやすく解説されていてかつ、pythonで動かせるのでとてもオススメです。
遠回りに感じるかもしれませんが、プログラミングをする前にキーボードの使い方を覚えなければならないようなものです。取り敢えず流し読みでもよいので、概観を把握しておきましょう。後々必ず基礎的な統計学の知識は必要になります。
近年機械学習を含むデータサイエンスを纏まって学べる書籍が増えてきているので紹介します
統計を含むデータサイエンスを体系的に学べる良書。これ一冊でざっくりと全体像を把握できる。
実際に使う技術が網羅的に紹介されている。時系列や音声、画像など扱いに知識が必要なものへの対処法が一冊で学べる。
2. 取り敢えずKaggleを見てみる
2.1 問題の把握
という問題を予測してみたいと思います。まず、この段階で分かることは分類をするのが目的であるということ。生き残るグループとそうではないグループに乗客を分類する訳です。
機械学習には他にも回帰問題があります。これは数値を推定するということで、例えば地域や間取りなどから住宅の価格を予測する問題があります。
機械学習の概要を知るのにオススメの本はこれ↓
与えられたデータについて見てみると、train setとtest setが与えられていることが分かります。機械学習のモデルを作成するのに使うデータはtrain setです。出来上がったモデルの精度を最後に確かめる為にだけ使うのがtest setです。test set の生き残ったかどうかという「答え」は未知のデータとして置いておかないといけません。なぜなら、このtest setにのみ最適化したモデルを無意識に作ってしまいかねないからです。機械学習の本当の目的は未知のデータの予測ですから、ここは我慢して正しいモデル評価の為に置いておきましょう。今回は元々、test setには答えが無いので安心してデータを見ていきましょう。
また、ある程度のデータの集まりなので、機械学習ではこれらのデータのことを総称してデータセットと呼ぶことが多いです。
2.2 ソースコードを眺める
では、なんとなく掴めたところで、プロの御業を見ていきたいと思います。
上のURLを開くとJupyter Notebookが開きます。Jupyter Notebookは慣れれば直感的に使えますが、ある程度概要について伝えておくと、普通のpythonプログラムはファイル単位で実行しなければならず、さらに途中でコードを書き換えると初めからやり直しですが、Jupyter Notebookは実行単位を好きに決められるので、一行ずつ試行錯誤しながら実行していくことができるのです。さらに、実行した内容は保持されるので、試行錯誤したい箇所のみ何度も書き換えるだけで済むので、とても楽です。
Notebookを見ていく際に、その区切りに注目すると、書き手の思考の切れ目を感じることができます。文章でいう段落のようなものですね。
KaggleにはJupyter Notebookを公開できるサービスがあり、上記は人気のものの内の一つです。有難い前置きはGoogle翻訳で読んで頂くとして、さっそくコードを見ていきます。
##[1] ライブラリのimport
# 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というこれまた酷使することになるライブラリから、予測するのに用いるアルゴリズムを幾つかインポートしているだけです。機械学習の概要についてある程度知っている人であれば、なんとなく分かっていただけるかと思います。英語なので分かりづらいですが、
- LogisticRegression == ロジスティック回帰
- SVC(SVM Classification) == SVMをクラス分類に用いる(SVMは回帰もできるので区別する。回帰はSVR: SVM Regression )
- RandomForestClassifier == ランダムフォレスト
- KneighborsClassifier == k近傍法
- GaussianNB == ナイーブベイズのガウス分布版
- Perceptron == パーセプトロン
- SGDClassifier == 確率的勾配降下(stochastic gradient descent)法を用いてSVMやロジスティック回帰を最適化する方法でクラス分類します。最適化については後で紹介する本に詳しく書かれていますので参照してください。
- DecisionTreeClassifier == 決定木
です。
ここまで紹介してきた本を読んで頂ければ、ざっくりとそれぞれの手法については理解していると思います。その状態からscikit-learnを使いこなす為にオススメなのが以下の本↓
- Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎
実践よりの本で、Tensorflowなどの解説はありませんが、各手法の利点や欠点などを分かりやすく解説しています。
- Python機械学習クックブック
最新の初級者向けの本。実際に機械学習に取り組む際に知っておきたい思考の引き出しがズラリと並んでいる。
この本の内容をスラスラと行うことができればもう前処理で悩むこともなくなると思います。
- scikit-learnとTensorFlowによる実践機械学習
これ一冊あればかなりの範囲をカバー出来ます。理論と実践がバランスよく書かれています。
- 統計的学習の基礎 ―データマイニング・推論・予測―
「アルゴリズムの詳細は? 数式ないと信用できないよ」という方や「最前線で戦える研究者並の知識が欲しい」という方にオススメなのはこれです。(原著は無料: https://web.stanford.edu/~hastie/ElemStatLearn/)
[2] CSVファイルからデータセットの取り込み
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']
この段階でデータセットにどんな特徴量(予測するために使えるデータ。性別や客室番号など)があるのかを把握するために、列名を表示しています。
特徴量の性質
特徴量には大きく分けて、カテゴリカルなものと、数値のものがあります。
ここでいう、質的データがカテゴリカルなもので、量的データが数値のものになります。
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」に相関しているかを知りたいと思っています。プロジェクトの早い段階でこの作業を行い、プロジェクトの後半でモデル化された相関関係と仮説で考えた相関関係を一致させたいと考えています。
欠損値の補完
- Survived と明らかに相関があるため、age の欠損値はなんらかの手段を用いて補完する必要があるかもしれません。
- Survived または他の重要な特徴量と相関する可能性があるため、Embarked の欠損値も補完する必要があるかもしれません。
修正
- 重複率(22%)が高く、TicketとSurvivedの間に相関がない可能性があるため、Ticket特徴量は分析から除外される可能性があります。
- Cabin特徴量は、トレーニングデータとテストデータセットの両方で非常に不完全であるか、または多くのnull値を含んでいるため、削除される可能性があります。
- Survivedに寄与しないため、PassengerIdはトレーニングデータセットから削除される可能性があります。
- nameの特徴量は比較的非標準であり、Survivedに直接貢献しない可能性があります。
作成
- ParchとSibSpに基づいてFamilyという新しい特徴量を作成し、家族の総数を取得したい場合があります。
- name特徴量を使用して、Titleを新しい特徴量として抽出すると良いかも。
- Age 特徴量ではある程度の幅をで表した、新しい特徴量を作成したい場合があります。これは、連続的な数値的特徴量を順序尺度のカテゴリカル特徴量に変えることで扱いやすくするためです。
- 分析に役立つ場合は、Ageと同じようにFareの範囲を特徴量として作成することもできます。
予測するのに重要そうな特徴量
また、当時の状況を鑑みると、以下の事柄を仮説に追加するかもしれません。
- 女性(性別=女性)は生存していた可能性が高い。
- 子供(年齢<?)は生存していた可能性が高い。
- 上位クラスの乗客(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を使いこなす~