原文: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 range