[ふわっと機械学習] 1.Titanicの生存者予測

## はじめに
ようやく重い腰を上げて手を動かす気になったので分野を問わず気の向くままふわっと機械学習に触れてみようと思います。記念すべき第一回はKaggle(データ分析の問題を解くコンテスト)の有名なチュートリアル[Titanic - Machine Learning from Disaster](https://www.kaggle.com/competitions/titanic)を取り上げます。有名な問題なのでちょっとググればたくさん解き方が出てきます。先人の知恵を拝借しつつゆるゆる取り組んでみましょう。Pythonを使います。

下記シリーズを参考にしています。とても丁寧です！
[Kaggle入門「タイタニックの生存予測」メダリストと一緒に解説！](https://youtu.be/-QuM_QAC748?si=zqD9OURNzXzYQ7m-)

出来上がるコードはこんな感じ。
```python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# トレーニングデータの読み込みと前処理
train = pd.read_csv('train.csv')
X = train[['Pclass', 'SibSp', 'Parch', 'Sex', 'Fare']]
Y = train['Survived']
X = pd.get_dummies(X, columns=['Pclass', 'Sex'])
X['Fare'] = X['Fare'].fillna(X['Fare'].mean())

# トレーニングデータを訓練セットと検証セットに分割
X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size=0.2, random_state=0)

# スケーリング
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)

# モデルのトレーニング
knn = KNeighborsClassifier()
knn.fit(X_train_scaled, Y_train)

# 検証セットでのモデル評価
Y_val_pred = knn.predict(X_val_scaled)
val_accuracy = accuracy_score(Y_val, Y_val_pred)
print(f"Validation Accuracy: {val_accuracy}")

# テストデータの読み込みと前処理
test = pd.read_csv('test.csv')
X_test = test[['Pclass', 'SibSp', 'Parch', 'Sex', 'Fare']]
submit = test[['PassengerId']]
X_test = pd.get_dummies(X_test, columns=['Pclass', 'Sex'])
X_test['Fare'] = X_test['Fare'].fillna(X_test['Fare'].mean())

# トレーニングデータと同じダミー変数列を持つように調整
X_test = X_test.reindex(columns=X_train.columns, fill_value=0)

# スケーリング
X_test_scaled = scaler.transform(X_test)

# 全トレーニングデータで最終モデルをトレーニング
knn_final = KNeighborsClassifier()
X_train_full_scaled = scaler.fit_transform(X)
knn_final.fit(X_train_full_scaled, Y)

# テストデータでの予測
submit['Survived'] = knn_final.predict(X_test_scaled)

print(submit)
```
コメントを付した通りですが、ひとつひとつ見ていきましょう。

## 基本情報チェック(※完成コードにはありません)
基本情報を確認します。とりあえずデータを読み込みましょう。
```python
# pandas入れる -> データ読み込む
import pandas as pd
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
```
中身をチェックして、nullがあるかを見ます。
```python
# trainの中身をチェック -> nullの数をカウント
train.dtypes
train.isnull().sum()
# testの中身をチェック -> nullの数をカウント
train.dtypes
train.isnull().sum()
```
たとえばtrainのデータはこんな感じでした。
| データ名    | 意味                   | データ型| null数 |
|:-----------:|:---------------------:|:------:|:-------:|
| PassengerId |乗客番号               | int64  | 0       |
| Survived    |生存                   | int64  | 0       |
| Pclass      |チケットクラス          | int64  | 0       |
| Name        |名前                   | object | 0       |
| Sex         |性別                   | object | 0       |
| Age         |年齢                   | float64| 177     |
| SibSp       |乗船した配偶者・兄弟の数| int64  | 0       |
| Parch       |乗船した親・子の数      | int64  | 0       |
| Ticket      |チケット番号           | object | 0       |
| Fare        |運賃                   | float64| 0       |
| Cabin       |客室番号               | object | 687     |
| Embarked    |乗船港                 | object | 2       |

## データ加工
上の表を見て、数値じゃない項目やnullがあることがわかります。これらの項目を捨てるのはもったいないのでなんとか数値にして使いたい。データ分析をしていると数値で表せる量的データ（身長とか体重とか）と分類を表す質的（カテゴリカル）データ（性別とか職業とか）に出くわしますが、質的データを処理する方法としてワンホットエンコーディングなるものがあります。
#### ワンホットエンコーディング
たとえば性別は数値ではないですが次のようにすれば数値として扱うことができます。
男, 女 -> (男である, 女である) = (1,0) or (0,1)
つまり、カテゴリの数のサイズで、該当の要素のみ1ほかが0になるようなベクトルのイメージです。
|   |性別| Sex_female | Sex_male|
|:-:|:--:|:----------:|:-------:|
| 1 | 男 | 0          | 1       |
| 2 | 女 | 1          | 0       |
| 3 | 女 | 1          | 0       |
| 4 | 女 | 1          | 0       |

`get_dummies`関数をつかうと一撃でワンホットコーディングしてくれます。
ということでこんな感じ。
```python
# トレーニングデータの読み込みと前処理
train = pd.read_csv('train.csv')
X = train[['Pclass', 'SibSp', 'Parch', 'Sex', 'Fare']]
Y = train['Survived']
X = pd.get_dummies(X, columns=['Pclass', 'Sex'])
```
#### 欠損値の補完
つぎはnullまみれのFareをなんとかします。
fillna関数を使って埋めましょう。平均値で埋めます。
```python
X['Fare'] = X['Fare'].fillna(X['Fare'].mean())
```

#### 訓練データをさらに訓練データと検証データに分割
モデルの性能を評価するために、トレーニングデータをさらに分割して検証データセットを作成します。
今回は初めから訓練データ(train.csv)と検証データ(test.csv)に分けてくれているのでこの作業は別に要らない気もしますが、まあ練習ということで。random_stateは固定値を与えればなんでもいいです。与えないと訓練用と検証用の振り分け方が毎回変わり、再現性に影響します。
```python
# トレーニングデータを訓練セットと検証セットに分割
X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size=0.2, random_state=0)
```
#### スケーリング
特徴量のスケール（値の範囲）が大きく異なる場合、スケールの大きい特徴量が機械学習モデルに与える影響が大きくなり、スケールの小さい特徴量が無視されてしまうことがあります。速度や精度上昇に寄与します。
```python
# スケーリング
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)
```
`fit_transform`関数は、訓練データに対して初めてフィッティングと変換を行うために使用し、`transform`関数は、すでにフィッティングされたスケーラーを用いて新しいデータを変換するときに使用します。これにより、訓練データとテストデータが同じ基準で標準化されます。

## トレーニング
今回はシンプルなK-近傍法を用います。
ざっくりいえば、ご近所さんを調べて参加チームを決めるイメージです。
引っ越し先のご近所さんに犬好きが多ければあなたも犬好きになります。
```python
# モデルのトレーニング
knn = KNeighborsClassifier()
knn.fit(X_train_scaled, Y_train)
```
コードばかりで何が起きているのかわからなくなってきたのでいったん整理。
`x_train_scaled`は各乗客の特徴量が入った行列（つまり生死を知る手がかりのデータ）です。`Y_train`は各乗客の生死を示すベクトル（つまり答え）です。
`knn.fit`によって、「手がかり」から「答え」を推論するようなハッピーなデータを生成します。（これがいわゆる機械学習の「モデル」）
ハッピーなデータを手に入れたので、予想->答え合わせをしてみましょう。
```python
# 検証セットでのモデル評価
Y_val_pred = knn.predict(X_val_scaled)
val_accuracy = accuracy_score(Y_val, Y_val_pred)
print(f"Validation Accuracy: {val_accuracy}")
```
## テストデータを使って提出用の予想を作る
モデルができたので、あとはこれを使って提出用のデータを作るだけです。
```python
# テストデータの読み込みと前処理
test = pd.read_csv('test.csv')
X_test = test[['Pclass', 'SibSp', 'Parch', 'Sex', 'Fare']]
submit = test[['PassengerId']]
X_test = pd.get_dummies(X_test, columns=['Pclass', 'Sex'])
X_test['Fare'] = X_test['Fare'].fillna(X_test['Fare'].mean())

# トレーニングデータと同じダミー変数列を持つように調整
X_test = X_test.reindex(columns=X_train.columns, fill_value=0)

# スケーリング
X_test_scaled = scaler.transform(X_test)

# 全トレーニングデータで最終モデルをトレーニング
knn_final = KNeighborsClassifier()
X_train_full_scaled = scaler.fit_transform(X)
knn_final.fit(X_train_full_scaled, Y)

# テストデータでの予測
submit['Survived'] = knn_final.predict(X_test_scaled)
submit.to_csv('submit.csv', index=False)
print(submit)
```
submit.csvをKaggleに提出して完了です！
Scoreは0.763くらいでした。
今回はK近傍法でモデルを構築しましたが、参考動画ではランダムフォレストだったりいろいろなアルゴリズムを試していました。
まだまだ奥深そうですが雰囲気はつかめた気がします。
では。
※！記事に嘘が混じってたらコメントお願いします！※