PyTorchとは
PyTorchはFacebookの開発するPython上でのテンソル計算・自動微分ライブラリで,特にディープラーニングに必要な機能が充実しています.2017年の初頭に公開され,瞬く間にTensorflow, Keras, Caffeに続くディープラーニングライブラリとして人気を博すこととなりました.
PyTorchはPreferred NetworkのディープラーニングライブラリChainerから影響を受けており,GoogleのTensorFlowやUniversité de MontréalのTheanoとは異なり,実行時に動的にグラフを構築するため,柔軟なコードを書くことができます.
PyTorchは,製品にも用いられているTensorFlowとは異なり,研究向けであることが明言されています.新機能の変更は多いものの,疎テンソルにいち早く対応するなど,最新の研究動向を追うにはよいのではないでしょうか.また,適当なレベルで書くことができて,素のTensorflowのように低レベルでもなく,Kerasの様に高度に抽象化されているわけでもなく,ラッパーによって書き方が多様でサンプルを見てもよく分からない,ということはないので,学びやすいと思います.
チュートリアル
とりあえず動かせるようになるチュートリアルです.
インストール
GPU環境は勿論,CPU環境でも動かすことができます.Linux,macOSの場合は 公式, Windowsの場合は Anaconda Cloudからインストールできます.
GPUを利用する場合,環境の設定が面倒なことが多いですがPyTorchでは特に設定せずにGPU対応版をダウンロードするとGPUが使えるようになるようです.
Tensor
PyTorchの基本はテンソルを操作するTensor
です.テンソルというと難しく聞こえますが,この場合は多次元配列と同義で,物理学のテンソルのような共変・反変を意識する必要はありません.慣例に従ってテンソル,と言う語を用います.
PyTorchにおけるTensor
は端的に言えば「GPU上でも動くnumpy.ndarray
のようなもの」ですが,違いも多いので注意が必要です.例えば
import numpy as np
import torch # PyTorch
>>> np_tensor = np.zeros([1, 2, 3])
array([[[ 0., 0., 0.],
[ 0., 0., 0.]]])
>>> np_tensor.shape
(1, 2, 3)
>>> torch_tensor = torch.zeros(1, 2, 3)
(0 ,.,.) =
0 0 0
0 0 0
[torch.FloatTensor of size 1x2x3]
>>> torch_tensor.size()
torch.Size([1, 2, 3])
などです.なお,numpy
の配列は
>>> torch.from_numpy(np_tensor)
(0 ,.,.) =
0 0 0
0 0 0
[torch.DoubleTensor of size 1x2x3]
によってTensor
に変換されます.上記のtorch.**Tensor
はCPU上で計算を行いますが,GPUを用いる場合にはtorch_tensor.cuda()
によってtorch.cuda.**Tensor
に変換します.反対に,GPUからCPUに移す場合はtorch_gpu_tensor.cpu()
です.
ほかのライブラリとの違いとして,画像のテンソルがであることが挙げられます.
Variable
Variable
はTensor
と今までの計算の履歴を保持しており,計算グラフの葉(leaf)の勾配を得ることができます.
Tensor
はVariable(torch_tensor)
によってVariable
とすることができます.逆にVariable
内のTensor
はvar.data
によって取り出すことができます.
from torch.autograd import Variable
>>> a = Variable(torch.Tensor([3, 2]))
>>> a.requires_grad = True
>>> b = a * a * a
>>> c = torch.sum(b)
>>> c.backward()
>>> a.grad
Variable containing:
27
12
[torch.FloatTensor of size 2]
つまりに対して,なのでとなります.
実際のニューラルネットワークでは下の図のように,Variable
がConv2d
などのレイヤーに葉として接続しています.誤差逆伝播を行うことで葉のテンソルを更新していきます.
計算グラフの一部
MNISTの分類
ここまで扱った範囲だけで簡単なネットワークをつくることができます1.手書きの数字認識を行ってみましょう.
手書き数字認識の課題にはMNISTというデータセットがよく用いられます.
weight とbias に対してMNISTの画像の入力を与え,そのをとったを出力とします.ここでです.
損失函数lossにはnegative log likelihood(NLL)を用い,これを最小化します.を目標のラベルだとすると,
です.これが最小になるのはつまり,の第要素が1,ほかが0となるときです.以上が順伝播のフェーズです.
weight, biasの更新は
です.誤差を元に重みを更新していく過程が逆伝播です.
# simplified code!
weight = Variable(torch.randn(28*28, 10), requires_grad=True)
bias = Variable(torch.randn(10), requires_grad=True)
lr = 0.001
for epoch in range(2):
for (input, target) in train_loader:
input, target = Variable(input), Variable(target)
input = input.view(-1, 28*28)
# weight, biasの勾配を0にする
weight.grad.data.zero_()
bias.grad.data.zero_()
# 順伝播
output = F.log_softmax(input.mm(weight).add(bias))
loss = F.nll_loss(output, target)
# 逆伝播
loss.backward()
# weight, biasの更新
weight.data -= lr * weight.grad.data
bias.data -= lr * bias.grad.data
correct = 0
for (input, target) in test_loader:
input, target = Variable(input), Variable(target)
input = input.view(-1, 28*28)
output = F.log_softmax(input.mm(weight).add(bias))
pred = output.data.max(1)[1]
correct += pred.eq(target.data).sum()
2エポックでも87%ほどの精度が出ました.
コードを見れば大体分かるかと思いますが,以下の点に注意してください.
input = input.view(-1, 28*28)
では のMNISTの画像を1次元にして,ベクトルとして扱っています.view
を行うためにはメモリ上において連続である必要があるので,torch.contiguous()
によって連続にすることが必要な場合があります.F
はtorch.nn.functional
のことで,ニューラルネットワークに必要な函数類があります.target
はonehotではなくてラベルで与えます.
詳細はこちらをご覧下さい.
nn
上述のMNISTの分類では重みを自分で定義し,手動で更新する必要がありましたが,複雑なモデルをつくっていくのは大変です.
PyTorchには上記が抽象化されたnn
モジュールが用意されています.nn
を使うと上記のコードは
from torch import nn
from torch.nn import functional as F
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.dense = nn.Linear(28*28, 10)
def forward(self, x):
x = self.dense(x)
return F.log_softmax(x)
model = SimpleNet()
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
for (input, target) in train_loader:
data, target = Variable(data), Variable(target)
# weight, biasの勾配を0にする
optimizer.zero_grad()
# 順伝播
output = model(data)
loss = F.nll_loss(output, target)
# 逆伝播
loss.backward()
# weight, biasの更新
optimizer.step()
...
のようになります.ここでは先ほどと違ってnn.Module
とtorch.optim.***
を使っています.
SimpleNet
の__init__
メソッドではモデルのレイヤーやブロックを定義します.forward
メソッドに順伝播時のデータの流れを記述していき,model(input)
によって出力を得ます.nn
には各種レイヤーが用意されていて,今回は全結合層,つまりのnn.Linear
を用いています.
torch.optim
にはSGDを初めとするoptimizerが用意されています.optimizerにモデルのパラメータを渡して,効率的に重みを更新していきます.今回は最も単純なoptim.SGD
を用いています.
さらに複雑なモデルを書くには
上に示した例は1層でしたが,もちろん更に複雑なネットワークを構築することができます.ここでは例として畳み込み層2,全結合層2の畳み込みニューラルネットワーク(CNN)を考えます.
Conv2d
,max_pool2d
の挙動については こちらが分かりやすいです.
class Net1(nn.Module):
def __init__(self):
super(Net1, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1,
out_channels=10,
kernel_size=5,
stride=1)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.dense1 = nn.Linear(in_features=320,
out_features=50)
self.dense2 = nn.Linear(50, 10)
def forward(self, x):
x = self.conv1(x)
x = F.max_pool2d(x, kernel_size=2)
x = F.relu(x)
x = self.conv2(x)
x = F.max_pool2d(x, 2)
x = F.relu(x)
x = x.view(-1, 320)
x = self.dense1(x)
x = F.relu(x)
x = self.dense2(x)
return F.log_softmax(x)
以下のように書くこともできます.
class Net2(nn.Module):
def __init__(self):
super(Net2, self).__init__()
self.head = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=10,
kernel_size=5, stride=1),
nn.MaxPool2d(kernel_size=2),
nn.ReLU(),
nn.Conv2d(10, 20, kernel_size=5),
nn.MaxPool2d(kernel_size=2),
nn.ReLU())
self.tail = nn.Sequential(
nn.Linear(320, 50),
nn.ReLU(),
nn.Linear(50, 10))
def forward(self, x):
x = self.head(x)
x = x.view(-1, 320)
x = self.tail(x)
return F.log_softmax(x)
両者は同一のネットワークを表していますが,
>>> Net1()
Net1 (
(conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
(dense1): Linear (320 -> 50)
(dense2): Linear (50 -> 10)
)
>>> Net2()
Net2 (
(head): Sequential (
(0): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
(1): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(2): ReLU ()
(3): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
(4): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(5): ReLU ()
)
(tail): Sequential (
(0): Linear (320 -> 50)
(1): ReLU ()
(2): Linear (50 -> 10)
)
)
>>> Net2().head
Sequential (
(0): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
(1): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(2): ReLU ()
(3): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
(4): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(5): ReLU ()
)
と違いがあります.前者はforward
メソッドの自由度が高く,後者はブロックの一部を再利用するのに向いています.層を積んでいき,重みを再利用することの多いCNNでは後者を用いた方が便利かもしれません
リンク集
PyTorchの基本は以上で説明できたと思います.更に知りたい方は以下をご覧下さい.
サポートが充実しているのも特徴です.
- Raschka氏のdiscussionを参考にしています. [return]