PyTorch 線形回帰

線形回帰は最も基本的な機械学習アルゴリズムの1つであり、連続値を予測するために使用されます。これは単純で一般的な回帰分析方法であり、線形関数をフィッティングすることによって出力を予測することを目的としています。

単純な線形回帰問題の場合、モデルは次のように表すことができます:

  • y は予測値(目標値)です。
  • \(x_1\)、\(x_2\)、\(x_n\) は入力特徴です。
  • \(w_1\)、\(w_2\)、\(w_n\) は学習対象の重み(モデルパラメータ)です。
  • b はバイアス項です。

PyTorch では、線形回帰モデルは継承によってnn.Moduleクラスを実装することで実現できます。簡単な例を使って、PyTorch で線形回帰モデルを実装する方法を詳しく説明します。


データ準備

まず、線形回帰モデルをトレーニングするためのダミーデータを準備します。ここでは、各サンプルが2つの特徴 \(x_1\)、\(x_2\) を持つ、単純な線形関係のデータセットを生成できます。

例

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

# 乱数シード、毎回の実行結果を一致させる
torch.manual_seed(42)

# トレーニングデータを生成
X = torch.randn(100, 2)  # 100個のサンプル、各サンプルは2つの特徴
true_w = torch.tensor([2.0, 3.0])  # 真の重みを仮定
true_b = 4.0  # バイアス項
Y = X @ true_w + true_b + torch.randn(100) * 0.1  # ノイズを追加

# データの一部を表示
print(X[:5])
print(Y[:5])

出力結果は次のとおりです:

tensor([[ 1.9269,  1.4873],
        [ 0.9007, -2.1055],
        [ 0.6784, -1.2345],
        [-0.0431, -1.6047],
        [-0.7521,  1.6487]])
tensor([12.4460, -0.4663,  1.7666, -0.9357,  7.4781])

このコードは、ノイズを含む線形データセットを作成します。

  • 入力 X は 100x2 の行列で、各サンプルは2つの特徴を持ちます。
  • 出力 Y は真の重みとバイアスから生成され、いくつかのランダムノイズが加えられています。
  • 使用torch.manual_seed(42)毎回の実行結果を一致させ、デバッグと再現を容易にします。

線形回帰モデルの定義

継承によってnn.Moduleシンプルな線形回帰モデルを定義できます。PyTorch では、線形回帰の核心はnn.Linear()層であり、重みとバイアスの初期化を自動的に処理します。

例

import torch.nn as nn

# 線形回帰モデルを定義
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super(LinearRegressionModel, self).__init__()
        # 線形層を定義。入力は2つの特徴、出力は1つの予測値
        self.linear = nn.Linear(2, 1)  # 入力次元2、出力次元1

    def forward(self, x):
        return self.linear(x)  # 順伝播、予測結果を返す

# モデルインスタンスを作成
model = LinearRegressionModel()

ここでのnn.Linear(2, 1)は線形層を表し、2つの入力特徴と1つの出力を持ちます。forwardメソッドは、この層を通じて順伝播を行う方法を定義します。

注意: nn.Linear自動的に重み行列とバイアスベクトルが作成されるため、手動で定義する必要はありません。


損失関数とオプティマイザの定義

線形回帰の一般的な損失関数は平均二乗誤差損失(MSELoss)、予測値と実際の値の差を測定するために使用されます。PyTorch には既製の MSELoss 関数が用意されています。

使用しますSGD(確率的勾配降下法)或Adamオプティマイザを使用して損失関数を最小化します。

例

# 損失関数(平均二乗誤差)
criterion = nn.MSELoss()

# オプティマイザ(SGD または Adam を使用)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # 学習率を0.01に設定

# Adam オプティマイザを使用することもできます
# optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
コンポーネント 説明
MSELoss 予測値と実際の値の平均二乗誤差を計算します。式は \(\frac{1}{n}\sum(y_{pred} - y_{true})^2\) です。
SGD 確率的勾配降下法を使用してパラメータを更新します。学習率は各ステップの更新幅を制御します。
Adam 適応的学習率オプティマイザで、通常より速く収束します。

モデルのトレーニング

トレーニングプロセスでは、次の手順を実行します:

  1. 入力データ X を使用して順伝播を行い、予測値を取得します。
  2. 損失(予測値と実際の値の差)を計算します。
  3. 逆伝播を使用して勾配を計算します。
  4. モデルパラメータ(重みとバイアス)を更新します。

モデルを1000エポックトレーニングし、100エポックごとに損失を表示します。

例

# モデルをトレーニング
num_epochs = 1000  # 1000エポックトレーニング
for epoch in range(num_epochs):
    model.train()  # モデルをトレーニングモードに設定

    # 順伝播
    predictions = model(X)  # モデルが予測値を出力
    loss = criterion(predictions.squeeze(), Y)  # 損失を計算(予測値を1Dに圧縮する必要があることに注意)

    # 逆伝播
    optimizer.zero_grad()  # 前の勾配をクリア
    loss.backward()  # 勾配を計算
    optimizer.step()  # モデルパラメータを更新

    # 損失を表示
    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/1000], Loss: {loss.item():.4f}')
  • predictions.squeeze():モデルの出力を2Dテンソルから1Dに圧縮します。なぜなら目標値はY1次元配列だからです。
  • optimizer.zero_grad():毎回の逆伝播の前に前の勾配をクリアする必要があります。そうしないと勾配が累積します。
  • loss.backward():すべてのトレーニング可能なパラメータの勾配を自動的に計算します。
  • optimizer.step():計算された勾配に基づいて重みとバイアスを更新します。

