機械学習【ラビットチャレンジ】

#0 はじめに
ラビットチャレンジを受講した際に提出が必要となるレポート記事である。

#1 機械学習
##機械学習モデリングプロセス
1.問題設定
2.データ選定
3.データの前処理
4.機械学習モデルの選定
5.モデルの学習
6.モデルの評価

##1.1 学習種類
（E資格範囲のみ）
###1.1.1 教師あり学習
####1.1.1.1 予測
線形回帰・非線形回帰
####1.1.1.2 分類
ロジスティック回帰
最近傍・K近傍アルゴリズム
サポートベクターマシン
###1.1.2 教師なし学習
####1.1.2.1 クラスタリング
K-meansアルゴリズム
####1.1.2.2 次元削減
主成分分析

##1.2 線形回帰モデル
###1.2.1 線形とは
比例関係のこと。

```math
y = Ax + B(2次元)\\
z = Ax + By + C(3次元)
```

n次元空間における超平面の方程式は以下となる。

```math
\begin{eqnarray}
y = a_{0} + a_1 x_1 + a_2 x_2 + \cdots + a_{n-1}x_{n-1}\\
&=a_{0} + \sum_{i}^{n-1}a_i x_i　(a_ = a_0 \times 1 = a_0 x_0)\\
&= \sum_{i = 0}^{n-1}a_i x_i (x_0 = 1)\\
&= \vec{a}^T \vec{x}\\
\vec{a} = 
\begin{pmatrix}
a0\\
a1\\
\vdots\\
a_{n-1}
\end{pmatrix}\\
\vec{a}^T = 
\begin{pmatrix}
a_0 & a_1 & \cdots & a_{n-1}
\end{pmatrix}\\
\vec{x} = 
\begin{pmatrix}
x_0\\
x_1\\
\vdots\\
x_{n-1}
\end{pmatrix}
\end{eqnarray}
```

##1.3 線形回帰モデル
回帰問題を解くための機械学習モデルのひとつ
教師あり学習
入力とm次元パラメータの線形結合を出力するモデル
　→予測値にはハットをつける
###1.3.1 回帰問題
入力から出力を予測する問題のこと。
直線での予測（1次関数）は線形回帰であり、
曲線での予測（1次関数以外）は非線形回帰である。
難しい問題（回帰問題）を解いて、簡単な問題（ランキング問題）を解決すべきではない。
###1.3.2 回帰で扱うデータ
####1.3.2.1 入力（各要素を説明変数または特徴量とよぶ）
・m次元のベクトル（m=1の場合はスカラー）

```math
\boldsymbol{x} =(x_1, x_2, \cdots, x_m)^T \in \mathbb{R}\\
\mathbb{R}は実数
```

出力（目的変数）
　・スカラー値（目的変数）

```math
\boldsymbol{y} \in \mathbb{R^1}
```

###1.3.3 線形結合

```math
\hat{y} = \boldsymbol{w^T}\boldsymbol{x} + w0 = \sum_{j=1}^{m}x_j w_j + w_0
```

###単回帰モデル（説明変数が1次元(m=1)）

```math
y = w_0 +w_1x_1 + \epsilon
```

###1.3.4 重回帰モデル（説明変数が多次元の場合(m>1)）

```math
y = w_0 +w_1x_1 + w_2x_2 + \epsilon
```

##1.4 データの分割
###1.4.1 なぜ分割するのか
汎化性能(Generalization)の測定のため。
用意されたデータへの当てはまりだけでなく、未知のデータに対しての性能を測りたいため。

##1.5 最小二乗法
###1.5.1 平均二乗誤差（残差平方和）
予測値と実データの二乗誤差の和のこと。
###1.5.2 最小二乗法
平均二乗誤差が最小となるような出力を出すための手法


##1.6 非線形回帰モデル
$y = w_0 +w_1x_1 + \cdots$の$x$の代わりに$x$の関数$φ(x)$を用いるイメージ。

###1.6.1 基底展開法
基底関数と呼ばれる非線形関数とパラメータベクトルの線形結合したものを回帰係数として利用する。
未知パラメータは線形回帰モデルと同様に求める。
（最小二乗法、最尤法）
よく利用される基底関数は以下の通り。
・多項式関数
・ガウス型基底関数
・スプライン関数/Bスプライン関数

