ラビットチャレンジ - 機械学習 第3章 ロジスティック回帰モデル

# 0．概要
本記事は日本ディープラーニング協会認定の講座プログラムである「ラビット・チャレンジ」が提供している科目の1つである機械学習のレポートである。
記事タイトルに記載のとおり、第3章のロジスティック回帰モデルについて以下にまとめる。

# 1．ロジスティック回帰モデル
回帰と付いているが回帰問題を扱うモデルではなく、分類問題を扱う。

## 1.1．分類問題
ある入力値からどのクラスに分類するか予測すること(離散値)。教師あり学習の1つ。

## 1.2．扱うデータ

### 1.2.1．説明変数

回帰問題と同様。
$m$次元ベクトル($m=1$ならスカラ値)。

```math
x=(x_{1},x_{2},...,x_{m})^T\in\mathbb{R}^m
```

### 1.2.2．目的変数
どのクラスに該当するかという離散値。

```math
y=\{0,1\}
```

例）犬(0)か猫(1)か

## 1.3．ロジスティック回帰モデル
入力と$m$次元パラメータの線形結合をシグモイド関数に入力して$y=1$となる確率を出力する。

パラメータ

```math
w=(w_{1},w_{2},...,w_{m})^T\in\mathbb{R}^m
```

線形結合

```math
\hat{y}=w^{T}x+w_{0}=\sum_{j=1}^{m}{w_{j}x_{j}+w_{0}}
```

線形結合した値をシグモイド関数に与えることで$y=1$となる確率が出力される。

```math
P(Y=1\mid{x})=\sigma(w_{0}+w_{1}x_{1}+...+w_{m}x_{m})
```

出力された確率が0.5以上なら1、未満なら0と予測する。

## 1.4．シグモイド関数
入力は実数、出力は必ず0～1($y=1$となる確率)の単調増加関数。
$a$を増加させると$x=0$付近での曲線の勾配が増える。

※単調増加関数：$x$が増えるほど$y$も増える関数。

```math
\sigma(x)=\frac{1}{1+exp(-ax)}
```

シグモイド関数の微分はシグモイド関数自身で表現できる。
パラメータの推定では微分が使われるため、計算量を抑えれられるシグモイド関数が使われやすい。

## 1.5．最尤推定
元のデータである$x$、$y$を生成するに至った尤もらしいパラメータを探す推定。
尤度関数を最大化させる未知のパラメータを探す。

## 1.5.1．尤度関数

```math
\begin{align}
P(y_{1},y_{2},...,y_{n};p)&=\prod_{i=1}^{n}{p^{y_{i}}(1-p)^{1-y_{i}}} \\
&=\prod_{i=1}^{n}{\sigma(w^{T}x_{i})^{y_{i}}(1-\sigma(w^{T}x_{i}))^{1-y_{i}}} \\
&=L(w)
\end{align}
```

ロジスティック回帰なので確率$p$がシグモイド関数となる。
元は確率$p$が未知だったが、シグモイド関数とすることで未知の値はパラメータ$w$となる。

## 1.5.2．対数尤度関数
対数を取ると微分の計算が簡単となる(同時確率の積は和に変換、指数は積の演算に変換)。
尤度関数が最大になる点と対数尤度関数が最大になる点は同じ。

```math
\begin{align}
E(w_{0},w_{1},...,w_{m})&=-logL(w_{0},w_{1},...,w_{m}) \\
&=\sum_{i=1}^{n}\{y_{i}logp_{i}+(1-p_{i})log(1-p_{i})\}
\end{align}
```

対数尤度関数を微分して0になる点を解くことはできない。そのため、勾配降下法を使って逐次的に解いていく。

## 1.6．勾配降下法(Gradient descent)
反復学習でパラメータを逐次的に更新するアプローチの1つ。
パラメータがｋ更新されなくなった場合、勾配が0(最適解が見つかった)となる。

