PyTorchチュートリアル:画像認識、自然言語処理、生成モデルの実装
PyTorchは、その柔軟性と使いやすさから、深層学習の研究と開発において広く採用されているオープンソースの機械学習ライブラリです。本チュートリアルでは、PyTorchを使って画像認識、自然言語処理、生成モデルという深層学習の主要な分野における具体的な実装を通して、PyTorchの基本的な使い方から応用までを段階的に解説します。
1. PyTorchの基礎
まず、PyTorchを使い始める上で重要な基礎概念を理解しましょう。
-
Tensor: PyTorchにおける基本的なデータ構造です。多次元配列を表し、NumPyのndarrayと似ていますが、GPU上で実行できるという重要な違いがあります。
-
Autograd: PyTorchの自動微分機能です。ニューラルネットワークの学習において、勾配を自動的に計算してくれます。
-
nn.Module: ニューラルネットワークの構成要素となるレイヤーやモデルを定義するための基本クラスです。
-
Optimizer: モデルのパラメータを更新するためのアルゴリズムです。SGD、Adam、RMSpropなどが利用可能です。
1.1 Tensorの作成と操作
Tensorは、torch.Tensor()
もしくはNumPyのndarrayから作成できます。
“`python
import torch
import numpy as np
リストからTensorを作成
data = [1, 2, 3, 4]
x = torch.Tensor(data)
print(x) # tensor([1., 2., 3., 4.])
NumPy配列からTensorを作成
np_array = np.array([1, 2, 3, 4])
x = torch.Tensor(np_array)
print(x) # tensor([1., 2., 3., 4.])
サイズを指定してTensorを作成
x = torch.zeros((2, 3)) # 2×3の0で埋められたTensor
print(x)
x = torch.ones((2, 3)) # 2×3の1で埋められたTensor
print(x)
x = torch.rand((2, 3)) # 2×3のランダムな値を持つTensor
print(x)
“`
Tensorの操作もNumPyと同様に可能です。
“`python
x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6], [7, 8]])
加算
z = x + y
print(z)
乗算 (要素ごとの積)
z = x * y
print(z)
行列の積
z = torch.matmul(x, y)
print(z)
スライシング
print(x[0, :]) # 最初の行のすべての要素
print(x[:, 1]) # 2番目の列のすべての要素
“`
1.2 Autograd
Autogradは、Tensorに対して行う演算の履歴を記録し、自動的に勾配を計算します。これを利用するには、requires_grad=True
をTensorに設定します。
“`python
x = torch.tensor(2.0, requires_grad=True)
y = x*2 + 2x + 1
勾配を計算
y.backward()
xの勾配を表示
print(x.grad) # tensor(6.) (2*x + 2)にx=2を代入した値
“`
backward()
メソッドを呼び出すことで、計算グラフを遡って勾配を計算できます。
1.3 nn.Module
nn.Module
は、ニューラルネットワークのレイヤーやモデルを定義するための基本クラスです。__init__
メソッドでレイヤーを定義し、forward
メソッドでデータの流れを定義します。
“`python
import torch.nn as nn
import torch.nn.functional as F
class SimpleNet(nn.Module):
def init(self):
super(SimpleNet, self).init()
self.fc1 = nn.Linear(10, 5) # 入力サイズ10、出力サイズ5の全結合層
self.fc2 = nn.Linear(5, 2) # 入力サイズ5、出力サイズ2の全結合層
def forward(self, x):
x = F.relu(self.fc1(x)) # 全結合層 -> ReLU活性化関数
x = self.fc2(x) # 全結合層
return x
net = SimpleNet()
print(net)
“`
この例では、SimpleNet
というクラスを作成し、2つの全結合層(nn.Linear
)を定義しています。forward
メソッドでは、データの流れを定義しており、1つ目の全結合層の後にReLU活性化関数を適用しています。
1.4 Optimizer
Optimizerは、モデルのパラメータを更新するためのアルゴリズムです。よく使われるOptimizerには、SGD、Adam、RMSpropなどがあります。
“`python
import torch.optim as optim
net = SimpleNet()
optimizer = optim.Adam(net.parameters(), lr=0.001) # Adamオプティマイザ、学習率0.001
損失関数
criterion = nn.CrossEntropyLoss()
入力データ
input_data = torch.randn(1, 10) # バッチサイズ1, 入力サイズ10のランダムなTensor
target = torch.tensor([1]) # 正解ラベル (0または1)
訓練ループ
for i in range(100):
# 勾配を初期化
optimizer.zero_grad()
# 予測
output = net(input_data)
# 損失を計算
loss = criterion(output, target)
# 勾配を計算
loss.backward()
# パラメータを更新
optimizer.step()
if i % 10 == 0:
print(f"Epoch: {i}, Loss: {loss.item()}")
“`
この例では、optim.Adam
を使用してSimpleNet
のパラメータを更新しています。optimizer.zero_grad()
で勾配を初期化し、loss.backward()
で勾配を計算し、optimizer.step()
でパラメータを更新します。
2. 画像認識
次に、画像認識のタスクに取り組みます。ここでは、MNISTデータセットを使用して、手書き数字の分類を行います。
2.1 MNISTデータセット
MNISTデータセットは、0から9までの手書き数字の画像と、それに対応するラベルで構成されています。PyTorchのtorchvision
ライブラリを使って簡単にダウンロードできます。
“`python
import torchvision
import torchvision.transforms as transforms
データ変換
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))]) # 画像をTensorに変換し、正規化
MNISTデータセットをダウンロード
trainset = torchvision.datasets.MNIST(root=’./data’, train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.MNIST(root=’./data’, train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = (‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’)
“`
このコードでは、transforms.Compose
を使って、画像をTensorに変換し、正規化しています。torchvision.datasets.MNIST
でMNISTデータセットをダウンロードし、torch.utils.data.DataLoader
でデータをミニバッチに分割しています。
2.2 CNNモデルの定義
ここでは、畳み込みニューラルネットワーク(CNN)を使って、MNISTの画像分類を行います。
“`python
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def init(self):
super(Net, self).init()
self.conv1 = nn.Conv2d(1, 6, 5) # 入力チャネル1, 出力チャネル6, カーネルサイズ5の畳み込み層
self.pool = nn.MaxPool2d(2, 2) # 2×2のMaxPooling層
self.conv2 = nn.Conv2d(6, 16, 5) # 入力チャネル6, 出力チャネル16, カーネルサイズ5の畳み込み層
self.fc1 = nn.Linear(16 * 4 * 4, 120) # 全結合層
self.fc2 = nn.Linear(120, 84) # 全結合層
self.fc3 = nn.Linear(84, 10) # 全結合層
def forward(self, x):
x = self.pool(F.relu(self.conv1(x))) # 畳み込み -> ReLU -> MaxPooling
x = self.pool(F.relu(self.conv2(x))) # 畳み込み -> ReLU -> MaxPooling
x = x.view(-1, 16 * 4 * 4) # Tensorを平坦化
x = F.relu(self.fc1(x)) # 全結合層 -> ReLU
x = F.relu(self.fc2(x)) # 全結合層 -> ReLU
x = self.fc3(x) # 全結合層
return x
net = Net()
“`
このモデルは、2つの畳み込み層、2つのMaxPooling層、3つの全結合層で構成されています。畳み込み層は、画像の特徴を抽出し、MaxPooling層は特徴マップのサイズを縮小します。全結合層は、抽出された特徴を使って分類を行います。
2.3 モデルの訓練
“`python
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
for epoch in range(2): # 2エポック訓練
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 入力データとラベルを取得
inputs, labels = data
# 勾配を初期化
optimizer.zero_grad()
# 予測
outputs = net(inputs)
# 損失を計算
loss = criterion(outputs, labels)
# 勾配を計算
loss.backward()
# パラメータを更新
optimizer.step()
# 損失を記録
running_loss += loss.item()
if i % 2000 == 1999: # 2000ミニバッチごとに損失を表示
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print(‘Finished Training’)
“`
このコードでは、nn.CrossEntropyLoss
を損失関数として、optim.Adam
をOptimizerとして使用して、モデルを訓練しています。訓練データセットを繰り返し処理し、各ミニバッチに対して予測を行い、損失を計算し、勾配を計算し、パラメータを更新します。
2.4 モデルの評価
“`python
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(‘Accuracy of the network on the 10000 test images: %d %%’ % (
100 * correct / total))
“`
このコードでは、訓練されたモデルをテストデータセットで評価しています。各画像に対して予測を行い、予測されたラベルと実際のラベルを比較し、正解率を計算します。
3. 自然言語処理
次に、自然言語処理のタスクに取り組みます。ここでは、テキスト分類を行います。
3.1 データセットの準備
テキスト分類のためのデータセットを準備します。ここでは、IMDBレビューデータセットを使用します。このデータセットは、映画レビューとその感情(肯定的なレビューか否定的なレビューか)で構成されています。
“`python
import torchtext
from torchtext.legacy.data import Field, TabularDataset, Iterator, BucketIterator
テキストとラベルの定義
TEXT = Field(tokenize=’spacy’, lower=True, include_lengths=True)
LABEL = Field(sequential=False, use_vocab=False)
データセットの読み込み
train_data, test_data = TabularDataset.splits(
path=’./data’, train=’imdb_train.csv’, test=’imdb_test.csv’, format=’csv’,
fields=[(‘text’, TEXT), (‘label’, LABEL)], skip_header=True
)
語彙の構築
TEXT.build_vocab(train_data, max_size=25000)
バッチイテレータの作成
BATCH_SIZE = 64
train_iterator, test_iterator = BucketIterator.splits(
(train_data, test_data), batch_size=BATCH_SIZE,
sort_key=lambda x: len(x.text), sort_within_batch=True
)
“`
このコードでは、torchtext
ライブラリを使用して、IMDBレビューデータセットを読み込み、テキストとラベルを定義し、語彙を構築し、バッチイテレータを作成しています。
3.2 RNNモデルの定義
ここでは、再帰型ニューラルネットワーク(RNN)を使って、テキスト分類を行います。
“`python
import torch.nn as nn
class RNN(nn.Module):
def init(self, vocab_size, embedding_dim, hidden_dim, output_dim):
super(RNN, self).init()
self.embedding = nn.Embedding(vocab_size, embedding_dim) # 単語埋め込み層
self.rnn = nn.RNN(embedding_dim, hidden_dim) # RNN層
self.fc = nn.Linear(hidden_dim, output_dim) # 全結合層
def forward(self, text, text_lengths):
embedded = self.embedding(text) # テキストを埋め込みベクトルに変換
# PackedSequenceを使ってRNNの処理を効率化
packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, text_lengths)
packed_output, hidden = self.rnn(packed_embedded)
# output, hidden = self.rnn(embedded) # 全ての時刻の出力と最後の隠れ状態
return self.fc(hidden.squeeze(0)) # 最後の隠れ状態を使って分類
“`
このモデルは、単語埋め込み層、RNN層、全結合層で構成されています。単語埋め込み層は、単語を埋め込みベクトルに変換し、RNN層はテキストのシーケンスを処理し、全結合層はRNN層の出力を使って分類を行います。
3.3 モデルの訓練
“`python
import torch.optim as optim
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
HIDDEN_DIM = 256
OUTPUT_DIM = 1
model = RNN(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM)
optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()
def train(model, iterator, optimizer, criterion):
epoch_loss = 0
epoch_acc = 0
model.train()
for batch in iterator:
optimizer.zero_grad()
text, text_lengths = batch.text
predictions = model(text, text_lengths).squeeze(1)
loss = criterion(predictions, batch.label.float())
acc = binary_accuracy(predictions, batch.label)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
def binary_accuracy(preds, y):
rounded_preds = torch.round(torch.sigmoid(preds))
correct = (rounded_preds == y).float()
acc = correct.sum() / len(correct)
return acc
N_EPOCHS = 5
for epoch in range(N_EPOCHS):
train_loss, train_acc = train(model, train_iterator, optimizer, criterion)
print(f’| Epoch: {epoch+1:02} | Train Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}% |’)
“`
このコードでは、nn.BCEWithLogitsLoss
を損失関数として、optim.Adam
をOptimizerとして使用して、モデルを訓練しています。訓練データセットを繰り返し処理し、各ミニバッチに対して予測を行い、損失を計算し、勾配を計算し、パラメータを更新します。
4. 生成モデル
最後に、生成モデルのタスクに取り組みます。ここでは、変分オートエンコーダ(VAE)を実装します。
4.1 VAEの概要
VAEは、潜在変数を使ってデータを生成する生成モデルです。VAEは、エンコーダとデコーダで構成されています。エンコーダは、入力データを潜在空間にマッピングし、デコーダは、潜在空間からデータを生成します。VAEは、オートエンコーダの変種であり、潜在空間に確率分布を仮定することで、より滑らかな潜在空間を獲得し、より多様なデータを生成することができます。
4.2 VAEの実装
“`python
import torch.nn as nn
import torch.distributions as distributions
class VAE(nn.Module):
def init(self, input_dim, hidden_dim, latent_dim):
super(VAE, self).init()
# エンコーダ
self.encoder = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, latent_dim * 2) # 平均と分散を予測
)
# デコーダ
self.decoder = nn.Sequential(
nn.Linear(latent_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim),
nn.Sigmoid() # 出力を0-1の範囲に制限
)
self.latent_dim = latent_dim
def encode(self, x):
h = self.encoder(x)
mu, logvar = torch.split(h, self.latent_dim, dim=-1)
return mu, logvar
def reparameterize(self, mu, logvar):
std = torch.exp(0.5*logvar)
eps = torch.randn_like(std)
return mu + eps*std
def decode(self, z):
return self.decoder(z)
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
x_recon = self.decode(z)
return x_recon, mu, logvar
def loss_function(recon_x, x, mu, logvar):
BCE = F.binary_cross_entropy(recon_x, x, reduction=’sum’)
KLD = -0.5 * torch.sum(1 + logvar – mu.pow(2) – logvar.exp())
return BCE + KLD
“`
このコードでは、VAE
クラスを定義し、エンコーダ、リパラメータ化、デコーダを実装しています。encode
メソッドは、入力データを潜在空間の平均と分散にマッピングします。reparameterize
メソッドは、潜在空間の平均と分散を使って、潜在変数をサンプルします。decode
メソッドは、潜在変数からデータを生成します。loss_function
関数は、VAEの損失関数を計算します。
4.3 VAEの訓練
“`python
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
データローダーの準備
transform = transforms.ToTensor()
train_dataset = datasets.MNIST(root=’./data’, train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
モデルのインスタンス化
INPUT_DIM = 784 # MNIST画像のサイズは28×28=784
HIDDEN_DIM = 400
LATENT_DIM = 20
model = VAE(INPUT_DIM, HIDDEN_DIM, LATENT_DIM)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
訓練ループ
def train(epoch):
model.train()
train_loss = 0
for batch_idx, (data, _) in enumerate(train_loader):
data = data.view(-1, 784) # MNIST画像を784次元ベクトルに変換
optimizer.zero_grad()
recon_batch, mu, logvar = model(data)
loss = loss_function(recon_batch, data, mu, logvar)
loss.backward()
train_loss += loss.item()
optimizer.step()
if batch_idx % 100 == 0:
print(‘Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}’.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader),
loss.item() / len(data)))
print('====> Epoch: {} Average loss: {:.4f}'.format(
epoch, train_loss / len(train_loader.dataset)))
EPOCHS = 10
for epoch in range(1, EPOCHS + 1):
train(epoch)
“`
このコードでは、MNISTデータセットを使用して、VAEを訓練しています。MNIST画像を784次元ベクトルに変換し、loss_function
関数を使って損失を計算し、optim.Adam
をOptimizerとして使用して、モデルを訓練します。
まとめ
本チュートリアルでは、PyTorchを使って画像認識、自然言語処理、生成モデルという深層学習の主要な分野における具体的な実装を通して、PyTorchの基本的な使い方から応用までを段階的に解説しました。これらの例を参考に、さまざまな深層学習のタスクに挑戦してみてください。PyTorchの柔軟性と使いやすさを活かして、独自のモデルを構築し、深層学習の世界を探求していきましょう。