###1.6.2 未学習と過学習
機械学習では、データを学習データと検証データの2つに分ける。
####1.6.2.1 未学習
学習データに対しての誤差が小さくならないモデルのこと。
対策
・表現力の高いモデルを利用する。
####1.6.2.2過学習
学習データに対しての誤差が小さくなるが、検証データに対しての誤差が小さくならないモデルのこと。
対策
・学習データの数を増やす。
・不要な変数を削除する。
・正則化法を利用して表現力を抑える。

###1.6.3 正則化（罰則化）
モデルの複雑さに伴い、値が大きくなる正則化項（罰則化項）を課した関数を最小化すること。
####1.6.3.1 Ridge回帰
正則化項にL2ノルム（ユークリッド距離）を利用したもののこと。
なめらかな関数になる。円のイメージ。
####1.6.3.2 Lasso回帰
正則化項にL1ノルム（マンハッタン距離）を利用したもののこと。
スパース推定ともよばれる。四角のイメージ。

###1.6.3 汎化性能測定手法
ホールドアウト法よりもクロスバリデーション法の結果を優先する。
####1.6.3.1 ホールドアウト法
データを学習用とテスト用に分割し、測定する手法のこと。
だいたい7:3か8:2ぐらいに分割してるイメージ。
####1.6.3.2 クロスバリデーション法（交差検証法）
データを学習用とテスト用に分割し、それを分割した数だけ繰り返し、それぞれ作成されたモデルに対し、テストデータを与え、平均値を算出する手法のこと。

##1.7 ロジスティック回帰モデル
教師あり学習の分類問題に扱われるモデルのこと。
###1.7.1 シグモイド関数
出力が0-1の値になる単調増加関数のこと。
パラメータαの値によって関数の形が変化する。

```math
\sigma(x) = \frac{1}{1 + e^{-\alpha x}}\\
\sigma '(x) = \alpha \sigma(x) (1 - \sigma(x))
```

###1.7.2 最尤推定
尤度関数（データが固定で、パラメータを変化させる関数）が最大となるようなパラメータを選ぶ推定方法のこと。

```math
P(y_1, y_2, \cdots, y_n|w_0, w_1, \cdots, w_m) = \prod_{i=1}^{n} p_i ^{y_i}(1 - p_i)^{1-y_i}\\
=\prod_{1=1}^{n} \sigma(w^T x_i)^{y_i}(1 - \sigma(w^T x_i))^{1-y_i}\\
=L(w)
```

###1.7.3 勾配降下法
反復学習によりパラメータを逐次的に更新するアプローチの一つ。
パラメータが更新されなくなった場合、勾配が0になったということであり、反復学習の範囲では最適解が求められたことになる。
ただし、大域最適解でなく、局所最適解に陥っている可能性がある。

###1.7.4 確率的勾配降下法（SGD）
データを一つずつランダム（確率的）に選んでパラメータを更新する手法のこと。
データを少量しか見ないため、勾配降下法で問題となるメモリ容量や計算時間の問題が解決される。

```math
w(k + 1) = w^k + \eta (y_i - p_i)x_i
```

##1.8 主成分分析
多変量のデータを持つ構造をより少数子の指標に圧縮する手法のこと。（次元削減）
変数を減らすことによる情報の損失はなるべく小さくする。データを2次元や3次元に圧縮できることから、可視化が実現可能。
情報を多くもつ特徴を抽出することとも捉えられる。
情報の量を分散の大きさと捉え、線形変換後の変数の分散が最大となるような射影軸を探索する。
極論射影軸上にデータが全部のっていてれ損失はないよねという話だと思う。
分散共分散行列を計算し、固有値問題を解く。求めた固有値の大きい順から第1主成分、第2主成分・・・第k主成分となる。
###1.8.1 寄与率
第k主成分の分散の全分散に対する割合のこと。

```math
c_k = \frac{\lambda_k}{\lambda_1 + \lambda_2 + \cdots + \lambda_m}= \frac{\lambda_k}{\sum_{i=1}^m \lambda_i}
```

###1.8.2 累積寄与率
第1主成分から第k主成分まで圧縮した際に生じる情報の損失量の割合のこと。