```math
w^{k+1}=w^{k}-\eta\sum_{i=1}^{n}{(y_{i}-p_{i})x_{i}}
```

$\eta$(イータ)：学習率。ハイパーパラメータであり、パラメータの収束のしやすさ(勾配をどれだけの勢いで降りるかのイメージ)。

データを全件使うため(バッチ勾配降下法)大量のメモリが必要になったり、計算時間が膨大となったりする問題がある。
また、全体最適解ではなく局所最適解に収束してしまう可能性もある。
ただし、全件使っていることで外れ値の影響は受けにくい。

## 1.7．確率的勾配降下法(SGD：Stochastic gradient descent)
データを1件ずつランダムに選んでパラメータを更新する勾配降下法。
データを一度に使う件数が全件から1件になっただけで基本的な式は勾配降下法と同じ。

```math
w^{k+1}=w^{k}-\eta(y_{i}-p_{i})x_{i}
```

一度に使用するデータ件数が減ったことで効率よく最適解を探索できる。
ただし、外れ値の影響は受けやすい。

勾配降下法と確率的勾配降下法の中間にあるものがミニバッチ勾配降下法。
これは一度に使うデータを減らして行う勾配降下法。

# 2．モデルの評価

## 2.1．混同行列
検証データに対するモデルの予測結果を分類した表。

![conmat2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/4c0f41c7-b693-cc06-1f17-3fa7e2a1a837.png)

※Excelで簡易的に作りました…

### 2.1.1．真陽性(TP:True Positive)
正しくPositiveであると判別できた予測結果の数。

例）画像が犬か猫かの判断において、犬の画像を与えた時に犬と予測。

### 2.1.2．偽陽性(FP:False Positive)
間違えてPositiveであると判別した予測結果の数。

例）画像が犬か猫かの判断において、犬ではない画像を与えた時に犬と予測。

### 2.1.3．真陰性(TN:True Negative)
正しくNegativeであると判別できた予測結果の数。

例）画像が犬か猫かの判断において、犬ではない画像を与えた時に正しく犬ではない(猫)と予測。

### 2.1.4．偽陰性(FN:False Negative)
間違えてNegativeであると判別した予測結果の数。

例）画像が犬か猫かの判断において、犬の画像を与えた時に猫と予測。

## 2.2．正解率(Accuracy)
全データのうち、どれだけ正解の予測をしたか。
データに偏りがあるとあまり意味がなくなる問題がある(犬の画像が9割、猫の画像が1割の場合、学習していなくても犬と予測しておけば9割の正解率になってしまう)。

```math
\frac{TP+TN}{TP+FN+FP+TN}
```

## 2.3．再現率(Recall)
Positiveなデータの中からPositiveと予測できる割合。
誤り(FP：間違ってPositiveと予測)が多少あってもPositiveなデータをできるだけ確実に予測をしたい場合に利用。

```math
\frac{TP}{TP+FN}
```

## 2.4．適合率(Precision)
モデルがPositiveと予測したものの中で本当にPositiveである割合。
誤り(FN：間違ってNegativeと予測)があってもより正確な予測をしたい場合に利用。

```math
\frac{TP}{TP+FP}
```

## 2.5．F値(F score)
再現率と適合率はトレードオフである。
そのため調和平均を取ることで再現率と適合率のいわばバランスを示した指標値となる。

```math
\frac{2Recall\cdot{Precision}}{Recall+Precision}
```

# 3．ハンズオン

## 3.1．使用するデータ
タイタニックの乗客データを使用する。

## 3.2．課題
30歳の男性乗客は生き残れるか？

## 3.3．演習

### 3.3.1．Google Driveのマウントとライブラリのインポート

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

