深度學(xué)習(xí)框架PyTorch入門與實(shí)踐:第二章 快速入門

本章主要介紹兩個(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
  1. 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)行的程序。常用的快捷鍵如下所示。
image.png
  • 魔術(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ù)命令如下所示。

image.png

“%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)試命令如下所示。
image.png

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,界面如下。

image.png

單擊頁(yè)面右上角的“new”選項(xiàng),選擇相應(yīng)的Notebook類型(Python3/Python2),可新建一個(gè)Notebook,在In[]后面的編輯區(qū)域輸入代碼,按Ctrl+Enter快捷鍵即可運(yùn)行代碼,如下所示。

image.png

遠(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)如下所示。

image.png

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)如下所示。

image.png

這是一個(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
image.png

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
image.png

(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
image.png

接著計(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ì)地講解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容