【python】【機械学習】何の知識もない初心者が機械学習をとりあえずやってみる

#前提
私：これまで機械学習を勉強したことも触れたこともない初心者
今後機械学習は知っておくべき技術だし、ちょっと使ってみたいなぁと思った
細かい部分は深堀りせず、とりあえず動く状態を目指して実装してみようというスタンスでやる
（本当に軽い気持ちで、機械学習への心理的ハードルを下げようくらいの気持ちです）

#環境準備
とりあえずpythonでpandasとscikit-learnが使える状態にする
pipでインストールすれば完了のはず…

```
$ pip install pandas
Traceback (most recent call last):
File "/home/myuser/.local/bin/pip", line 7, in <module>
from pip._internal import main
ImportError: No module named 'pip._internal'
```
詳しくはよく分からないけど、とりあえず動かないと話にならないので、
[公式サイト](https://pip.pypa.io/en/stable/installing/)からget-pip.pyをダウンロード

```
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
```

python,python3で実行

```
$ sudo python get-pip.py
$ sudo python3 get-pip.py
```

pipコマンドが使用可能か確認

```
$ pip --version
pip 20.2.4 from /Library/Python/3.7/site-packages/pip (python 3.7)
```

無事pipが使用できる状態になった
これでpandas,scikit-learnをインストールできる
↓インストールできたことを確認

```
$ pip show pandas
Name: pandas
Version: 1.1.4
Summary: Powerful data structures for data analysis, time series, and statistics
Home-page: https://pandas.pydata.org
Author: None
Author-email: None
License: BSD
Location:
Requires: python-dateutil, numpy, pytz
Required-by: 

$ pip show scikit-learn
Name: scikit-learn
Version: 0.23.2
Summary: A set of python modules for machine learning and data mining
Home-page: http://scikit-learn.org
Author: None
Author-email: None
License: new BSD
Location:
Requires: joblib, threadpoolctl, scipy, numpy
Required-by: sklearn
```

#機械学習のおおまかな手順
さらっと調べてみると機械学習はおおまかに以下の流れで進めていくみたい

1. データの入手
2. データの事前処理
3. 手法の選択
4. ハイパーパラメータの選択
5. モデルの学習
6. 評価　（→ 2 or 3 or 4 に戻って試行錯誤）

#Titanic: Machine Learning from Disaster(タイタニック号生存予測)
とりあえず機械学習の入門でよく目にするKaggleのタイタニック号生存予測をやってみる
##データの入手
###Kaggleのサイトから使用するデータをダウンロード
[Kaggleのサイト](https://www.kaggle.com/c/titanic/data?select=train.csv)から以下のデータをダウンロード(データをダウンロードするにはKaggleへのアカウント登録が必要)

+ gender_submission.csv：こんな感じで提出してねっていう例
+ test.csv：テストデータ
+ train.csv：学習用データ

内容を確認するとこんな感じ

```
>>> import pandas as pd
>>> gender_submission = pd.read_csv("./Data/gender_submission.csv")
>>> test = pd.read_csv("./Data/test.csv")
>>> train = pd.read_csv("./Data/train.csv")
>>> 
>>> gender_submission.head(5)
   PassengerId  Survived
0          892         0
1          893         1
2          894         0
3          895         0
4          896         1
>>> test.head(5)
   PassengerId  Pclass                                          Name     Sex   Age  SibSp  Parch   Ticket     Fare Cabin Embarked
0          892       3                              Kelly, Mr. James    male  34.5      0      0   330911   7.8292   NaN        Q
1          893       3              Wilkes, Mrs. James (Ellen Needs)  female  47.0      1      0   363272   7.0000   NaN        S
2          894       2                     Myles, Mr. Thomas Francis    male  62.0      0      0   240276   9.6875   NaN        Q
3          895       3                              Wirz, Mr. Albert    male  27.0      0      0   315154   8.6625   NaN        S
4          896       3  Hirvonen, Mrs. Alexander (Helga E Lindqvist)  female  22.0      1      1  3101298  12.2875   NaN        S
>>> train.head(5)
   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
```

gender_submission(PassengerId, Survived)
test(PassengerId, Pclass, Name, Sex, Age, SibSp, Parch, Ticket, Fare, Cabin, Embarked)
train(PassengerId, Survived, Pclass, Name, Sex, Age, SibSp, Parch, Ticket, Fare, Cabin)

###各列間の相関係数を確認
pandasではcorr()メソッドでデータフレームの各列間の相関係数が求められる
ちなみにcorr()メソッドは引数methodを指定することで３つの算出方法を指定できるらしい
今回はよくわからないのでデフォルトを使用

+ 'pearson' : ピアソンの積率相関係数 ← デフォルト
+ 'kendall' : ケンドールの順位相関係数
+ 'spearman' : スピルマンの順位相関係数

```
>>> train_corr = train.corr()
>>> train_corr
             PassengerId  Survived    Pclass       Age     SibSp     Parch      Fare
PassengerId     1.000000 -0.005007 -0.035144  0.036847 -0.057527 -0.001652  0.012658
Survived       -0.005007  1.000000 -0.338481 -0.077221 -0.035322  0.081629  0.257307
Pclass         -0.035144 -0.338481  1.000000 -0.369226  0.083081  0.018443 -0.549500
Age             0.036847 -0.077221 -0.369226  1.000000 -0.308247 -0.189119  0.096067
SibSp          -0.057527 -0.035322  0.083081 -0.308247  1.000000  0.414838  0.159651
Parch          -0.001652  0.081629  0.018443 -0.189119  0.414838  1.000000  0.216225
Fare            0.012658  0.257307 -0.549500  0.096067  0.159651  0.216225  1.000000
```

###ヒートマップにしてみる
seabornというライブラリを使うと簡単にヒートマップとして可視化できるらしい
やってみよう！

```
>>> import seaborn
>>> import matplotlib as mpl
>>> import matplotlib.pyplot as plt
>>> 
>>> seaborn.heatmap(train_corr,vmax=1, vmin=-1, center=0)
<AxesSubplot:>
>>> plt.show()
```
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130414/17a212d5-d74c-846d-24fd-eca5d7b7368d.png)

なるほど、見やすくなった
たぶんこの中だとPclassとFareの相関が強いってことかな…？
今回予測するSurvivedとはPclassとFareが弱いけど相関がありそう…？

##データの事前処理
NA（欠損値）の補完、SexやEmbarked、Cabinなどの文字列を数値に補正する
今回NAは基本的には平均値を補完するが、Embarkedは最も多い"S"に補完する
Cabinは頭文字のみに補正し（おそらく客室のランクとか表してるよね）、NAは最も多いCに補完

```
>>> train.Embarked.value_counts()
S    644
C    168
Q     77
Name: Embarked, dtype: int64
```
↓使用した補正用関数

```python
def CorrectTitanicData(df):
    #Age : NA -> 平均値
    df.Age = df.Age.fillna(df.Age.median())
    #Sex : male -> 0, female -> 1
    df.Sex = df.Sex.replace(['male', 'female'],[0,1])
    #Embarked : NA -> S, C -> 0, S -> 1, Q -> 2
    df.Embarked = df.Embarked.fillna("S")
    df.Embarked = df.Embarked.replace(['C', 'S', 'Q'], [0, 1, 2])
    #Fare : NA -> 平均値
    df.Fare = df.Fare.fillna(df.Fare.median())
    #Cabin : NA -> C, A~G -> 0~6, T -> 7
    df.Cabin = df.Cabin.fillna('C')
    df.Cabin = df.Cabin.replace('A(.*)','A',regex=True)
    df.Cabin = df.Cabin.replace('B(.*)','B',regex=True)
    df.Cabin = df.Cabin.replace('C(.*)','C',regex=True)
    df.Cabin = df.Cabin.replace('D(.*)','D',regex=True)
    df.Cabin = df.Cabin.replace('E(.*)','E',regex=True)
    df.Cabin = df.Cabin.replace('F(.*)','F',regex=True)
    df.Cabin = df.Cabin.replace('G(.*)','G',regex=True)
    df.Cabin = df.Cabin.replace(['A','B','C','D','E','F','G','T'], [0,1,2,3,4,5,6,7])
    
    return df
```
事前処理後に再度各列間の相関を確認

```
>>> train = CorrectTitanicData(train)
>>> train_corr = train.corr()
>>> train_corr
             PassengerId  Survived    Pclass       Sex       Age     SibSp     Parch      Fare     Cabin  Embarked
PassengerId     1.000000 -0.005007 -0.035144 -0.042939  0.034212 -0.057527 -0.001652  0.012658 -0.035748 -0.017443
Survived       -0.005007  1.000000 -0.338481  0.543351 -0.064910 -0.035322  0.081629  0.257307  0.080643 -0.125953
Pclass         -0.035144 -0.338481  1.000000 -0.131900 -0.339898  0.083081  0.018443 -0.549500  0.009851  0.305762
Sex            -0.042939  0.543351 -0.131900  1.000000 -0.081163  0.114631  0.245489  0.182333  0.070780 -0.022521
Age             0.034212 -0.064910 -0.339898 -0.081163  1.000000 -0.233296 -0.172482  0.096688 -0.032105 -0.040166
SibSp          -0.057527 -0.035322  0.083081  0.114631 -0.233296  1.000000  0.414838  0.159651  0.000224  0.030874
Parch          -0.001652  0.081629  0.018443  0.245489 -0.172482  0.414838  1.000000  0.216225  0.018232 -0.035957
Fare            0.012658  0.257307 -0.549500  0.182333  0.096688  0.159651  0.216225  1.000000 -0.098064 -0.268865
Cabin          -0.035748  0.080643  0.009851  0.070780 -0.032105  0.000224  0.018232 -0.098064  1.000000  0.069852
Embarked       -0.017443 -0.125953  0.305762 -0.022521 -0.040166  0.030874 -0.035957 -0.268865  0.069852  1.000000
>>> 
>>> seaborn.heatmap(train_corr,vmax=1, vmin=-1, center=0)
<AxesSubplot:>
>>> plt.show()
```
![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130414/45cc6704-82f0-4738-ad5e-cec22f038ed5.png)
Sexがより強い相関があることが分かるようになった

##手法の選択
今回予測因子として使用するのは"Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Cabin", "Embarked"の８項目（PassengerID以外）
学習手法としては以下の7つの手法で実施して、交差検証を行う

+ LgisticRegression
+ SVC
+ LinearSVC
+ KNeighbors
+ DecisionTree
+ RandomForest
+ MLPClassifier



```
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.svm import SVC, LinearSVC
>>> from sklearn.neighbors import KNeighborsClassifier
>>> from sklearn.tree import DecisionTreeClassifier
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.neural_network import MLPClassifier
>>> from sklearn.model_selection import cross_val_score
>>> 
>>> predictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Cabin", "Embarked"]
>>> models = []
>>> models.append(("LogisticRegression",LogisticRegression()))
>>> models.append(("SVC",SVC()))
>>> models.append(("LinearSVC",LinearSVC()))
>>> models.append(("KNeighbors",KNeighborsClassifier()))
>>> models.append(("DecisionTree",DecisionTreeClassifier()))
>>> models.append(("RandomForest",RandomForestClassifier()))
>>> models.append(("MLPClassifier",MLPClassifier(solver='lbfgs', random_state=0)))
>>> 
>>> results = []
>>> names = []
>>> 
>>> for name,model in models:
...     result = cross_val_score(model, train[predictors], train["Survived"],  cv=3)
...     names.append(name)
...     results.append(result)
... 

>>> for i in range(len(names)):
...     print(names[i],results[i].mean())
... 
LogisticRegression 0.7811447811447811
SVC 0.6554433221099888
LinearSVC 0.7317620650953985
KNeighbors 0.7070707070707071
DecisionTree 0.7721661054994389
RandomForest 0.7957351290684623
MLPClassifier 0.7901234567901234
```

RandomForestが最もよい評価みたい

##提出用に予測結果をCSVファイルに保存
ランダムフォレストで学習用データを学習し、テストデータで予想を行う。
結果をCSV形式で保存

```
>>> test = pd.read_csv("./Data/test.csv")
>>> test = CorrectTitanicData(test)
>>> algorithm = RandomForestClassifier()
>>> algorithm.fit(train[predictors], train["Survived"])
RandomForestClassifier()
>>> predictions = algorithm.predict(test[predictors])
>>> submission = pd.DataFrame({
...     "PassengerId":test["PassengerId"],
...     "Survived":predictions
... })
>>> submission.to_csv("submission.csv", index=False)
```

###提出結果
せっかくなのでKaggleで提出してみました
結果Scoreは0.74162

![スクリーンショット 2020-11-07 19.28.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130414/81a3aa2b-08d2-b802-92d6-ac485a3f9e30.png)

ここから色々試行錯誤して正答率を上げていきたいけど、今回は一旦ここまで
scikit-learnにはハイパーパラメータを探索するGridSearchCVとかがあるみたいなので、
それを使うともっと正答率があがりそうだなぁ…