```math
c_k = \frac{\lambda_1 + \lambda_2 + \cdots + \lambda_k}{\lambda_1 + \lambda_2 + \cdots + \lambda_k + \cdots + \lambda_m}= \frac{\sum_{j=1}^k \lambda_j}{\sum_{i=1}^m \lambda_i}
```

##1.9 k近傍法（k-NN）
教師あり学習の一つであり、分類問題のための機械学習手法である。
最近傍の（一番距離が近い）データをk個とってきて、それらが最も多く属するクラスに分類する。
kがハイパーパラメータとなり、kの値次第で結果が変わる。また、kを大きくすると分類の決定境界は滑らかになり、ノイズの影響を低減することもできる。
特にk=1の場合を最近傍法といい、最も近いデータと同じクラスに分類することになる。

##1.10 k-平均法（k-means）
教師なし学習の一つであり、クラスタリングによる機械学習手法である。
与えられたデータをハイパーパラメータであるk個のクラスタに分類することができる。
k-meansのアルゴリズムは以下の手順で行われる。
1. 各クラスタの中心の初期値を設定する。
2. 各データ点に対して、各クラスタの中心との距離を計算し、最も距離が近いクラスタに分類する。
3. 各クラスタの平均ベクトル（中心）を計算する。
4. 収束するまで、2-3の処理を繰り返す。
特徴として、中心の初期値（手順：1）によってクラスタリング結果が変化しうる。初期値が離れているとうまくクラスタリングでき、初期値が近いとうまくクラスタリングできない。また、k-NNと同じでkの値によってクラスタリング結果も変わりうる。
k-NNと同じ点は距離を計算していることである。

##1.11 サポートベクターマシン（SVM）
2クラス分類問題のために考案された（教師あり学習の分類問題）手法であるが、回帰問題や教師なし学習にも応用されている手法である。ハードマージンと呼ばれる線形サポートベクトル分類と、完璧に分類できない場合に利用されるソフトマージンと呼ばれる分類手法がある。
###1.11.1 サポートベクトル
境界線に最も近いデータ点のことをサポートベクトルという。このサポートベクトルと決定境界との距離をマージンといい、SVMではこのマージンが最大になるように境界を決定する。
###1.11.2 決定関数
決定関数は以下で表される。
また、決定関数の正負によって2クラスに分類される。

```math
f(x) = wx + b
```

###1.11.3 カーネルトリック
線形分類できない場合、特徴ベクトルを非線形返還し、その空間で線形分類を行うことでうまく分類できないケースを解くことができる。
その変換の際にカーネル関数を用いる。
###1.11.4 カーネル関数
カーネル関数は以下で表される。

```math
K(x_i, x_j) = \phi(x_i)^T \phi(x_j)
```

以下に代表的な関数系を示す。

・多項式カーネル$K(X_i, x_j) = \begin{bmatrix} x_i^Tx_j + c \end{bmatrix}^d $
・ガウスカーネル$K(X_i, x_j) = exp(- \gamma \begin{Vmatrix} x_i - x_j \end{Vmatrix}^2 )$
・シグモイドカーネル $K(X_i, x_j) = tanh(bx_i^Tx_j + c)$

#2 機械学習のハンズオン
##2.1 線形回帰モデルのハンズオン
必要モジュールとデータをインポートする。

```php:regression
from sklearn.datasets import load_boston
from pandas import DataFrame
import numpy as np

boston = load_boston()
```

データの中身を確認する。

```php:regression
print(boston)
print(boston['DESCR'])
print(boston['feature_names'])
print(boston['data'])
print(boston['target'])
```

説明変数、目的変数をDataFrameへ追加し、最初の5行を表示

```php:regression
df = DataFrame(data=boston.data, columns = boston.feature_names)
df['PRICE'] = np.array(boston.target)
df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/96db7c58-cf73-a3bf-b03d-622db33c9bb9.png)

CHASが全て0.0だったのでもう少し表示

```php:regression
df.head(20)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/a8af07c6-3754-98da-5984-3ec6c7dc6a45.png)

全て0.0な気がするのでそのまま進める。
説明変数、目的変数を代入する。