# from モジュール名 import クラス名（もしくは関数名や変数名）
import pandas as pd
from pandas import DataFrame
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# matplotlibをinlineで表示するためのおまじない (plt.show()しなくていい)
%matplotlib inline
```

Google Driveに置いたCSVを使用するためGoogle Colabから参照できるようマウントする。
使用するライブラリはこれまでと同様。

### 3.3.2．データ確認

```Python:
# titanic data csvファイルの読み込み
titanic_df = pd.read_csv('/content/drive/My Drive/study_ai_ml/data/titanic_train.csv')

# ファイルの先頭部を表示し、データセットを確認する
titanic_df.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|

別のファイルで用意したデータをDataFrameに読み込みたい場合はread_csvやread_tableを使う。

### 3.3.3．列の削除

```Python:
# 予測に不要と考えるカラムをドロップ (本当はここの情報もしっかり使うべきだと思っています)
titanic_df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 一部カラムをドロップしたデータを表示
titanic_df.head()
```

| |Survived|Pclass|Sex|Age|SibSp|Parch|Fare|Embarked|
|------------:|------------:|------------:|:------------|------------:|------------:|------------:|------------:|:------------|
|0|0|3|male|22.0|1|0|7.2500|S|
|1|1|1|female|38.0|1|0|71.2833|C|
|2|1|3|female|26.0|0|0|7.9250|S|
|3|1|1|female|35.0|1|0|53.1000|S|
|4|0|3|male|35.0|0|0|8.0500|S|

このハンズオンでは簡略化のために一部のカラムを削除。
デフォルトではdropでカラムを削除しても元のDataFrameはそのままだが、inplace=Trueとしているので元のDataFrame(titanic_df)からカラムが削除されている。

### 3.3.4．欠損値の補完

```Python:
# nullを含んでいる行を表示
titanic_df[titanic_df.isnull().any(axis=1)].head(10)
```

| |Survived|Pclass|Sex|Age|SibSp|Parch|Fare|Embarked|
|------------:|------------:|------------:|:------------|------------:|------------:|------------:|------------:|:------------|
|5|0|3|male|NaN|0|0|8.4583|Q|
|17|1|2|male|NaN|0|0|13.0000|S|
|19|1|3|female|NaN|0|0|7.2250|C|
|26|0|3|male|NaN|0|0|7.2250|C|
|28|1|3|female|NaN|0|0|7.8792|Q|
|29|0|3|male|NaN|0|0|7.8958|S|
|31|1|1|female|NaN|1|0|146.5208|C|
|32|1|3|female|NaN|0|0|7.7500|Q|
|36|1|3|male|NaN|0|0|7.2292|C|
|42|0|3|male|NaN|0|0|7.8958|C|

行のいずれかの要素にNaNがある行の頭10件を表示してみる。
AgeがNaNになっていることが分かる。

```Python:
# Ageカラムのnullを平均値で補完
titanic_df['AgeFill'] = titanic_df['Age'].fillna(titanic_df['Age'].mean())

# 再度nullを含んでいる行を表示 (Ageのnullは補完されている)
titanic_df[titanic_df.isnull().any(axis=1)].head(10)
```

| |Survived|Pclass|Sex|Age|SibSp|Parch|Fare|Embarked|AgeFill|
|------------:|------------:|------------:|:------------|------------:|------------:|------------:|------------:|:------------|:------------|
|5|0|3|male|NaN|0|0|8.4583|Q|29.699118|
|17|1|2|male|NaN|0|0|13.0000|S|29.699118|
|19|1|3|female|NaN|0|0|7.2250|C|29.699118|
|26|0|3|male|NaN|0|0|7.2250|C|29.699118|
|28|1|3|female|NaN|0|0|7.8792|Q|29.699118|
|29|0|3|male|NaN|0|0|7.8958|S|29.699118|
|31|1|1|female|NaN|1|0|146.5208|C|29.699118|
|32|1|3|female|NaN|0|0|7.7500|Q|29.699118|
|36|1|3|male|NaN|0|0|7.2292|C|29.699118|
|42|0|3|male|NaN|0|0|7.8958|C|29.699118|

