PyTorchによる深層学習

深層学習パッケージ Pytorch の基本的な使用法を紹介する.

簡単な計算

PyTorchパッケージを用いて \(N\)\(D\) 列のランダムな行列 \(x,y,z\) を作り, \(c = \sum x*y + z\) を計算をする. 最後に, 計算された \(c\)\(x\) に対する勾配(微分値)をbackwardで計算する.

randでランダムな行列をを生成する際に, 引数 requires_grad を True に設定しておくことによって, 勾配が計算される. 

変数に対しては, dataで値が, 勾配を計算するように指定した変数に対しては gradで勾配が得られる.

import torch
N, D = 3,4 #3行4列のランダムなテンソルを生成し, 勾配を計算する
x = torch.rand( (N, D), requires_grad=True)
y = torch.rand( (N, D), requires_grad=True)
z = torch.rand( (N, D), requires_grad=True)

a = x*y
b = a+z
c = torch.sum(b)

c.backward()

c.data
tensor(7.9498)
print(x.data)
tensor([[0.1379, 0.2976, 0.8908, 0.8022],
        [0.2833, 0.0333, 0.8762, 0.2876],
        [0.3688, 0.8043, 0.9437, 0.0616]])
print(x.grad)
tensor([[0.7403, 0.0179, 0.0335, 0.0769],
        [0.5766, 0.5749, 0.5347, 0.4289],
        [0.7062, 0.0719, 0.6709, 0.1248]])

最小2乗法

\(y = a x\) のパラメータ \(a\) の最適化を最小2乗法によって行う.

from_numpyでNumPyの配列をPyTorchのテンソルに変換できる. 

パラメータ \(a\) をランダムに設定し,勾配を計算するように指示する.

予測値 yhat を計算した後で, 損出関数 loss を誤差の2乗平均として計算し, backward で勾配を計算する.

勾配の逆方向に学習率 lr (learning rate) だけ移動させたものを新しい \(a\) とし, それをn_epochs回繰り返す.

ここで,反復ごとに勾配を \(0\) に初期化する a.grad.zero_ を呼び出すことに注意されたい.

import numpy as np
import torch
import torch.optim as optim
import torch.nn as nn
x_numpy = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=np.float32)
y_numpy = np.array([15.0, 20.0, 34.0, 42.0, 58.0], dtype=np.float32)

x = torch.from_numpy(x_numpy)
y = torch.from_numpy(y_numpy)

a = torch.rand(1, requires_grad=True) #スカラーを定義

n_epochs = 10  #反復回数(エポック数)
lr = 0.01      #学習率
for epoch in range(n_epochs):
    yhat = a*x 
    error = y - yhat
    loss =(error**2).mean()
    
    loss.backward()
    
    with torch.no_grad():
        a -= lr*a.grad

    print(loss.data, a.data)
    
    a.grad.zero_()
tensor(1317.2814) tensor([2.6670])
tensor(803.9544) tensor([4.5403])
tensor(491.6464) tensor([6.0014])
tensor(301.6381) tensor([7.1411])
tensor(186.0371) tensor([8.0301])
tensor(115.7055) tensor([8.7234])
tensor(72.9157) tensor([9.2643])
tensor(46.8824) tensor([9.6861])
tensor(31.0437) tensor([10.0152])
tensor(21.4075) tensor([10.2719])

線形回帰

クラス

nn.Moduleクラスから派生させて線形回帰を行うクラス LinearRegression を作る.

コンストラクタ init で親クラスを呼び出した後でパラメータ \(a\) を定義する.

与えられた \(x\) に対して予測値 \(y=ax\) を計算するための関数 forward を定義する.

モデルのインスタンス model のstate_dicメソッドでパラメータ \(a\) の現在の値の辞書を得ることができる.

class LinearRegression(nn.Module):
    #コンストラクタ
    def __init__(self):
        super().__init__() #親クラスのコンストラクタを呼ぶ
        self.a = nn.Parameter(torch.rand(1, requires_grad=True)) #モデルのパラメータを準備
        
    #予測値の計算
    def forward(self, x):
        return self.a*x
        
model = LinearRegression()

model.state_dict()  #パラメータと現在の値の辞書
OrderedDict([('a', tensor([0.5324]))])

訓練

損出関数を計算する関数 loss_fn を最小2乗誤差 nn.MSELoss とし, 最適化は率的勾配降下法 (SGD: Stochastic Fradient Descent) optim.SDG とする. 引数はモデルのパラメータ model.parameters() と学習率 lr である.

optimizerのstepで勾配降下法の1反復を行い, 反復ごとに zero_grad で勾配を \(0\) に初期化する.

loss_fn = nn.MSELoss()  #損出関数(最小2乗誤差)
optimizer = optim.SGD(model.parameters(), lr)  # 最適化(確率的勾配降下法)を準備; model.parameters()はパラメータを返すジェネレータ