```php:regression
data = df.loc[:, ['RM']].values
target = df.loc[:, 'PRICE'].values
```
###2.1.1 単回帰分析
LinearRegressionをインポートし、オブジェクト生成、パラメータ推定、予測まで行う。

```php:regression
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(data, target)
model.predict([[1]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/9e5e13a6-2deb-f0cb-4f35-f710a0e3fcc7.png)


###2.1.2 重回帰分析
今度は説明変数に犯罪率と部屋数を代入する。

```php:regression
data2 = df.loc[:, ['CRIM', 'RM']].values
target2 = df.loc[:, 'PRICE'].values
```

単回帰と同様に予測まで行う。

```php:regression
model2 = LinearRegression()
model2.fit(data2, target2)
model2.predict([[0.2, 10]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/826fe4f0-b842-c240-e02d-95248e71b218.png)

課題にある部屋数4、犯罪率0.3を試してみる。

```php:regression
model2.predict([[0.3, 4]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/c41326dd-66eb-55d5-2621-3910a2976d5c.png)

部屋数の値で大きく値が変わりそうなので部屋数だけ元に戻してみる。

```php:regression
model2.predict([[0.3, 10]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/85d92ba9-9f58-c09b-6fc0-529d792c37ab.png)

犯罪率はあまり影響していないことがわかった。

##2.2 非線形回帰のハンズオン
諸々初期設定

```php:nonlinear_regression
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

#seaborn設定
sns.set()
sns.set_style("darkgrid", {'grid.linestyle': '--'})
sns.set_context("paper")

n=100

def true_func(x):
    z = 1-48*x+218*x**2-315*x**3+145*x**4
    return z 

def linear_func(x):
    z = x
    return z 
```

データを生成してノイズを加える

```php:nonlinear_regression
data = np.random.rand(n).astype(np.float32)
data = np.sort(data)
target = true_func(data)

noise = 0.5 * np.random.randn(n) 
target = target  + noise


plt.scatter(data, target)

plt.title('NonLinear Regression')
plt.legend(loc=2)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/c1000bfc-0d49-7a33-207b-07473f0ce6d5.png)

線形回帰だと無理そうだけど、非線形回帰であれば予測できそうなグラフがプロットされた。
実際に線形回帰で無理そうだということを確認する。

```php:nonlinear_regression
from sklearn.linear_model import LinearRegression

clf = LinearRegression()
data = data.reshape(-1,1)
target = target.reshape(-1,1)
clf.fit(data, target)

p_lin = clf.predict(data)

plt.scatter(data, target, label='data')
plt.plot(data, p_lin, color='darkorange', marker='', linestyle='-', linewidth=1, markersize=6, label='linear regression')
plt.legend()
print(clf.score(data, target))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/7ae505c2-c8a9-510f-0806-3882552a899b.png)

まあ無理だよね...
非線形回帰を実装してみる。

```php:nonlinear_regression
from sklearn.kernel_ridge import KernelRidge

clf = KernelRidge(alpha=0.0002, kernel='rbf')
clf.fit(data, target)

p_kridge = clf.predict(data)

plt.scatter(data, target, color='blue', label='data')

plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/a620e545-9a25-a808-0556-9f1d895bce81.png)
結構いい感じにできてるんじゃなかろうか。
ふとKernelRidgeのalpha（正則化の強さ？）をデフォルトにすると何が変わるのかが気になったので試してみた。

```php:nonlinear_regression
from sklearn.kernel_ridge import KernelRidge

clf = KernelRidge(alpha=1.0, kernel='rbf')
clf.fit(data, target)

p_kridge = clf.predict(data)

plt.scatter(data, target, color='blue', label='data')

plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/378e8ec3-969a-17f8-1339-0c539c64090d.png)
罰則が強すぎるのか未学習状態になってしまった。
小さくすると過学習状態になるのかも確認してみた。

```php:nonlinear_regression
from sklearn.kernel_ridge import KernelRidge

clf = KernelRidge(alpha=0.000001, kernel='rbf')
clf.fit(data, target)

p_kridge = clf.predict(data)

plt.scatter(data, target, color='blue', label='data')

plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/3f1fe8b6-afbf-6c66-aa58-f72b64a1d1fa.png)
正則化項の違いでこんなに違うことが確認できた。

##2.3 ロジスティック回帰のハンズオン
毎度毎度の初期設定
google colab慣れてないせいで少し戸惑ったのは秘密

```php:logistic_regression
from google.colab import drive
drive.mount('/content/drive')