AgeFillという新しい列を追加し、Ageの平均で補完している。
fillna関数でどうfill(埋めるか)を指定する。ここではmeanなので平均。

### 3.3.5．1つの説明変数(運賃)で学習

```Python:
# 運賃だけのリストを作成(説明変数)
data1 = titanic_df.loc[:, ["Fare"]].values

# 生死フラグのみのリストを作成(目的変数)
label1 = titanic_df.loc[:, ["Survived"]].values

from sklearn.linear_model import LogisticRegression

# ロジスティック回帰モデルのインスタンスを生成して学習
model = LogisticRegression()
model.fit(data1, label1)
```

線形回帰等と同じscikit-learnを使っているので似た実装(fit関数で学習)で学習を行える。

### 3.3.6．予測結果の確認

```Python:
print(model.predict([[61]]))
print(model.predict([[62]]))
```

> [0]
> [1]

運賃が\$61の場合は死亡(0)、\$62の場合は生存(1)と予測されていることが分かる。

```Python:
print(model.predict_proba([[61]]))
print(model.predict_proba([[62]]))
```

> [[0.50358033 0.49641967]]
> [[0.49978123 0.50021877]]

predict_proba関数で、それぞれの運賃で予測した時の死亡(0)と予測した確率、生存(1)と予測した確率が確認できる。
ちょうど\$61と\$62の間に境界があり、確率が0.5以上であれば1と予測されているだろうということが分かる。

```Python:
# 回帰係数
print(model.intercept_)
# 切片
print(model.coef_)
# 決定係数
print(model.score(data1, label1))
```

> [-0.94131796]
> [[0.01519666]]
> 0.6655443322109988

回帰係数、切片、決定係数もLinearRegressionを使っていたときと同じように確認できる。

### 3.3.7．可視化

```Python:
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), '-')
```

![graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/8e046d6d-0a47-0768-2b0e-56ee43add65e.png)

学習データを生存(1)と予測した結果が緑の点、学習データを死亡(0)と予測した結果が橙の点。
赤の線が-1～500の範囲で要素数3000個の等差数列をモデルの回帰係数と切片を使ったシグモイド関数で計算した線。
緑の点と赤の線が重なり、この赤の線のように予測されることが分かる。

### 3.3.8．特徴エンジニアリング

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

まず、性別は文言でデータが入っているためそのままでは扱えない。
扱えるよう、数値に置き換えてGenderというカラムにする。

特徴エンジニアリングとは、既にあるデータから新しい特徴量(説明変数)を作ること。
ここでは社会階級(Pclass)の高さや男性より女性の方が(レディーファーストで)生存確率が高かったのではという仮説のもと、新しい特徴(Pclass_Gender)を作っている。

※Pclassは1～3の値であり、小さい方が階級が高い。
　Genderと単純に足し合わせているのでPclass_Genderは小さい方が生存しやすかったのでは？という特徴となっている。
　最小値は階級最上位(1)で女性(0)=1、最大値は階級最下位(3)+男性(1)=4

### 3.3.9．特徴量の可視化

```Python:
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))
```

![graph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/74e6aafe-9ecd-6fcb-3434-ad55953af462.png)

横軸に平均で補完した年齢、縦軸に3.3.8で作った特徴量(Pclass_Gender)を表示している。
階級最上位(1)で女性(0)の人がほとんど生存(Survived)しているのに対してPclass_Genderが大きくなるほど死亡している人が多いことが分かり、3.3.8の仮説は正しいと言える。
また、年齢が高いほど死亡している人が多い。

### 3.3.10．2つの説明変数(補完した年齢、作った特徴Pclass_Gender)で学習

```Python:
# 補完した年齢、作った特徴のリストを作成(説明変数)
data2 = titanic_df.loc[:, ["AgeFill", "Pclass_Gender"]].values

# 生死フラグのみのリストを作成(目的変数)
label2 = titanic_df.loc[:, ["Survived"]].values

model2 = LogisticRegression()
model2.fit(data2, label2)

print(model2.predict([[10, 1]]))
print(model2.predict_proba([[10, 1]]))
```