for epoch in range(n_epochs):
    model.train()           #モデルを訓練モードにする
    yhat = model(x)         #forwardメソッドで予測値を計算する
    loss = loss_fn(y, yhat) #損出関数
    loss.backward()         #誤差逆伝播で勾配を計算
    
    optimizer.step()        #最適化の1反復
    optimizer.zero_grad()   #勾配を0にリセット
    
    print(loss.data, model.state_dict())
tensor(1253.9412) OrderedDict([('a', tensor([2.8753]))])
tensor(765.4183) OrderedDict([('a', tensor([4.7027]))])
tensor(468.2010) OrderedDict([('a', tensor([6.1281]))])
tensor(287.3740) OrderedDict([('a', tensor([7.2399]))])
tensor(177.3588) OrderedDict([('a', tensor([8.1072]))])
tensor(110.4256) OrderedDict([('a', tensor([8.7836]))])
tensor(69.7034) OrderedDict([('a', tensor([9.3112]))])
tensor(44.9280) OrderedDict([('a', tensor([9.7227]))])
tensor(29.8547) OrderedDict([('a', tensor([10.0437]))])
tensor(20.6841) OrderedDict([('a', tensor([10.2941]))])

線形層の追加

nn.Linear(入力数, 出力数)で線形層をモデルに追加する. モデルは \(y = w_0 + w_1 x\) となる.

データの \(x,y\) は縦ベクトル(shapeは (5,1) )になおしておく.

class LayerLinearRegression(nn.Module):
    #コンストラクタ
    def __init__(self):
        super().__init__() #親クラスのコンストラクタを呼ぶ
        self.linear = nn.Linear(1,1, dtype=torch.float32) #1入力・1出力の線形層
        #self.linear =nn.Sequential(nn.Linear(1,2), nn.ReLU(), nn.Linear(2,1)) #多層のモデルもSequentialを用いて作れる
    #予測値の計算
    def forward(self, x):
        return self.linear(x)
        
model = LayerLinearRegression()

model.state_dict()  #パラメータと現在の値の辞書
OrderedDict([('linear.weight', tensor([[-0.9852]])),
             ('linear.bias', tensor([0.3170]))])
x = x.reshape(-1,1)
y = y.reshape(-1,1)
loss_fn = nn.MSELoss()  #損出関数(最小2乗誤差)
optimizer = optim.SGD(model.parameters(), lr)  # 最適化(確率的勾配降下法)を準備; model.parameters()はパラメータを返すジェネレータ

n_epochs=10
for epoch in range(n_epochs):
    model.train()           #モデルを訓練モードにする
    yhat = model(x)         #forwardメソッドで予測値を計算する
    loss = loss_fn(y, yhat) #損出関数
    loss.backward()         #誤差逆伝播で勾配を計算
    
    optimizer.step()        #最適化の1反復
    optimizer.zero_grad()   #勾配を0にリセット
    
    print(loss.data, model.state_dict())
tensor(1611.6226) OrderedDict([('linear.weight', tensor([[1.6726]])), ('linear.bias', tensor([1.0458]))])
tensor(942.0174) OrderedDict([('linear.weight', tensor([[3.7018]])), ('linear.bias', tensor([1.6005]))])
tensor(551.8026) OrderedDict([('linear.weight', tensor([[5.2514]])), ('linear.bias', tensor([2.0224]))])
tensor(324.4026) OrderedDict([('linear.weight', tensor([[6.4348]])), ('linear.bias', tensor([2.3428]))])
tensor(191.8832) OrderedDict([('linear.weight', tensor([[7.3385]])), ('linear.bias', tensor([2.5859]))])
tensor(114.6554) OrderedDict([('linear.weight', tensor([[8.0289]])), ('linear.bias', tensor([2.7699]))])
tensor(69.6488) OrderedDict([('linear.weight', tensor([[8.5564]])), ('linear.bias', tensor([2.9087]))])
tensor(43.4192) OrderedDict([('linear.weight', tensor([[8.9594]])), ('linear.bias', tensor([3.0132]))])
tensor(28.1319) OrderedDict([('linear.weight', tensor([[9.2676]])), ('linear.bias', tensor([3.0914]))])
tensor(19.2212) OrderedDict([('linear.weight', tensor([[9.5032]])), ('linear.bias', tensor([3.1495]))])

データセットとデータローダー

データセットはTensorDatasetで生成されるサプライチェーン.

1バッチずつデータを出力するデータローダーは DataLoaderクラスに,データセットを入れることによって生成される.

from torch.utils.data import TensorDataset, DataLoader
train_data = TensorDataset(x,y)
train_data[0]
(tensor([1.]), tensor([15.]))
train_loader = DataLoader(dataset = train_data, batch_size=2, shuffle=True)
for x_batch, y_batch in train_loader:
    print(x_batch, y_batch)
tensor([[4.],
        [2.]]) tensor([[42.],
        [20.]])
tensor([[3.],
        [5.]]) tensor([[34.],
        [58.]])
