21
16

More than 3 years have passed since last update.

TensorFlowしか使ったことない初心者がPytorchも触って比較した感想

Posted at

はじめに

[第3版]Python機械学習プログラミング 達人データサイエンティストによる理論と実践を使用して機械学習を2ヶ月ほど勉強しております。

深層学習を学ぶにあたって、大きくフレームワークとしてはTensorFlowPytorchの2つがあると思います。この書籍ではTensorFlowを用いていたため、それに従ってTensorFlowを学び、ある程度は使えるようになったかと思います。ただPytorchはノータッチ。

そのためKaggleなどのコードがPytorchで書かれてたりすると「ん、Pytorchかスルーしよ」ってなってました。
ただ最近GANに興味があり、最新のモデルの論文がPytorchで書かれていることが多く、まぁ使えるライブラリは多いに越したことないよなってことでコチラの公式チュートリアルを日本語訳版にして頂いたサイトを全力で感謝しながら、とりあえずCIFAR-10の画像分類までやってみました。

すでにPytorchをメインで触っている方の比較記事もありますが、
TensorFlow(とkeras)をメインで触っている初心者の比較ということで見て頂けたら。

またTensorFlow単体で書くことはほとんどないので、TensorFlow/kerasPytorchの比較として見てください。

環境・バージョン・データ

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は大きくtorchtorchvisionの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は色々と定義する必要がある。
この辺りはTensorFlowtf.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の方がいいのかなと。

幸いどちらも公式チュートリアル的なものはあるので、一から学ぶことのハードルは低いかなと思います。

結論、どっちもいいところがあるので好きなとこ選ぼうということで雑にまとめさせていただきます()

何かの参考になれば幸いです。

21
16
1

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
21
16