LoginSignup
17
21

More than 3 years have passed since last update.

クイックPyTorch入門

Last updated at Posted at 2021-03-12

いままで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()

スクリーンショット 2021-03-13 0.58.21.png
バッチサイズの分数字ラベルと、画像データが取り出されていることが確認できます。

モデル

あえて単純なモデルにしてます。

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で使う
  • 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)でネットワークとデータをデバイスに移すようにします
17
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
21