> [1]
> [[0.03754749 0.96245251]]

10歳で階級最上位(1)の女性(0)は96%とかなり高い確率で生存すると予測されている。
可視化したデータを見てもこの予測は正しそうだと言える。

```Python:
print(model2.predict([[30, 2]]))
print(model2.predict_proba([[30, 2]]))
print()

print(model2.predict([[30, 3]]))
print(model2.predict_proba([[30, 3]]))
print()

print(model2.predict([[30, 4]]))
print(model2.predict_proba([[30, 4]]))
```

> [1]
> [[0.30829809 0.69170191]]

> [0]
> [[0.66890121 0.33109879]]

> [0]
> [[0.90154649 0.09845351]]

このハンズオンの課題「30歳の男性乗客は生き残れるか？」について予測するとこのような結果となる。

生き残れる確率の方が高いのは階級最上位の人だけで階級最下位ともなると生存確率が10%すら切っている。

### 3.3.11．境界の可視化

```Python:
h = 0.02
xmin, xmax = -5, 85
ymin, ymax = 0.5, 4.5
xx, yy = np.meshgrid(np.arange(xmin, xmax, h), np.arange(ymin, ymax, h))
Z = model2.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]
Z = Z.reshape(xx.shape)

fig, ax = plt.subplots()
levels = np.linspace(0, 1.0)
cm = plt.cm.RdBu
cm_bright = ListedColormap(['#FF0000', '#0000FF'])
#contour = ax.contourf(xx, yy, Z, cmap=cm, levels=levels, alpha=0.5)

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)
#fig.colorbar(contour)

x1 = xmin
x2 = xmax
y1 = -1 * (model2.intercept_[0] + model2.coef_[0][0] * xmin) / model2.coef_[0][1]
y2 = -1 * (model2.intercept_[0] + model2.coef_[0][0] * xmax) / model2.coef_[0][1]
ax.plot([x1, x2] ,[y1, y2], 'k--')

# 回帰係数
print(model.intercept_)
# 切片
print(model.coef_)
```

> [-0.94131796]
> [[0.01519666]]

![graph3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/94256d22-9817-4a46-b4ba-e1835657c94e.png)

生存するか死亡するかの境界を回帰係数と切片を元に可視化すると上記のようになる。

### 3.3.12．モデルの評価

#### 3.3.12.1．データを分割して学習

```Python:
from sklearn.model_selection import train_test_split

# 1つの説明変数(Fare)のデータ
traindata1, testdata1, trainlabel1, testlabel1 = train_test_split(data1, label1, test_size=0.2)
traindata1.shape
trainlabel1.shape

# 2つの説明変数(AgeFill, Pclass_Gender)のデータ
traindata2, testdata2, trainlabel2, testlabel2 = train_test_split(data2, label2, test_size=0.2)
traindata2.shape
trainlabel2.shape
# 本来は同じデータセットを分割しなければいけない。(簡易的に別々に分割している。)

eval_model1=LogisticRegression()
eval_model2=LogisticRegression()

# 1つの説明変数、2つの説明変数それぞれで学習
predictor_eval1 = eval_model1.fit(traindata1, trainlabel1).predict(testdata1)
predictor_eval2 = eval_model2.fit(traindata2, trainlabel2).predict(testdata2)

score = eval_model1.score(traindata1, trainlabel1)
print('説明変数1つ 学習データの決定係数：', score)

score = eval_model1.score(testdata1, testlabel1)
print('説明変数1つ テストデータの決定係数：', score)

score = eval_model2.score(traindata2, trainlabel2)
print('説明変数2つ 学習データの決定係数：', score)

score = eval_model2.score(testdata2, testlabel2)
print('説明変数2つ テストデータの決定係数：', score)
```

