🔰PyTorchでニューラルネットワーク基礎 #02 【回帰分析・非線形化】

# 概要
個人的な備忘録を兼ねたPyTorchの基本的な解説とまとめになります．

回帰問題をニューラルネットワークで扱う特徴の一つは、モデルを非線形で捉えることができる点にあると言えます。つまり、データの関係を複雑な形で表現できる点です。[前回](https://qiita.com/AzukiImo/items/d38f095f098607c32720)の回帰分析では直線的な関係しか扱いませんでしたが、ニューラルネットワークなら曲がった関係や複雑なパターンも学習できます。非線形な構造のネットワークモデルを作成する方法を簡単にまとめたいと思います。

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


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


# 1. 普遍性定理（全結合層と活性化関数）
ニューラルネットワークの登場する全結合層を単純に２つ重ねても線形のままです。どうやって非線形化するのか？それが活性化関数の１つの役割です。全結合層の出力値に活性化関数を作用させ、変換された値を全結合層の入力とすることで様々な関数を近似表現することができます。この「全結合層→活性化関数→全結合層→...」のように全結合層と活性化関数を交互に利用することで様々な関数を近似できるという定理は**普遍性定理（Universal Approximation Theorem）** と呼ばれていて、いくつかの数学的な証明があります。

https://qiita.com/mochimochidog/items/ca04bf3df7071041561a

次の3編が有名所かと思います。

* Cybenko (1989)  "_Approximation by Superpositions of a Sigmoidal Fuction_", Mathematics of Controle, Signals, ans Systems

* Hornik (1991) "_Approximation capabilities of multilayer feedforward networks_",
Neural Networks

* Moshe Leshno, Vladimir Ya. Lin, Allan Pinkus, Shimon Schocken (1993) "_Multilayer feedforward networks with a nonpolynomial activation function can approximate any function_", Neural Networks

理論上、全結合層と活性化関数のシグモイド関数やRelu関数を使うことで様々な関数を表現できることが普遍性定理によって保証されています。今回は下図のような全結合層とRelu関数を利用した非線形のネットワークモデルを構築してみたいと思います[^1]。


　　　　　　　　　　<img src="
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/9356e30d-f50b-4fb7-9d53-1f0fafa39520.png" width=50%>

# 2. コードと解説
ネットワークを定義する部分以外は前回と同一、2.2のネットワークを定義する部分も大部分は前回と類似しています。


## 2.1 データの読み込みとtorchテンソルへの変換
numpyでcsvファイルを読み込んでtorchテンソルに変換します。前回と同じなので説明は割愛します。

~~~python: データの読み込み
import numpy as np     # データの読み込みに利用
import torch           # PyTorchのライブラリ
import torch.nn as nn  # ネットワーク層のライブラリ
import torch.nn.functional as F  # PyTorchの関数のライブラリ（新規に追加）

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)
~~~

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

下図のように全結合層とRelu関数を利用したネットワークのモデルを作成します。

　　　　<img src="
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/9356e30d-f50b-4fb7-9d53-1f0fafa39520.png" width=70% border="0">

具体的には、month、temp、pop、holidayの４つの特徴量を入力して、最終的にsalesを予測する関数
$$sales = model(month, temp, pop, holiday)$$
として表現されます。modelは、FC1、FC2という全結合層、Relu関数の3種類の関数の合成で表現されます。上図の流れをコードで表現するには、ネットワークのクラスの\_\_init__()にFC1とFC2の定義を、forward()関数に「fc1 → relu → fc2」の流れを記述することになります[^2]。


```python: ネットワークモデルの定義と作成
# ネットワークの定義
class DNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(4,10)    # ４個から特徴量10個
        self.fc2 = nn.Linear(10,1)    # 10個の特徴量から1個（売上）

    # forward部分が実際の流れ。この例だと、FC1 ->  relu -> FC2
    def forward(self, x):
        h = self.fc1(x)    # 全結合層１つ目
        h = F.relu(h)      # Relu関数
        y = self.fc2(h)    # 全結合層２つ目
        return y

# モデルの作成
model = DNN()
```
### ネットワークの定義
* 全結合層の場合は、``nn.Linear（入力の特徴量数，出力の特徴量数）`` という形になります。
* fc1に入力される特徴量は（月，気温，人口比率，休日フラグ）の４種類。出力の特徴量の数（次元）は自由に選択できます。今回は10種類にしてみました。 コードでは**nn.Linear(4, 10)** となります。
* fc2に入力される特徴量は10種類、出力は売上の１種類ですから、 コードでは**nn.Linear(10, 1)** となります。
* fc1のあとに、Relu関数を通すので、``F.relu(self.fc1(x))``という流れになります。

## 2.3 誤差関数と誤差最小化の手法の選択と変数更新のループ
この部分も前回と同一になります。説明は割愛します。

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

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の約束事項
~~~
## 2.4 検証や予測など

modelを使って予測するには、month、temp、population、holidayの4つを決める必要があります。10月、気温25度、人口比率0.9、平日の場合の売上予測は次のように求めることができます。

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

## 次回
ニューラルネットワークのHello Worldであるアヤメの分類問題に代表される分類問題の予定です。

https://qiita.com/AzukiImo/items/01b348f5beb3d2979d44

もはや解説不要だと思いますが:sweat::smile::sweat:

# 注

[^1]: 活性化関数については、[PyTorchドキュメントtorch.nn.functional](https://pytorch.org/docs/stable/nn.functional.html)に使い方、[PyTorchドキュメントのtorch.nn](https://pytorch.org/docs/stable/nn.html)にはグラフの形も解説されています。
[^2]: 活性化関数も\_\_init__()にnn.ReLU()としてモジュール形式で書く方法もあります。詳細はPyTorchのドキュメントを参照してください。