【Python】PyTorchの基本

# はじめに
PyTorchの使い方もすっかり忘れているので、復習します。

# やったこと
身長と体重のデータ(n=5)から、相関直線（1次関数）を引きます。統計的な計算は行わず、直線の傾きと切片をパラメータとし、最小二乗誤差を損失関数機として、械学習的な手法（勾配降下法）で線を求めます。
![身長と体重の相関曲線（加工後） - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/6763128d-e563-af18-90f5-d152d7fc82ad.png)



# ライブラリ
```python
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline
import torch
from torchviz import make_dot
```

# 前処理
```python
# サンプルデータの宣言
sampleData1 = np.array([
    [166, 58.7],
    [176.0, 75.7],
    [171.0, 62.1],
    [173.0, 70.4],
    [169.0, 60.1]
])
print(sampleData1)

# 機械学習モデルで扱うため、身長だけを抜き出した変数xと体重だけを抜き出した変数yをセットする
x = sampleData1[:, 0]
y = sampleData1[:, 1]

X = x - x.mean()
Y = y - y.mean()

# 散布図表示で状況の確認
plt.scatter(X, Y, c='k', s=50)
plt.xlabel('$X$')
plt.ylabel('$Y$')
plt.title('加工後の身長と体重の関係')
plt.show()
```
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/6bd45215-89f5-86e9-a4b8-406504424fca.png)

# 予測計算
```python
# XとYをテンソル変数化する
X = torch.tensor(X).float()
Y = torch.tensor(Y).float()

# パラメータの定義
# WとBは勾配計算するので、requires_grad=Trueとする
W = torch.tensor(1.0, requires_grad=True).float()
B = torch.tensor(1.0, requires_grad=True).float()

# 予測関数は1次関数
def pred(X):
    return W * X + B
# 予測値の計算
Yp = pred(X)
# 結果表示
print(Yp)
```

予測関数は一次関数とします。WとBを学習するので、requires_grad=Trueとしました。

```Python
# 予測値の計算グラフ表示
params = {'W': W, 'B': B}
g = make_dot(Yp, params=params)
display(g)
```
![スクリーンショット 2024-09-22 165847.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/c823441a-db00-609a-e356-931c2a76aef7.png)

# 損失計算
```python
# 損失関数は平均二乗誤差
def mse(Yp, Y):
    loss = ((Yp - Y) ** 2).mean()
    return loss
# 損失計算
loss = mse(Yp, Y)
# 結果表示
print(loss)
```

```python
# 損失の計算グラフ表示
params = {'W': W, 'B': B}
g = make_dot(loss, params=params)
display(g)
```
![スクリーンショット 2024-09-22 170028.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/0add52d2-3145-2bd8-a429-8aded435b20d.png)

# 勾配計算
```python
# 勾配計算
loss.backward()
# 勾配値確認
print(W.grad)
print(B.grad)
```

# パラメータ修正
```Python
# 学習率の定義
lr = 0.001
# 勾配を基にパラメータ修正
# with torch.no_grad()をつける必要がある
with torch.no_grad():
    W -= lr * W.grad
    B -= lr * B.grad

    # 計算済みの勾配値をリセットする
    W.grad.zero_()
    B.grad.zero_()
```
パラメータの修正が終わったら、計算済みの勾配値をリセットするのを忘れない。

```Python
# パラメータと勾配値確認
print(W)
print(B)
print(W.grad)
print(B.grad)
```

# 繰り返し計算
以上が、学習のプロセスでした。このプロセスを何度も繰り返すことで徐々にパラメータが最適化されていくことになります。ので、繰り返し計算を実装。まずは初期化。エポック数は1,000、学習率は0.001とします。

```python
# 初期化
# WとBは変数として扱う
W = torch.tensor(1.0, requires_grad=True).float()
B = torch.tensor(1.0, requires_grad=True).float()

# 繰り返し回数
num_epochs = 1000

# 学習率
lr = 0.001

# 記録用配列初期化
history = np.zeros((0, 2))
```

ループ処理を実装。
```python
# ループ処理
for epoch in range(num_epochs):
    # 予測値の計算
    Yp = pred(X)

    # 損失の計算
    loss = mse(Yp, Y)
    
    # 勾配計算
    loss.backward()

    with torch.no_grad():
        # パラメータ修正
        W -= lr * W.grad
        B -= lr * B.grad

        # 計算済みの勾配値をリセットする
        W.grad.zero_()
        B.grad.zero_()

    # 損失の記録
    if (epoch % 10 == 0):
        item = np.array(([epoch, loss.item()]))
        history = np.vstack((history, item))
        print(f'epoch = {epoch}, loss = {loss:.4f}')
```

# 結果評価
```python
# パラメータの最終値
print('W = ', W.data.numpy())
print('B = ', B.data.numpy())

# 損失の確認
print(f'初期状態: 損失: {history[0,1]:.4f}')
print(f'最終状態: 損失: {history[-1,1]:.4f}')
```

結果はこんな感じでした↓。損失は13.3から4.5まで減少しました。その時の一次関数の傾きは1.82で、切片は-0.135だったようです。
```
W =  1.8206872
B =  0.1350645
初期状態: 損失: 13.3520
最終状態: 損失: 4.5580
```
```python
# 学習曲線の表示（損失）
plt.plot(history[:,0], history[:,1], 'b')
plt.xlabel('反復回数')
plt.ylabel('損失')
plt.title('学習曲線(損失)')
plt.show()
```
![学習曲線（損失）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/79ce6f1c-bfe8-b198-1433-3b3df24b109f.png)
学習曲線もちゃんと収束しているので、学習はうまくいったようです。最後に元のデータ（身長と体重）と相関直線を図示します。

```python
# xの範囲を求める(Xrange)
X_max = X.max()
X_min = X.min()
X_range = np.array((X_min, X_max))
X_range = torch.from_numpy(X_range).float()
print(X_range)

# 対応するyの予測値を求める
Y_range = pred(X_range)
print(Y_range.data)
```
```python
# グラフ描画
plt.scatter(X, Y, c='k', s=50)
plt.xlabel('$X$')
plt.ylabel('$Y$')
plt.plot(X_range.data, Y_range.data, lw=2, c='b')
plt.title('身長と体重の相関直線（加工後）')
plt.show()
```
![身長と体重の相関曲線（加工後）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/57ecaf2d-9580-7d68-75a1-51dab00c1d64.png)

# 終わりに
今回は、PyTorchの使い方を復習するため、相関直線の学習方法を実装しました。

# 出典
https://www.amazon.co.jp/%E6%9C%80%E7%9F%AD%E3%82%B3%E3%83%BC%E3%82%B9%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-PyTorch-%E6%B7%B1%E5%B1%A4%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E8%B5%A4%E7%9F%B3-%E9%9B%85%E5%85%B8/dp/4296110322