tensor([[1.]]) tensor([[15.]])
loss_fn = nn.MSELoss()  #損出関数(最小2乗誤差)
optimizer = optim.SGD(model.parameters(), lr)  # 最適化(確率的勾配降下法)を準備; model.parameters()はパラメータを返すジェネレータ

n_epochs=10
for epoch in range(n_epochs):
    for x_batch, y_batch in train_loader:
        model.train()           #モデルを訓練モードにする
        yhat = model(x_batch)         #forwardメソッドで予測値を計算する
        loss = loss_fn(y_batch, yhat) #損出関数
        loss.backward()         #誤差逆伝播で勾配を計算

        optimizer.step()        #最適化の1反復
        optimizer.zero_grad()   #勾配を0にリセット

        print(loss.data, model.state_dict())
tensor(5.4948) OrderedDict([('linear.weight', tensor([[9.5969]])), ('linear.bias', tensor([3.1964]))])
tensor(26.1062) OrderedDict([('linear.weight', tensor([[9.8901]])), ('linear.bias', tensor([3.2406]))])
tensor(0.6415) OrderedDict([('linear.weight', tensor([[9.8260]])), ('linear.bias', tensor([3.2246]))])
tensor(2.0398) OrderedDict([('linear.weight', tensor([[9.8243]])), ('linear.bias', tensor([3.2388]))])
tensor(4.9985) OrderedDict([('linear.weight', tensor([[9.8052]])), ('linear.bias', tensor([3.2228]))])
tensor(33.0736) OrderedDict([('linear.weight', tensor([[10.3803]])), ('linear.bias', tensor([3.3379]))])
tensor(12.2090) OrderedDict([('linear.weight', tensor([[10.4364]])), ('linear.bias', tensor([3.3245]))])
tensor(0.9685) OrderedDict([('linear.weight', tensor([[10.4298]])), ('linear.bias', tensor([3.3305]))])
tensor(9.3001) OrderedDict([('linear.weight', tensor([[10.1858]])), ('linear.bias', tensor([3.2695]))])
tensor(8.4185) OrderedDict([('linear.weight', tensor([[10.3913]])), ('linear.bias', tensor([3.3230]))])
tensor(8.5516) OrderedDict([('linear.weight', tensor([[10.2943]])), ('linear.bias', tensor([3.2770]))])
tensor(6.0230) OrderedDict([('linear.weight', tensor([[10.0980]])), ('linear.bias', tensor([3.2279]))])
tensor(5.9756) OrderedDict([('linear.weight', tensor([[10.0438]])), ('linear.bias', tensor([3.1984]))])
tensor(2.4884) OrderedDict([('linear.weight', tensor([[10.0065]])), ('linear.bias', tensor([3.2023]))])
tensor(22.7092) OrderedDict([('linear.weight', tensor([[10.4830]])), ('linear.bias', tensor([3.2976]))])
tensor(2.8948) OrderedDict([('linear.weight', tensor([[10.5750]])), ('linear.bias', tensor([3.3130]))])
tensor(16.4854) OrderedDict([('linear.weight', tensor([[10.3412]])), ('linear.bias', tensor([3.2322]))])
tensor(2.0351) OrderedDict([('linear.weight', tensor([[10.3697]])), ('linear.bias', tensor([3.2608]))])
tensor(8.0694) OrderedDict([('linear.weight', tensor([[10.2786]])), ('linear.bias', tensor([3.2171]))])
tensor(6.8768) OrderedDict([('linear.weight', tensor([[10.4632]])), ('linear.bias', tensor([3.2660]))])
tensor(9.7260) OrderedDict([('linear.weight', tensor([[10.2137]])), ('linear.bias', tensor([3.2036]))])
tensor(1.2645) OrderedDict([('linear.weight', tensor([[10.2342]])), ('linear.bias', tensor([3.2210]))])
tensor(8.8373) OrderedDict([('linear.weight', tensor([[10.3283]])), ('linear.bias', tensor([3.2355]))])
tensor(15.1480) OrderedDict([('linear.weight', tensor([[10.1726]])), ('linear.bias', tensor([3.1577]))])
tensor(14.0528) OrderedDict([('linear.weight', tensor([[10.3015]])), ('linear.bias', tensor([3.1624]))])
tensor(1.1820) OrderedDict([('linear.weight', tensor([[10.3148]])), ('linear.bias', tensor([3.1771]))])
tensor(5.9366) OrderedDict([('linear.weight', tensor([[10.1199]])), ('linear.bias', tensor([3.1284]))])
tensor(7.2067) OrderedDict([('linear.weight', tensor([[10.0701]])), ('linear.bias', tensor([3.1122]))])
tensor(10.5234) OrderedDict([('linear.weight', tensor([[10.3173]])), ('linear.bias', tensor([3.1644]))])
tensor(5.9218) OrderedDict([('linear.weight', tensor([[10.1226]])), ('linear.bias', tensor([3.1157]))])