自分用のメモとして使い方をざっとまとめてみました
1. 3つのプロジェクト
PyTorchはNeural Networkがよく使われる文章、画像、音声の3つの用途それぞれについてのプロジェクトに分けられているようです。
2. datasets
上記の3つのプロジェクトごとにdatasetをダウンロード出来るようにしてくれています。
3. インストール
公式サイトにインストールコマンドが載っています
https://pytorch.org/get-started/locally/
上記ページを開くと環境に併せたビルドをcondaやpipなどでインストールするコマンドを表示してくれます
4. 使い方
このページに沿ってやっていきます
https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html
4-1. 準備
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda, Compose
import matplotlib.pyplot as plt
4-2. データセットの読み込み
FashionMNISTを使っていきます
train=True にするとtrainデータが得られます
# Download training data from open datasets.
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
)
# Download test data from open datasets.
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),
)
datasets.FashionMNIST()の返り値はクラスインスタンスになっているようです
>>> training_data
Dataset FashionMNIST
Number of datapoints: 60000
Root location: data
Split: Train
StandardTransform
Transform: ToTensor()
入力データはdataプロパティ、出力データはtargetsプロパティに入っています
>>> test_data.data
tensor([[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
...,
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]]], dtype=torch.uint8)
>>> test_data.targets
tensor([9, 2, 1, ..., 8, 1, 5])
入力データは28x28サイズのグレースケール画像になっています
fig, ax = plt.subplots(1, 5, figsize=(22, 4))
for k in range(5):
ax[k].imshow(test_data.data[k], cmap='gray')
ax[k].set_xticks([])
ax[k].set_yticks([])
plt.show()
4-3. DataLoaderをつくる
iterableなDataLoaderをつくって学習します
batch_size = 64
# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)
for X, y in test_dataloader:
print('Shape of data [N, H, W]: ', test_data.data.shape)
print("Shape of X [N, C, H, W]: ", X.shape)
print("Shape of y: ", y.shape, y.dtype)
break
上記ではbatch_sizeを64に設定していますので1バッチ分のXが64枚分出てきています
Shape of data [N, H, W]: torch.Size([10000, 28, 28])
Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])
Shape of y: torch.Size([64]) torch.int64
DataLoaderにすると自動的にチャンネルを作ってくれるのでshapeが変わっていますね
4-4. GPUかCPUか
PyTorchではデータやモデルをCPUで扱うかGPUで扱うかをtoメソッドを使って明示的に指定します。to('cuda')すればGPUに、to('cpu')すればCPUにアサインされます。modelがGPU、データがCPUみたいに混在した状態で扱おうとするとエラー停止しますので注意が必要です。
PyTorchがGPUを使用可能かどうかをtorch.cuda.is_available()で調べられますので、以下を実行しておいてto(device)すればGPUが使用可能か否かで自動的に動作を変えるという書き方が出来ます。
# Get cpu or gpu device for training.
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))
4-5. モデルをつくる
この例では全結合のみで構成されるNeural Networkモデルを作成します。
以下は入力が28x28の2次元データですのでnn.Flatten()で1次元に並べ替えて、512ノードの隠れ層を2層通してから出力段に繋げる構成になっています。FashionMNISTは10クラス分類ですのでcross entropy errorを使う為に出力は10ノードになっています。
# Define model
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
nn.ReLU()
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork().to(device)
print(model)
上記ではsoftmaxがないのでおかしいと思われるかも知れませんが、PyTorchではsoftmaxがオーバーフローして結果が不安定になるのを防ぐ為にlossの算出時にまとめてsoftmaxするようにしています。詳しくは以下を参照してください。
https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
4-6. 損失関数とoptimizerを設定する
softmaxしてcross entropy誤差を算出するnn.CrossEntropyLoss()を損失関数に、SGDをoptimizerにします。softmax済みの出力をnn.CrossEntropyLoss()に食わせるとlogが2回掛かってしまって変化が小さくなり過ぎるので意図したように学習が進まない筈ですから注意します。
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
使用可能な損失関数
https://pytorch.org/docs/stable/nn.html#loss-functions
使用可能なoptimizer
https://pytorch.org/docs/stable/optim.html
4-7. 学習用の関数と評価用の関数を作る
tensorflow.kerasの場合はモデルのインスタンスを作ってfit()するだけで学習が全自動で行われる書き方がスタンダードですが、PyTorchは以下のように自分で学習動作を書くのがスタンダードになっていて、学習プロセスの動作をちょこちょこ変えて実験するというのが気軽に出来るようになっています。
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
# Compute prediction error
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
torch.TensorはautoGradにより自動で微分が行われる仕様になっていて、自由度の高い書き方が出来るようになっているんだそうです。
https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html
これは学習の際には都合が良いんですが推定する際には必要ないので、その場合にはwith torch.no_grad()で明示的に無効にしてやります。
def test(dataloader, model):
size = len(dataloader.dataset)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= size
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
4-8. 学習する
PyTorchはepoch管理も自分で書きます
epochs = 5
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model)
print("Done!")
Epoch 1
-------------------------------
loss: 2.308795 [ 0/60000]
loss: 2.302261 [ 6400/60000]
loss: 2.289171 [12800/60000]
loss: 2.287153 [19200/60000]
loss: 2.266660 [25600/60000]
loss: 2.273998 [32000/60000]
loss: 2.258193 [38400/60000]
loss: 2.249706 [44800/60000]
loss: 2.246202 [51200/60000]
loss: 2.229442 [57600/60000]
Test Error:
Accuracy: 36.7%, Avg loss: 0.035078
...
Epoch 5
-------------------------------
loss: 1.774351 [ 0/60000]
loss: 1.843552 [ 6400/60000]
loss: 1.684049 [12800/60000]
loss: 1.775084 [19200/60000]
loss: 1.713003 [25600/60000]
loss: 1.710983 [32000/60000]
loss: 1.612547 [38400/60000]
loss: 1.593650 [44800/60000]
loss: 1.691809 [51200/60000]
loss: 1.610419 [57600/60000]
Test Error:
Accuracy: 46.0%, Avg loss: 0.026361
Done!
4-9. 推定する
classes = [
"T-shirt/top",
"Trouser",
"Pullover",
"Dress",
"Coat",
"Sandal",
"Shirt",
"Sneaker",
"Bag",
"Ankle boot",
]
model = model.to('cpu')
model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
pred = model(x)
predicted, actual = classes[pred[0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')
4-10. モデルのパラメータ
モデルの構成は以下で確認できます
>>> model.parameters
<bound method Module.parameters of NeuralNetwork(
(flatten): Flatten()
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
(5): ReLU()
)
)>
学習したweightやbiasは以下のようにして確認出来ます
>>> for param in model.parameters():
>>> print(param)
Parameter containing:
tensor([[-0.0103, -0.0277, -0.0012, ..., -0.0327, 0.0069, 0.0077],
[-0.0131, -0.0320, 0.0250, ..., 0.0132, 0.0125, -0.0227],
...,
[-0.0266, 0.0209, -0.0313, ..., 0.0115, 0.0292, 0.0354],
[-0.0337, -0.0203, 0.0159, ..., -0.0146, -0.0245, 0.0355]],
device='cuda:0', requires_grad=True)
Parameter containing:
tensor([-0.0006, -0.0170, -0.0109, 0.0206, -0.0258, -0.0104, 0.0273, -0.0256,
0.0164, 0.0151, 0.0255, 0.0077, -0.0127, 0.0343, -0.0034, -0.0269,
-0.0269, -0.0181, -0.0285, 0.0134, -0.0067, -0.0300, -0.0027, -0.0158,
0.0115, -0.0013, -0.0129, 0.0030, 0.0272, 0.0348, -0.0206, 0.0109,
0.0046, -0.0308, 0.0043, 0.0196, 0.0031, -0.0061, 0.0323, 0.0238,
0.0181, -0.0082, -0.0134, -0.0345, 0.0242, -0.0304, -0.0189, 0.0344,
-0.0300, -0.0112, -0.0151, 0.0025, -0.0027, 0.0392, -0.0147, 0.0280,
...
同じ内容をstate_dict()から参照することも出来ます
>>> model.state_dict()
OrderedDict([('linear_relu_stack.0.weight',
tensor([[-0.0103, -0.0277, -0.0012, ..., -0.0327, 0.0069, 0.0077],
[-0.0131, -0.0320, 0.0250, ..., 0.0132, 0.0125, -0.0227],
...,
[-0.0266, 0.0209, -0.0313, ..., 0.0115, 0.0292, 0.0354],
[-0.0337, -0.0203, 0.0159, ..., -0.0146, -0.0245, 0.0355]],
device='cuda:0')),
('linear_relu_stack.0.bias',
tensor([-0.0006, -0.0170, -0.0109, 0.0206, -0.0258, -0.0104, 0.0273, -0.0256,
0.0164, 0.0151, 0.0255, 0.0077, -0.0127, 0.0343, -0.0034, -0.0269,
-0.0269, -0.0181, -0.0285, 0.0134, -0.0067, -0.0300, -0.0027, -0.0158,
0.0115, -0.0013, -0.0129, 0.0030, 0.0272, 0.0348, -0.0206, 0.0109,
...
4-11. モデルの保存と読み込み
torch.save()により上記のstate_dict()を保存すればパラメータ一式を保存することが出来ます
torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")
同じモデルを作っておいてtorch.load()で上記のpthファイルを読めば同じモデルを再現できます
model2 = NeuralNetwork()
model2.load_state_dict(torch.load("model.pth"))
4-12. 初期化
初期化に使える関数は以下にあります
https://pytorch.org/docs/master/nn.init.html
レイヤーを定義する際に引数で与えるやり方は出来ないようなので、一度定義しておいてから置き換えてやります。以下のようにすると対象の値を書き換えてくれます。
nn.init.xavier_uniform_(model.linear_relu_stack[0].weight)
nn.init.kaiming_normal_(model.linear_relu_stack[0].weight)
nn.init.uniform_(model.linear_relu_stack[0].bias, 0, 1)
初期化はcpuモードのtorch.Tensorで与えられるようで、GPUにアサインしたモデルを後から書き換えるとややこしいので、to('gpu')する前に書き換えちゃうのが良いと思います。
model = NeuralNetwork()
initializer1 = nn.init.xavier_uniform_ # Gromet uniform
initializer2 = nn.init.uniform_
initializer1(model.linear_relu_stack[0].weight)
initializer2(model.linear_relu_stack[0].bias)
initializer1(model.linear_relu_stack[2].weight)
initializer2(model.linear_relu_stack[2].bias)
initializer1(model.linear_relu_stack[4].weight)
initializer2(model.linear_relu_stack[4].bias)
model = model.to(device)
なお、nn.LinearはデフォルトでweightをHeの正規分布(init.kaiming_uniform_)で初期化、biasを一様分布(init.uniform_)で初期化しますので、これで問題ない場合はそのままでOKです。
https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/linear.py
4-13. パラメータを書きかえる
パラメータは各レイヤーオブジェクトが保持しています
>>> model.linear_relu_stack[0].weight
Parameter containing:
tensor([[ 0.0327, -0.0140, 0.0057, ..., 0.0028, -0.0288, -0.0010],
[-0.0104, -0.0136, 0.0318, ..., 0.0161, -0.0176, -0.0022],
...,
[ 0.0006, -0.0308, 0.0015, ..., 0.0025, 0.0117, 0.0192],
[ 0.0191, -0.0007, -0.0314, ..., -0.0190, 0.0318, 0.0097]],
device='cuda:0', requires_grad=True)
>>> model.linear_relu_stack[0].bias
Parameter containing:
tensor([-0.0231, -0.0011, 0.0320, 0.0004, 0.0071, -0.0201, 0.0232, -0.0280,
0.0315, 0.0169, 0.0208, 0.0202, 0.0241, -0.0245, -0.0040, 0.0356,
-0.0100, -0.0037, 0.0312, -0.0099, 0.0017, -0.0071, 0.0355, -0.0262,
...
torch.Tensorなので、これを直接書き換えることが出来ます。例えば学習済みのmodelのパラメータを新規に作成したmodel2に上書きして使うことが出来ます。
w1 = model.linear_relu_stack[0].weight
b1 = model.linear_relu_stack[0].bias
w2 = model.linear_relu_stack[2].weight
b2 = model.linear_relu_stack[2].bias
w3 = model.linear_relu_stack[4].weight
b3 = model.linear_relu_stack[4].bias
model2 = NeuralNetwork()
model2.linear_relu_stack[0].weight = w1
model2.linear_relu_stack[0].bias = b1
model2.linear_relu_stack[2].weight = w2
model2.linear_relu_stack[2].bias = b2
model2.linear_relu_stack[4].weight = w3
model2.linear_relu_stack[4].bias = b3
classes = [
"T-shirt/top",
"Trouser",
"Pullover",
"Dress",
"Coat",
"Sandal",
"Shirt",
"Sneaker",
"Bag",
"Ankle boot",
]
model2.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
pred = model2(x)
predicted, actual = classes[pred[0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')
Predicted: "Ankle boot", Actual: "Ankle boot"
同じ結果が得られました
5. datasetsとdataloaderの作り方
こちらの解説が分かりやすかったのでコードをお借りします
https://qiita.com/mathlive/items/2a512831878b8018db02
5-1. datasetsの作成
data要素に入力データ、label要素に出力データ、datanum要素にデータの長さを入れておいて、出力する際の挙動を__getitem__()に書けば最小構成のdatasetsが作れます。
class Mydatasets(torch.utils.data.Dataset):
def __init__(self, transform = None):
self.transform = transform
self.data = [1, 2, 3, 4, 5, 6]
self.label = [0, 1, 0, 1, 0, 1]
self.datanum = 6
def __len__(self):
return self.datanum
def __getitem__(self, idx):
out_data = self.data[idx]
out_label = self.label[idx]
if self.transform:
out_data = self.transform(out_data)
return out_data, out_label
datasets = Mydatasets()
print('data: {}'.format(datasets.data))
print('label: {}\n'.format(datasets.label))
print(datasets.__getitem__(0))
print(datasets.__getitem__(1))
print(datasets.__getitem__(2))
data: [1, 2, 3, 4, 5, 6]
label: [0, 1, 0, 1, 0, 1]
(1, 0)
(2, 1)
(3, 0)
実用的にはdata, label, datanumを__init__()の引数で渡すようにした方が使いやすいかもしれません。
5-2. dataloaderの作成
torch.utils.data.DataLoader()が上記の__getitem__()を使ってdataloaderを作ってくれます。
dataloader = DataLoader(datasets, batch_size=2)
for x, y in dataloader:
print(x, y)
tensor([1, 2]) tensor([0, 1])
tensor([3, 4]) tensor([0, 1])
tensor([5, 6]) tensor([0, 1])
6. learning rate scheduler
6-1. 使い方
1epoch分の処理をやった後にscheduler.step()とする事でlearning rateが更新されます
>>> scheduler = ...
>>> for epoch in range(100):
>>> train(...)
>>> validate(...)
>>> scheduler.step()
6-2. 実際の使い方
先程やったFashion MNISTの学習で使おうと思ったら以下のようになります
def train(dataloader, model, loss_fn, optimizer, scheduler):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
# Compute prediction error
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}] lr: {scheduler.get_last_lr()[0]:1.4f}") # 変更
scheduler.step() # 追加
model = NeuralNetwork().to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-2)
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.8) # 追加
epochs = 5
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer, scheduler) # 変更
test(test_dataloader, model)
print("Done!")
Epoch 1
-------------------------------
loss: 2.300871 [ 0/60000] lr: 0.0100
loss: 2.229759 [ 6400/60000] lr: 0.0100
loss: 2.042035 [12800/60000] lr: 0.0100
loss: 1.979453 [19200/60000] lr: 0.0100
loss: 1.622117 [25600/60000] lr: 0.0100
loss: 1.392005 [32000/60000] lr: 0.0100
loss: 1.534957 [38400/60000] lr: 0.0100
loss: 1.227475 [44800/60000] lr: 0.0100
loss: 1.359196 [51200/60000] lr: 0.0100
loss: 1.329959 [57600/60000] lr: 0.0100
Test Error:
Accuracy: 58.3%, Avg loss: 0.018586
Epoch 2
-------------------------------
loss: 1.245046 [ 0/60000] lr: 0.0095
loss: 1.233158 [ 6400/60000] lr: 0.0095
loss: 0.924072 [12800/60000] lr: 0.0095
loss: 1.149151 [19200/60000] lr: 0.0095
loss: 1.119033 [25600/60000] lr: 0.0095
loss: 0.951288 [32000/60000] lr: 0.0095
loss: 1.260948 [38400/60000] lr: 0.0095
loss: 1.062005 [44800/60000] lr: 0.0095
loss: 1.209858 [51200/60000] lr: 0.0095
loss: 1.129560 [57600/60000] lr: 0.0095
Test Error:
Accuracy: 66.1%, Avg loss: 0.016154
Epoch 3
-------------------------------
loss: 1.050888 [ 0/60000] lr: 0.0090
loss: 1.074153 [ 6400/60000] lr: 0.0090
loss: 0.801356 [12800/60000] lr: 0.0090
loss: 1.031100 [19200/60000] lr: 0.0090
loss: 1.044556 [25600/60000] lr: 0.0090
loss: 0.885438 [32000/60000] lr: 0.0090
loss: 1.167787 [38400/60000] lr: 0.0090
loss: 1.047352 [44800/60000] lr: 0.0090
loss: 1.199912 [51200/60000] lr: 0.0090
loss: 1.053414 [57600/60000] lr: 0.0090
Test Error:
Accuracy: 69.7%, Avg loss: 0.015347
learning rateが指示通り減っているのが分かります
6-3. 挙動の確認
schedulerはいろいろあるので以下のコードで挙動を確認してみます
import torch
def check_lr(optimizer, scheduler):
s, lr = [], []
for epoch in range(100):
optimizer.step()
scheduler.step()
s.append(scheduler.get_last_lr()[0])
for param_group in optimizer.param_groups:
lr.append(param_group['lr'])
plt.figure(figsize=(4, 3))
plt.plot(s)
plt.plot(lr)
plt.show()
model = [torch.nn.Parameter(torch.randn(2, 2, requires_grad=True))]
optimizer = torch.optim.SGD(model, 0.1) # ここを書き換えて確認
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)
check_lr(optimizer, scheduler)
6-4. StepLR
step_sizeだけepochが進む度にlearning rateをgamma倍するschedulerです
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)
6-3. ExponentialLR
epochごとにlearning rateをgamma倍するschedulerです
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95)
6-4. CosineAnnealingWarmRestarts
この論文で紹介されているやり方です
https://arxiv.org/abs/1608.03983
T_0を周期とするコサインカーブで減衰して、あるところまで減衰したところで再び高いlearning rateに戻すような挙動により局所最適を脱出してもっと良いパラメータを探索します
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=0.)
6-5. LambdaLR
自分で書いた関数でlearning rateを操作するschedulerです
def func(epoch):
return 0.95 ** epoch
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, func)
7. まとめ
書き方の自由度が高く、いろいろ実験できるのが良いですね
レッツトライ