PyTorchを使った線形回帰モデル：初心者向け解説

# PyTorchを使った線形回帰モデル：初心者向け解説

このチュートリアルでは、**PyTorch**を使ってシンプルな線形回帰モデルを構築し、訓練する方法を説明します。特に、初心者にとって重要なポイントであるループ構造 `for epoch in range(num_epochs):` の部分に焦点を当て、なぜこのような構造が採用されているのか解説します。

---

## 線形回帰モデルの概要

線形回帰とは、データの入力（特徴量）と出力（ターゲット）の間に線形な関係があると仮定して、その関係をモデル化する手法です。ここでは、次の式を使ってデータを予測します。

$$
y = x \cdot \text{weight} + \text{bias}
$$
この式の中で、`weight` と `bias` を学習することで、入力 `x` に対して最適な出力 `y` を得ることが目的です。

---

## コードの解説

### 1. 必要なライブラリとデータの準備

まず、必要なライブラリをインポートし、データを準備します。

```python
import torch
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
```

#### 各ライブラリの説明

- **`torch`**: PyTorch のコアライブラリで、テンソル計算や自動微分をサポート。
- **`numpy`**: 科学計算ライブラリ。データの前処理や数値計算に使用。
- **`matplotlib.pyplot`**: データ可視化ライブラリ。訓練結果のプロットなどで使用。
- **`TensorDataset`**: PyTorch のデータセットクラス。入力データとターゲットデータを結びつけて扱う。
- **`DataLoader`**: PyTorch のデータローダークラス。データを効率的にミニバッチに分割してモデルに供給。

---

### 2.データの準備と前処理

次に、訓練データ `X_train` とターゲットデータ `y_train` を準備し、正規化します。

```python
X_train = np.arange(10, dtype='float32').reshape((10, 1))
y_train = np.array([1.0, 1.3, 3.1, 2.0, 5.0, 6.3, 6.6, 7.4, 8.0, 9.0], dtype='float32')

X_train_norm = (X_train - np.mean(X_train)) / np.std(X_train)
X_train_norm = torch.from_numpy(X_train_norm)
y_train = torch.from_numpy(y_train).float()
```

---

### 3.モデルと損失関数の定義

次に、線形モデルと損失関数を定義します。

```python
def model(xb):
    return xb @ weight + bias

def loss_fn(input, target):
    return (input - target).pow(2).mean()
```

#### `@` 演算子の説明

`@` 演算子は、Pythonで**行列積（マトリックス乗算）**を行うための演算子です。特に、PyTorchなどのテンソル計算ライブラリでは、効率的な数値計算のために、この演算子が頻繁に使用されます。これにより、テンソルや行列同士の計算が直感的に、そして高速に行えるようになっています。

---

##### `def model(xb):`

```python
def model(xb):
    return xb @ weight + bias
```

この部分の `xb @ weight` は、**行列積**を意味しています。`@` 演算子を使うと、次のことが効率的になります。

1. **要素ごとの掛け算ではなく、行列全体の掛け算を行う**  
   - 通常の掛け算（`*` 演算子）だと、各要素ごとに計算されますが、`@` は行列の全体としての積を計算します。これは、線形代数における行列積の概念に基づいており、複数のデータを一度に扱うのに便利です。

2. **計算量を減らし、速度を向上させる**  
   - 行列積は、例えば画像データや数値データのように多次元データを扱うときに、非常に計算効率が高まります。たとえば、`xb` が 10 個のデータ点を持ち、各データ点が 1 次元（1 列）のベクトルであれば、`xb @ weight` を使うことで、すべてのデータ点を一度に計算できます。

3. **並列計算が可能**  
   - 行列積を効率的に行うことで、GPUを使った並列計算にも最適化されています。これにより、データサイズが大きい場合でも計算が高速化されます。

---

##### 行列積の例

例えば、`xb` が `[[2], [3], [4]]` という行列だとしましょう。これに対して、`weight` が `[1.5]` という重みだった場合、`@` 演算子を使うと次のような計算が行われます。

```
xb @ weight:
[[2]    * [1.5]   =  [[3]
 [3]               [4.5]
 [4]]              [6]]
```

このように、`xb` のすべての要素に対して一度に重みが掛けられ、最終的な出力が効率的に計算されます。

---

##### 効率性のポイント

- **メモリの効率化**: 行列全体を一度に計算することで、要素ごとの個別処理に比べてメモリ使用量が少なくなります。
- **計算速度の向上**: 行列積は、高度に最適化されたアルゴリズムを使っているため、大規模なデータを高速に処理できます。
- **GPUの活用**: 特にPyTorchでは、行列積の計算はGPUを使うとさらに高速化され、ディープラーニングのような大規模計算にも対応できます。

---

##### まとめ

`@` 演算子は、行列全体の掛け算を効率的に行うために使われます。これにより、**メモリ効率と計算速度が向上**し、大量のデータを扱う機械学習やディープラーニングの処理において非常に重要な役割を果たします。

---

## 3.`for epoch in range(num_epochs):` の解説

