
作者 | Lee
來源 | Medium
編輯 | 代碼醫生團隊
關于技術架構,一個有趣的事情是,從一開始,似乎總是被各種選擇。但是随着時間的推移,比賽将演變為隻剩下兩個強有力的競争者。例如“ PC vs Mac”,“ iOS vs Android”,“ React.js vs Vue.js”等。現在,在機器學習中擁有“ PyTorch vs TensorFlow”。
由Google支援的TensorFlow無疑是這裡的領先者。它于2015年作為開放源代碼的機器學習架構釋出,迅速獲得了廣泛的關注和認可,尤其是在生産準備和部署至關重要的行業中。PyTorch于2017年在Facebook上推出的很晚,但由于其動态的計算圖和`` pythonic ''風格而很快赢得了從業者和研究人員的廣泛喜愛。
圖檔來自漸變
The Gradient的最新研究表明,PyTorch在研究人員方面做得很好,而TensorFlow在行業界占主導地位:
在2019年,機器學習架構之戰還有兩個主要競争者:PyTorch和TensorFlow。我的分析表明,研究人員正在放棄TensorFlow并大量湧向PyTorch。同時,在行業中,Tensorflow目前是首選平台,但長期以來可能并非如此。— 漸變
PyTorch 1.3的最新版本引入了PyTorch Mobile,量化和其他功能,它們都在正确的方向上縮小了差距。如果對神經網絡基礎有所了解,但想嘗試使用PyTorch作為其他樣式,請繼續閱讀。将嘗試說明如何使用PyTorch從頭開始為Fashion-MNIST資料集建構卷積神經網絡分類器。如果沒有強大的本地環境,則可以在Google Colab和Tensor Board上使用此處的代碼。事不宜遲開始吧。可以在下面找到Google Colab Notebook和GitHub連結:
Co Google Colab筆記本
https://colab.research.google.com/drive/1YWzAjpAnLI23irBQtLvDTYT1A94uCloM
GitHub上
https://github.com/wayofnumbers/SideProjects/blob/master/PyTorch_Tutorial_Basic_v1.ipynb
Import
首先,導入必要的子產品。
# import standard PyTorch modulesimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torch.utils.tensorboard import SummaryWriter # TensorBoard support # import torchvision module to handle image manipulationimport torchvisionimport torchvision.transforms as transforms # calculate train time, writing train data to files etc.import timeimport pandas as pdimport jsonfrom IPython.display import clear_output torch.set_printoptions(linewidth=120)torch.set_grad_enabled(True) # On by default, leave it here for clarity
PyTorch子產品非常簡單。
Torch
torch是包含Tensor計算所需的所有内容的主要子產品。可以單獨使用Tensor計算來建構功能齊全的神經網絡,但這不是本文的目的。将利用更強大和便捷torch.nn,torch.optim而torchvision類快速建構CNN。
torch.nn和torch.nn.functional
Alphacolor在Unsplash上拍攝的照片
該torch.nn子產品提供了許多類和函數來建構神經網絡。可以将其視為神經網絡的基本建構塊:模型,各種層,激活函數,參數類等。它可以像将一些LEGO集放在一起一樣構模組化型。
Torch優化
torch.optim 提供了SGD,ADAM等所有優化程式,是以無需從頭開始編寫。
Torch視覺
torchvision包含許多用于計算機視覺的流行資料集,模型架構和常見圖像轉換。我們從中擷取Fashion MNIST資料集,并使用其變換。
SummaryWriter(張量闆)
SummaryWriter使PyTorch可以為Tensor Board生成報告。将使用Tensor Board檢視訓練資料,比較結果并獲得直覺。Tensor Board曾經是TensorFlow相對于PyTorch的最大優勢,但是現在從v1.2開始,PyTorch正式支援它。
也引進了一些其他實用子產品,如time,json,pandas,等。
資料集
torchvision已經具有Fashion MNIST資料集。如果不熟悉Fashion MNIST資料集:
Fashion-MNIST是Zalando文章圖像的資料集-包含60,000個示例的訓練集和10,000個示例的測試集。每個示例都是一個28x28灰階圖像,與來自10個類别的标簽相關聯。我們打算Fashion-MNIST直接替代原始MNIST資料集,以對機器學習算法進行基準測試。它具有相同的圖像大小以及訓練和測試分割的結構。— 來自Github
https://github.com/zalandoresearch/fashion-mnist
Fashion-MNIST資料集— 來自GitHub
# Use standard FashionMNIST datasettrain_set = torchvision.datasets.FashionMNIST( root = './data/FashionMNIST', train = True, download = True, transform = transforms.Compose([ transforms.ToTensor() ]))
這不需要太多解釋。指定了根目錄來存儲資料集,擷取訓練資料,允許将其下載下傳(如果本地計算機上不存在的話),然後應用transforms.ToTensor将圖像轉換為Tensor,以便可以在網絡中直接使用它。資料集存儲在dataset名為train_set.
網絡
在PyTorch中建立實際的神經網絡既有趣又容易。假設對卷積神經網絡的工作原理有一些基本概念。如果沒有,可以參考Deeplizard的以下視訊:
Fashion MNIST的尺寸僅為28x28像素,是以實際上不需要非常複雜的網絡。可以像這樣建構一
CNN拓撲
有兩個卷積層,每個都有5x5核心。在每個卷積層之後,都有一個最大步距為2的最大合并層。這能夠從圖像中提取必要的特征。然後,将張量展平并放入密集層中,通過多層感覺器(MLP)來完成10類分類的任務。
現在已經了解了網絡的結構,看看如何使用PyTorch來建構它:
# Build the neural network, expand on top of nn.Moduleclass Network(nn.Module): def __init__(self): super().__init__() # define layers self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5) self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5) self.fc1 = nn.Linear(in_features=12*4*4, out_features=120) self.fc2 = nn.Linear(in_features=120, out_features=60) self.out = nn.Linear(in_features=60, out_features=10) # define forward function def forward(self, t): # conv 1 t = self.conv1(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # conv 2 t = self.conv2(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # fc1 t = t.reshape(-1, 12*4*4) t = self.fc1(t) t = F.relu(t) # fc2 t = self.fc2(t) t = F.relu(t) # output t = self.out(t) # don't need softmax here since we'll use cross-entropy as activation. return t
首先,PyTorch中的所有網絡類都在基類上擴充nn.Module。它包含了所有基礎知識:權重,偏差,正向方法,以及一些實用程式屬性和方法,例如.parameters()以及.zero_grad()将使用的方法。
網絡結構在__init__dunder函數中定義。
def __init__(self): super().__init__() # define layers self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5) self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5) self.fc1 = nn.Linear(in_features=12*4*4, out_features=120) self.fc2 = nn.Linear(in_features=120, out_features=60) self.out = nn.Linear(in_features=60, out_features=10)
nn.Conv2d并且nn.Linear是内限定兩個标準PyTorch層torch.nn子產品。這些是不言而喻的。需要注意的一件事是,僅在此處定義了實際的圖層。激活和最大池操作包含在下面說明的正向功能中。
# define forward function def forward(self, t): # conv 1 t = self.conv1(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # conv 2 t = self.conv2(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # fc1 t = t.reshape(-1, 12*4*4) t = self.fc1(t) t = F.relu(t) # fc2 t = self.fc2(t) t = F.relu(t) # output t = self.out(t) # don't need softmax here since we'll use cross-entropy as activation. return t
一旦定義了層,就可以使用層本身來計算每個層的前向結果,再加上激活函數(ReLu)和最大池操作,可以輕松地編寫上述網絡的前向函數。請注意,在fc1(完全連接配接層1)上,使用了PyTorch的張量操作t.reshape來拉平張量,以便随後可以将其傳遞到密集層。另外,沒有在輸出層添加softmax激活函數,因為PyTorch的CrossEntropy函數将解決這個問題。
超參數
可以精選一組超參數和做一些實驗和他們在一起。在這個例子中,想通過引入一些結構來做更多的事情。将建構一個系統來生成不同的超參數組合,并使用它們進行訓練“運作”。每個“運作”使用一組超參數組合。将每次運作的訓練資料/結果導出到Tensor Board,以便可以直接比較并檢視哪個超參數集表現最佳。
将所有超參數存儲在OrderedDict中:
# put all hyper params into a OrderedDict, easily expandableparams = OrderedDict( lr = [.01, .001], batch_size = [100, 1000], shuffle = [True, False])epochs = 3
lr:學習率。想為模型嘗試0.01和0.001。
batch_size:批次大小以加快訓練過程。将使用100和1000。
shuffle:随機切換,是否在訓練之前對批次進行随機混合。
一旦參數關閉。使用兩個幫助程式類:RunBuilder和RunManager管理超參數和訓練過程。
運作建構器
該類的主要目的RunBuilder是提供一個靜态方法get_runs。它以OrderedDict(所有超參數都存儲在其中)為參數,并生成一個命名元組Run,每個的元素run表示超參數的一種可能組合。此命名的元組稍後由訓練循環使用。該代碼很容易了解。
# import modules to build RunBuilder and RunManager helper classesfrom collections import OrderedDictfrom collections import namedtuplefrom itertools import product # Read in the hyper-parameters and return a Run namedtuple containing all the# combinations of hyper-parametersclass RunBuilder(): @staticmethod def get_runs(params): Run = namedtuple('Run', params.keys()) runs = [] for v in product(*params.values()): runs.append(Run(*v)) return runs
運作管理器
本RunManager 課程有四個主要目的。
- 計算并記錄每個時期和運作的持續時間。
- 計算每個時期和跑步的訓練損失和準确性。
- 記錄每個時期的訓練資料(例如,損失,準确性,權重,梯度,計算圖等)并運作,然後将其導出到Tensor Board中進行進一步分析。
- 儲存所有訓練結果csv,json以備将來參考或提取API。
如您所見,它可以幫助處理物流,這對于成功訓練模型也很重要。看一下代碼。它有點長,是以請忍受:
# Helper class, help track loss, accuracy, epoch time, run time,# hyper-parameters etc. Also record to TensorBoard and write into csv, jsonclass RunManager(): def __init__(self): # tracking every epoch count, loss, accuracy, time self.epoch_count = 0 self.epoch_loss = 0 self.epoch_num_correct = 0 self.epoch_start_time = None # tracking every run count, run data, hyper-params used, time self.run_params = None self.run_count = 0 self.run_data = [] self.run_start_time = None # record model, loader and TensorBoard self.network = None self.loader = None self.tb = None # record the count, hyper-param, model, loader of each run # record sample images and network graph to TensorBoard def begin_run(self, run, network, loader): self.run_start_time = time.time() self.run_params = run self.run_count += 1 self.network = network self.loader = loader self.tb = SummaryWriter(comment=f'-{run}') images, labels = next(iter(self.loader)) grid = torchvision.utils.make_grid(images) self.tb.add_image('images', grid) self.tb.add_graph(self.network, images) # when run ends, close TensorBoard, zero epoch count def end_run(self): self.tb.close() self.epoch_count = 0 # zero epoch count, loss, accuracy, def begin_epoch(self): self.epoch_start_time = time.time() self.epoch_count += 1 self.epoch_loss = 0 self.epoch_num_correct = 0 # def end_epoch(self): # calculate epoch duration and run duration(accumulate) epoch_duration = time.time() - self.epoch_start_time run_duration = time.time() - self.run_start_time # record epoch loss and accuracy loss = self.epoch_loss / len(self.loader.dataset) accuracy = self.epoch_num_correct / len(self.loader.dataset) # Record epoch loss and accuracy to TensorBoard self.tb.add_scalar('Loss', loss, self.epoch_count) self.tb.add_scalar('Accuracy', accuracy, self.epoch_count) # Record params to TensorBoard for name, param in self.network.named_parameters(): self.tb.add_histogram(name, param, self.epoch_count) self.tb.add_histogram(f'{name}.grad', param.grad, self.epoch_count) # Write into 'results' (OrderedDict) for all run related data results = OrderedDict() results["run"] = self.run_count results["epoch"] = self.epoch_count results["loss"] = loss results["accuracy"] = accuracy results["epoch duration"] = epoch_duration results["run duration"] = run_duration # Record hyper-params into 'results' for k,v in self.run_params._asdict().items(): results[k] = v self.run_data.append(results) df = pd.DataFrame.from_dict(self.run_data, orient = 'columns') # display epoch information and show progress clear_output(wait=True) display(df) # accumulate loss of batch into entire epoch loss def track_loss(self, loss): # multiply batch size so variety of batch sizes can be compared self.epoch_loss += loss.item() * self.loader.batch_size # accumulate number of corrects of batch into entire epoch num_correct def track_num_correct(self, preds, labels): self.epoch_num_correct += self._get_num_correct(preds, labels) @torch.no_grad() def _get_num_correct(self, preds, labels): return preds.argmax(dim=1).eq(labels).sum().item() # save end results of all runs into csv, json for further analysis def save(self, fileName): pd.DataFrame.from_dict( self.run_data, orient = 'columns', ).to_csv(f'{fileName}.csv') with open(f'{fileName}.json', 'w', encoding='utf-8') as f: json.dump(self.run_data, f, ensure_ascii=False, indent=4)
__init__:初始化必要的屬性,例如計數,損失,正确預測的數量,開始時間等。
begin_run:記錄運作的開始時間,以便在運作結束時可以計算出運作的持續時間。建立一個SummaryWriter對象以存儲我們想要在運作期間導出到Tensor Board中的所有内容。将網絡圖和樣本圖像寫入SummaryWriter對象。
end_run:運作完成後,關閉SummaryWriter對象,并将紀元計數重置為0(為下一次運作做好準備)。
begin_epoch:記錄紀元開始時間,以便紀元結束時可以計算紀元持續時間。重置epoch_loss并epoch_num_correct。
end_epoch:大多數情況下都會發生此功能。當一個紀元結束時,将計算該紀元持續時間和運作持續時間(直到該紀元,除非最終的運作紀元,否則不是最終的運作持續時間)。将計算該時期的總損失和準确性,然後将記錄的損失,準确性,權重/偏差,梯度導出到Tensor Board中。為了便于在Jupyter Notebook中進行跟蹤,還建立了一個OrderedDict對象results,并将所有運作資料(損耗,準确性,運作計數,時期計數,運作持續時間,時期持續時間,所有超參數)放入其中。然後,将使用Pandas讀取它并以整潔的表格格式顯示它。
track_loss,track_num_correct,_get_num_correct:這些是實用功能以累積損耗,每批是以曆元損失和準确性可以在以後計算的正确預測的數目。
save:儲存所有運作資料(名單results OrderedDict所有實驗對象)到csv和json作進一步的分析或API通路的格式。
這RunManager堂課有很多内容。恭喜到此為止!最困難的部分已經在身後。
訓練
準備做一些訓練!在RunBuilder 和RunManager的幫助下,訓練過程變得輕而易舉:
m = RunManager() # get all runs from params using RunBuilder classfor run in RunBuilder.get_runs(params): # if params changes, following line of code should reflect the changes too network = Network() loader = torch.utils.data.DataLoader(train_set, batch_size = run.batch_size) optimizer = optim.Adam(network.parameters(), lr=run.lr) m.begin_run(run, network, loader) for epoch in range(epochs): m.begin_epoch() for batch in loader: images = batch[0] labels = batch[1] preds = network(images) loss = F.cross_entropy(preds, labels) optimizer.zero_grad() loss.backward() optimizer.step() m.track_loss(loss) m.track_num_correct(preds, labels) m.end_epoch() m.end_run() # when all runs are done, save results to filesm.save('results')
首先,用于RunBuilder建立超參數的疊代器,然後循環周遊每種超參數組合以進行訓練:
for run in RunBuilder.get_runs(params):
然後,network從Network上面定義的類建立對象。network = Network()。該network物體支撐着我們需要訓練的所有重量/偏向。
還需要建立一個DataLoader 對象。這是一個儲存訓練/驗證/測試資料集的PyTorch類,它将疊代該資料集,并以與batch_size指定數量相同的批次提供訓練資料。
loader = torch.utils.data.DataLoader(train_set, batch_size = run.batch_size)
之後,将使用torch.optim類建立優化器。該optim課程将網絡參數和學習率作為輸入,将幫助逐漸完成訓練過程并更新梯度等。在這裡,将使用Adam作為優化算法。
optimizer = optim.Adam(network.parameters(), lr=run.lr)
現在已經建立了網絡,準備了資料加載器并選擇了優化器。開始訓練吧!
将循環周遊所有想要訓練的紀元(此處為3),是以将所有内容包裝在“紀元”循環中。還使用班級的begin_run方法RunManager來開始跟蹤跑步訓練資料。
m.begin_run(run, network, loader) for epoch in range(epochs):
對于每個時期,将周遊每批圖像以進行訓練。
m.begin_epoch() for batch in loader: images = batch[0] labels = batch[1] preds = network(images) loss = F.cross_entropy(preds, labels) optimizer.zero_grad() loss.backward() optimizer.step() m.track_loss(loss) m.track_num_correct(preds, labels)
上面的代碼是進行實際訓練的地方。從批進行中讀取圖像和标簽,使用network類進行正向傳播(還記得forward上面的方法嗎?)并獲得預測。通過預測,可以使用cross_entropy函數計算該批次的損失。一旦計算出損失,就用重置梯度(否則PyTorch将積累不想要的梯度).zero_grad(),執行一種反向傳播使用loss.backward()方法來計算權重/偏差的所有梯度。然後,使用上面定義的優化程式來更新權重/偏差。現在,針對目前批次更新了網絡,将計算損失和正确預測的數量,并使用類的track_loss和track_num_correct方法進行累積/跟蹤RunManager。
完成所有操作後,将使用将結果儲存到檔案中m.save('results')。
張量闆
圖檔來自Tensorboard.org
Tensor Board是一個TensorFlow可視化工具,現在也PyTorch支援。已經采取了将所有内容導出到'./runs'檔案夾的工作,Tensor Board将在其中查找要使用的記錄。現在需要做的隻是啟動張量闆并檢查。由于在Google Colab上運作此模型,是以将使用一種稱為的服務ngrok來代理和通路在Colab虛拟機上運作的Tensor Board。ngrok 首先安裝:
!wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip!unzip ngrok-stable-linux-amd64.zip
然後,指定要從中運作Tensor Board的檔案夾并啟動Tensor Board Web界面(./runs為預設值):
LOG_DIR = './runs'get_ipython().system_raw('tensorboard --logdir {} --host 0.0.0.0 --port 6006 &'.format(LOG_DIR))
啟動ngrok代理:
get_ipython().system_raw('./ngrok http 6006 &')
生成一個URL,以便可以從Jupyter Notebook中通路Tensor Board:
! curl -s http://localhost:4040/api/tunnels | python3 -c \"import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])"
如下所示,TensorBoard是一個非常友善的可視化工具,可深入了解訓練,并可以極大地幫助調整超參數。可以輕松地找出哪個超參數comp表現最佳,然後使用它來進行真正的訓練。
結論
如您所見,PyTorch作為一種機器學習架構是靈活,強大和富于表現力的。隻需編寫Python代碼。由于本文的主要重點是展示如何使用PyTorch建構卷積神經網絡并以結構化方式對其進行訓練,是以我并未完成整個訓練時期,并且準确性也不是最佳的。可以自己嘗試一下,看看模型的性能如何。
推薦閱讀
如何建構PyTorch項目