> 説明変数1つ 学習データの決定係数： 0.6530898876404494
> 説明変数1つ テストデータの決定係数： 0.7206703910614525
> 説明変数2つ 学習データの決定係数： 0.7823033707865169
> 説明変数2つ テストデータの決定係数： 0.7374301675977654

shapeの出力は省略。

2つの説明変数を使ったモデルの方が若干ではあるが性能が良いことが分かる。

#### 3.3.12.2．モデルの評価指標

```Python:
from sklearn import metrics

print(metrics.classification_report(testlabel1, predictor_eval1))
print(metrics.classification_report(testlabel2, predictor_eval2))
```

| |precision|recall|f1-score|support|
|:------------|------------:|------------:|------------:|------------:|
|0|0.70|0.97|0.81|113|
|1|0.86|0.29|0.43|66|
|accuracy|||0.72|179|
|macro avg|0.78|0.63|0.62|179|
|weighted avg|0.76|0.72|0.67|179|

説明変数が1つのモデルの評価指標。
生存の場合の適合率は高いが再現率が低いことから、間違えて生存したと予測することは少ないが、生存のデータに対して死亡と予測していることは多い。
適合率は高くても再現率が低いためもちろんF1スコアは小さくなる。

| |precision|recall|f1-score|support|
|:------------|------------:|------------:|------------:|------------:|
|0|0.73|0.88|0.80|106|
|1|0.75|0.53|0.62|73|
|accuracy|||0.74|179|
|macro avg|0.74|0.71|0.71|179|
|weighted avg|0.74|0.74|0.73|179|

説明変数が2つのモデルの評価指標。
説明変数が1つのモデルと比べて生存の場合の再現率が改善しているがまだ低いことが分かる。

macro avgはマクロ平均。
http://ibisforest.org/index.php?マクロ平均

#### 3.3.12.3．混同行列

```Python:
from sklearn.metrics import confusion_matrix

confusion_matrix1 = confusion_matrix(testlabel1, predictor_eval1)
confusion_matrix2 = confusion_matrix(testlabel2, predictor_eval2)

print(confusion_matrix1)
print(confusion_matrix2)
```

> [[110   3]
>  [ 47  19]]

説明変数が1つのモデルの混同行列。

> [[93 13]
>  [34 39]]

説明変数が2つのモデルの混同行列。

```Python:
def confusion_matrix_flatten(cm):
  tn, fp, fn, tp = cm.flatten()

  print('TP:', tp)
  print('FP:', fp)
  print('TN:', tn)
  print('FN:', fn)
  print()

confusion_matrix_flatten(confusion_matrix1)
confusion_matrix_flatten(confusion_matrix2)
```

confusion_matrixで出力した混同行列はついつい左上からTPで始まっているように思えてしまうが、違うので注意が必要である。
TP, FP, TN, FNをそれぞれ出力すると以下のようになる。

> TP: 19
> FP: 3
> TN: 110
> FN: 47

説明変数が1つのモデルの混同行列。

> TP: 39
> FP: 13
> TN: 93
> FN: 34

説明変数が2つのモデルの混同行列。

```Python:
def reshape_confusion_matrix(cm):
  tn, fp, fn, tp = cm.flatten()
  rcm = np.array([[tp, fp], [fn, tn]])
  return rcm

fig = plt.figure(figsize = (7, 7))
sns.heatmap(
    reshape_confusion_matrix(confusion_matrix1),
    vmin=None,
    vmax=None,
    cmap="Blues",
    center=None,
    robust=False,
    annot=True, fmt='.2g',
    annot_kws=None,
    linewidths=0,
    linecolor='white',
    cbar=True,
    cbar_kws=None,
    cbar_ax=None,
    square=True, ax=None, 
    mask=None)
```

![heat2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/c8dd390a-8b84-9edd-77bd-5b1d0d734a67.png)


