機械学習の基礎コード解説〜回帰モデル〜

# ニューラルネットワークの構造理解
https://qiita.com/so2/items/d95a13cc0cab7c44de75

上記の記事のコードを用いて、ニューラルネットワークではどのような操作が行われているのかを学習する。

理解が及んでいない部分もあるため、間違っている点のご指摘や、より有効なコードのアドバイス等ありましたら、ご教授願います。

## 必要ライブラリの読み込み
```
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import datetime, os, random
import seaborn as sns

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.utils import Sequence
%load_ext tensorboard

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread
```



## 学習用データの準備
学習データ用のファイルdataesetを用意する。

datesetのデータをランダムな順番に並べ替え、そのうち８割を訓練用のデータセットに、残りをテスト用のデータセットにする。

::: note info
frac=0.8 は８割抜きだし
random_state=0　はデータをランダムにする。=0でなくても、数値はなんでも良い。
:::

https://note.nkmk.me/python-pandas-sample/

```
train_dataset = dataset.sample(frac=0.8,random_state=0)
test_dataset = dataset.drop(train_dataset.index)
```
### ラベル

train_datesetの中のbmiの列を削除し、bmiという名のラベルをつける。
train_datesetの中のlife_expectancyの列を削除、life_expectancyという名のラベルをつける。
そして、このラベルをtrain_labelsに入れる。

テストデータセットの方も同様。

::: note info
POPは削除
:::

つまり、ここでは、　train_dateの中から各行を削除し、その削除した行をtrain_labelに入れている。

```
train_labels = pd.DataFrame.from_dict({
    'bmi': train_dataset.pop('bmi'),
    'life_expectancy': train_dataset.pop('life_expectancy')
})
train_dataset.pop('age')

test_labels = pd.DataFrame.from_dict({
    'bmi': test_dataset.pop('bmi'),
    'life_expectancy': test_dataset.pop('life_expectancy')
})
```

テストデータの中からageの行を削除

```
test_dataset.pop('age')
```

### 学習用データの正規化

::: note
describeでデータフレームの型がpandas.DataFrameであることを確認
→データフレームがpandas.DataFrameでないと、後述のtransposeなどが使用できない
:::

https://note.nkmk.me/python-pandas-describe/
```
train_stats = train_dataset.describe()
```
:::note 
transposeは行と列を入れ替える
:::

https://note.nkmk.me/python-pandas-t-transpose/
```
train_stats = train_stats.transpose()
```
norm(x)はxを正規化する関数


train_datasetを正規化したものをnormed_train_dataにいれ、test_datasetを正規化したものをnormed_test_dataに入れる。

```
def norm(x):
  return (x - train_stats['mean']) / train_stats['std']

normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)
normed_train_data.sample(5)
```
::: note
❓return (x - train_stats['mean']) / train_stats['std']
はtrain_statsでいいの？
:::

## モデル
### モデルの定義
http://marupeke296.com/IKDADV_DL_No2_Keras.html

https://keras.io/ja/layers/core/

:::note
Denseで新しいレイヤーを追加。Denseの引数(64, activation='relu')は、64は中間層のノードの数。activationは活性化関数名。
Dropoutは一定割合のノードを不活性化させることで過学習の防止を図るもの。
```
y = layers.Dense(256, activation='sigmoid')(x)
```
入力xに対する中間層をyと定義。
:::
```
def build_model():
  inputs = layers.Input(shape=(len(train_dataset.keys()),))

  x = layers.Dense(64, activation='relu')(inputs)

  y = layers.Dense(256, activation='sigmoid')(x)
  y = layers.Dropout(0.2)(y)
  y = layers.Dense(64, activation='relu')(y)
  y = layers.Flatten()(y)
  y = layers.Dense(10, activation='relu')(y)

  bmi = layers.Dense(1, name='bmi')(y)

  z = layers.Dense(256, activation='tanh')(x)
  z = layers.Dropout(0.4)(z)
  z = layers.Dense(64, activation='relu')(z)

  life_expectancy = layers.Dense(1, name='life_expectancy')(z)

　　　　model = keras.Model(inputs=inputs, outputs=[bmi, life_expectancy], name='Health')

  model.compile(
      optimizer=tf.keras.optimizers.RMSprop(0.001),
      loss_weights=[1., 0.8],
      loss=['mse', 'huber'],
      metrics=['mae', 'mse'])

  return model
```