import pandas as pd
from pandas import DataFrame
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

titanic_df = pd.read_csv('/content/drive/MyDrive/study_ai_ml_google/data/titanic_train.csv')

titanic_df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/bd429700-c823-e205-e3d3-9b661eb73af2.png)

予測に必要なさそうなデータをドロップしたり、nullを中央値で保管したりする。
Ageに直接上書きするんじゃなくて、AgeFillっていう列を作るよ。

```php:logistic_regression
titanic_df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)
titanic_df['AgeFill'] = titanic_df['Age'].fillna(titanic_df['Age'].mean())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/13b201fa-e2c5-fe54-863b-a330d0aa4439.png)

チケット価格から生死の判別をしてみる。

```php:logistic_regression
data1 = titanic_df.loc[:, ["Fare"]].values
label1 =  titanic_df.loc[:,["Survived"]].values
from sklearn.linear_model import LogisticRegression
model=LogisticRegression()
model.fit(data1, label1)
model.predict([[100]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/3a14f4c6-c810-3907-1e21-f0fc801ec1fc.png)

運賃100だと生き残ったけど減らしたらどうなる？

```php:logistic_regression
model.predict([[60]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/23fc4c78-fb56-4c62-4cf7-1d9f219875cf.png)

60まで運賃を下げると生き残れなくなっちゃうらしい。
確率も出してみよう。

```php:logistic_regression
model.predict_proba([[100]])
model.predict_proba([[60]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/8b7d37e7-acbd-6804-8f2a-4eaf1c5f0db4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/f24af116-0239-9d7a-798d-267d11febdda.png)
60ぐらいで生死の確率がひっくり返ることがわかった。

```
X_test_value = model.decision_function(data1) 

w_0 = model.intercept_[0]
w_1 = model.coef_[0,0]

def sigmoid(x):
    return 1 / (1+np.exp(-(w_1*x+w_0)))

x_range = np.linspace(-1, 500, 3000)

plt.figure(figsize=(9,5))
plt.legend(loc=2)

plt.plot(data1,np.zeros(len(data1)), 'o')
plt.plot(data1, model.predict_proba(data1), 'o')
plt.plot(x_range, sigmoid(x_range), '-')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/3b0aec2d-9e66-464d-a52a-197e3b5c8585.png)
図示したらだいたいそれぐらいなのがわかるよね。
次は2変数で予測してみよう。せっかくだからnull埋めしたAgeFill使おうね。

```php:logistic_regression
titanic_df['Gender'] = titanic_df['Sex'].map({'female': 0, 'male': 1}).astype(int)
titanic_df['Pclass_Gender'] = titanic_df['Pclass'] + titanic_df['Gender']
titanic_df = titanic_df.drop(['Pclass', 'Sex', 'Gender','Age'], axis=1)

np.random.seed = 0

xmin, xmax = -5, 85
ymin, ymax = 0.5, 4.5

index_survived = titanic_df[titanic_df["Survived"]==0].index
index_notsurvived = titanic_df[titanic_df["Survived"]==1].index

from matplotlib.colors import ListedColormap
fig, ax = plt.subplots()
cm = plt.cm.RdBu
cm_bright = ListedColormap(['#FF0000', '#0000FF'])
sc = ax.scatter(titanic_df.loc[index_survived, 'AgeFill'],
                titanic_df.loc[index_survived, 'Pclass_Gender']+(np.random.rand(len(index_survived))-0.5)*0.1,
                color='r', label='Not Survived', alpha=0.3)
sc = ax.scatter(titanic_df.loc[index_notsurvived, 'AgeFill'],
                titanic_df.loc[index_notsurvived, 'Pclass_Gender']+(np.random.rand(len(index_notsurvived))-0.5)*0.1,
                color='b', label='Survived', alpha=0.3)
ax.set_xlabel('AgeFill')
ax.set_ylabel('Pclass_Gender')
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.legend(bbox_to_anchor=(1.4, 1.03))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/315c0011-80d4-bc1b-bb12-3cceb4f4f5b3.png)

課題になってる30歳は一応生き残れそうな気がするよね。ただPclass+Genderの値が大きいほど生き残れなくなることもわかる。

```php:logistic_regression
data2 = titanic_df.loc[:, ["AgeFill", "Pclass_Gender"]].values
label2 = titanic_df.loc[:,["Survived"]].values
model2 = LogisticRegression()
model2.fit(data2, label2)
model2.predict([[10,1]])
model2.predict_proba([[10,1]])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/72010dfe-6998-5ce0-bd40-af2ad8bbf6d2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/4933d80a-398d-d77b-4508-c8b07e2dfd1a.png)

Pclass+Gender1の10歳は96%生き残れるよ。
課題の30歳男性も一応上流階級下流階級試してみるよ。

```php:logistic_regression
model2.predict_proba([[30,2]])
model2.predict_proba([[30,4]])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/fde614ea-5a12-564e-8aa7-8c90fd600447.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/3ed65c3f-f208-9787-cad4-569ef9a07bf6.png)

