
[!] この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
PyTorchとは
PyTorchは、Lua言語で実装されたTorchというライブラリをもとに作られた、Python向けのオープンソース機械学習のライブラリです。
Facebook社の人工知能研究グループAI Research labにより開発され、2018年10月に安定版がはじめてリリースされた比較的新しいライブラリで、注目すべき機械学習ライブラリのひとつになっています。
この記事では、画像認識を題材にPyTorchのインストールからモデルの学習・予測を行います。
特徴
Kerasなどの機械学習用ライブラリと比較すると、以下のような特徴があります。
- 記述・判読がしやすくメンテナンス性に優れている
- NumPyの操作性に似ているため、操作しやすい
- ニューラルネットワークの構成をカスタマイズでき、オプティマイザーなど学習時の調整が可能
- 低レベルAPIであるため、デバックも簡単に行うことができる
環境構築
Pythonで動作するので、事前にPythonをインストールしてください。
この記事の開発環境は以下のとおりです。
バージョン | |
---|---|
OS | Windows 10 Pro |
Python | 3.9.6 |
Pythonの仮想環境を作成
Pythonの開発では、パッケージのバージョン違いや依存関係が問題になることがよくあります。
そのため、仮想環境を作成して開発を行うことが一般的です。
ここでは「.venv」という名前の仮想環境を作成します。
以下のコマンドを実行して、仮想環境を作成し、有効化させます。
> python -m venv .venv> ./.venv/Scripts/activate
以下のように表示されていれば、仮想環境が有効化されています。
(.venv) >
PyTorchのインストール
まずpipでPyTorchのインストールをします。
pip install torch torchvision
公式サイト ⧉で環境にあったPyTorchのインストール方法を確認できます。
使用するデータセット(CIFAR-10)
CIFAR-10とは、10クラス(airplane, automabile, bird, cat, deer, dog, frog, horse, ship, truck)のRGB画像(カラー画像)を含むデータセットです。
画像サイズは32×32ピクセルで、学習用データが5万枚、検証用データが1万枚の計6万枚のデータセットとなっています。
また、100クラスのデータを含むCIFAR-100というデータセットもあります。
画像認識の流れ
準備
必要なパッケージのインポート
必要なパッケージのインポートをします。
import torchimport torchvisionimport torchvision.transforms as transformsimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optim
transformを定義
PyTorchでデータを前処理する場合、あらかじめ用意されているtransformsパッケージを使用します。
transformsを利用することで簡単に画像の前処理ができるだけでなく、複数の前処理を同時に行ったり、
自分で定義した関数を使用できます。
またPyTorchで機械学習を行う際は、必ずTensorに変換する必要があります。
Tensorとは、PyTorchのデータを扱う際にもっとも基本となるデータ構造です。
今回は以下のクラスを使用して定義を行います。
ToTensor
:PIL Image・Numpy配列をTensorに変換Normalize
:指定した平均・標準偏差でTensorを正規化Compose
:複数のTransformをまとめて実行
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
学習データと検証データの準備
CIFAR-10のデータをダウンロードします。
今回は./data
に保存しています。
# 学習データtrain_data_with_teacher_labels = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)train_data_loader = torch.utils.data.DataLoader(train_data_with_teacher_labels, batch_size=4, shuffle=True, num_workers=2)
# 検証データtest_data_with_teacher_labels = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)test_data_loader = torch.utils.data.DataLoader(test_data_with_teacher_labels, batch_size=4, shuffle=False, num_workers=2)
torchvision.datasets.CIFAR10
の引数は以下のとおりです。
- root:保存ディレクトリ先を指定
- train:Trueであれば学習用データとしてロード、Falseであれば検証用データとしてロードする
- download:Trueの場合はデータセットをダウンロードし、rootで指定したディレクトリに配置する
- transform:transformを指定
torch.utils.data.DataLoader
の引数は以下のとおりです。
- batch_size:ミニバッチのサイズを指定
- shuffle:Trueの場合エポックごとにデータをシャッフルする
- num_workers:ミニバッチを作成する際の並行実行数を指定
モデル定義
学習のために畳み込みニューラルネットワークのモデルを定義します。
ニューラルネットワークは入力層・畳み込み層・プーリング層・全結合層・出力層から構成されています。
- 入力層:ピクセルで構成されている画像データの入力
- 畳み込み層:入力層で読み込んだ画像にフィルターをかけて特徴を検出する
- プーリング層:圧縮処理を行う
- 全結合層:入力層で検出された特徴を最後の出力層に渡す
- 出力層:ソフトマックス関数を利用し、出力の確率の変換をおこなう
PyTorchのモデル定義
class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.layer1 = nn.Linear(16 * 5 * 5, 120) self.layer2 = nn.Linear(120, 84) self.layer3 = nn.Linear(84, 10)
def forward(self, input_data): input_data = self.pool(F.relu(self.conv1(input_data))) input_data = self.pool(F.relu(self.conv2(input_data))) input_data = input_data.view(-1, 16 * 5 * 5) input_data = F.relu(self.layer1(input_data)) input_data = F.relu(self.layer2(input_data)) input_data = self.layer3(input_data) return input_data
PyTorchでは、Init関数とForward関数の2つの関数が必要になります。
-
init
:スーパーコンストラクターを呼び出し、学習・予測に必要なパラメーターの初期化を行うnn.Conv2d
:畳み込み層- 引数に入力チャンネル数、フィルター数、フィルタサイズを持つ
nn.MaxPool2d
:プーリング層- 引数に領域のサイズ、ストライドを持つ
nn.Linear
:全結合層- 引数に入力のベクトルサイズ、出力後のベクトルサイズを持つ
-
forward
:実際の処理を書くF.relu
:活性化関数であり、各層の後に使用する
※ ほかのデータセットを使用する場合、パラメーターは変わります。
このように、PyTorchにおけるモデル定義の方法は、ほかの機械学習ライブラリとは大きく異なります。
たとえば、Kerasでのモデル定義は以下のようになります。
from keras.models import Sequentialfrom keras.layers import Conv2Dfrom keras.layers import Activation, Flatten, Dense
model = Sequential()
model.add(Conv2D(filters=10, kernel_size=(3, 3), padding='same', input_shape=(32,32,3)) activation='relu'))model.add(Flatten())model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics=['accuracy'])
KerasではSequentialを使ったモデル定義の場合、model.add()
で使用する層と必要なパラメーターを連続的に繋げるだけで、簡単に定義できることがわかります。
PyTorchはモデルの定義を自分で行う必要がありますが、層の種類や伝搬の仕方などの低レベルなところまで定義できます。
optimizerの設定
PyTorchには代表的なコスト関数や最適化手法はあらかじめ提供されています。
今回は以下を使用します。
- コスト関数:クロスエントロピー
- 最適化手法:SGD
criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(params=model.parameters(), lr=0.001, momentum=0.9)
SGD
の引数は以下のとおりです。
- params:更新したいパラメーター
- lr:学習率
- momentum:過去の勾配が加わり、学習が不安定になる問題を防ぐ
学習
実際に動作させてみます。
今回は、ミニバッチ学習を行います。
ミニバッチ学習とは、学習用データからランダムにデータを取り出し、取り出したデータごとに勾配の計算とパラメーターの更新を行う学習方法です。
取り出した学習用データをミニバッチといい、すべてのミニバッチで学習したときの回数を1エポックといいます。
ミニバッチ学習では、学習の停滞を防ぐメリットがあります。
今回は4つずつデータを取り出し、3エポックで学習します。
学習用のデータは5万枚あるため、12500回学習が終了した場合1エポック終了することになります。
以下のコードをtorch_sample.py
として保存します。
import torchimport torchvisionimport torchvision.transforms as transformsimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optim
# エポック数MAX_EPOCH = 3
# ニューラルネットワークの定義class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.layer1 = nn.Linear(16 * 5 * 5, 120) self.layer2 = nn.Linear(120, 84) self.layer3 = nn.Linear(84, 10)
def forward(self, input_data): input_data = self.pool(F.relu(self.conv1(input_data))) input_data = self.pool(F.relu(self.conv2(input_data))) input_data = input_data.view(-1, 16 * 5 * 5) input_data = F.relu(self.layer1(input_data)) input_data = F.relu(self.layer2(input_data)) input_data = self.layer3(input_data) return input_data
def main():
# transform定義 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 学習データ train_data_with_teacher_labels = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) train_data_loader = torch.utils.data.DataLoader(train_data_with_teacher_labels, batch_size=4, shuffle=True, num_workers=2)
model = CNN()
# optimizerの設定 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(params=model.parameters(), lr=0.001, momentum=0.9)
# 学習 for epoch in range(MAX_EPOCH): total_loss = 0.0 for i, data in enumerate(train_data_loader, 0): # 学習データと教師ラベルデータを取得 train_data, teacher_labels = data # 勾配情報を削除 optimizer.zero_grad() # モデルで予測を計算 outputs = model(train_data) # 微分計算 loss = criterion(outputs, teacher_labels) loss.backward() # 勾配を更新 optimizer.step() # 誤差 total_loss += loss.item() # 1000ミニバッチずつ進捗を表示 if i % 1000 == 999: print('学習進捗:[学習回数:%d, ミニバッチ数:%5d] loss: %.3f' % (epoch + 1, i + 1, total_loss / 1000)) total_loss = 0.0
# モデル保存 torch.save(model.state_dict(), "model.pth")
print("-----学習完了-----")
以下のコマンドを実行して学習させます。
(.venv) > python torch_sample.py
実行すると以下のように進捗状況がターミナルに出力されます。
学習進捗:[学習回数:1, ミニバッチ数: 1000] loss: 2.300学習進捗:[学習回数:1, ミニバッチ数: 2000] loss: 2.170学習進捗:[学習回数:1, ミニバッチ数: 3000] loss: 1.965学習進捗:[学習回数:1, ミニバッチ数: 4000] loss: 1.865学習進捗:[学習回数:1, ミニバッチ数: 5000] loss: 1.745学習進捗:[学習回数:1, ミニバッチ数: 6000] loss: 1.690学習進捗:[学習回数:1, ミニバッチ数: 7000] loss: 1.641学習進捗:[学習回数:1, ミニバッチ数: 8000] loss: 1.615学習進捗:[学習回数:1, ミニバッチ数: 9000] loss: 1.543学習進捗:[学習回数:1, ミニバッチ数: 10000] loss: 1.541学習進捗:[学習回数:1, ミニバッチ数: 11000] loss: 1.506学習進捗:[学習回数:1, ミニバッチ数: 12000] loss: 1.498学習進捗:[学習回数:2, ミニバッチ数: 1000] loss: 1.404学習進捗:[学習回数:2, ミニバッチ数: 2000] loss: 1.434学習進捗:[学習回数:2, ミニバッチ数: 3000] loss: 1.410学習進捗:[学習回数:2, ミニバッチ数: 4000] loss: 1.398学習進捗:[学習回数:2, ミニバッチ数: 5000] loss: 1.340学習進捗:[学習回数:2, ミニバッチ数: 6000] loss: 1.360学習進捗:[学習回数:2, ミニバッチ数: 7000] loss: 1.334学習進捗:[学習回数:2, ミニバッチ数: 8000] loss: 1.337学習進捗:[学習回数:2, ミニバッチ数: 9000] loss: 1.296学習進捗:[学習回数:2, ミニバッチ数: 10000] loss: 1.333学習進捗:[学習回数:2, ミニバッチ数: 11000] loss: 1.294学習進捗:[学習回数:2, ミニバッチ数: 12000] loss: 1.308学習進捗:[学習回数:3, ミニバッチ数: 1000] loss: 1.240学習進捗:[学習回数:3, ミニバッチ数: 2000] loss: 1.218学習進捗:[学習回数:3, ミニバッチ数: 3000] loss: 1.226学習進捗:[学習回数:3, ミニバッチ数: 4000] loss: 1.230学習進捗:[学習回数:3, ミニバッチ数: 5000] loss: 1.236学習進捗:[学習回数:3, ミニバッチ数: 6000] loss: 1.264学習進捗:[学習回数:3, ミニバッチ数: 7000] loss: 1.210学習進捗:[学習回数:3, ミニバッチ数: 8000] loss: 1.206学習進捗:[学習回数:3, ミニバッチ数: 9000] loss: 1.203学習進捗:[学習回数:3, ミニバッチ数: 10000] loss: 1.209学習進捗:[学習回数:3, ミニバッチ数: 11000] loss: 1.180学習進捗:[学習回数:3, ミニバッチ数: 12000] loss: 1.185-----学習完了-----
予測
学習したモデルを読み込み、検証を行います。
今回はクラスごとの精度を検証してみます。
import torchimport torchvisionimport torchvision.transforms as transformsfrom torch_sample import CNN
def main():
# モデル読み込み model = CNN() model.load_state_dict(torch.load("model.pth"))
# transform定義 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 検証データ test_data_with_teacher_labels = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) test_data_loader = torch.utils.data.DataLoader(test_data_with_teacher_labels, batch_size=4, shuffle=False, num_workers=2)
# クラスの中身を設定 class_names = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# クラスごとの検証結果 class_corrent = list(0. for i in range(10)) class_total = list(0.for i in range(10))
with torch.no_grad(): # 勾配の計算をしない for data in test_data_loader: # 検証データと教師ラベルデータを取得 test_data, teacher_labels = data # 検証データをモデルに渡し予測 results = model(test_data) # 予測結果を取得 _, predicted = torch.max(results, 1) c = (predicted == teacher_labels).squeeze() for i in range(4): label = teacher_labels[i] class_corrent[label] += c[i].item() class_total[label] += 1 # 結果表示 for i in range(10): print(' %5s クラスの正解率: %2d %%' % (class_names[i], 100 * class_corrent[i] / class_total[i]))
結果は以下のとおりになりました。
plane クラスの正解率: 48 % car クラスの正解率: 82 % bird クラスの正解率: 43 % cat クラスの正解率: 22 % deer クラスの正解率: 32 % dog クラスの正解率: 66 % frog クラスの正解率: 75 % horse クラスの正解率: 58 % ship クラスの正解率: 68 % truck クラスの正解率: 56 %
今回はPyTorchの特徴に関する解説のため、正解率向上の方法については省略します。
まとめ
今回はPyTorchで一からニューラルネットワークを構成し、学習・予測を行いました。
ほかのライブラリと異なり、ニューラルネットワークの定義・optimizerの定義を柔軟に扱うことができ、低レベルのパラメーターの変更も簡単にできます。
たとえば、ニューラルネットワークの構成を変更したい場合は、 モデルの定義で定義したクラスのinit
に層を追加したり、forward
で伝搬の仕方を変更できます。
はじめての方も他のライブラリを使ったことがある方も、ぜひ一度PyTorchを使ってみてください。
参考文献
- [1]: PyTorch, https://pytorch.org/ ⧉
- [2]: 画像認識プログラミングレシピ、川島賢、秀和システム、2020