説明変数が1つのモデルの混同行列をヒートマップで視覚化すると上記のようになる。
※左上からTP, FP, FN, TNの順に入れ替えて表示するようにしました。

```Python:
fig = plt.figure(figsize = (7, 7))
sns.heatmap(
    reshape_confusion_matrix(confusion_matrix2),
    vmin=None,
    vmax=None,
    cmap="Blues",
    center=None,
    robust=False,
    annot=True, fmt='.2g',
    annot_kws=None,
    linewidths=0,
    linecolor='white',
    cbar=True,
    cbar_kws=None,
    cbar_ax=None,
    square=True, ax=None, 
    mask=None)
```

![heat3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/0e601686-2921-8f6a-c0f9-7c703aba6798.png)

説明変数が2つのモデルの混同行列をヒートマップで視覚化すると上記のようになる。
こっちも同じようにTP, FP, FN, TNの順で表示しています。

### 3.3.13．Seabornのデータで確認

```Python:
#Paired categorical plots

import seaborn as sns
sns.set(style="whitegrid")

# Load the example Titanic dataset
titanic = sns.load_dataset("titanic")

# Set up a grid to plot survival probability against several variables
g = sns.PairGrid(titanic, y_vars="survived",
                 x_vars=["class", "sex", "who", "alone"],
                 size=5, aspect=.5)

# Draw a seaborn pointplot onto each Axes
g.map(sns.pointplot, color=sns.xkcd_rgb["plum"])
g.set(ylim=(0, 1))
sns.despine(fig=g.fig, left=True)

plt.show()
```

![sea1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/daf951eb-76be-fd54-e3f5-5d1657992bad.png)

class(チケットのクラス)、sex(性別)、who(男性or女性or子供)、alone(1人か)という観点で生存率を比較したグラフが上記となる。
グラフから以下のことが言える。

* 良いチケットの方が生存率が高い。
* 男性より女性の方が生存率が高い。しかも約6割ほども差がある。
* 男性より女性の方が生存率が高いのは前述したとおりだが、男性より子供の方が生存率が高い。
* 1人ではなく誰かと来た人の方が生存率が高い。

```Python:
#Faceted logistic regression

import seaborn as sns
sns.set(style="darkgrid")

# Load the example titanic dataset
df = sns.load_dataset("titanic")

# Make a custom palette with gendered colors
pal = dict(male="#6495ED", female="#F08080")

# Show the survival proability as a function of age and sex
g = sns.lmplot(x="age", y="survived", col="sex", hue="sex", data=df,
               palette=pal, y_jitter=.02, logistic=True)
g.set(xlim=(0, 80), ylim=(-.05, 1.05))
plt.show()
```

![sea2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/1546e443-95fa-6670-2a82-908afa535cc0.png)

今度は性別、年齢別で可視化したグラフである。
グラフから以下のことが言える。

* どの年代においても女性の方が生存率が高い。
* 女性は年齢が上がるにつれて生存率が上がるのに対して男性が年齢が上がるにつれてただでさえ低い生存率がさらに下がっている。
* どの年代においても男性は生存率が半分もないが、女性は半分以上ある。

# X．ラビットチャレンジとは
ラビットチャレンジとは、日本ディープラーニング協会認定の講座プログラムの1つ。
E資格を受験するためにはこのラビットチャレンジ等、いずれかの講座プログラムを修了しなければならない。

ラビットチャレンジの特徴は「現場で潰しが効くディープラーニング講座」の通学講座録画ビデオを編集した教材を使用した自習スタイルであるという点。
サポートは他の講座より少なく、受け身ではなく自主的に学んでいく姿勢でなければ進められないが、その分、他の講座に比べると安価であり、手が出しやすい。
ある程度知識がある人、自力で頑張るぞというガッツのある人向けではないかと感じる。

![bnr_jdla.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/bce66671-a2f5-a7f9-de6f-3f87e15c253b.png)