上流なら7割ぐらいの確率で生き残れそうだけど、下流だと1割ぐらいでしか生き残れなさそう。
一番生死に関わりそうなのは階級なんですかね。

##2.4 主成分分析のハンズオン
初期設定およびデータセット、分割

```php:pca
from google.colab import drive
drive.mount('/content/drive')

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegressionCV
from sklearn.metrics import confusion_matrix
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
%matplotlib inline

cancer_df = pd.read_csv('/content/drive/MyDrive/study_ai_ml_google/data/cancer.csv')

cancer_df.drop('Unnamed: 32', axis=1, inplace=True)

y = cancer_df.diagnosis.apply(lambda d: 1 if d == 'M' else 0)
X = cancer_df.loc[:, 'radius_mean':]

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

logistic = LogisticRegressionCV(cv=10, random_state=0)
logistic.fit(X_train_scaled, y_train)

print('Train score: {:.3f}'.format(logistic.score(X_train_scaled, y_train)))
print('Test score: {:.3f}'.format(logistic.score(X_test_scaled, y_test)))
print('Confustion matrix:\n{}'.format(confusion_matrix(y_true=y_test, y_pred=logistic.predict(X_test_scaled))))

pca = PCA(n_components=30)
pca.fit(X_train_scaled)
plt.bar([n for n in range(1, len(pca.explained_variance_ratio_)+1)], pca.explained_variance_ratio_)
```

成分がもつ特徴量を可視化してみた。

```php:pca
pca = PCA(n_components=30)
pca.fit(X_train_scaled)
plt.bar([n for n in range(1, len(pca.explained_variance_ratio_)+1)], pca.explained_variance_ratio_)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/caf940fd-67c6-3a7e-7ca9-0eabdcf5caa5.png)

第一主成分がだいたい60%ちょいで第二、第三が20%と10%程度の特徴をもっているらしい。第六主成分ぐらいまで使えば9割ぐらい抽出できるけど、今回は可視化できちんとできているかを見たいため、第二主成分までを使うこととする。

```php:pca
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_scaled)
print('X_train_pca shape: {}'.format(X_train_pca.shape))

print('explained variance ratio: {}'.format(pca.explained_variance_ratio_))

temp = pd.DataFrame(X_train_pca)
temp['Outcome'] = y_train.values
b = temp[temp['Outcome'] == 0]
m = temp[temp['Outcome'] == 1]
plt.scatter(x=b[0], y=b[1], marker='o') # 良性は○でマーク
plt.scatter(x=m[0], y=m[1], marker='^') # 悪性は△でマーク
plt.xlabel('PC 1') # 第1主成分をx軸
plt.ylabel('PC 2') # 第2主成分をy軸
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/f49db4ff-4730-99af-38de-ffcc5e151a94.png)

やっぱり特徴が6割ちょっとしか抽出できてないため、境界線もあいまいだしあんまり分類出来てる気がしないね。課題も2次元上に次元圧縮だからこれで終わり。

##2.5 k-近傍法のハンズオン
初期設定

