いままでKerasしか使ったことなかったが、他の人のコードを読んだり、修正したりするのにPyTorch需要が出てきたので、いまさらPyTorchを学びました。
細かいことは後から知っていけばよいので、とにかく超簡単な例で一連の流れを学びます。
今回使うnotebookはこちら。
notebookだけ見てもわかるようにコメント書いてあります。
データ準備
手書き数字データのmnistを使っていきます。
import torch
import torchvision
import torchvision.transforms as transforms
# データの変換用
transform = transforms.Compose(
[
# numpy.ndarrayをテンソルに変換
transforms.ToTensor(),
# 平均0.5、標準偏差0.5へ。チャンネル数のタプルで渡す。
transforms.Normalize(mean=(0.5, ), std=(0.5, ))
]
)
# 学習データ用意
trainset = torchvision.datasets.MNIST(
root='./dataset', # 保存先
train=True,
download=True,
transform=transform
)
# 学習用ローダー
trainloader = torch.utils.data.DataLoader(
trainset,
batch_size=128,
shuffle=True,
num_workers=2
)
-
transforms
: データセットを前処理するためのツールです -
torchvision.datasets.MNIST
: pytorchがMNISTデータセットを用意してくれているのでこれを使います -
DataLoader
: MNISTに限らず、自分で用意したデータでもよく使うもの。指定したデータセットをバッチサイズごと取り出してくれる。
データの確認
dataiter = iter(trainloader)
images, labels = dataiter.next()
バッチサイズの分数字ラベルと、画像データが取り出されていることが確認できます。
モデル
あえて単純なモデルにしてます。
import torch.nn as nn
import torch.nn.functional as F
# ネットワークの定義
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 28x28次元 -> 256次元
self.fc1 = nn.Linear(1*28*28, 256)
# 256次元 -> 10次元(10クラスあるため)
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
# shapeを変形する(バッチサイズ x 次元数)
x = x.view(-1, 1*28*28)
# 線形変換後ReLuをかます
x = F.relu(self.fc1(x))
# 【注意】Kerasだと最後にsoftmaxをかますが、
# pytorchだと損失関数を計算する nn.CrossEntropyLoss の中にsoftmaxの計算が含まれている
# ただし、予測時に確率にしたい場合はアウトプットを nn.functional.softmax() で変換する必要がある
x = self.fc2(x)
return x
-
nn.Module
を継承してモデルを定義します -
__init__
内では、モデルで使う層を定義しておきます- KerasだとSequentialにaddしていくだけだったが、PyTorchでは事前に層を定義してから
forward
で使う
- KerasだとSequentialにaddしていくだけだったが、PyTorchでは事前に層を定義してから
-
forward
では、データ入力されたときのモデルの出力を書きます- この例では、1次元ベクトルにしたあとに、線形変換に活性化関数をかまして、最後に線形変換して返す
- 【注意】のコメントはぜひ見ておいてください
net = Net()
import torch.optim as optim
# 目的関数
criterion = nn.CrossEntropyLoss()
# 最適化手法
optimizer = optim.Adam(net.parameters(), lr=0.001)
- モデルのインスタンス作成
- 目的関数の設定(今回は
CrossEntropyLoss
) - 最適化手法を設定します(今回は
Adam
)
学習
Kerasと違って自分で学習のループを書くことになります。
# Kerasやsklearnでいうfit
epochs=10
varbose=100
for epoch in tqdm(range(epochs)):
# 損失(表示用)
running_loss = 0.0
for i, (inputs, labels) in enumerate(trainloader):
# 勾配の初期化
optimizer.zero_grad()
# Netのforwardに基づいて出力が計算される
outputs = net(inputs)
# 損失の計算
loss = criterion(outputs, labels)
# ↑ここまでで計算グラフの構築が行われ、勾配に使う情報が設定される
# 勾配を計算
loss.backward()
# 最適化法に基づいてパラメータ更新
optimizer.step()
# varbose回分の損失の和。item() で値を取り出している
running_loss += loss.item()
if i % varbose == varbose-1:
print(f"epoch:{epoch + 1}, batch:{i + 1}, loss: {running_loss / varbose}")
running_loss = 0.0
-
optimizer.zero_grad()
: 勾配情報をクリアします- 前の勾配情報が残っているため、ゼロクリアする必要がある
-
outputs = net(inputs)
: モデルのforwardに基づいて出力が計算されます -
loss = criterion(outputs, labels)
: 先程指定したCrossEntropyLoss
に基づいて損失計算されます -
loss.backward()
: 逆伝播で勾配を計算します- この段階ではまだパラメータは更新されてません
-
optimizer.step()
: パラメータ更新されます
その他の記述は学習の進捗が見えるようにするもので、特に意味はありません。
epoch:1, batch:100, loss: 0.6820216017961502
epoch:1, batch:200, loss: 0.3693145060539246
epoch:1, batch:300, loss: 0.332705043554306
epoch:1, batch:400, loss: 0.29559941463172434
epoch:2, batch:100, loss: 0.23795114882290364
epoch:2, batch:200, loss: 0.20222241077572106
epoch:2, batch:300, loss: 0.1960952030867338
epoch:2, batch:400, loss: 0.1974168461561203
epoch:3, batch:100, loss: 0.15257078558206558
epoch:3, batch:200, loss: 0.1440433470159769
epoch:3, batch:300, loss: 0.142299246750772
epoch:3, batch:400, loss: 0.13810560397803784
epoch:4, batch:100, loss: 0.11472118373960256
epoch:4, batch:200, loss: 0.10906709022819996
epoch:4, batch:300, loss: 0.11881133031100034
epoch:4, batch:400, loss: 0.10873796993866564
epoch:5, batch:100, loss: 0.09303514676168562
epoch:5, batch:200, loss: 0.09735027667135
epoch:5, batch:300, loss: 0.09419337672181427
epoch:5, batch:400, loss: 0.08241723172366619
実行するとこのように表示されます。
予測
correct = 0
total = 0
# 予測時に勾配更新しないため、no_gradを指定
with torch.no_grad():
for (images, labels) in testloader:
# forwardした結果
# これを nn.functional.softmax() すると確率が得られるが、ここでは使わない
outputs = net(images)
# torch.max() は (最大値, そのindex)を返す関数。dimは最大値を見る軸
_, predicted = torch.max(outputs.data, dim=1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f"Accuracy: {100 * float(correct/total)}")
Accuracy: 97.26
-
outputs = net(images)
: モデルのforward
に基づいて予測されます- 【注意】でも書いたとおり、
nn.functional.softmax()
を取らないと確率になってません
- 【注意】でも書いたとおり、
-
_, predicted = torch.max(outputs.data, dim=1)
: 予測値が最大となるindexを取ってきます(これが予測した数字) -
total += labels.size(0)
: 予測したサンプル数の合計を計算してます -
correct += (predicted == labels).sum().item()
: 正解した個数を記録してます-
item()
はTensorから値を取ってくる関数です
-
GPUを使うには
コードの書き方だけのメモで、環境構築は別のサイトを参照してください。
# cudaが使えるか確認
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
device(type='cpu')
-
torch.cuda.is_available()
: GCPを使えるかどうか判定します
# .toメソッドでデバイスに移すようにする
# ネットワークに関して
net = Net().to(device)
# データに関して
images, labels = dataiter.next()
images, labels = images.to(device), labels.to(device)
-
.to(device)
でネットワークとデータをデバイスに移すようにします