Keras 和 PyTorch 的對比選擇
原文:https://medium.com/@karan_jakhar/keras-vs-pytorch-dilemma-dc434e5b5ae0
作者:Karan Jakhar
前言
上一篇2020年計算機視覺學習指南介紹了兩種深度學習框架--Keras 和 PyTorch ,這篇文章的作者就對這兩個框架進行了對比,分別通過實現一個簡單的模型來對比兩個不同的程式碼風格,最後還給出了他的個人建議。
當你決定開始學習深度學習,那麼應該選擇使用什麼工具呢?目前有很多深度學習的框架或者庫,但本文會對比兩個框架,Keras 和 PyTorch ,這是兩個非常好開始使用的框架,並且它們都有一個很低的學習曲線,初學者可以很快就學會它們,因此在本文,我將分享一個辦法來解決如何選擇其中一個框架進行使用。
最好的辦法就是檢視兩個框架各自的程式碼風格。設計任何方案的前提和最重要的事情就是你的工具,當你開始一個專案前必須安裝配置好你的工具,並且一旦開始專案後,就不應該更改時用的工具。它會影響到你的生產力。作為一個初學者,你應該儘量嘗試不同的工具,並且找到合適你的,但如果你正在參加一個非常正式的專案工作,那麼這些事情都應該提早計劃好。
每天都會有新的框架和工具面世,對你最好的工具應該是在個性化和抽象做好平衡的,它應該可以同步你的思考和程式碼風格,那麼如何找到這樣合適的工具呢,答案就是你需要嘗試不同的工具。
接下來,讓我們分別用 Keras 和 PyTorch 訓練一個簡單的模型吧。如果你是深度學習的初學者,那麼不要擔心理解不了某些名詞概念,目前你只需要關注這兩個框架的程式碼風格,並思考哪個才是最合適你的,也就是讓你感覺更舒適並且更容易上手的。
這兩個框架的主要不同點是 PyTorch 預設是eager
模式,而 Keras 是在 TensorFlow 和其他框架的基礎上進行工作,但目前主要是基於 TensorFlow 框架的,因此其預設是圖(graph
)模式。當然,最新版本的 TensorFlow 也提供了和 PyTorch 一樣的eager
模式。如果你對 NumPy 很熟悉的話,你可以把 PyTorch 看作是有 GPU 支援的 NumPy 。此外,也有不少類似 Keras 一樣的第三方庫作為高階 API 介面,它們使用 PyTorch 作為後端支援,比如Fastai
(提供了免費的很好的課程)、Lightning
,Ignite
等等。也可以去了解這些框架,如果你發現它們很有趣,那你就多了一個理由使用 PyTorch 。
這兩種框架都有不同的方法來實現一個模型。這裡都分別選擇了一種簡單的實現方式。下面是分別在谷歌的 Colab 上實現的程式碼的連結,開啟連結並執行程式碼,這更加有助於找到更合適你的框架:
本文並不會介紹太細節的東西,因為我們的目標只是對兩個框架的程式碼結構和風格進行檢視和了解。
基於 Keras 的模型實現
下面是實現數字識別的程式碼實現。程式碼非常容易理解,你最好在 colab 中檢視並且進行實驗,至少要開始執行起來。
from keras.datasets import mnist
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D
img_rows, img_cols = 28, 28
num_classes = 10
batch_size = 128
epochs = 10
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
x_train = x_train/255
x_test = x_test/255
y_train = to_categorical(y_train, num_classes)
y_test = to_categorical(y_test, num_classes)
在 Keras 中有一些作為樣例的資料集,其中一個就是 MNIST 手寫數字資料集,上述程式碼主要是實現載入資料集的功能,圖片是 NumPy 的陣列格式。另外,上述程式碼也做了一點的影象處理來將資料可以應用到模型中。
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=(img_rows, img_cols, 1)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
上述程式碼就是模型的程式碼實現。在 Keras(TensorFlow) 中,我們需要先定義想使用的所有東西,然後它們會只執行一次。我們不能對它們進行實驗,但是在 PyTorch 中是可以做到的。
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
model.save("test_model.h5")
# load the model
from keras.models import load_model
model = load_model("test_model.h5")
# predict digit
prediction = model.predict(gray)
print(prediction.argmax())
上述程式碼就是訓練和驗證模型,可以使用save()
方法來儲存模型,然後通過load_model()
方法來載入儲存的模型檔案,predict()
方法是用於對測試資料進行預測得到預測結果。
這就是使用 Keras 簡單實現一個模型的概覽,下面看看 PyTorch 是怎麼實現模型的吧。
基於 PyTorch 的模型實現
研究者主要用 PyTorch ,因為它的靈活性以及偏實驗的程式碼風格,這包括可以對 PyTorch 的一切都進行修改調整,對 也就是可以完全控制一切,進行實驗也是非常容易。在 PyTorch 中,不需要先定義所有的事情再執行,對每個單獨的步驟的測試都非常容易。因此,它比 Keras 更容易除錯。
下面也是利用 PyTorch 實現一個簡單的數字識別模型。
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
n_epochs = 3
batch_size_train = 64
batch_size_test = 1000
learning_rate = 0.01
momentum = 0.5
log_interval = 10
random_seed = 1
torch.backends.cudnn.enabled = False
torch.manual_seed(random_seed)
上述程式碼主要是匯入需要的庫以及定義了一些變數,這些變數如n_epochs, momentum
等都是必須設定的超引數,但這裡不會詳細展開說明,因為我們也說過本文的目標是理解框架的程式碼結構和風格。
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('/files/', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size_train, shuffle=True)
test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('/files/', train=False, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size_test, shuffle=True)
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
example_data.shape
這段程式碼則是聲明瞭一個數據載入器用於載入訓練資料集進行訓練和測試。資料集有多種下載資料的方法,這和框架沒有關係。當然上面這段程式碼對於深度學習的初學者可能是有些複雜了。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x)
接下來這段程式碼就是定義模型。這是一個很通用的建立一個網路模型的方法,定義一個類繼承nn.Module
,forward()
方法是實現網路的前向傳播。PyTorch 的實現是非常直接,並且可以根據需要進行修改。
network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate,
momentum=momentum)
train_losses = []
train_counter = []
test_losses = []
test_counter = [i*len(train_loader.dataset) for i in range(n_epochs + 1)]
def train(epoch):
network.train()
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad()
output = network(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % log_interval == 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()))
train_losses.append(loss.item())
train_counter.append(
(batch_idx*64) + ((epoch-1)*len(train_loader.dataset)))
torch.save(network.state_dict(), 'model.pth')
torch.save(optimizer.state_dict(), 'optimizer.pth')
def test():
network.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
output = network(data)
test_loss += F.nll_loss(output, target, size_average=False).item()
pred = output.data.max(1, keepdim=True)[1]
correct += pred.eq(target.data.view_as(pred)).sum()
test_loss /= len(test_loader.dataset)
test_losses.append(test_loss)
print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
test()
for epoch in