```php:k-NN
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

def gen_data():
    x0 = np.random.normal(size=50).reshape(-1, 2) - 1
    x1 = np.random.normal(size=50).reshape(-1, 2) + 1.
    x_train = np.concatenate([x0, x1])
    y_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)
    return x_train, y_train

X_train, ys_train = gen_data()
plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/618945b3-91e0-a201-98bf-428ad11712a2.png)

ちょっと分類難しそうなデータが生成された。
k-NNに訓練ステップはないから予測する。

```php:k-NN
def distance(x1, x2):
    return np.sum((x1 - x2)**2, axis=1)

def knc_predict(n_neighbors, x_train, y_train, X_test):
    y_pred = np.empty(len(X_test), dtype=y_train.dtype)
    for i, x in enumerate(X_test):
        distances = distance(x, X_train)
        nearest_index = distances.argsort()[:n_neighbors]
        mode, _ = stats.mode(y_train[nearest_index])
        y_pred[i] = mode
    return y_pred

def plt_resut(x_train, y_train, y_pred):
    xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
    xx = np.array([xx0, xx1]).reshape(2, -1).T
    plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
    plt.contourf(xx0, xx1, y_pred.reshape(100, 100).astype(dtype=np.float), alpha=0.2, levels=np.linspace(0, 1, 3))

n_neighbors = 3

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
X_test = np.array([xx0, xx1]).reshape(2, -1).T

y_pred = knc_predict(n_neighbors, X_train, ys_train, X_test)
plt_resut(X_train, ys_train, y_pred)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/30f2b4a3-34a0-5fa4-9054-6f3b44e33c76.png)
結構おしいところまで分類できてるけど分類しきるのは無理だったね。
どうせだからkの数を変えてみたものも試してみた。

```php:k-NN
n_neighbors = 5

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
X_test = np.array([xx0, xx1]).reshape(2, -1).T

y_pred = knc_predict(n_neighbors, X_train, ys_train, X_test)
plt_resut(X_train, ys_train, y_pred)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/c3a6553b-b6a4-166f-454c-ce97401384b9.png)
ダメになっちゃったね...
最近傍法も試してみる。

```php:k-NN
n_neighbors = 1

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
X_test = np.array([xx0, xx1]).reshape(2, -1).T

y_pred = knc_predict(n_neighbors, X_train, ys_train, X_test)
plt_resut(X_train, ys_train, y_pred)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/35f9b7f2-473b-995a-7183-9e66d948fef9.png)

うまく分類できた。やっぱりkの値によってよく結果が変わることがわかった。

##2.6 k-meansのハンズオン

```php:k-means
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import cluster, preprocessing, datasets

from sklearn.cluster import KMeans

wine = datasets.load_wine()
X = wine.data
y = wine.target

model = KMeans(n_clusters=3)
labels = model.fit_predict(X)
df = pd.DataFrame({'labels': labels})

def species_label(theta):
    if theta == 0:
        return wine.target_names[0]
    if theta == 1:
        return wine.target_names[1]
    if theta == 2:
        return wine.target_names[2]

df['species'] = [species_label(theta) for theta in wine.target]
pd.crosstab(df['labels'], df['species'])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/e3274d5b-6d65-d0e3-b646-81b55a6a0777.png)

うまく分類できてないことだけわかった。色々試してみたかったけどこれはできなさそうなのでおわり。

##2.7 SVMのハンズオン
線形分離可能なデータを生成する。

```php:svm
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

def gen_data():
    x0 = np.random.normal(size=50).reshape(-1, 2) - 2.
    x1 = np.random.normal(size=50).reshape(-1, 2) + 2.
    X_train = np.concatenate([x0, x1])
    ys_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)
    return X_train, ys_train

X_train, ys_train = gen_data()
plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/2ad13899-705b-0423-bcd0-03cc4e39bed2.png)

SVMで完全に分けれそうなデータが作成できた。
次に学習、予測、データの可視化を行う。

