Learning Pytorch with examples

#Learning Pytorch with examples

##TL;DR
PytorchのLearning Pytorch with examplesのまとめ

##Pytorchで二層のネットワークを愚直に書いてみる

GPUを扱えるようにPyTorchを使いましたが、numpyでも簡単に再現できる二層のニューラルネットワークを作成しました。これをうまくPytorchっぽく書き直していこうと思います。

```python
import torch
import numpy as np
import matplotlib.pyplot as plt

#device, dtypeの定義
dtype = torch.float
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

#入力と出力、レイヤーごとの重みの定義
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

#学習率などの定義
learning_rate = 1e-6

#plotのための準備
t_list=np.arange(0,100)
loss_list=[]
for t in range(100):
    #forward
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)

    # lossの計算
    loss = (y_pred - y).pow(2).sum().item()
    loss_list.append(loss)

    #backward
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)

    # 重み行列の更新
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
  
plt.scatter(t_list, loss_list)
```
出力
![image.png](https://qiita-image-store.s3.amazonaws.com/0/306203/e6a09a35-cfa3-2948-bf39-1ceb57bebb38.png)

## classやnnモジュールを使ってより抽象的に書く

### 1. Classとnnモジュールを使って二層のニューラルネットワークの構造と順伝播を定義する

#### 愚直に書いたやつ
基本的に非常に単純で、クラスで表すと実は長くなるけど、ネットワークを重ねるごとにクラスを使うメリットが出てくる。

```python
#forward
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
```

#### Classとnnモジュールを使って書いたやつ
- `nn.Liner`:FC層を作る
- `F.relu`  :relu関数

```python
import torch.nn as nn
import torch.nn.functional as F

#torch.nn.Moduleを継承してクラスを作る
class TwoLayerNet(torch.nn.Module):
  def __init__(self, D_in, H, D_out):
    #TwoLayerNetの継承
    super(TwoLayerNet, self).__init__()
    self.fc1=nn.Linear(D_in, H)
    self.fc2=nn.Linear(H, D_out)
  
  def forward(self, x):
    #順伝播の時に起こる処理を書く
    h=F.relu(self.fc1(x))
    y=self.fc2(h)
    return y
```

### 2. 損失関数を定義

```python
# 愚直に書いたやつ
loss = (y_pred - y).pow(2).sum().item()
# nnモジュールを使ったやつ
criterion=nn.MSELoss(reduction='sum')
loss=criterion(y_pred, y)
```

### 3. 自動微分
これは非常に短くなる。loss関数を微分していくのはこのくらいならかけるけど大きくなると大変になるが、nnモジュールを使えば一行でかける。
#### 愚直に書いたやつ
```python
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
```

#### nnモジュールを使った場合
```python
loss.backward()
```

### 4. optimizerの定義
MomentumSGDやAdamなんかが気軽に使える。複雑なモデルになってもこれで一瞬。

#### 愚直に書いたやつ
```
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
```

#### optimizerを作成する場合
ここでモデルを作ったのが効いてくる。愚直に書くと重み行列の量だけ更新する必要があるが、optimizerをすれば複雑なモデルでもやってくれる。`lr=learning rate`はSGDなので低め。`momentum=0.9`などとすることでMomentumSGDになるし、`optimizer=torch.optim.Adam(model.parameters()`とかすればAdamが使える。

```python
optimizer=torch.optim.SGD(model.parameters(), lr=1e-4)
```

### 5. model verで学習させる。
最後にこれまでのをまとめると以下のようなコードになる。構造がわかりやすくなっていると思う。

```python
import torch.nn as nn
import torch.nn.functional as F

#modelの作成
class TwoLayerNet(torch.nn.Module):
  def __init__(self, D_in, H, D_out):
    
    super(TwoLayerNet, self).__init__()
    self.fc1=nn.Linear(D_in, H)
    self.fc2=nn.Linear(H, D_out)
  
  def forward(self, x):
    
    h=F.relu(self.fc1(x))
    y=self.fc2(h)
    return y

model=TwoLayerNet(D_in, H, D_out)
model=model.to(device)　#modelをGPUへ送る

#損失関数とoptimizerの定義
criterion=nn.MSELoss(reduction='sum')
optimizer=torch.optim.SGD(model.parameters(), lr=1e-4)

#データ作成
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

t_list=np.arange(0,100)
loss_list=[]

#学習
for t in range(100):
  #順伝播
  y_pred=model(x)
  
  #損失関数の計算
  loss=criterion(y_pred, y)
  loss_list.append(loss.cpu().detach().numpy()) # variableかつGPUなのでCPUに移してnumpyに変換できるようにする
  
  #optimzeされてTensorを初期化する
  optimizer.zero_grad()
  #自動微分
  loss.backward()
  #optimizerによる重みの更新
  optimizer.step()
  
plt.scatter(t_list,loss_list)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/306203/58c53c37-f646-4d83-be6a-13394a4f61fe.png)

## 結論
とりあえずどの部分がどの部分に当たるのかはなんとなくわかった。次は転移学習しましょう。