【深度强化学习】深度学习:Pytorch的使用
文章目錄
- 前言
- 第三章 深度學習:Pytorch的使用
- Pytorch庫基礎
- Tensor 張量
- 標量張量
- 張量的操作
- 張量 與 梯度
- Pytorch 的神經網絡包:torch.nn
- 自定義網絡層
- 損失函數與優化器
- 損失函數
- 優化器
- 通過Tensorboard監控網絡訓練
前言
重讀《Deep Reinforcemnet Learning Hands-on》, 常讀常新, 極其深入淺出的一本深度強化學習教程。 本文的唯一貢獻是對其進行了翻譯和提煉, 加一點自己的理解組織成一篇中文筆記。
原英文書下載地址: 傳送門
原代碼地址: 傳送門
第三章 深度學習:Pytorch的使用
在之前的章節中,我們對強化學習有了初步的了解。 但是最新的技術中,強化學習往往與深度學習相結合, 以解決更復雜更具挑戰的難題。
這一章就是詳細介紹了, 如何通過pytorch 實現 深度學習的方法。 本章的目的并不是詳細到如同用戶手冊般地介紹pytorch的框架,更多地是為了讓你們熟悉 這個 框架及常用的API, 為節約時間, 已假設讀者擁有基本的深度學習知識。
Pytorch庫基礎
Tensor 張量
張量 是 深度學習庫的最基本組成部分。 其本質就是一個多維的數組。一個單獨的數(標量)就是一個0維 (zero-dimension)的張量, 一個向量 就是 一維 張量, 矩陣 則是 二維張量。三維以上的張量則統一稱為高維張量。
根據數據類型的不同, pytorch中的張量也分為不同的類。 最常用的是 : FloatTensor, ByteTensor 和 LongTensor, 分別代表32位浮點數, 8位整數和64位整數。
有三種方式創建 tensor:
- 直接調用 對應類型張量的構造器
上例中, 我們調用了 torch.FloatTensor()得到了一個未初始化的張量。 其接受的參數數量不限, 參數代表每一維的維度。 如上例中, 共有兩個參數3和2, 那么就生成了 3*2 的張量。 Pytorch為其分配了內存, 但并未初始化其值。 為了清零該張量(從上例中可以看到, 其值是隨機生成的莫名其妙數據), 可以使用zero_()方法:
>>> a.zero_() tensor([[ 0., 0.], [ 0., 0.], [ 0., 0.]])在pytorch中, 后面加了下劃線 “_” 的方法,代表 替換操作 (Inplace)。即, 該方法直接在本張量上操作,返回值即是被操作后的張量。 如上例中, a.zero_()即是對張量a本身進行操作。
- 另一種創建方式 是 轉換 python中的 可迭代對象, 如 list, tuple, numpy數組等, 將其作為張量的內容來創建張量。
上例中, 分別可以將 python 列表, 和 numpy 數組, 轉化為 新創建的張量的 內容。 第二個例子中, 發現有numpy 數組轉化的tensor b, 自動繼承了numpy的數據類型, 即torch.float64, 一般而言, 64位的高精度浮點數是不必要的且嚴重浪費內存, 我們可以這樣改變:
>>> n = np.zeros(shape=(3, 2), dtype=np.float32) >>> torch.tensor(n) tensor([[ 0., 0.], [ 0., 0.], [ 0., 0.]])即, 指定numpy的數據類型, 這樣轉換后的torch.tensor也會繼承該類型。 另一種是在轉換時直接指定tensor的數據類型:
>>> n = np.zeros(shape=(3,2)) >>> torch.tensor(n, dtype=torch.float32) tensor([[ 0., 0.], [ 0., 0.], [ 0., 0.]])- 直接使用已有API來創建張量。
標量張量
目前pytorch已經支持標量張量, 而無需再像以前一樣,創建一個維度為1的一維張量。
>>> a = torch.tensor([1,2,3]) >>> a tensor([ 1, 2, 3]) >>> s = a.sum() >>> s tensor(6) >>> s.item() 6 >>> torch.tensor(1) tensor(1)s就是一維張量, 通過item()方法,可以直接獲取其數值。
張量的操作
絕大部分時候, Numpy庫的操作,一般在Pytorch中也有對應類似的API。比如 torch.stack(), torch.transpose(), and torch.cat().。更一般的API接口們可以到pytorch的官方文檔中查詢。
張量 與 梯度
作為深度學習的庫, 張量需要有一個極為重要的性質: 自動計算梯度值。 當然你也可以選擇,自己手動計算梯度值, 來完成神經網絡的訓練。 這樣有助于理論的理解, 但無疑, 每次重復這樣的步驟是極為枯燥的。 因此,我們希望能自動計算梯度。
當今所有流形的深度學習框架, 都擁有自動計算梯度的機制——雖然具體實踐上會有所區別,但是都貫徹一點: 你指定你的網絡中輸入到輸出的具體順序, 框架會自動幫你計算梯度,后向傳播。
- 靜態圖框架: Tensorflow1.0, Theano等框架。 在開始運算前, 你必須完整定義你的計算圖(即輸入到輸出的具體結構)。 在開始運算前, 框架會對其進行計算優化。 靜態圖的優點是 運行效率更高速度更快, 缺點則是難以進行調試, 且無法在計算圖生成后動態修改。
- 動態圖框架: pytorch。 不需要預先定制好整個計算圖。 框架會記錄你當前的計算順序, 儲存對應的梯度值, 來服務于神經網絡的計算。 調試 和 搭建網絡的靈活性上, 動態圖遠遠優于靜態圖。 這一方法也被稱為 notebook gradient。
Pytorch 的 張量, 內置了 梯度計算和跟蹤機制, 因此, 你只需要把數據轉換為張量, 然后使用pytorch提供的API進行操作即可。 每個張量都含有以下幾個與梯度相關的屬性:
- grad:自動計算的梯度——是和張量本身維度一樣的張量。
- is_leaf: 如果張量由用戶創建, 則為True; 有轉化函數得到,則為False。
- requires_grad: 張量是否需要計算梯度, 是則為True。默認值為False,即無需計算。
為了讓pytorch 的 gradient-leaf機制 更為清晰, 我們舉一個例子:
>>> v1 = torch.tensor([1.0, 1.0], requires_grad=True) >>> v2 = torch.tensor([2.0, 2.0]) >>> v_sum = v1 + v2 >>> v_res = (v_sum*2).sum() >>> v_res tensor(12.) >>> v1.is_leaf, v2.is_leaf (True, True) >>> v_sum.is_leaf, v_res.is_leaf (False, False) >>> v1.requires_grad True >>> v2.requires_grad False >>> v_sum.requires_grad True >>> v_res.requires_grad True >>> v1.grad >>> >>> v_res.backward() >>> v1.grad tensor([ 2., 2.]) >>> v2.grad >>>這個例子中,我們可以知道:
- 因為v1 的 requires_grad屬性為真,因此由其計算得到的v_sum, v_res的該屬性默認為真。
- 對最后的結果v_res使用backward()方法, pytorch會自動求出整個計算圖中, requires_grad屬性為真的張量的梯度。可以看到, 在使用backward()前, v1.grad返回為空。使用后, v1返回了相對于v_res的正確梯度。
- v2由于requires_grad為False, 因此沒有算出其grad。
- 注意, 這里只能對v_res,即最后的結果使用backward()。 因為該方法只能對最后的標量結果使用, v_sum是張量, 無法使用backward(),去求出v1對于v_sum的梯度。
requires_grad屬性, 可以有效地把計算資源用于計算我們關心的變量——如神經網絡中,需要加以優化的權重矩陣, 而對于那些無關的變量等,我們可以將屬性設為False,不必浪費資源去計算其梯度。
Pytorch 的神經網絡包:torch.nn
神經網絡多年的積累后, 已有了許多通用的網絡結構。 pytorch中提供了快速實現這些結構的API,使得你不需要自己手動從底層開始搭建。 這些API歸在torch.nn包中。
這些網絡API由類實現, 由于實現了__callable__ 內置方法, 可以像調用函數一樣使用。 以 實現 普通全連接網絡的線性層Linear為例:
>>> import torch.nn as nn >>> l = nn.Linear(2, 5) # 參數為 輸入維度 和 輸出維度 >>> v = torch.FloatTensor([1, 2]) >>> l(v) tensor([ 0.1975, 0.1639, 1.1130, -0.2376, -0.7873])這里,我們創建了一個隨機初始化的前饋線性層 ,來處理我們的張量v。torch.nn中所有類都繼承自基類nn.Module, 你也可以自定義自己的網絡結構層。 nn.Module 主要提供了以下這些基本方法:
- parameters(): 返回了一個迭代器, 里面包括所有需要求梯度的網絡變量(比如網絡的權重) 。 注:迭代器實現了__next__方法, 對迭代器a可以使用next(a),獲取a中的值。
- zero_grad():顧名思義, 將所有變量的梯度清零,一般用于初始化。
- to(device):使用GPU加速
- state_dict():返回網絡中的所有變量值
- load_state_dict():以現有的字典數據,初始化網絡參數。
以下是這些方法的示例:
>>>l1 = nn.Linear(2,3) >>>l2 = nn.Linear(2,3) >>>a = l1.state_dict() >>>a Out[73]: OrderedDict([('weight',tensor([[-0.3434, 0.6596],[ 0.4947, 0.6010],[-0.1376, 0.5829]])),('bias', tensor([-0.2760, 0.4102, 0.4186]))]) >>>b = l2.state_dict() >>>b Out[75]: OrderedDict([('weight',tensor([[ 0.2113, 0.0015],[-0.4391, 0.2420],[-0.2873, 0.0567]])),('bias', tensor([0.6091, 0.2362, 0.2396]))]) >>>l2.load_state_dict(a) Out[76]: <All keys matched successfully> >>>l2.state_dict() Out[77]: OrderedDict([('weight',tensor([[-0.3434, 0.6596],[ 0.4947, 0.6010],[-0.1376, 0.5829]])),('bias', tensor([-0.2760, 0.4102, 0.4186]))])接著,再介紹能將多個網絡層 串聯在一起 構成一個神經網絡的類:nn.Sequential, 同樣,也用例子來詮釋他的用法:
>>> s = nn.Sequential( ... nn.Linear(2, 5), ... nn.ReLU(), ... nn.Linear(5, 20), ... nn.ReLU(), ... nn.Linear(20, 10), ... nn.Dropout(p=0.3), ... nn.Softmax(dim=1)) >>> s Sequential ( (0): Linear (2 -> 5) (1): ReLU () (2): Linear (5 -> 20) (3): ReLU () (4): Linear (20 -> 10) (5): Dropout (p = 0.3) (6): Softmax () ) >>> s(torch.FloatTensor([[1,2]])) tensor([[ 0.1410, 0.1380, 0.0591, 0.1091, 0.1395, 0.0635, 0.0607, 0.1033, 0.1397, 0.0460]])nn.Linear, nn.ReLu, nn.Dropout, nn.Softmax,都是同名的知名網絡層/激活函數的相應類。Sequential接受這些類作為參數, 并拼接成一個完整的神經網絡。
以上就是簡單地對 torch.nn包預定義的API進行簡單應用的講述啦!
自定義網絡層
在上一節中,我們介紹了nn的Module類——所有預定義的知名網絡層都繼承自它。 同時,你也可以通過創建其子類, 定義自己的網絡層,并完美地融入pytorch框架之中。你可以構建一個 一層的小網絡層, 也可以構建一個1000多層的ResNet。接下來,還是用代碼示例來說明:
import torch import torch.nn as nnclass OurModule(nn.Module):def __init__(self, num_inputs, num_classes, dropout_prob=0.3):super(OurModule, self).__init__()self.pipe = nn.Sequential(nn.Linear(num_inputs, 5),nn.ReLU(),nn.Linear(5, 20),nn.ReLU(),nn.Linear(20, num_classes),nn.Dropout(p=dropout_prob),nn.Softmax(dim=1))def forward(self, x):return self.pipe(x)if __name__ == "__main__":net = OurModule(num_inputs=2, num_classes=3)print(net)v = torch.FloatTensor([[2, 3]])out = net(v)print(out)這一段代碼中, 我們先在初始化__init__函數中,調用了父類的初始化函數,借著利用Sequential類, 創建了一個簡單的網絡。 然后我們重寫了 forward()函數——這個是自定義函數中必不可少的, 其接受輸入數據, 返回輸出結果。 在實例使用中, 首先創建自定義類的一個實例net, 注意:Module類實現了__str__方法,因此可以直接print(net)來打印網絡。 需要注意的是,雖然我們定義的是forward()函數,但我們使用的時候, 是直接使用net(v)來對v做變換。 這是因為Module類實現了__callable__方法,直接使用net(v)就是調用了forward()。因此,不要使用net.forward(v), 直接使用net(v)即可。
損失函數與優化器
損失函數
pytorch中,預定義了許多著名的損失函數:
- nn.MSELoss
- nn.BCELoss: Binary cross-entropy loss. 二元交叉熵。
- nn.CrossEntropyLoss: 用于多分類問題的交叉熵。
優化器
pytorch中,預定義了常用的優化器:
- torch.optim.SGD:隨機梯度下降優化器
- torch.optim.Adam: Adam優化器
- torch.optim.RMSprop: RMSprop優化器
創建優化器的時候, 你可以自定義參數來控制具體的優化,如:
torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0)
第一個參數為 需要梯度優化的參數。 第二個參數lr就是學習率的縮寫。 第三個參數代表學習率的衰減,默認為0, 第四個代表權重衰減。
以下是實例中對優化器的使用:
for batch_samples, batch_labels in iterate_batches(data, batch_size=32):batch_samples_t = torch.tensor(batch_samples))batch_labels_t = torch.tensor(batch_labels))out_t = net(batch_samples_t)loss_t = loss_function(out_t, batch_labels_t)loss_t.backward()optimizer.step()optimizer.zero_grad()這段代碼的流程是:
在循環中,每次取出一部分的樣本(mini-batch), 用于網絡優化訓練。 首先,前兩行代碼,將原始數據samples和labels轉為torch的張量。 然后用定義的net網絡對其進行處理, 得到輸出out。 將out和標簽輸入loss函數,可以得到損失值。 對損失值使用backward()方法,pytorch會自動對網絡處理中所有需要計算梯度的變量,求取其梯度。 接著調用優化器optimizer的step()方法, 優化器會根據求好的梯度,對網絡進行優化。最后一步優化器調用zero_grad(),清零本次的梯度。
我們已經了解了pytorch庫 搭建一個神經網絡及訓練的 核心要素。 但在以一個實際的例子融匯之前, 我們再了解一個重要的工具:監視器。
通過Tensorboard監控網絡訓練
只要你訓練過神經網絡,你就知道這是一件不確定性極大的事情。 哪怕時至今日, 即使你有很強的直覺, 也無法第一次就跑通完美的網絡。 因此,你希望可以監控訓練的過程,來找出問題所在。 訓練中,人們一般會監測這些指標:
- 損失值
- 在驗證集和測試集上的表現
- 梯度 和 權重 的相關數據統計
- 學習率, 以及其他超參數
Tensorboard 就是 滿足這一需求的經典工具。
他可以監控到訓練中的各種數據。 Tensorboard是使用流程如下:
- 進行訓練, 并存儲數據到本地字典
- 打開tensorboard的網絡服務
- 打開瀏覽器, 輸入正確的端口號, 觀看監控結果
當你是使用非本地計算機時,這一功能尤其完美——你可以通過網頁訪問云端服務器的訓練情況。
照例,我們用實例來說明:
在使用前, 請務必使用
pip install tensorboard-pytorch下載相關的組件。下載該包會自動下載tensorboard。 接下來, 找到下載到的tensorboard.exe的路徑, 將其添加到windows的環境變量中(這里不會的百度)。
運行如下代碼 02_tensorboard.py
import math from tensorboardX import SummaryWriterif __name__ == "__main__":writer = SummaryWriter()funcs = {"sin": math.sin, "cos": math.cos, "tan": math.tan}for angle in range(-360, 360):angle_rad = angle * math.pi / 180for name, fun in funcs.items():val = fun(angle_rad)writer.add_scalar(name, val, angle)writer.close()這段代碼并不涉及神經網絡, 只是展示了如何使用tensorboard。 即, 用SummaryWriter()創建了一個實例, 接著使用add_scalar方法, 寫入參數, 用于后續的可視化展示。
運行完上述代碼后, 路徑中應該出現了一個名為run的文件夾, 里面的子文件夾是以運行時間命名的, 子文件夾內就是剛剛運行后保存的數據文件, 用于tensorboard的可視化。 注意, 每次運行這段py代碼,都會生成一個以運行時間命名的子文件夾。我的路徑結構如下(代碼大家可以直接clone上面給出的全書源代碼的github庫,我就是這么做的):
確保已經生成上述文件后,就可以進行可視化的操作了!
首先,復制自己要可視化的文件夾的路徑(注意, 是文件夾的路徑,而不是文件的路徑),比如, 我的路徑是 C:\Deep-Reinforcement-Learning-Hands-On\Chapter03\runs。
接下來, 打開命令行, 輸入以下代碼
tensorboard --logdir C:\Deep-Reinforcement-Learning-Hands-On\Chapter03\runs
出現如下結果(如果出現說 tensorboard不是命令之類的報錯,那時因為你沒有把tensorboard的路徑配置到環境路徑中):
忽略中間的無聊警告,看到最后一句,就是tensorboard已經在本地的6006號端口上運行了。 這時候, 我們打開瀏覽器 (我用的是chrome,也推薦大家使用,谷歌自家的), 在地址欄輸入http://localhost:6006/,可以看到如下的網頁:
完美地可視化了我們剛剛寫入的數據!
總結
以上是生活随笔為你收集整理的【深度强化学习】深度学习:Pytorch的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《吊打面试官》系列-HashMap
- 下一篇: Spark运行任务时报错:org.apa