PyTorch (3) Linear Regression
まずは基本ということで線形回帰(Linear Regression)から。人工データとBoston house price datasetを試してみた。まだ簡単なのでCPUモードのみ。GPU対応はまた今度。
人工データセット
import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt # hyper parameters input_size = 1 output_size = 1 num_epochs = 60 learning_rate = 0.001
データセット作成
# toy dataset # 15 samples, 1 features x_train = np.array([3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, 10.791, 5.313, 7.997, 3.1], dtype=np.float32) y_train = np.array([1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, 2.827, 3.465, 1.65, 2.904, 1.3], dtype=np.float32) x_train = x_train.reshape(15, 1) y_train = y_train.reshape(15, 1)
nn.Linear
への入力は (N,∗,in_features)
であるため reshape
が必要。*
には任意の次元を追加できるが今回は1次元データなのでない。
モデルを構築
# linear regression model class LinearRegression(nn.Module): def __init__(self, input_size, output_size): super(LinearRegression, self).__init__() self.linear = nn.Linear(input_size, output_size) def forward(self, x): out = self.linear(x) return out model = LinearRegression(input_size, output_size)
PyTorchのモデルはChainerと似ている。
- nn.Module を継承したクラスを作成
__init__()
に層オブジェクトを定義forward()
に順方向の処理
LossとOptimizer
# loss and optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
- 線形回帰なので平均二乗誤差(mean squared error)
- OptimizerはもっともシンプルなStochastic Gradient Descentを指定
訓練ループ
# train the model for epoch in range(num_epochs): inputs = torch.from_numpy(x_train) targets = torch.from_numpy(y_train) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: print('Epoch [%d/%d], Loss: %.4f' % (epoch + 1, num_epochs, loss.item())) # save the model torch.save(model.state_dict(), 'model.pkl')
- 各エポックでは
zero_grad()
で勾配をクリアすること! - パラメータは
optimizer.step()
で更新される - 10エポックごとに訓練lossを表示する
- 最後にモデルを保存
訓練ループはもっと洗練させないと実用的ではないな。
実行結果
Epoch [10/100], Loss: 1.4917 Epoch [20/100], Loss: 0.3877 Epoch [30/100], Loss: 0.2065 Epoch [40/100], Loss: 0.1767 Epoch [50/100], Loss: 0.1719 Epoch [60/100], Loss: 0.1710 Epoch [70/100], Loss: 0.1709 Epoch [80/100], Loss: 0.1709 Epoch [90/100], Loss: 0.1709 Epoch [100/100], Loss: 0.1708
最後に訓練データと予測した直線を描画してみよう。
# plot the graph predicted = model(torch.from_numpy(x_train)).detach().numpy() plt.plot(x_train, y_train, 'ro', label='Original data') plt.plot(x_train, predicted, label='Fitted line') plt.legend() plt.show()
- 勾配(grad)を持っているTensorはそのままnumpy arrayに変換できない。
detach()
が必要
RuntimeError: Can't call numpy() on Variable that requires grad. Use var.detach().numpy() instead.
Boston house price dataset
次は家の価格のデータセットもやってみよう。13個の特徴量をもとに家の値段を予測する。入力層が13ユニットで出力層が1ユニットの線形回帰のネットワークを書いた。
import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt # hyper parameters input_size = 13 output_size = 1 num_epochs = 5000 learning_rate = 0.01 boston = load_boston() X = boston.data y = boston.target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state = 5) # データの標準化 scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test) y_train = np.expand_dims(y_train, axis=1) y_test = np.expand_dims(y_test, axis=1) # linear regression model class LinearRegression(nn.Module): def __init__(self, input_size, output_size): super(LinearRegression, self).__init__() self.linear = nn.Linear(input_size, output_size) def forward(self, x): out = self.linear(x) return out model = LinearRegression(input_size, output_size) # loss and optimizer criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) def train(X_train, y_train): inputs = torch.from_numpy(X_train).float() targets = torch.from_numpy(y_train).float() optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() return loss.item() def valid(X_test, y_test): inputs = torch.from_numpy(X_test).float() targets = torch.from_numpy(y_test).float() outputs = model(inputs) val_loss = criterion(outputs, targets) return val_loss.item() # train the model loss_list = [] val_loss_list = [] for epoch in range(num_epochs): # data shuffle perm = np.arange(X_train.shape[0]) np.random.shuffle(perm) X_train = X_train[perm] y_train = y_train[perm] loss = train(X_train, y_train) val_loss = valid(X_test, y_test) if epoch % 200 == 0: print('epoch %d, loss: %.4f val_loss: %.4f' % (epoch, loss, val_loss)) loss_list.append(loss) val_loss_list.append(val_loss) # plot learning curve plt.plot(range(num_epochs), loss_list, 'r-', label='train_loss') plt.plot(range(num_epochs), val_loss_list, 'b-', label='val_loss') plt.legend()
- データを平均0、標準偏差1に標準化すると結果が安定する
- テストデータには訓練データでfitしたscalerを適用する
train()
とvalid()
をそれぞれ関数として独立させた。このようにまとめると訓練ループがすっきりするのでよいかも。- 200エポックごとにログを出力した
実験結果
epoch 0, loss: 582.9910 val_loss: 594.2480 epoch 200, loss: 453.9804 val_loss: 479.6869 epoch 400, loss: 373.9557 val_loss: 402.7326 epoch 600, loss: 308.8472 val_loss: 337.8119 epoch 800, loss: 253.5647 val_loss: 281.1577 epoch 1000, loss: 206.5357 val_loss: 232.3899 epoch 1200, loss: 166.8685 val_loss: 191.0127 epoch 1400, loss: 133.7838 val_loss: 156.2874 epoch 1600, loss: 106.5488 val_loss: 127.4714 epoch 1800, loss: 84.4694 val_loss: 103.8716 epoch 2000, loss: 66.8853 val_loss: 84.8388 epoch 2200, loss: 53.1687 val_loss: 69.7598 epoch 2400, loss: 42.7244 val_loss: 58.0542 epoch 2600, loss: 34.9919 val_loss: 49.1742 epoch 2800, loss: 29.4506 val_loss: 42.6082 epoch 3000, loss: 25.6266 val_loss: 37.8878 epoch 3200, loss: 23.0996 val_loss: 34.5939 epoch 3400, loss: 21.5107 val_loss: 32.3652 epoch 3600, loss: 20.5666 val_loss: 30.9023 epoch 3800, loss: 20.0406 val_loss: 29.9688 epoch 4000, loss: 19.7679 val_loss: 29.3874 epoch 4200, loss: 19.6375 val_loss: 29.0325 epoch 4400, loss: 19.5806 val_loss: 28.8196 epoch 4600, loss: 19.5581 val_loss: 28.6940 epoch 4800, loss: 19.5501 val_loss: 28.6216
参考
PyTorch (2) 自動微分
PyTorchの自動微分を試してみた。
import numpy as np import torch import torch.nn as nn
まずは必要なライブラリをインポート。
# テンソルを作成 # requires_grad=Falseだと微分の対象にならず勾配はNoneが返る x = torch.tensor(1.0, requires_grad=True) w = torch.tensor(2.0, requires_grad=True) b = torch.tensor(3.0, requires_grad=True) # 計算グラフを構築 # y = 2 * x + 3 y = w * x + b # 勾配を計算 y.backward() # 勾配を表示 print(x.grad) # dy/dx = w = 2 print(w.grad) # dy/dw = x = 1 print(b.grad) # dy/db = 1
tensor(2.) tensor(1.) tensor(1.)
requires_grad=False
だと微分の対象にならず勾配はNoneが返るrequires_grad=Fase
はFine-tuningで層のパラメータを固定したいときに便利- 計算グラフを構築して
backward()
を実行するとグラフを構築する各変数のgrad
に勾配が入る
Theanoの使い方 (2) 自動微分(2015/5/18)をTheanoではなくPyTorchでやってみる
例1
x = torch.tensor(2.0, requires_grad=True) y = x ** 2 y.backward() print(x.grad)
tensor(4.)
- yは変数xの式で成り立っていて、yの
backward()
を呼び出すとそれぞれの変数のgrad
プロパティに勾配が入る。
例2
x = torch.tensor(2.0, requires_grad=True) y = torch.exp(x) y.backward() print(x.grad)
tensor(7.3891)
- 計算グラフを構築するときは
numpy
の関数numpy.exp()
を使ってはダメ - テンソル計算を行う専用の関数を使う
torch.exp()
- これらの関数は微分可能なので計算グラフ上で誤差逆伝搬が可能
例3
x = torch.tensor(np.pi, requires_grad=True) y = torch.sin(x) y.backward() print(x.grad)
tensor(-1.)
例4
x = torch.tensor(0.0, requires_grad=True) y = (x - 4) * (x ** 2 + 6) y.backward() print(x.grad)
tensor(6.)
例5
x = torch.tensor(2.0, requires_grad=True) y = (torch.sqrt(x) + 1) ** 3 y.backward() print(x.grad)
tensor(6.1820)
例6
最後は偏微分の例。
x = torch.tensor(1.0, requires_grad=True) y = torch.tensor(2.0, requires_grad=True) z = (x + 2 * y) ** 2 z.backward() print(x.grad) # dz/dx print(y.grad) # dz/dy
tensor(10.) tensor(20.)
lossを微分する
ニューラルネットの場合は、lossをパラメータ(重みやバイアス)で偏微分した値を使って勾配降下法でパラメータを更新するのが一般的。
# バッチサンプル数=5、入力特徴量の次元数=3 x = torch.randn(5, 3) # バッチサンプル数=5、出力特徴量の次元数=2 y = torch.randn(5, 2) # Linear層を作成 # 3ユニット => 2ユニット linear = nn.Linear(3, 2) # Linear層のパラメータ print('w:', linear.weight) print('b:', linear.bias) # lossとoptimizer criterion = nn.MSELoss() optimizer = torch.optim.SGD(linear.parameters(), lr=0.01) # forward pred = linear(x) # loss = L loss = criterion(pred, y) print('loss:', loss) # backpropagation loss.backward() # 勾配を表示 print('dL/dw:', linear.weight.grad) print('dL/db:', linear.bias.grad) # 勾配を用いてパラメータを更新 print('*** by hand') print(linear.weight.sub(0.01 * linear.weight.grad)) print(linear.bias.sub(0.01 * linear.bias.grad)) # 勾配降下法 optimizer.step() # 1ステップ更新後のパラメータを表示 # 上の式と結果が一致することがわかる print('*** by optimizer.step()') print(linear.weight) print(linear.bias)
w: Parameter containing: tensor([[ 0.4176, 0.2302, 0.3942], [-0.3258, 0.0489, -0.3333]], requires_grad=True) b: Parameter containing: tensor([0.4269, 0.2872], requires_grad=True) loss: tensor(1.3395, grad_fn=<MseLossBackward>) dL/dw: tensor([[ 0.4404, 0.4512, 0.9893], [-0.6777, -0.2535, -0.5191]]) dL/db: tensor([0.6095, 0.6305]) *** by hand tensor([[ 0.4132, 0.2257, 0.3843], [-0.3191, 0.0514, -0.3281]], grad_fn=<ThSubBackward>) tensor([0.4208, 0.2809], grad_fn=<ThSubBackward>) *** by optimizer.step() Parameter containing: tensor([[ 0.4132, 0.2257, 0.3843], [-0.3191, 0.0514, -0.3281]], requires_grad=True) Parameter containing: tensor([0.4208, 0.2809], requires_grad=True)
参考
PyTorch (1) リンク集
今年の目標(2018/1/1)で宣言したとおり今年はPyTorchを使えるようにしていこうと思ってます!
ここにPyTorchのリソースをまとめる予定です。一気に追加すると収拾つかないため内容を吟味してから追加してこうと思います。外部リンク集の2つのサイトはPyTorchに関するチュートリアルや論文の再現実装など大量のリソースがまとまっていてとてもおすすめです。あと公式のチュートリアルはとてもしっかり書かれていて勉強になります。こちらもおすすめ。
- PyTorch - 本家
- PyTorch Forums - 本家フォーラム
- PyTorch Official Tutorials - 本家のチュートリアル
- PyTorch Official Examples - さまざまな実装例
外部リンク集
- The Incredible Pytorch - チュートリアルから各種アルゴリズムの実装まで大量に紹介
- Awesome PyTorch List - 上と同じくチュートリアルから論文実装まで大量に紹介
チュートリアル
ビデオチュートリアル
- Practical Deep Learning with PyTorch - UdemyのMOOC
- PyTorch Zero To All - ビデオチュートリアル
- EuroSciPy 2017: Keynote: PyTorch: Framework for fast, dynamic deep learning and scientific computing - Soumithさんのプレゼン
GAN
- PyTorch Generative Model Collections - GANの実装集(コードも図もわかりやすい)