上記のプログラムでは、yの中間層を持つニューラルネットワーク(入力に対してbmiを予想する)と、zの中間層を持つニューラルネットワーク(入力に対してlife_expectancyを予想する)の二つを生成している。それぞれのニューラルネットワークは、それぞれ異なる入力をもとに生成されている。

:::note
model = keras.Model(inputs=inputs, outputs=[bmi, life_expectancy], name='Health')
入力をもとにbmiとlife_expectancyを出力するモデルを作成。このモデル名をHealthとしている。
:::

```
model = build_model()
model.summary()
```
:::note
built_model関数で定義したモデルをmodelとする
:::

## 訓練
★用語★
エポック = 1つの訓練データを何回繰り返して学習させるか

モデルを1000エポック訓練し、TensorBoardに記録。
エポックの終わりに呼び出され、エポックが終わるごとにドットを一つ出力することで学習の進捗を表示
→学習中に下部に出力される学習進行状況のバーのこと

```
class PrintDot(keras.callbacks.Callback):
  def on_epoch_end(self, epoch, logs):
    if epoch % 100 == 0: print('')
    print('.', end='')

logdir = os.path.join("logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)

EPOCHS = 1000
```
https://note.nkmk.me/python-tensorflow-keras-basics/

:::note
fit()で訓練の実行を行う。
引数でバッチサイズやエポック数などを指定。
下記のコードでは、normed_train_dataを入力として、  [train_labels.bmi, train_labels.life_expectancy]を出力としている。
:::

```
model.fit(
  normed_train_data, 
  [train_labels.bmi, train_labels.life_expectancy],
  epochs=EPOCHS, 
  validation_split=0.1, 
  verbose=0,
  callbacks=[tensorboard_callback, PrintDot()])
```

:::note
引数verboseは表示される学習状況の出力のモード。デフォルトはverbose=1で、エポックごとのログがプログレスバーの進行状況とともに示される。verbose=2はプログレスバーなし（エポックごとのログはあり）、verbose=0は出力が一切なし。

引数callbacksは、訓練中（エポック終了時など）に呼び出したい関数をリストで指定。
:::

### Tensorboardで可視化
https://qiita.com/kannkyo/items/3ff9c5c66c449450d7ab
```
%tensorboard --logdir logs
```
## テストデータで予測

テストデータを使用した予測を行い、結果を出力

```
test_predictions = model.predict(normed_test_data)

result = normed_test_data.copy()
result['height'] = dataset['height']
result['weight'] = dataset['weight']
result['sex'] = dataset['sex'].map(lambda x: 'female' if x == 0 else 'male')
result['age'] = dataset['age']
result['bmi'] = dataset['bmi']
result['life_expectancy'] = dataset['life_expectancy']
result['predict_BMI'] = test_predictions[0]
result['diff_BMI'] = round(result['predict_BMI']) - result['bmi']
result['predict_LE'] = test_predictions[1]
result['diff_LE'] = round(result['predict_LE']) - result['life_expectancy']

result.reindex(columns=['height', 'weight', 'bmi', 'predict_BMI', 'diff_BMI', 'sex', 'age', 'life_expectancy', 'predict_LE', 'diff_LE']).sort_values('diff_LE')
```

:::note
ここでは、入力データと予想データを並べて表示するコードを書いている。
result['bmi'] = dataset['bmi']では入力データをそのまま表示。
result['predict_BMI'] = test_predictions[0]では、予想したBMIの値を表示。
result['diff_BMI'] = round(result['predict_BMI']) - result['bmi']では、実際のBMI(入力データ)と予測したbmiのデータの差異を表示している。
:::