トレーニングモード vs 評価モード:トレーニングループで呼び出しmodel.train()は必要です。この例では Dropout や BatchNorm を使用していませんが、複雑なモデルにとってこの習慣を身につけることは重要です。


モデルの評価

トレーニングが完了したら、モデルの重みとバイアスを確認することでモデルの効果を評価できます。また、新しいデータで予測を行い、実際の値と比較することもできます。

例

# トレーニング後の重みとバイアスを確認
print(f'Predicted weight: {model.linear.weight.data.numpy()}')
print(f'Predicted bias: {model.linear.bias.data.numpy()}')

# 新しいデータで予測を行う
with torch.no_grad():  # 評価時に勾配を計算する必要はありません
    predictions = model(X)

# 予測値と実際の値を可視化
plt.scatter(X[:, 0], Y, color='blue', label='True values')
plt.scatter(X[:, 0], predictions, color='red', label='Predictions')
plt.legend()
plt.show()
  • model.linear.weight.dataとmodel.linear.bias.data:これらの属性はモデルの重みとバイアスを格納します。
  • torch.no_grad():評価モードでは勾配を計算する必要がないため、メモリを節約し推論速度を向上させることができます。

完全なサンプルコード

以下は、上記のすべての手順をまとめた完全なコードで、そのまま実行できます:

例

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 1. データを準備
torch.manual_seed(42)
X = torch.randn(100, 2)  # 100個のサンプル、2つの特徴
true_w = torch.tensor([2.0, 3.0])
true_b = 4.0
Y = X @ true_w + true_b + torch.randn(100) * 0.1

# 2. モデルを定義
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)

    def forward(self, x):
        return self.linear(x)

model = LinearRegressionModel()

# 3. 損失関数とオプティマイザを定義
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 4. モデルをトレーニング
num_epochs = 1000
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# 5. モデルを評価
print(f'\nトレーニング後の重み: {model.linear.weight.data.numpy()}')
print(f'トレーニング後のバイアス: {model.linear.bias.data.numpy()}')
print(f'実際の重み: {true_w.numpy()}')
print(f'実際のバイアス: {true_b}')

実行結果の解析

上記のコードを実行すると、次のような出力が表示されます:

Epoch [100/1000], Loss: 0.0654
Epoch [200/1000], Loss: 0.0398
Epoch [300/1000], Loss: 0.0243
Epoch [400/1000], Loss: 0.0148
Epoch [500/1000], Loss: 0.0090
Epoch [600/1000], Loss: 0.0055
Epoch [700/1000], Loss: 0.0033
Epoch [800/1000], Loss: 0.0020
Epoch [900/1000], Loss: 0.0012
Epoch [1000/1000], Loss: 0.0008

训练后的权重: [[2.0016 2.9973]]
训练后的偏置: [4.0015]
真实权重: [2. 3.]
真实偏置: 4.0

トレーニングエポック数が増えるにつれて損失値が減少し、最終的にモデルの重みとバイアスが実際の値に非常に近づくことがわかります。これは、モデルがデータの線形関係を学習できたことを示しています。


発展:より複雑なオプティマイザを使用する

SGD 以外にも、PyTorch には多くのオプティマイザが用意されています。以下は Adam オプティマイザを使用する例です:

例

# Adam オプティマイザを使用(通常はより速く収束)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# モデルをトレーニング
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
オプティマイザ 特徴 適用シーン
SGD シンプルで直接的、学習率を手動で調整する必要がある 大規模データセット、古典的なシーン
Adam 適応的学習率、収束が速い ほとんどのシーンで、デフォルトとして推奨
RMSprop リカレントニューラルネットワークに適している RNN、LSTM などの系列モデル

よくある質問

問題1:損失値が下がらない

トレーニング中に損失値が下がらない場合、考えられる原因は次のとおりです:

  • 学習率が小さすぎると、パラメータの更新幅が小さくなります。
  • 学習率が大きすぎると、最適解を飛び越えてしまいます。
  • データに問題がある場合、例えば特徴値の範囲の差が大きすぎます。

解決策:

  • 学習率の調整を試みてください(0.1、0.01、0.001から試します)。
  • 入力データに対して標準化処理を行います。
  • データに外れ値や欠損値が存在するか確認します。

問題2:予測値の形状が一致しない

損失の計算時に形状不一致のエラーが発生した場合、次を使用する必要があるかもしれません:squeeze() 或 unsqueeze()テンソルの形状を調整します。

例

# 出力形状を確認
predictions = model(X)
print(f'モデル出力形状: {predictions.shape}')  # 形状は [100, 1] になるはず

# 1Dに圧縮
predictions = predictions.squeeze()
print(f'圧縮後の形状: {predictions.shape}')  # 形状は

# 損失を計算
loss = criterion(predictions, Y)

問題3:モデルの精度を向上させる方法

  • トレーニングのエポック数を増やします。
  • 学習率を調整します。
  • より複雑なモデル構造(多層ニューラルネットワークなど)を使用します。
  • トレーニングデータの量を増やします。
その他の拡張