```python
for epoch in range(num_epochs):
    for x_batch, y_batch in train_dl:
        pred = model(x_batch)
        loss = loss_fn(pred, y_batch)
        loss.backward()

        # 重みとバイアスの更新
        with torch.no_grad():
            weight -= weight.grad * learning_rate
            bias -= bias.grad * learning_rate
            weight.grad.zero_()
            bias.grad.zero_()

    if epoch % log_epochs == 0:
        print(f'Epoch {epoch}  Loss {loss.item():.4f}')
```

#### エポックとループの目的

- **エポック**: すべての訓練データが一度モデルに渡されたときのサイクル。複数回データを通すことで、モデルがデータのパターンを学習します。
- **ループ内の処理**:
  1. **データをバッチで処理**: 大量のデータを効率よく処理するために、バッチ単位でデータをモデルに渡します。
  2. **損失の計算と勾配の計算**: モデルの予測結果とターゲットデータを比較し、誤差を計算します。その誤差に基づいて勾配を計算し、重みやバイアスを調整します。

---

#### `with torch.no_grad():` の説明

`with torch.no_grad():` ブロック内では、**自動微分を無効化**して、勾配に基づくパラメータの更新を行います。PyTorch では通常、すべての計算が勾配として記録されますが手動で重みやバイアスを更新する際にはこの記録を無効にする必要があります。**手動で重みやバイアスを更新する際にこの記録を無効にする理由**は、更新そのものが勾配計算に影響を与えないようにするためです。勾配は誤差に基づいて計算され、更新処理自体は学習の一部ではないため、これを記録すると不必要な計算が追加されてしまいます。そのため、パラメータ更新の際には自動微分を無効化しておく必要があります。

---

##### `weight.grad.zero_()` と `bias.grad.zero_()` の目的

```python
weight.grad.zero_()
bias.grad.zero_()
```

これらの行は、勾配をリセットするためのものです。勾配が累積されると、次のエポックで正しい更新が行われなくなるため、リセットして次のバッチに備えます。

---

## 4.結果

```bash
Epoch 0  Loss 45.0782
Epoch 10  Loss 26.4366
...
Epoch 1970  Loss 0.2523
Epoch 1980  Loss 0.2521
Epoch 1990  Loss 0.0471

Final Parameters: 2.706723928451538 4.969954013824463
```

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/54eb29ba-d9ab-fe54-4dab-d0b49fa472ce.png)




## 5.まとめ

このチュートリアルでは、シンプルな線形回帰モデルをPyTorchで実装しました。特に、`for epoch in range(num_epochs):` のループ構造がモデルの訓練過程を繰り返し、精度を向上させるために重要な役割を果たしていることを理解できたかと思います。

---

## 6.全体コード

```python
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

import torch
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader

X_train = np.arange(10, dtype='float32').reshape((10, 1))
y_train = np.array([1.0, 1.3, 3.1, 2.0, 5.0, 6.3, 6.6, 7.4, 8.0, 9.0], dtype='float32')

X_train_norm = (X_train - np.mean(X_train)) / np.std(X_train)
X_train_norm = torch.from_numpy(X_train_norm)
y_train = torch.from_numpy(y_train).float()

train_ds = TensorDataset(X_train_norm, y_train)
batch_size = 1
train_dl = DataLoader(train_ds, batch_size, shuffle=True)

torch.manual_seed(1)
weight = torch.randn(1)
weight.requires_grad_()
bias = torch.zeros(1, requires_grad=True)

def loss_fn(input, target):
    return (input - target).pow(2).mean()

def model(xb):
    return xb @ weight + bias

learning_rate = 0.001
num_epochs = 2000
log_epochs = 10

for epoch in range(num_epochs):
    for x_batch, y_batch in train_dl:
        pred = model(x_batch)
        loss = loss_fn(pred, y_batch)
        loss.backward()

        with torch.no_grad():
            weight -= weight.grad * learning_rate
            bias -= bias.grad * learning_rate
            weight.grad.zero_()
            bias.grad.zero_()

    if epoch % log_epochs == 0:
        print(f'Epoch {epoch}  Loss {loss.item():.4f}')

print('Final Parameters:', weight.item(), bias.item())

X_test = np.linspace(0, 9, num=100, dtype='float32').reshape(-1, 1)
X_test_norm = (X_test - np.mean(X_train)) / np.std(X_train)
X_test_norm = torch.from_numpy(X_test_norm)
y_pred = model(X_test_norm).detach().numpy()

fig = plt.figure(figsize=(13, 5))
ax = fig.add_subplot(1, 2, 1)
plt.plot(X_train_norm, y_train, 'o', markersize=10)
plt.plot(X_test_norm, y_pred, '--', lw=3)
plt.legend(['Training examples', 'Linear Reg.'], fontsize=15)
ax.set_xlabel('x', size=15)
ax.set_ylabel('y', size=15)
ax.tick_params(axis='both', which='major', labelsize=15)

plt.show()
```



## 参考文献

Python機械学習プログラミング［PyTorch＆scikit-learn編］

https://amzn.asia/d/dk6GyVQ