🔰 PyTorchでニューラルネットワーク基礎 #01 【回帰分析】

# 概要
個人的な備忘録を兼ねたPyTorchの基本的な解説とまとめになります．順番にまとめてLLMを利用した内容の[TinySwallow-1.5BをSFTして大喜利生成してみた](https://qiita.com/AzukiImo/items/bd8175cc6714dd72aee7)にまでつなげる予定です。

:::note
**方針** 
1. できるだけ同じコード進行
1. できるだけ簡潔（細かい内容は割愛）
1. 特徴量などの部分，あえて数値で記入（どのように変わるかがわかりやすい）
:::


### 演習用のファイル（Githubからダウンロード可）
* [データのファイル　vending_machine.csv](https://github.com/azukiimo/qiita/blob/main/data/vending_machine.csv)
* [コード sample_01.ipynb (ipynb形式)](https://github.com/azukiimo/qiita/blob/main/sample_01.ipynb)


# 1. ニューラルネットワークによる回帰モデル
回帰モデルとは、出力変数が連続値の状況を扱うモデルです。ニューラルネットワークを用いた回帰では、複雑な非線形関係も捉えることができ、最終層に線形層，損失関数には通常MSE（平均二乗誤差）を用いることが一般的です。

PyTorchによるプログラムの流れを確認します。基本的に下記の５つの流れとなります。Juypyter Labなどで実際に入力しながら進めるのがオススメ。
1. データの読み込みとtorchテンソルへの変換
2. ネットワークモデルの定義と作成
3. 誤差関数と誤差最小化の手法の選択
4. 変数更新のループ (ここまでで学習は完了)
5. 検証や予測など

# 2. コードと解説
流れの1、3、4 については，ほぼ一定の書き方をします。流れの２に相当するモデルの定義部分が重要となります。

まず利用するライブラリを読み込みます。

~~~python: ライブラリ
import numpy as np     # データの読み込みに利用
import torch           # PyTorchのライブラリ
import torch.nn as nn  # ネットワーク層のライブラリ
~~~


## 2.1 データの読み込みとtorchテンソルへの変換
演習用に利用するデータは大学のような施設の架空の店舗の飲料水の売上に関するものを使います。基本的に気温が高いほど売上が大きくなるように作られています。ただ大学生が休暇（？）の月は売上が減るように調整してあります。以下の表は今回利用するCSVファイルの概要とサンプルです。


### データの概要
|変数名|意味|    |
|-----|---|-----|
|month | 月|1〜12 |
| temp | 気温|℃|
|population | 大学に来ている人数比率| 0〜1の数字。１が最大|
| holiday | 休日フラグ|0 : 平日、1 : 休日|
| sales | 売上| 本数|

### サンプル

|month | temp | population  | holiday | sales |
|------|------|-------------|---------|-------|
|7 |	24.2 | 	0.98 | 	1 |	537 |
|1 |	4.9 |	0.85 |	1 |	224 |
|3 |	11.2 |	0.55 |	1 |	209 |
|4 |	13.0 |	0.96 |	0 |	372 |
|3 |	9.7 |	0.56 |	0 |	271 |
|2 |	6.8 	|0.36 |	...|	...|

### データの読み込み
ファイル名をvending_machine.csvとしています。numpyのloadtxtを利用してcsvファイルを読み込みましょう。**x** は月、気温などの説明変数、**t** は売上で実際に観測された値で目的変数や教師データと呼ばれています[^1]。

~~~python: データの読み込み
filename = "vending_machine.csv"
x = np.loadtxt(filename, delimiter=",", skiprows=1, usecols=(0,1,2,3))
t = np.loadtxt(filename, delimiter=",", skiprows=1, usecols=(4))
x = torch.FloatTensor(x)
t = torch.FloatTensor(t).view(-1,1)
~~~

* np.loadtxt(ファイル名，句切り記号 ，利用列)
* torchテンソルに変換 torch.FloatTensor()でフロート型、torch.LongTensor()で整数型
* 回帰モデルのとき、目的変数・教師データの形状は（データサーズ，１）とします。```view(-1,1)```の部分です。

面倒なので月や休日フラグもフロート型でまとめて読み込んでしまいましょう:bow::bow_tone1::bow_tone2:

## 2.2 ネットワークモデルの定義と作成

今回は下図のような単純な線形のネットワークのモデルを作成してみます。

<img src="
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/073d5b22-b778-4767-8cc7-a8205f2e7a31.png" width=70% border="0">

具体的には、month、temp、pop、holidayの４つの特徴量を入力してsalesを予測する形なので、

$$sales = a_1 month + a_2 temp + a_3 pop + a_4 holiday + b$$
となります。利用するネットワークは全結合層（Fully Connected Layer）・線形層（Linear Layer）と呼ばれ、$y=ax+b$や $y=xA^T+b$の形で書かれます。$a$や行列$A$が重み$b$がバイアス・定数項です。

:::note
**全結合層・線形層の書き方**
**nn.Linear(in_features, out_features, bias=True)**

* in_features : 入力される特徴量の数
* out_features : 出力される特徴量の数
* bias : 定数項。定数項ありがデフォルト
:::
詳細はPyTorchの公式ドキュメントに記載されています。

https://pytorch.org/docs/stable/nn.html#linear-layers


ネットワークはクラスを利用して記述するタイプを選択します。クラスを利用するタイプは、
　(1)　\_\_init___()部分に利用するネットワーク名を、
　(2)　forward部分に実際の流れを記述する
ことで完成します。

```python: ネットワークモデルの定義と作成
# ネットワークの定義
class DNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(4,1)    # ここに利用するネットワーク層を書く

    # forward部分が実際の流れ。この例だと、全結合層一つだけ
    def forward(self, x):
        y = self.fc(x)
        return y

# モデルの作成
model = DNN()
```
### ネットワークの定義
* **\_\_init___()** の部分に利用するネットワーク層の定義を記述
* 全結合層の場合は、``nn.Linear（入力の特徴量数，出力の特徴量数）`` という形になります。
* 入力される特徴量は（月，気温，人口比率，休日フラグ）の４種類、出力は売上という１種類なので **nn.Linear(4, 1)** となります。
* **forward()** の部分に実際の流れを記述します。データが入力されて予測値が出力されるまでの関数となります。（いわゆる順伝搬）

### 作成
作ったネットワークを実際に利用するには **model = DNN()** とします。ネットワークがクラスなので、そのクラスをmodelという名称で使う形となります。



## 2.3 誤差関数と誤差最小化の手法の選択

回帰分析では予測値**y** と実測値（教師データ）**t** の二乗誤差を最小にする最小二乗法が用いられることが一般的なようです[^2]。PyTorchでは**MSELoss()** で記述できます。

~~~python: 誤差関数と最適化
criterion = nn.MSELoss()                                 # 損失関数を記述
# optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # SGDを使う場合
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)  # Adamを使う場合
~~~

* **nn.MSELoss()** は予測値と実測値（教師データ）の平均二乗誤差を損失として利用するという意味。使うときは **criterion(x,t)** とする。
* **torch.optim.SGD()** は誤差の最小値を求める方法の一つで、SGDは確率的勾配降下法を使うという意味。
* **torch.optim.Adam()** も誤差の最小値を求める方法の一つで[Adam: A Method for Stochastic Optimization](https://arxiv.org/abs/1412.6980)参照。
* **model.parameters()** は更新されるパラメータで、Linearの重みやバイアスになります。
* **lr** は学習率で損失の減少を見ながら適宜大きさを変更すると良い？
* 最小値を求める最適化（SGDやAdamなど）の手法はたくさんあり、PyTorchのドキュメントから使い方のサンプルや数式に至るまで確認できます。

https://pytorch.org/docs/stable/optim.html




## 2.4 変数更新のループ
LOOPで指定した回数、2.3で準備したoptimizerにしたがってLinearの変数を更新します。毎回、**y=model(x)** で予測値を求めて、**criterion(y, t)** で指定した誤差関数を使って、予測値と実測値（教師データ）の誤差を計算します。あとは、損失を小さくするようにLinearの変数が更新されていくはずです。

~~~python: 変数更新
LOOP = 5000                              # LOOP : 学習回数
for epoch in range(LOOP):
    optimizer.zero_grad()                # 勾配初期化PyTorchの約束事項
    y = model(x)                         # ネットワークモデルによる予測
    loss = criterion(y, t)               # 誤差計算PyTorchの約束事項
    print(epoch,"\tloss:", loss.item())  # lossの値 (loss.item()) を表示
    loss.backward()                      # backward(微分する部分)
    optimizer.step()                     # パラメータ更新PyTorchの約束事項
~~~
forループで変数を更新することになります。損失の減少を観察しながら、学習回数の**LOOP** や学習率の**lr** を適宜変更することになります。

ここまでで、基本的な学習は終わりとなります。


## 2.5 検証や予測など

モデルの重みやバイアス項の値を確認するには、次の値を利用します。

~~~
model.fc.weight : 全結合層fcの重みの表示
model.fc.bias : 全結合層fcのモデルのバイアス項
~~~

損失 (loss) の値が409〜410あたりになったらそろそろ学習終了の予感です。実際に、重みやバイアス項を表示させてみます[^3]。

~~~python
model.fc.weight
>>>  tensor([[ 1.6786e-01,  1.5302e+01,  1.9984e+02, -4.8021e+01]], requires_grad=True),
model.fc.bias
>>> tensor([-7.5517], requires_grad=True))
~~~
見やすく整理すると、表のようになります。

|変数名|係数|
|-----|----:|
|bias|-7.55|
|month|0.17|
|temp |15.30|
|pop |199.84|
|holiday| -48.02|


確認のため、重回帰分析の結果も掲載しておきます。モデルの学習が成功していることがわかります。

~~~:statsmodelsで重回帰した結果
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         -7.5519     11.624     -0.650      0.519     -30.911      15.807
month          0.1679      0.976      0.172      0.864      -1.793       2.128
temp          15.3021      0.426     35.960      0.000      14.447      16.157
population   199.8442     11.794     16.944      0.000     176.143     223.546
holiday      -48.0209      6.231     -7.707      0.000     -60.543     -35.499
==============================================================================
~~~

modelを使って予測するには、month、temp、population、holidayの4つを決める必要があります。

10月、気温25度、人口比率0.9、平日の場合の売上予測は次のように求めることができます。

~~~python
x_test = torch.FloatTensor([10, 25, 0.9, 0])
y_test = model(x_test)
print(y_test)
# tensor([556.5385], grad_fn=<ViewBackward0>)
~~~

# 3. 次回
ニューラルネットワークを用いた回帰では、複雑な非線形関係も捉えることができます。
[次回](https://qiita.com/AzukiImo/items/1f1497e5c52668147461)はちょっとだけ複雑なネットワークの作成方法の予定です。


https://qiita.com/AzukiImo/items/1f1497e5c52668147461

# 注

[^1]: 検証用のデータに分割することもありますが、簡潔さを優先して割愛します。
[^2]: 詳しい内容については統計学の書籍を参考にしてください。
[^3]: 5000回くらいLOOPに設定しました。