本章主要介紹兩個(gè)內(nèi)容,2.1節(jié)介紹如何安裝PyTorch,以及如何配置學(xué)習(xí)環(huán)境;2.2節(jié)將帶領(lǐng)讀者快速瀏覽PyTorch中主要內(nèi)容,給讀者一個(gè)關(guān)于PyTorch的大致印象。
2.1 安裝與配置
2.1.1 安裝PyTorch
PyTorch是一款以Python語(yǔ)言主導(dǎo)開(kāi)發(fā)的輕量級(jí)深度學(xué)習(xí)框架。在使用PyTorch之前,需要安裝Python環(huán)境及其pip包管理工具,推薦使用Virtualenv配置虛擬Python環(huán)境。本書中所有代碼使用PyTorch 0.3版本,同時(shí)兼容Python2和Python3,并全部在Python2環(huán)境中運(yùn)行得到最終結(jié)果,在Python3環(huán)境測(cè)試未報(bào)錯(cuò),但并不保證得到和Python2環(huán)境一致的結(jié)果。另外,本書默認(rèn)使用Linux作為開(kāi)發(fā)環(huán)境。
為方便用戶安裝使用,PyTorch官方提供了多種安裝方法。本節(jié)將介紹幾種常用的安裝方式,讀者可以根據(jù)自己的需求選用。
(1)使用pip安裝
目前,使用pip安裝PyTorch二進(jìn)制包是最簡(jiǎn)單、最不容易出錯(cuò),同時(shí)也是最適合新手的安裝方式。從PyTorch官網(wǎng)選擇操作系統(tǒng)、包管理器pip、Python版本及CUDA版本,會(huì)對(duì)應(yīng)不同的安裝命令。
以Linux平臺(tái)、pip安裝、Python2.7及CUDA8.0為例,安裝命令如下(根據(jù)不同系統(tǒng)配置,可將pip改為pip2或pip3):
pip install http://download.pytorch.org/whl/cu80/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
pip install torchvision
安裝好PyTorch之后,還需要安裝Numpy,安裝命令如下:
pip install --upgrade numpy
或者使用系統(tǒng)自帶的包管理器(apt,yum等)安裝Numpy,然后使用pip升級(jí)。
apt install python-numpy
pip install --upgrade numpy
全部安裝完成后,打開(kāi)Python,運(yùn)行如下命令。
>>> import torch as t
沒(méi)有報(bào)錯(cuò)則表示PyTorch安裝成功。
安裝過(guò)程中需要注意以下幾點(diǎn):
- PyTorch對(duì)應(yīng)的Python包名為torch而非pytorch。
- 若需使用GPU版本的PyTorch,需要先配置英偉達(dá)顯卡驅(qū)動(dòng),再安裝PyTorch。
(2)使用conda安裝
conda是Anaconda自帶的包管理器。如果使用Anaconda作為Python環(huán)境,則除了使用pip安裝,還可以使用conda進(jìn)行安裝。同樣,在PyTorch官網(wǎng)選擇操作系統(tǒng)、包管理器conda、Python版本及CUDA版本,對(duì)應(yīng)不同的安裝命令。我們?cè)贠S X下安裝Python3.6、CPU版本的PyTorch為例介紹。
安裝命令如下:
conda install pytorch torchvision -c soumith
conda的安裝速度可能較慢,建議國(guó)內(nèi)用戶,尤其是教育網(wǎng)用戶把conda源設(shè)置為清華tuna。在命令行輸入如下命令即可完成修改。
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --set show_channel_urls yes
即使是使用Anaconda的用戶,也建議使用pip安裝PyTorch,一勞永逸,而且不易出錯(cuò)。
(3)從源碼編譯安裝
不建議新手從源碼編譯安裝,因?yàn)檫@種安裝方式對(duì)環(huán)境比較敏感,需要用戶具備一定的編譯安裝知識(shí),以及應(yīng)對(duì)錯(cuò)誤的能力。但若想使用官方未發(fā)布的最新功能,或某個(gè)BUG剛修復(fù),官方還未提供二進(jìn)制安裝包,而讀者又亟需這個(gè)補(bǔ)丁,此時(shí)就需要從GitHub上下載源碼編譯安裝。
從源碼編譯安裝,推薦使用Anaconda環(huán)境。如果想使用GPU版本,則需安裝CUDA7.5及以上和cuDNN v5及以上(如果已裝有CUDA,但不想被PyTorch使用,只需設(shè)置環(huán)境變量NO_CUDA=1)。
首先,安裝可選依賴。
1)Linux
export CMAKE_PREFIX_PATH="$(dirname $(which conda))/../"
# 安裝基礎(chǔ)依賴
conda install numpy pyyaml mkl setuptools cmake gcc cffi
# 為GPU添加LAPACK支持
conda install -c soumith magma-cuda80 # 如果使用CUDA 7.5,則將magma-cuda80改成magma-cuda77
- OS
export CMKAE_PREFIX_PATH="$(dirnames $(which conda))/../"
conda install numpy pyyaml setuptools cmake cffi
其次,下載PyTorch源碼。
git clone https://github.com/pytorch/pytorch
cd pytorch
最后,完成編譯安裝。
1)Linux
python setup.py install
2)OS
MACOSX_DEPLOYMMENT_TRACET=10.9 CC=clang CXX=clang++ python setup.py install
(4)使用Docker部署
Docker是一個(gè)開(kāi)源的應(yīng)用容器引擎,讓開(kāi)發(fā)者可以打包他們的應(yīng)用及依賴包到一個(gè)可移植的容器中,并發(fā)布到融合流行的Linux機(jī)器上,也可實(shí)現(xiàn)虛擬化。PyTorch官方提供了Dockerfile,支持CUDA和cuDNN v6。可通過(guò)如下命令構(gòu)建Docker鏡像。
docker build -t pytorch-cudnnv6 .
通過(guò)如下命令運(yùn)行:
nvidia-docker run --rm -ti --ipc=host pytorch-cudnnv6
注意:PyTorch中數(shù)據(jù)加載(DataLoader)使用了大量的共享內(nèi)存,可能超過(guò)容器限制,需設(shè)置--shm-size選項(xiàng)或使用--ipc=host選項(xiàng)解決。
(5)Windows用戶安裝PyTorch
Windows用戶安裝PyTorch跟Linux、OS差不多。同樣,在PyTorch官網(wǎng)選擇操作系統(tǒng)、包管理器pip、Python版本及CUDA版本,對(duì)應(yīng)不同的安裝命令。我們?cè)赪indows下安裝Python3.7、GPU版本的PyTorch為例介紹。
pip3 install torch torchvision
2.1.2 學(xué)習(xí)環(huán)境配置
工欲善其事,必先利其器。在從事科學(xué)計(jì)算相關(guān)工作時(shí),IPython和Jupyter是兩個(gè)必不可少的工具。推薦使用IPython和Jupyter Notebook學(xué)習(xí)本書的示例代碼。
(1)IPython
IPython是一個(gè)交互式計(jì)算系統(tǒng),可認(rèn)為是增強(qiáng)版的Python Shell,提供強(qiáng)大的REPL(交互式解析器)功能。對(duì)從事科學(xué)計(jì)算的用戶來(lái)說(shuō),它提供方便的可交互式學(xué)習(xí)及調(diào)試功能。
安裝IPython十分簡(jiǎn)單,對(duì)于Python2的用戶,安裝命令如下。
pip2 install ipython==5.1
IPython 5.x是最后一個(gè)支持Python2的IPython。Python3的用戶可通過(guò)如下命令安裝最新版的IPython。
pip install ipython
安裝完成后,在命令行輸入ipython即可啟動(dòng)IPython,啟動(dòng)界面如下。
ipython
Python 3.7.4 (default, Aug 9 2019, 18:34:13) [MSC v.1915 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.8.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
輸入exit命令或者按Ctrl+D快捷鍵可退出IPython。IPython有許多強(qiáng)大的功能,其中最常用的功能如下。
- 自動(dòng)補(bǔ)全:IPython最方便的功能之一是自動(dòng)補(bǔ)全,輸入一個(gè)函數(shù)或者變量的前幾個(gè)字幕,按下Tab鍵,就能實(shí)現(xiàn)自動(dòng)補(bǔ)全,如下圖:
- 內(nèi)省:所謂內(nèi)省,主要是指在Runtime時(shí)獲得一個(gè)對(duì)象的全部類型信息,這對(duì)實(shí)際的學(xué)習(xí)有很大的幫助。輸入某一個(gè)函數(shù)或者模塊之后,接著輸入?可看到它對(duì)應(yīng)的幫助文檔,有些幫助文檔比較長(zhǎng),可能跨頁(yè),這是可按空格鍵翻頁(yè),輸入q退出。例如:
In [1]: import torch as t
In [2]: t.abs?
Docstring:
abs(input, out=None) -> Tensor
Computes the element-wise absolute value of the given :attr:`input` tensor.
.. math::
\text{out}_{i} = |\text{input}_{i}|
Args:
input (Tensor): the input tensor
out (Tensor, optional): the output tensor
Example::
>>> torch.abs(torch.tensor([-1, -2, 3]))
tensor([ 1, 2, 3])
Type: builtin_function_or_method
在函數(shù)或模塊名之后輸入兩個(gè)問(wèn)號(hào),例如:t.FloatTensor??即可查看這個(gè)對(duì)象的源碼,但只能查看對(duì)應(yīng)Python的源碼,無(wú)法查看C/C++的源碼。
- 快捷鍵:IPython提供了很多快捷鍵。例如,按上箭頭可以重新輸入上一條代碼;一致按上箭頭,可以追溯到之前輸入的代碼。按Ctrl+C快捷鍵可以取消當(dāng)前輸入或停止運(yùn)行的程序。常用的快捷鍵如下所示。
- 魔術(shù)方法:IPython中還提供了一些特殊的命令,這些命令以%開(kāi)頭,稱為魔術(shù)命令(本質(zhì)是執(zhí)行本地系統(tǒng)的命令),例如可通過(guò)%hist查看當(dāng)前IPython下的輸入歷史等,示例如下。
In [1]: import torch as t
In [2]: a = t.Tensor(3,4)
In [3]: %timeit a.sum() # 檢測(cè)某條語(yǔ)句的執(zhí)行時(shí)間
3.24 μs ± 23.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [4]: %hist # 查看輸入歷史
import torch as t
a = t.Tensor(3,4)
%timeit a.sum()
%hist
In [5]: %paste # 執(zhí)行粘貼板中的代碼,如果只粘貼不執(zhí)行使用Ctrl+Shift+V快捷鍵
def add(x,y,z):
return x+y+z
## -- End pasted text --
In [6]: %cat a.py # 查看文件內(nèi)容
b = a + 1
print(b.size())
In [7]: %run -i a.py # 執(zhí)行文件,-i選項(xiàng)代表在當(dāng)前命名空間中執(zhí)行
torch.Size([3, 4])
In [8]: b
Out[9]:
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
和普通Python對(duì)象一樣,魔術(shù)方法也支持自省,因此也可以在命令后面加“?”或者"??"來(lái)查看對(duì)應(yīng)的幫助文檔或者源代碼,例如通過(guò)%run?可查看它的使用說(shuō)明。其他常用魔術(shù)命令如下所示。
“%xdel”與“del”的不同在于前者會(huì)刪除其在IPython上的一切引用,具體例子如下。
In [1]: import torch as t
In [2]: a = t.Tensor(5,5)
In [3]: a
Out[3]:
tensor([[-4.2300e-33, 6.7823e-43, -1.1426e-29, 6.7823e-43, -1.1426e-29],
[ 6.7823e-43, -1.1426e-29, 6.7823e-43, 9.8265e-39, 9.4592e-39],
[ 1.0561e-38, 1.0653e-38, 1.0469e-38, 9.5510e-39, 9.0918e-39],
[ 9.1837e-39, 8.4490e-39, 8.7245e-39, 1.4013e-45, 1.0653e-38],
[ 0.0000e+00, 0.0000e+00, 4.6838e-39, 4.1327e-39, 0.0000e+00]])
In [4]: del a
In [5]: Out[3]
Out[5]:
tensor([[-4.2300e-33, 6.7823e-43, -1.1426e-29, 6.7823e-43, -1.1426e-29],
[ 6.7823e-43, -1.1426e-29, 6.7823e-43, 9.8265e-39, 9.4592e-39],
[ 1.0561e-38, 1.0653e-38, 1.0469e-38, 9.5510e-39, 9.0918e-39],
[ 9.1837e-39, 8.4490e-39, 8.7245e-39, 1.4013e-45, 1.0653e-38],
[ 0.0000e+00, 0.0000e+00, 4.6838e-39, 4.1327e-39, 0.0000e+00]])
In [6]: c = t.Tensor(1000,1000)
In [7]: c
Out[7]:
tensor([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
In [8]: %xdel c
In [9]: Out[7]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-9-2bf219d909a6> in <module>
----> 1 Out[7]
KeyError: 7
- 粘貼:IPython支持多種格式的粘貼,除了%paste魔術(shù)方法,還可以直接粘貼多行代碼、doctest代碼和IPython代碼,舉例如下(下面的代碼都使用Ctrl+V)快捷鍵的方式直接粘貼。如果是Linux終端,則應(yīng)該使用Ctrl+Shift+V快捷鍵直接粘貼,或者單擊鼠標(biāo)右鍵,選擇粘貼選項(xiàng))。
In [1]: In [1]: import torch as t^M
...: ^M
...: In [2]: a = t.Tensor(5,5)^M
...: ^M
...: In [3]: a
Out[1]:
tensor([[9.1837e-39, 4.6837e-39, 9.9184e-39, 9.0000e-39, 1.0561e-38],
[1.0653e-38, 4.1327e-39, 8.9082e-39, 9.8265e-39, 9.4592e-39],
[1.0561e-38, 1.0653e-38, 1.0469e-38, 9.5510e-39, 9.1837e-39],
[1.0561e-38, 1.0469e-38, 9.0000e-39, 1.0653e-38, 1.0194e-38],
[1.0561e-38, 1.0469e-38, 9.9184e-39, 1.1020e-38, 9.1837e-39]])
In [2]: >>> import torch as t^M
...: >>> a = t.Tensor(5,5)^M
...: >>> a
Out[2]:
tensor([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
In [3]: import torch as t^M
...: a = t.Tensor(5,5)^M
...: a
Out[3]:
tensor([[-1.2787e+02, 5.6472e-43, -3.6906e+05, 5.6472e-43, -3.6906e+05],
[ 5.6472e-43, -3.6906e+05, 5.6472e-43, -1.3039e+02, 5.6472e-43],
[-1.3693e+02, 5.6472e-43, -2.1370e+03, 5.6472e-43, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.4013e-45, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])
- 使用IPython進(jìn)行調(diào)試:IPython的調(diào)試器ipdb增強(qiáng)了pdb,提供了很多使用功能,例如Tab鍵自動(dòng)補(bǔ)全、語(yǔ)法高亮等。在IPython中進(jìn)入ipdb的最快速方式是使用魔術(shù)命令%debug,此時(shí)用戶能夠直接跳到報(bào)錯(cuò)的代碼處,可通過(guò)u,d實(shí)現(xiàn)堆棧中的上下移動(dòng),常用的調(diào)試命令如下所示。
debug是一個(gè)重要功能,不僅在學(xué)習(xí)PyTorch時(shí)需要用到,在平時(shí)學(xué)習(xí)Python或使用IPython時(shí)也會(huì)經(jīng)常用到。更多的debug功能,可通過(guò)h <命令>查看該命令的使用方法。
如果想在IPython之外使用debug功能,則需要安裝ipdb(pip install ipdb),而后在需要進(jìn)入調(diào)試的地方加上如下代碼即可。
import ipdb
ipdb.set_trace()
當(dāng)程序運(yùn)行到這一步時(shí),會(huì)自動(dòng)進(jìn)入debug模式。
(2)Jupyter Notebook
Jupyter Notebook是一個(gè)交互式筆記本,前身是IPython Notebook,后來(lái)從IPython中獨(dú)立出來(lái),現(xiàn)支持運(yùn)行40多種編程語(yǔ)言。對(duì)希望編寫漂亮的交互式文檔和從科學(xué)計(jì)算的用戶來(lái)說(shuō)是一個(gè)不錯(cuò)的選擇。
Jupyter Notebook的使用方法與IPython非常類似,推薦使用Jupyter Notebook主要有如下三個(gè)原因。
- 更美觀的界面:相比在終端下使用IPython,Notebook提供圖形化操作界面,對(duì)新手而言更美觀簡(jiǎn)潔。
- 更好的可視化支持:Notebook與Web技術(shù)深度融合,支持在Notebook中直接可視化,這對(duì)需要經(jīng)常繪圖的科學(xué)運(yùn)算實(shí)驗(yàn)來(lái)說(shuō)很方便。
- 方便遠(yuǎn)程訪問(wèn):在服務(wù)端開(kāi)啟Notebook服務(wù)后,客戶端只需有瀏覽器且能訪問(wèn)服務(wù)器,就可以使用服務(wù)器上的Notebook,這對(duì)于很多使用Linux服務(wù)器,但辦公電腦使用Windows的人來(lái)說(shuō)十分方便,避免了在本地配置環(huán)境的復(fù)雜流程。
安裝Jupyter只需一條pip命令。
pip install jupyter
安裝完成后,在命令行輸入jupyter notebook命令即可啟動(dòng)Jupyter,此時(shí)瀏覽器會(huì)自動(dòng)彈出,并打開(kāi)Jupyter主界面,也可以手動(dòng)打開(kāi)瀏覽器,輸入http://127.0.0.1:8888訪問(wèn)Jupyter,界面如下。
單擊頁(yè)面右上角的“new”選項(xiàng),選擇相應(yīng)的Notebook類型(Python3/Python2),可新建一個(gè)Notebook,在In[]后面的編輯區(qū)域輸入代碼,按Ctrl+Enter快捷鍵即可運(yùn)行代碼,如下所示。
遠(yuǎn)程訪問(wèn)服務(wù)器Jupyter的用戶需要在服務(wù)器中搭建Jupyter Notebook服務(wù),然后通過(guò)瀏覽器訪問(wèn)。可以根據(jù)需要對(duì)Jupyter設(shè)置訪問(wèn)密碼。
首先,打開(kāi)IPython,設(shè)置密碼,獲取加密后的密碼。
In [1] : from notebook.auth import passwd
In [2] : passwd() # 輸入密碼
Enter password:
Verify password:
Out [2] : 'sha1:f9c17b4cc163:43b6b4c8c....'
sha1:f9c17b...即為加密后的密碼,新建jupyter_config.py,輸入如下配置。
# 加密后的密碼
c.NotebookApp.password = u'sha1:f9c17b4cc163:43b6b4c8c....'
# ::綁定所有IP地址,包括IPv4/IPv6的地址,如果只想綁定某個(gè)IP,改成對(duì)應(yīng)的IP即可
c.NotebookApp.ip = '::'
# 綁定的端口號(hào),如果該端口號(hào)已經(jīng)被占用,會(huì)自動(dòng)使用下一個(gè)端口號(hào)10000
c.NotebookApp.port = 9999
其次,啟動(dòng)Jupyter Notebook并指定配置文件,輸入如下命令。
jupyter notebook --config=jupyter_config.py
最后,客戶端打開(kāi)瀏覽器,訪問(wèn)url http://[服務(wù)器IP]:9999,輸入密碼,即可訪問(wèn)Jupyter。
若客戶端瀏覽器無(wú)法打開(kāi)Jupyter,有可能是防火墻的緣故,輸入如下命令開(kāi)放對(duì)應(yīng)的端口(若使用IPv6,把命令iptables改成ip6tables)。
iptables -I INPUT -p tcp --dport 9999 -j ACCEPT
iptables save
Jupyter的使用和IPython極為類似,我們介紹的IPython使用技巧對(duì)Jupyter基本都適用。它支持自動(dòng)補(bǔ)全、自省、魔術(shù)方法、debug等功能,但它的快捷鍵與IPython有較大不同,可以通過(guò)菜單欄的【help】->【Keyboard Shortcuts】查看詳細(xì)的快捷鍵。
Jupyter還支持很多功能,如Markdown語(yǔ)法、HTML、各種可視化等。更多關(guān)于IPython和Jupyter Notebook的使用技巧,讀者可以從網(wǎng)上找到很多學(xué)習(xí)資源,這里只介紹一些最基礎(chǔ)的、本書會(huì)用到的內(nèi)容。
2.2 PyTorch入門第一步
PyTorch的簡(jiǎn)潔設(shè)計(jì)使得它易于入門,在深入介紹PyTorch之前,本節(jié)先介紹一些PyTorch的基礎(chǔ)知識(shí),使讀者能夠?qū)yTorch有一個(gè)大致的了解,并能夠用PyTorch搭建一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)。部分內(nèi)容讀者可能不太理解,可先不予研究,本書的第3章和第4章將會(huì)對(duì)此進(jìn)行深入講解。
本節(jié)內(nèi)容參考了PyTorch官方教程并做了相應(yīng)的增刪,使得內(nèi)容更貼合新版本的PyTorch接口,同時(shí)也更適合新手快速入門。另外,本書需要讀者先掌握基礎(chǔ)的numpy使用,numpy的基礎(chǔ)知識(shí)可以參考CS231n上關(guān)于numpy的教程。
2.2.1 Tensor
Tensor是PyTorch中重要的數(shù)據(jù)結(jié)構(gòu),可認(rèn)為是一個(gè)高維數(shù)組。它可以是一個(gè)數(shù)(標(biāo)量)、一維數(shù)組(向量)、二維數(shù)組(矩陣)或更高維的數(shù)組。Tensor和numpy的ndarrays類似,但Tensor可以使用GPU加速。Tensor的使用和numpy及MATLAB的接口十分相似,下面通過(guò)幾個(gè)示例了解Tensor的基本使用方法。
In [1] : from __future__ import print_function
import torch as t
x = t.Tensor(5,3) # 構(gòu)建5 * 3矩陣,只是分配了空間,未初始化
x
Out [1] : tensor([[1.0561e-38, 1.0653e-38, 4.1327e-39],
[8.9082e-39, 9.8265e-39, 9.4592e-39],
[1.0561e-38, 1.0653e-38, 1.0469e-38],
[9.5510e-39, 8.7245e-39, 9.0918e-39],
[1.0102e-38, 9.6429e-39, 8.7245e-39]])
In [2] : x == t.rand(5,3) # 使用[0,1]均勻分布隨機(jī)初始化二維數(shù)組
x
Out [2] : tensor([[1.0561e-38, 1.0653e-38, 4.1327e-39],
[8.9082e-39, 9.8265e-39, 9.4592e-39],
[1.0561e-38, 1.0653e-38, 1.0469e-38],
[9.5510e-39, 8.7245e-39, 9.0918e-39],
[1.0102e-38, 9.6429e-39, 8.7245e-39]])
In [3] : print(x.size()) # 查看x的形狀
torch.Size([5, 3])
torch.Size是tuple對(duì)象的子類,因此它支持tuple的所有操作,如x.size()[0]。
In [4] : y = t.rand(5,3)
x + y # 加法的第一種寫法
Out [4] : tensor([[0.6707, 0.6551, 0.5273],
[0.5378, 0.6252, 0.1105],
[0.3754, 0.6970, 0.5691],
[0.8858, 0.6057, 0.9119],
[0.2084, 0.0882, 0.7523]])
In [5] : t.add(x,y) # 加法的第二種寫法
Out [5] : tensor([[0.6707, 0.6551, 0.5273],
[0.5378, 0.6252, 0.1105],
[0.3754, 0.6970, 0.5691],
[0.8858, 0.6057, 0.9119],
[0.2084, 0.0882, 0.7523]])
In [6] : # 加法的第三種寫法:指定加法結(jié)果的輸出目標(biāo)為result
result = t.Tensor(5,3) # 預(yù)先分配空間
t.add(x,y,out=result) # 輸出到result
result
Out [6] : tensor([[0.6707, 0.6551, 0.5273],
[0.5378, 0.6252, 0.1105],
[0.3754, 0.6970, 0.5691],
[0.8858, 0.6057, 0.9119],
[0.2084, 0.0882, 0.7523]])
In [7] : x = t.Tensor([1,2,3]) # 使用list定義Tensor
y = t.Tensor([1,1,1]) # 使用list定義Tensor
In [8] : x.add(y) # 普通加法,x的值不變
x
Out [8] : tensor([1., 2., 3.])
In [9] : x.add_(y) # inplace加法,x的值改變
x
Out [9] : tensor([2., 3., 4.])
注意:函數(shù)名后面帶下劃線的函數(shù)會(huì)修改Tensor本身。例如:x.add_(y)和x.t_()會(huì)改變x,但x.add(y)和x.t()會(huì)返回一個(gè)新的Tensor,而x不變。
In [10] : x = t.rand(3,4)
x
Out [10] : tensor([[0.1282, 0.3337, 0.5191, 0.5905],
[0.1274, 0.4602, 0.9954, 0.8498],
[0.8286, 0.3794, 0.6181, 0.8584]])
In [11] : x[:,1] # Tensor的選取操作與numpy類似
Out [11] : tensor([0.3337, 0.4602, 0.3794])
Tensor還支持很多操作,包括數(shù)學(xué)運(yùn)算、線性代數(shù)、選擇、切片等,其接口設(shè)計(jì)與numpy極為相似。更詳細(xì)的使用方法會(huì)在第3章系統(tǒng)講解。
Tensor和numpy的數(shù)組間的互操作非常容易且快速。Tensor不支持的操作,可以先轉(zhuǎn)為numpy數(shù)組處理,之后再轉(zhuǎn)回Tensor。
In [12] : a = t.ones(5)
a
Out [12] : tensor([1., 1., 1., 1., 1.])
In [13] : b = a.numpy() # Tensor —> numpy
b
Out [13] : array([1., 1., 1., 1., 1.], dtype=float32)
In [14] : import numpy as np
a = np.ones(5)
a
Out [14] : array([1., 1., 1., 1., 1.])
In [15] : b = t.from_numpy(a) # numpy —> Tensor
b
Out [15] : tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
Tensor和numpy對(duì)象共享內(nèi)存,所以它們之間的轉(zhuǎn)換很快,而且?guī)缀醪恍枰馁Y源。這也意味著,如果其中一個(gè)變了,另外一個(gè)也會(huì)隨之改變。
In [16] : b.add_(1) # 以下劃線結(jié)尾的函數(shù)會(huì)修改自身
b
Out [15] : tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
In [17] : a
Out [17] : array([2., 2., 2., 2., 2.])
Tensor可通過(guò).cuda()方法轉(zhuǎn)為GPU的Tensor,從而享受GPU帶來(lái)的加速運(yùn)算。
In [18] : x = t.Tensor([1,1,1])
y = t.Tensor([2,2,2])
# 在不支持CUDA的機(jī)器上,下一步不會(huì)運(yùn)行
if t.cuda.is_available():
x = x.cuda()
y = x.cuda()
x + y
Out [18] : tensor([2., 2., 2.], device='cuda:0')
在此處可能會(huì)發(fā)現(xiàn)CPU運(yùn)算的速度并未提升太多,這是因?yàn)閤和y太小且運(yùn)算也較簡(jiǎn)單,而且將數(shù)據(jù)從內(nèi)存轉(zhuǎn)移到顯存還需要花費(fèi)額外的開(kāi)銷。GPU的優(yōu)勢(shì)在大規(guī)模數(shù)據(jù)和復(fù)雜運(yùn)算下才能體現(xiàn)出來(lái)。
2.2.2 Autograd:自動(dòng)微分
自動(dòng)微分的算法本質(zhì)上是通過(guò)反向傳播求導(dǎo)數(shù),PyTorch的Autograd模塊實(shí)現(xiàn)了此功能。在Tensor上的所有操作,Autograd都能為它們自動(dòng)提供微分,避免手動(dòng)計(jì)算導(dǎo)數(shù)的復(fù)雜過(guò)程。
autograd.Variable是Autograd的核心類,它簡(jiǎn)單封裝了Tensor,并支持幾乎所有Tensor的操作。Tensor在被封裝為Variable之后,可以調(diào)用它的.backward實(shí)現(xiàn)反向傳播,自動(dòng)計(jì)算所有梯度。Variable的數(shù)據(jù)結(jié)構(gòu)如下所示。
Variable主要包含三個(gè)屬性。
- data:保存Variable所包含的Tensor。
- grad:保存data對(duì)應(yīng)的梯度,grad也是個(gè)Variable,而不是Tensor,它和data的形狀一樣。
- grad_fn:指向一個(gè)Function對(duì)象,這個(gè)Function用來(lái)計(jì)算反向傳播計(jì)算輸入的梯度,具體細(xì)節(jié)會(huì)在第3章講解。
In [19] : from torch.autograd import Variable # 導(dǎo)入Variable
In [20] : x = Variable(t.ones(2,2),requires_grad=True) # 使用Tensor新建一個(gè)Variable
x
Out [20] : tensor([[1., 1.],
[1., 1.]], requires_grad=True)
In [21] : y = x.sum()
y
Out [21] : tensor(4., grad_fn=<SumBackward0>)
In [22] : y.grad_fn
Out [22] : <SumBackward0 at 0x1908338ed08>
In [23] : y.backward()
# y = x.sum = (x[0][0] + x[0][1] + x[1][0] + x[1][1])
# 每個(gè)值的梯度都為1
x.grad
Out [23] : tensor([[1., 1.],
[1., 1.]])
注意:grad在反向傳播過(guò)程中是累加的(accumulated),這意味著每次運(yùn)行反向傳播,梯度都會(huì)累加之前的梯度,所以反向傳播之前需把梯度清零。
In [24] : y.backward()
x.grad
Out [24] : tensor([[2., 2.],
[2., 2.]])
In [25] : y.backward()
x.grad
Out [25] : tensor([[3., 3.],
[3., 3.]])
In [26] : x.grad.data.zero_() # 以下劃線結(jié)束的函數(shù)是inplace操作
In [27] : y.backward()
x.grad
Out [27] : tensor([[1., 1.],
[1., 1.]])
Variable和Tensor具有近乎一致的接口,在實(shí)際使用中可以無(wú)縫切換。
In [28] : x = Variable(t.ones(4,5))
y = t.cos(x)
x_tensor_cos = t.cos(x.data)
print(y)
x_tensor_cos
tensor([[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403]])
Out [28] : tensor([[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
[0.5403, 0.5403, 0.5403, 0.5403, 0.5403]])
2.2.3 神經(jīng)網(wǎng)絡(luò)
Autograd實(shí)現(xiàn)了反向傳播功能,但是直接用來(lái)寫深度學(xué)習(xí)的代碼在很多情況下還是稍顯復(fù)雜,torch.nn是專門為神經(jīng)網(wǎng)絡(luò)設(shè)計(jì)的模塊化接口。nn構(gòu)建于Autograd之上,可用來(lái)定義和運(yùn)行神經(jīng)網(wǎng)絡(luò)。nn.Module是nn中最重要的類,可以把它看做是一個(gè)網(wǎng)絡(luò)的封裝,包括網(wǎng)絡(luò)各層定義以及forward方法,調(diào)用forward(input)方法,可返回前向傳播的結(jié)果。我們以最早的卷積神經(jīng)網(wǎng)絡(luò)LeNet為例,來(lái)看看如何用nn.Module實(shí)現(xiàn)。LeNet的網(wǎng)絡(luò)結(jié)構(gòu)如下所示。
這是一個(gè)基礎(chǔ)的前向傳播(feed-forward)網(wǎng)絡(luò):接收輸入,經(jīng)過(guò)層層傳遞運(yùn)算,得到輸出。
(1)定義網(wǎng)絡(luò)
定義網(wǎng)絡(luò)時(shí),需要繼承nn.Module,并實(shí)現(xiàn)它的forward方法,把網(wǎng)絡(luò)中具有可學(xué)習(xí)參數(shù)的層放在構(gòu)造函數(shù)init中。如果某一層(如ReLU)不具有可學(xué)習(xí)的參數(shù),則既可以放在構(gòu)造函數(shù)中,也可以不放。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
# nn.Module子類的函數(shù)必須在構(gòu)造函數(shù)中執(zhí)行父類的構(gòu)造函數(shù)
# 下式等價(jià)于nn.Module.__init__()
super(Net,self).__init__()
# 卷積層:‘1’表示輸入圖片為單通道,‘6’表示輸出通道數(shù),‘5’表示卷積核為5 * 5
self.conv1 = nn.Conv2d(1,6,5)
# 卷積層:
self.conv2 = nn.Conv2d(6,16,5)
# 仿射層/全連接層,y = Wx + b
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forword(self,x):
# 卷積 -> 激活 -> 池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# reshape, '-1'表示自適應(yīng)
x = x.view(s.size()[0],-1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net)
輸出如下:
Net(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(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)
)
只要在nn.Module的子類中定義了forward函數(shù),backward函數(shù)就會(huì)被自動(dòng)實(shí)現(xiàn)(利用Autograd)。在forward函數(shù)中可使用任何Variable支持的函數(shù),還可以使用if、for循環(huán)、print、log等Python語(yǔ)法,寫法和標(biāo)準(zhǔn)的Python寫法一致。
網(wǎng)絡(luò)的可學(xué)習(xí)參數(shù)通過(guò)net.parameters()返回,net.named_parameters可同時(shí)返回可學(xué)習(xí)的參數(shù)及名稱。
params = list(net.parameters())
print(len(params))
輸出如下:
10
for name,parameters in net.named_parameters():
print(name,':',parameters.size())
輸出如下:
conv1.weight : torch.Size([6, 1, 5, 5])
conv1.bias : torch.Size([6])
conv2.weight : torch.Size([16, 6, 5, 5])
conv2.bias : torch.Size([16])
fc1.weight : torch.Size([120, 400])
fc1.bias : torch.Size([120])
fc2.weight : torch.Size([84, 120])
fc2.bias : torch.Size([84])
fc3.weight : torch.Size([10, 84])
fc3.bias : torch.Size([10])
foward函數(shù)的輸入和輸出都是Variable,只有Variable才具有自動(dòng)求導(dǎo)功能,Tensor是沒(méi)有,所以在輸入時(shí),需要把Tensor封裝成Variable。
input = Variable(t.randn(1,1,32,32))
out = net(input)
out.size()
輸出如下:
torch.Size([1, 10])
所有參數(shù)的梯度清零。
net.zero_grad() # 所有參數(shù)的梯度清零
out.backward(Variable(t.ones(1,10))) # 反向傳播
需要注意的是,torch.nn只支持mini-batch,不支持一次只輸入一個(gè)樣本,即一次必須是一個(gè)batch。如果只想輸入一個(gè)樣本,則用input.unsqueeze(0)將batch_size設(shè)為1。例如,nn.Conv2d輸入必須是4維的,形如nSamples * nChannels * Height * Width。可將nSamples設(shè)為1,即1 * nChannels * Height * Width。
(2)損失函數(shù)
nn實(shí)現(xiàn)了神經(jīng)網(wǎng)絡(luò)中大多數(shù)的損失函數(shù),例如nn.MSELoss用來(lái)計(jì)算均方誤差,nn.CrossEntropyLoss用來(lái)計(jì)算交叉熵?fù)p失。
output = net(input)
target = Variable(t.arange(0.0,10))
criterion = nn.MSELoss()
loss = criterion(output, target)
loss
輸出如下:
tensor(28.5165, grad_fn=<MseLossBackward>)
如果對(duì)loss進(jìn)行反向傳播溯源(使用grad_fn屬性),可以看到它的計(jì)算圖如下。
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
當(dāng)調(diào)用loss.backward()時(shí),該圖會(huì)動(dòng)態(tài)生成并自動(dòng)微分,也會(huì)自動(dòng)計(jì)算圖中參數(shù)(Parameter)的導(dǎo)數(shù)。
# 運(yùn)行.backward,觀察調(diào)用之前和調(diào)用之后的grad
net.zero_grad() # 把net中所有可學(xué)習(xí)的參數(shù)的梯度清零
print('反向傳播之前conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向傳播之后conv1.bias的梯度')
print(net.conv1.bias.grad)
輸出如下:
# 運(yùn)行.backward,觀察調(diào)用之前和調(diào)用之后的grad
net.zero_grad() # 把net中所有可學(xué)習(xí)的參數(shù)的梯度清零
print('反向傳播之前conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向傳播之后conv1.bias的梯度')
print(net.conv1.bias.grad)
(3)優(yōu)化器
在反向傳播計(jì)算完所有參數(shù)的梯度后,還需要使用優(yōu)化方法更新網(wǎng)絡(luò)的權(quán)重和參數(shù)。例如,隨機(jī)梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
手動(dòng)實(shí)現(xiàn)如下:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate) # inplcae 減法
torch.optim中實(shí)現(xiàn)了深度學(xué)習(xí)中絕大多數(shù)的優(yōu)化方法,例如RMSProp、Adam、SGD等,更便于使用,因此通常并不需要手動(dòng)寫上述代碼。
import torch.optim as optim
# 新建一個(gè)優(yōu)化器,指定要調(diào)整的參數(shù)和學(xué)習(xí)率
optimizer = optim.SGD(net.parameters(), lr = 0.01)
# 在訓(xùn)練過(guò)程中, 先梯度清零(與net.zero_grad()效果一樣)
optimizer.zero_grad()
# 計(jì)算損失
output = net(input)
loss = criterion(output, target)
# 反向傳播
loss.backward()
# 更新參數(shù)
optimizer.step()
(4)數(shù)據(jù)加載與預(yù)處理
在深度學(xué)習(xí)中數(shù)據(jù)加載與預(yù)處理是非常復(fù)雜繁瑣的,但PyTorch提供了一些可簡(jiǎn)答簡(jiǎn)化和加快數(shù)據(jù)處理流程的工具。同時(shí),對(duì)于常用的數(shù)據(jù)集,PyTorch也提供了封裝好的接口供用戶快速調(diào)用,這些數(shù)據(jù)集主要保存在torchvision中。
torchvision實(shí)現(xiàn)了常用的圖像數(shù)據(jù)加載功能,例如ImageNet、CIFAR10、MNIST等,以及常用的數(shù)據(jù)轉(zhuǎn)換操作,這極大地方便了數(shù)據(jù)加載。
2.2.4 小試牛刀:CIFAR-10分類
下面我們來(lái)嘗試實(shí)現(xiàn)對(duì)CIFAR10數(shù)據(jù)集的分類,步驟如下:
(1)使用torchvision加載并預(yù)處理CIFAR10數(shù)據(jù)集。
(2)定義網(wǎng)絡(luò)。
(3)定義損失函數(shù)和優(yōu)化器。
(4)訓(xùn)練網(wǎng)絡(luò)并更新網(wǎng)絡(luò)參數(shù)。
(5)測(cè)試網(wǎng)絡(luò)。
(1)CIFAR-10數(shù)據(jù)集加載及預(yù)處理
CIFAR-10是一個(gè)常用的彩色圖片數(shù)據(jù)集,它有10個(gè)類別:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每張圖片都是3 * 32 * 32,也即3通道彩色圖片,分辨率為32 * 32。因?yàn)槭褂贸绦驍?shù)據(jù)加載器下載該數(shù)據(jù)集太慢,這里給出一個(gè)百度云盤下載,提前下載數(shù)據(jù)集放到指定目錄下,如E:/data/,在加載器中root參數(shù)指向該目錄,程序檢測(cè)到該文件已存在就直接解壓,不再去外網(wǎng)下載了,節(jié)省時(shí)間。
import torch as t
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
import torchvision as tv
from torch.autograd import Variable
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
show = ToPILImage() # 可以把Tensor轉(zhuǎn)成Image,方便可視化
# 第一次運(yùn)行程序torchvision會(huì)自動(dòng)下載CIFAR-10數(shù)據(jù)集,大約100MB,需花費(fèi)一定的時(shí)間
# 如果已經(jīng)下載有CIFAR-10,可通過(guò)root參數(shù)指定
# 定義對(duì)數(shù)據(jù)的預(yù)處理
transform = transforms.Compose([
transforms.ToTensor(), # 轉(zhuǎn)為Tensor
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 歸一化
])
# 訓(xùn)練集
trainset = tv.datasets.CIFAR10(
root='E:/data/',
train=True,
download=True,
transform=transform)
trainloader = t.utils.data.DataLoader(
trainset,
batch_size=4,
shuffle=True,
num_workers=2)
# 測(cè)試集
testset = tv.datasets.CIFAR10(
root='E:/data/',
train=False,
download=True,
transform=transform)
testloader = t.utils.data.DataLoader(
testset,
batch_size=4,
shuffle=False,
num_workers=2)
classes = ('plane','car','bird','cat','deer','dog','frog','horse','ship','truck')
輸出如下:
Files already downloaded and verified
Files already downloaded and verified
Dataset對(duì)象是一個(gè)數(shù)據(jù)集,可以按下表訪問(wèn),返回形如(data,label)的數(shù)據(jù)。
(data,label) = trainset[100]
print(classes[label])
# (data+1)/2 是為了還原被歸一化的數(shù)據(jù),程序輸出的圖片如下。
show((data+1)/2).resize((100,100))
輸出如下:
ship
DataLoader是一個(gè)可迭代的對(duì)象,它將dataset返回的每一條數(shù)據(jù)樣本拼接成一個(gè)batch,并提供多線程加速優(yōu)化和數(shù)據(jù)打亂等操作。當(dāng)程序?qū)ataset的所有數(shù)據(jù)遍歷完一遍之后,對(duì)DataLoader也完成了一次迭代。
dataiter = iter(trainloader)
images,labels = dataiter.next() # 返回4張圖片及標(biāo)簽
print(' '.join('%11s' % classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images+1)/2)).resize((400,100))
輸出如下:
dog frog truck dog
(2)定義網(wǎng)絡(luò)
復(fù)制上面的LeNet網(wǎng)絡(luò),修改self.conv1中第一個(gè)參數(shù)為3通道,因?yàn)镃IFAR-10是3通道彩色圖。
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3,6,5)
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 = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
x = F.max_pool2d(F.relu(self.conv2(x)),2)
x = x.view(x.size()[0],-1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net)
輸出如下:
Net(
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(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)
)
(3)定義損失函數(shù)和優(yōu)化器(loss和optimizer)
criterion = nn.CrossEntropyLoss() # 交叉熵?fù)p失函數(shù)
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
(4)訓(xùn)練網(wǎng)絡(luò)
所有網(wǎng)絡(luò)的訓(xùn)練流程都是類似的,不斷地執(zhí)行如下流程。
- 輸入數(shù)據(jù)。
- 前向傳播 + 反向傳播。
- 更新參數(shù)。
for epoch in range(2):
running_loss = 0.0
for i,data in enumerate(trainloader,0):
# 輸入數(shù)據(jù)
inputs,labels = data
inputs,labels = Variable(inputs),Variable(labels)
# 梯度清零
optimizer.zero_grad()
# forward + backward
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
# 更新參數(shù)
optimizer.step()
# 打印log信息
running_loss += loss
if i % 2000 == 1999: # 每2000個(gè)batch打印一次訓(xùn)練狀態(tài)
print('[%d,%5d] loss: %.3f' % (epoch+1, i+1, running_loss / 2000))
running_loss = 0.0
print('Finished Trainning')
輸出如下:
[1, 2000] loss: 2.244
[1, 4000] loss: 1.956
[1, 6000] loss: 1.717
[1, 8000] loss: 1.600
[1,10000] loss: 1.535
[1,12000] loss: 1.485
[2, 2000] loss: 1.408
[2, 4000] loss: 1.386
[2, 6000] loss: 1.331
[2, 8000] loss: 1.340
[2,10000] loss: 1.283
[2,12000] loss: 1.272
Finished Trainning
此處僅訓(xùn)練了2個(gè)epoch(遍歷完一遍數(shù)據(jù)集稱為一個(gè)epoch),我們來(lái)看看網(wǎng)絡(luò)有沒(méi)有效果。將測(cè)試圖片輸入網(wǎng)絡(luò),計(jì)算它的label,然后與實(shí)際的label進(jìn)行比較。
dataiter = iter(testloader)
images,labels = dataiter.next() # 一個(gè)batch返回4張圖片
print('實(shí)際的label:',' '.join('%08s' % classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid(images / 2.0 - 0.5)).resize((400,100))
輸出如下:
實(shí)際的label: cat ship ship plane
接著計(jì)算網(wǎng)絡(luò)預(yù)測(cè)的label:
# 計(jì)算圖片在每個(gè)類別上的分?jǐn)?shù)
outputs = net(Variable(images))
# 得分最高的那個(gè)類
_,predicted = t.max(outputs.data,1)
print('預(yù)測(cè)結(jié)果:',' '.join('%5s' % classes[predicted[j]] for j in range(4)))
輸出結(jié)果如下:
預(yù)測(cè)結(jié)果: cat ship ship ship
我們已經(jīng)可以看出效果,準(zhǔn)確率為75%,但這這是一部分圖片,我們?cè)賮?lái)看看在整個(gè)測(cè)試集上的效果。
correct = 0 # 預(yù)測(cè)正確的圖片數(shù)
total = 0 # 總共的圖片數(shù)
for data in testloader:
images,labels = data
outputs = net(Variable(images))
_,predicted = t.max(outputs.data,1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('10000張測(cè)試集中的準(zhǔn)確率為:%d %%' % (100 * correct / total))
輸出如下:
10000張測(cè)試集中的準(zhǔn)確率為:56 %
訓(xùn)練的準(zhǔn)確率遠(yuǎn)比隨機(jī)猜測(cè)(準(zhǔn)確率為10%)好,證明網(wǎng)絡(luò)確實(shí)學(xué)到了東西。
(5)在GPU上訓(xùn)練
就像之前把Tensor從CPU轉(zhuǎn)到GPU一樣,模型也可以類似地從CPU轉(zhuǎn)到GPU。
if t.cuda.is_available():
net.cuda()
images = images.cuda()
labels = labels.cuda()
outputs = net(Variable(images))
loss = criterion(outputs, Variable(labels))
如果發(fā)現(xiàn)在GPU上訓(xùn)練的速度并沒(méi)并在CPU上提速很多,實(shí)際是因?yàn)榫W(wǎng)絡(luò)比較小,CPU沒(méi)有完全發(fā)揮自己的真正實(shí)力。
對(duì)于PyTorch的基礎(chǔ)介紹至此結(jié)束。總結(jié)一下,本節(jié)主要介紹以下內(nèi)容。
(1)Tensor:類似numpy數(shù)組的數(shù)據(jù)結(jié)構(gòu),與numpy接口類似,可方便地相互轉(zhuǎn)換。
(2)autograd/Variable:Variable分裝了Tensor,并提供了自動(dòng)求導(dǎo)功能。
(3)nn:專門為神經(jīng)網(wǎng)絡(luò)設(shè)計(jì)的接口,提供了很多有用的功能(神經(jīng)網(wǎng)絡(luò)層、損失函數(shù)、優(yōu)化器等)。
(4)神經(jīng)網(wǎng)絡(luò)訓(xùn)練:以CIFAR-10分類為例演示了神經(jīng)網(wǎng)絡(luò)的訓(xùn)練流程,包括數(shù)據(jù)加載、網(wǎng)絡(luò)搭建、訓(xùn)練及測(cè)試。
通過(guò)本章的學(xué)習(xí),讀者能夠配置PyTorch+Jupyter+IPython的學(xué)習(xí)環(huán)境。另外,通過(guò)2.2節(jié)關(guān)于PyTorch的概要介紹,相信讀者可以體會(huì)出PyTorch接口簡(jiǎn)單、使用靈活等特點(diǎn)。如果有哪些內(nèi)容讀者沒(méi)有理解,不用著急,這些內(nèi)容會(huì)在后續(xù)章節(jié)深入和詳細(xì)地講解。