```php:svm
t = np.where(ys_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
K = X_train.dot(X_train.T)

eta1 = 0.01
eta2 = 0.001
n_iter = 500

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
    grad = 1 - H.dot(a)
    a += eta1 * grad
    a -= eta2 * a.dot(t) * t
    a = np.where(a > 0, a, 0)

index = a > 1e-6
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * sv.dot(X_test[i])
y_pred = np.sign(y_project)

plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],s=100, facecolors='none', edgecolors='k')

plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',
                     levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])

plt.quiver(0, 0, 0.1, 0.35, width=0.01, scale=1, color='pink')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/22361dde-cd2f-7a3e-e5b1-3f2d718d1320.png)

今回のデータではサポートベクトルが3つあることがわかった。
次に線形分離不可能なデータを考えてみる。

```php:svm
factor = .2
n_samples = 50
linspace = np.linspace(0, 2 * np.pi, n_samples // 2 + 1)[:-1]
outer_circ_x = np.cos(linspace)
outer_circ_y = np.sin(linspace)
inner_circ_x = outer_circ_x * factor
inner_circ_y = outer_circ_y * factor

X = np.vstack((np.append(outer_circ_x, inner_circ_x),
               np.append(outer_circ_y, inner_circ_y))).T
y = np.hstack([np.zeros(n_samples // 2, dtype=np.intp),
               np.ones(n_samples // 2, dtype=np.intp)])
X += np.random.normal(scale=0.15, size=X.shape)
x_train = X
y_train = y

plt.scatter(x_train[:,0], x_train[:,1], c=y_train)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/4a903a6b-9853-70da-7fbe-d7440cbbf077.png)
線形分離は無理なデータが生成された。
RBFカーネルを用いて特徴空間上で線形分離を行う。

```php:svm
def rbf(u, v):
        sigma = 0.8
        return np.exp(-0.5 * ((u - v)**2).sum() / sigma**2)
    
X_train = x_train
t = np.where(y_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
K = np.zeros((n_samples, n_samples))
for i in range(n_samples):
    for j in range(n_samples):
        K[i, j] = rbf(X_train[i], X_train[j])

eta1 = 0.01
eta2 = 0.001
n_iter = 5000

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
    grad = 1 - H.dot(a)
    a += eta1 * grad
    a -= eta2 * a.dot(t) * t
    a = np.where(a > 0, a, 0)

index = a > 1e-6
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()

xx0, xx1 = np.meshgrid(np.linspace(-1.5, 1.5, 100), np.linspace(-1.5, 1.5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * rbf(X_test[i], sv)
y_pred = np.sign(y_project)

plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],s=100, facecolors='none', edgecolors='k')

plt.contourf(xx0, xx1, y_pred.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))

plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/3f973f98-627c-7d09-23a2-7699d92cb762.png)

綺麗な楕円でなく、少し歪な円で分離できることが確認できた。
次にデータの重なりがある訓練データについて確認する。

```php:svm
x0 = np.random.normal(size=50).reshape(-1, 2) - 1.
x1 = np.random.normal(size=50).reshape(-1, 2) + 1.
x_train = np.concatenate([x0, x1])
y_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)

plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/8f6a231c-d156-cd10-c7c7-cbf7d47d48cd.png)

重なりのあるデータが生成され、誤差を許容するソフトマージンを試す。

```php:svm
X_train = x_train
t = np.where(y_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
K = X_train.dot(X_train.T)

C = 1
eta1 = 0.01
eta2 = 0.001
n_iter = 1000

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
    grad = 1 - H.dot(a)
    a += eta1 * grad
    a -= eta2 * a.dot(t) * t
    a = np.clip(a, 0, C)

index = a > 1e-8
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()

xx0, xx1 = np.meshgrid(np.linspace(-4, 4, 100), np.linspace(-4, 4, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * sv.dot(X_test[i])
y_pred = np.sign(y_project)

plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],s=100, facecolors='none', edgecolors='k')
plt.contourf(xx0, xx1, y_pred.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))
plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1591221/4d5ddee0-2381-4908-a529-1209c44efcfa.png)
境界線内部にデータが入っているものもあるが、概ね直線による線形分離ができていることがわかった。

#おわりに
ハンズオンが結構がっつりあって疲れた。ただ全部可視化してくれているので、自分の理解が深まる気がする。
ステージテストの方はステージ2では今回の内容は殆ど触れられず、ステージ3のテストで大量に問題がでてきた。
自分で解いていくうちに理解が深まる良い問題な気がする。受ける前にきちんと理解できていれば一発で合格できるんだろうけど、自分には無理だった。あとで見返すときちんと問題と内容を理解してれば解ける問題だと思うから反省。