はじめに
[第3版]Python機械学習プログラミング 達人データサイエンティストによる理論と実践を使用して機械学習を2ヶ月ほど勉強しております。
深層学習を学ぶにあたって、大きくフレームワークとしてはTensorFlow
とPytorch
の2つがあると思います。この書籍ではTensorFlow
を用いていたため、それに従ってTensorFlow
を学び、ある程度は使えるようになったかと思います。ただPytorch
はノータッチ。
そのためKaggleなどのコードがPytorch
で書かれてたりすると「ん、Pytorch
かスルーしよ」ってなってました。
ただ最近GANに興味があり、最新のモデルの論文がPytorch
で書かれていることが多く、まぁ使えるライブラリは多いに越したことないよなってことでコチラの公式チュートリアルを日本語訳版にして頂いたサイトを全力で感謝しながら、とりあえずCIFAR-10の画像分類までやってみました。
すでにPytorch
をメインで触っている方の比較記事もありますが、
TensorFlow
(とkeras
)をメインで触っている初心者の比較ということで見て頂けたら。
またTensorFlow
単体で書くことはほとんどないので、TensorFlow/keras
と Pytorch
の比較として見てください。
環境・バージョン・データ
AnacondaのJupyter labを使用。
バージョンは
・TensorFlow : 2.5.0
・Pytorch : 1.9.0
データは先述したようにCIFAR-10を使います。
import
TensorFlow
import tensorflow as tf
from tensorflow.keras.layers import *
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.datasets.cifar10 import load_data
Pytorch
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
TensorFlow
の中のkeras
から必要なものは持ってこれます。
keras.layers
がCNNモデル構築の時のさまざまな層(Dense, Conv2Dなど)を表現
keras.models
がモデルを表現
TensorFlow
単体というよりkeras
とセットで使うことがほとんどですね
Pytorch
は大きくtorch
とtorchvision
の2つから必要なものを引っ張ってくるのかな。
torch
がモデルや層やアルゴリズムなどモデル構築に必要なもの。
torchvision
はデータセットの用意などに使う印象です。
データの用意・前処理
TensorFlow
# データセットを取得
(X_train, y_train), (X_test, y_test) = load_data()
# [-1,1]スケールに変換
X_train = (X_train - 127.5 ) / 127.5
X_test = (X_test - 127.5) / 127.5
Pytorch
# データに対してどういう変換をするかまとめて定義
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# データセットのDL,バッチ・シャッフル
trainset = torchvision.datasets.CIFAR10(
root = './data', train = True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(
trainset, batch_size=4, shuffle=True)
testset = torchvision.datasets.CIFAR10(
root = './data', train = False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(
testset, batch_size=4, shuffle=False)
TensorFlow
の方はload_data
をimportしているのもありますが、すぐにデータを用意してくれますね。
CIFAR-10の場合、データを取得した段階で既に4次元データ(データ数、高さ、幅、チャネル数)なので楽です。
この後tf.data.Dataset.from_tensor_slices
を使えばデータセット化、バッチ、シャッフルなどできますが、別にこのままでもfitできるので、、、
とにかく楽という印象。
Pytorch
はtrainとtest別々にデータセットを読み取ってDataloader
でバッチ、シャッフルする形。
transform
で行いたい前処理をまとめてできる。
そう考えるとTensorflowでいう、tf.data.Dataset.from_tensor_slices
とやってることは似てるのかな。
どちらにしても慣れてしまえば簡単そう。
モデル構築
TensorFlow
model = Sequential()
model.add(Conv2D(filters=6, kernel_size=5, input_shape=(32, 32, 3))) # 畳み込み層
model.add(Activation('relu')) # 活性化関数
model.add(MaxPooling2D(2, 2)) # プーリング層
model.add(Conv2D(filters=16, kernel_size=5))
model.add(Activation('relu'))
model.add(MaxPooling2D(2, 2))
model.add(Flatten()) # 1次元化
model.add(Dense(120)) # 全結合層
model.add(Activation('relu'))
model.add(Dense(84))
model.add(Activation('relu'))
model.add(Dense(10))
model.add(Activation('softmax'))
Pytorch
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5) # 畳み込み層
self.pool = nn.MaxPool2d(2, 2) # プーリング層
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 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)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
一緒のモデル構造になるようにしています。
TensorFlow
の方は、Sequential()でインスタンス化した後に
addで層を上から追加していくイメージで書きやすい。
ただ同じことを何回も書くのでめんどくさいと言えばめんどくさいか。
Pythoch
はクラス定義を行って、__init__関数の中に使う層をまず定義しておきます。
その後forward関数で、入力から出力までの流れを書いていく。
ちゃんと理解していないとPytorch
は書けなさそう。
TensorFlow
の場合とりあえずConv2D, Activation, MaxPoolingって書いてても、成立するけど、Pytorch
の場合、入力と出力の形がどうなるかわかってないと書けないかなと。
TensorFlow
は楽だけど、知識・理論的な部分は曖昧でも動くものは書ける。
Pytorch
はしっかり理解していないと難しいけど、入力から出力までの流れがわかって良いかも。
モデル概要
TensorFlow
model.summary()
# ->
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 28, 28, 6) 456
_________________________________________________________________
activation (Activation) (None, 28, 28, 6) 0
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 6) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 10, 10, 16) 2416
_________________________________________________________________
activation_1 (Activation) (None, 10, 10, 16) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 16) 0
_________________________________________________________________
flatten (Flatten) (None, 400) 0
_________________________________________________________________
dense (Dense) (None, 120) 48120
_________________________________________________________________
activation_2 (Activation) (None, 120) 0
_________________________________________________________________
dense_1 (Dense) (None, 84) 10164
_________________________________________________________________
activation_3 (Activation) (None, 84) 0
_________________________________________________________________
dense_2 (Dense) (None, 10) 850
_________________________________________________________________
activation_4 (Activation) (None, 10) 0
=================================================================
Total params: 62,006
Trainable params: 62,006
Non-trainable params: 0
_________________________________________________________________
Pytorch
print(net)
# ->
Net(
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
付属のメソッドで見る分にはTensorFlowの方が情報量は多いのかな。
拡張パッケージを使えばどちらも、細かいところまで見れるし変わらないかも。
損失関数・アルゴリズム
TensorFlow
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
model.compile(loss='sparse_categorical_crossentropy', optimizer = optimizer, metrics=['accuracy'])
Pytorch
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr = 0.001)
大きく変わらないかな。
学習
TensorFlow
history = model.fit(X_train, y_train, batch_size=4, epochs = 10)
Pytorch
for epoch in range(10):
print(f'Epoch {epoch + 1} start')
start = time.time()
running_loss = 0.0
for idx, data in enumerate(trainloader, 0):
# データセットのデータを [inputs, labels]の形で取得
inputs, labels = data
# パラメータの勾配をリセット
optimizer.zero_grad()
# 順伝搬+逆伝搬+パラメータ更新
outputs = net(inputs)
loss = loss_fn(outputs, labels)
loss.backward()
optimizer.step()
# 統計情報の表示
running_loss += loss.item()
if idx % 2000 == 1999:
print('[%d, %5d] loss: %.3f' %
(epoch + 1, idx + 1, running_loss / 2000))
running_loss = 0.0
print(f'Time: {time.time() - start}')
print('Finished Training')
TensorFlow
はfitメソッドが勝手にやってくれるので楽ですね。エポック数やデータの定義などをこっちが指定すれば勝手にやってくれる。
Pytorch
は色々と定義する必要がある。
この辺りはTensorFlow
のtf.GradientTape
で勾配や損失を計算していく過程を書いていくのと似ているのかなと。
TensorFlow
は楽だけど色々ブラックボックス化されているので、理論的な部分が曖昧でも動いちゃう。
Pytorch
はデータ入力から誤差逆伝播の一連の流れを省略せずに書く必要があるので、理解が深まる。めんどくさいと言えばめんどくさいけど丁寧。
(参考)学習時間
TensorFlow
Epoch 1/10
12500/12500 [==============================] - 69s 5ms/step - loss: 1.5127 - accuracy: 0.4510
Epoch 2/10
12500/12500 [==============================] - 64s 5ms/step - loss: 1.2761 - accuracy: 0.5453
Epoch 3/10
12500/12500 [==============================] - 67s 5ms/step - loss: 1.1790 - accuracy: 0.5819
Epoch 4/10
12500/12500 [==============================] - 65s 5ms/step - loss: 1.1090 - accuracy: 0.6098
Epoch 5/10
12500/12500 [==============================] - 66s 5ms/step - loss: 1.0573 - accuracy: 0.6280
Epoch 6/10
12500/12500 [==============================] - 69s 6ms/step - loss: 1.0182 - accuracy: 0.6410
Epoch 7/10
12500/12500 [==============================] - 68s 5ms/step - loss: 0.9842 - accuracy: 0.6560
Epoch 8/10
12500/12500 [==============================] - 68s 5ms/step - loss: 0.9560 - accuracy: 0.6657
Epoch 9/10
12500/12500 [==============================] - 67s 5ms/step - loss: 0.9325 - accuracy: 0.6746
Epoch 10/10
12500/12500 [==============================] - 74s 6ms/step - loss: 0.9113 - accuracy: 0.6834
Pytorch
Epoch 1 start
[1, 2000] loss: 1.164
[1, 4000] loss: 1.161
[1, 6000] loss: 1.162
[1, 8000] loss: 1.156
[1, 10000] loss: 1.201
[1, 12000] loss: 1.161
Time: 27.156450986862183
Epoch 2 start
[2, 2000] loss: 1.100
[2, 4000] loss: 1.105
[2, 6000] loss: 1.098
[2, 8000] loss: 1.135
[2, 10000] loss: 1.103
[2, 12000] loss: 1.122
Time: 27.26793599128723
Epoch 3 start
[3, 2000] loss: 1.038
[3, 4000] loss: 1.049
[3, 6000] loss: 1.049
[3, 8000] loss: 1.073
[3, 10000] loss: 1.064
[3, 12000] loss: 1.084
Time: 29.154211282730103
Epoch 4 start
[4, 2000] loss: 0.993
[4, 4000] loss: 1.018
[4, 6000] loss: 1.036
[4, 8000] loss: 1.027
[4, 10000] loss: 1.028
[4, 12000] loss: 1.020
Time: 27.163395643234253
Epoch 5 start
[5, 2000] loss: 0.945
[5, 4000] loss: 0.976
[5, 6000] loss: 0.975
[5, 8000] loss: 0.998
[5, 10000] loss: 1.011
[5, 12000] loss: 1.033
Time: 27.090753078460693
Epoch 6 start
[6, 2000] loss: 0.933
[6, 4000] loss: 0.958
[6, 6000] loss: 0.962
[6, 8000] loss: 0.956
[6, 10000] loss: 0.981
[6, 12000] loss: 0.988
Time: 27.317527055740356
Epoch 7 start
[7, 2000] loss: 0.906
[7, 4000] loss: 0.922
[7, 6000] loss: 0.936
[7, 8000] loss: 0.955
[7, 10000] loss: 0.968
[7, 12000] loss: 0.971
Time: 29.902886152267456
Epoch 8 start
[8, 2000] loss: 0.908
[8, 4000] loss: 0.917
[8, 6000] loss: 0.926
[8, 8000] loss: 0.927
[8, 10000] loss: 0.939
[8, 12000] loss: 0.952
Time: 27.228978872299194
Epoch 9 start
[9, 2000] loss: 0.875
[9, 4000] loss: 0.893
[9, 6000] loss: 0.912
[9, 8000] loss: 0.920
[9, 10000] loss: 0.930
[9, 12000] loss: 0.924
Time: 26.909887075424194
Epoch 10 start
[10, 2000] loss: 0.851
[10, 4000] loss: 0.908
[10, 6000] loss: 0.868
[10, 8000] loss: 0.892
[10, 10000] loss: 0.915
[10, 12000] loss: 0.902
Time: 26.92044472694397
Finished Training
GPUの関係もあるのかもですけど、TensorFlow
が遅い...(多分自分のローカル環境がゴミだからですね)
まとめ
印象としては細かく丁寧に書くPytorch
、簡単なTensorFlow
という感じですかね。
個人的には楽なのでTensorFlow
が好きですが、その弊害として理論的な部分が曖昧になりやすいことも実感しているので、しっかりと理解した上でTensorFlow
の簡潔さを享受するのがいいのかなという感じです。
Pytorch
はニューラルネットワークの一連の流れを省略せずに書いていくので、学んだことを復習しながら書けるというのが強みかなと思いました。省略せずに書いていくというのをめんどくさいと感じるかは人それぞれだと思います。
とにかく画像分類してみたい、深層学習触れてみたいとかだったらTensorFlow
の方が楽だと思いますし、理論的な部分も含めて丁寧に理解しながら実装したいとかだったらPytorch
の方がいいのかなと。
幸いどちらも公式チュートリアル的なものはあるので、一から学ぶことのハードルは低いかなと思います。
結論、どっちもいいところがあるので好きなとこ選ぼうということで雑にまとめさせていただきます()
何かの参考になれば幸いです。