55PyTorch 入門與實戰(zhàn)--PyTorch 基礎(chǔ)入門

有關(guān)張量(Tensor)運算的練習(xí)

使用 Tensor

PyTorch 是一個開源的深度學(xué)習(xí)框架,由 Facebook 支持開發(fā)。它的前身為 Torch,但因為 Torch 使用的編程語言是 Lua,在國內(nèi)流行度很小。Facebook 為了迎合數(shù)量更多的 Python 用戶的需求,推出了 PyTorch。PyTorch 完全開源意味著你可以輕易獲取它的代碼,并按照自己的需求對它進行修改。比如讓 PyTorch 支持復(fù)數(shù)運算等等。PyTorch 還有另外一個非常出眾的特點是,使用 PyTorch 框架編寫出的神經(jīng)網(wǎng)絡(luò)模型的代碼非常簡潔。實現(xiàn)同樣的功能,使用 PyTorch 框架編寫的代碼往往更清晰明了,這點我們可以從下圖中略見一斑:


image.png

PyTorch 的基本數(shù)據(jù)單元是張量(Tensor),它實際上是一種 N 維數(shù)組。
下面我們列舉了三種張量,可以看到它們的維度階數(shù)是不同的。


image.png

1 階的張量可以看做是一個向量,通過索引可以取到一個“值”。
2 階張量可以看做為一個矩陣,通過索引可以取到一個個的向量。

3 階張量有點抽象,不過我們可以從圖中看出,
3 階張量其實就是在 2 階張量的矩陣中增加了一個深度。

也就是說在 3 階張量中我們可以通過索引取到一個個的矩陣。我們不難想象,4 階張量也就是在 3 階張量上增加了另外一個軸……我們可以使用 Tensor.size() 方法獲得一個張量的“尺寸”。
在這里注意“尺寸”和維度是兩個概念。
就比如對于上圖中的 1 階張量,它的維度為 1,尺寸為 8;
對于上圖中的 2 階張量,它的維度為 2,尺寸為(8,6)。
要使用 PyTorch,首先需要在 Python 中引入 PyTorch 的包。

import torch  #導(dǎo)入torch包

可以通過以下代碼查看當(dāng)前系統(tǒng)中 PyTorch 的版本:

print(torch.__version__)

可以生成隨機數(shù)張量:

y = torch.ones(5, 3) #產(chǎn)生一個5*3的Tensor,元素都是1
y

基本 Tensor 運算

兩個 2 階張量相加的方法實際上就是矩陣加法。
注意,要使兩個張量相加,必須保證兩個張量的尺寸是一致的。

z = x + y #兩個tensor可以直接相加
z

下面的語句展示了兩個 tensor 按照矩陣的方式相乘,注意 x 的尺寸是 53,y 的尺寸也是 53 無法進行矩陣乘法,所以先將 y 進行轉(zhuǎn)置。轉(zhuǎn)置操作可以用 .t() 來完成,也可以用 transpose(0, 1) 來完成。

image.png

q = x.mm(y.t()) #x乘以y的轉(zhuǎn)置
q

所有的Tensor的使用方法請見參考鏈接中的“Tensor支持的所有操作”。

Tensor 與 numpy.ndarray 之間的轉(zhuǎn)換

PyTorch 的 Tensor 可以與 Python 的常用數(shù)據(jù)處理包 Numpy 中的多維數(shù)組進行轉(zhuǎn)換。

import numpy as np #導(dǎo)入numpy包
a = np.ones([5, 3]) #建立一個5*3全是1的二維數(shù)組(矩陣)
b = torch.from_numpy(a) #利用from_numpy將其轉(zhuǎn)換為tensor
b

下面是另外一種轉(zhuǎn)換 Tensor 的方法,類型為 FloatTensor。

# 還可以是LongTensor,整型數(shù)據(jù)類型
c = torch.FloatTensor(a) 
c

還可以從一個 tensor 轉(zhuǎn)化為 numpy 的多維數(shù)組

b.numpy() 

Tensor 和 Numpy 的最大區(qū)別在于 Tensor 可以在 GPU 上進行運算。默認情況下,Tensor 是在 CPU 上進行運算的,如果我們需要一個 Tensor 在 GPU 上的實例,需要運行這個 Tensor 的 .cuda() 方法。在下面的代碼中,首先判斷在本機上是否有 GPU 環(huán)境可用(有 NVIDIA的 GPU,并安裝了驅(qū)動)。如果有 GPU 環(huán)境可用,那么再去獲得張量 x,y 的 GPU 實例。
注意在最后打印 x 和 y 這兩個 GPU 張量的和的時候,我們調(diào)用了 .cpu() 方法,意思是將 GPU 張量轉(zhuǎn)化為 CPU 張量,否則系統(tǒng)會報錯。

if torch.cuda.is_available():  #檢測本機器上有無GPU可用
    x = x.cuda() #返回x的GPU上運算的版本
    y = y.cuda()
    z = x + y
print(z.cpu()) # 打印時注意要把GPU變量轉(zhuǎn)化為CPU變量。

有關(guān)自動微分(Autograd)變量的練習(xí)

動態(tài)運算圖(Dynamic Computation Graph)是 PyTorch 的最主要特性,它可以讓我們的計算模型更靈活、復(fù)雜,并可以讓反向傳播算法隨時進行。而反向傳播算法就是深度神經(jīng)網(wǎng)絡(luò)的核心。下面是一個計算圖的結(jié)構(gòu)以及與它對應(yīng)的 PyTorch 代碼:


image.png

用來構(gòu)建計算圖的數(shù)據(jù)叫做自動微分變量(Variable),它與 Tensor 不同。每個 Variable 包含三個屬性,分別對應(yīng)著數(shù)據(jù)(data),父節(jié)點(creator),以及梯度(grad)。其中“梯度”就是反向傳播算法所要傳播的信息。而父節(jié)點用于將每個節(jié)點連接起來構(gòu)建計算圖(如上圖所示)。


image.png

下面我們編寫代碼實際使用自動微分變量。
#導(dǎo)入自動梯度的運算包,主要用Variable這個類
from torch.autograd import Variable  

#創(chuàng)建一個Variable,包裹了一個2*2張量,將需要計算梯度屬性置為True

x = Variable(torch.ones(2, 2), requires_grad=True)  
x

可以按照 Tensor 的方式進行計算

y = x + 2  #可以按照Tensor的方式進行計算
y.grad_fn  #每個Variable都有一個creator(創(chuàng)造者節(jié)點)

經(jīng)過上面變量 x 和 y 的運算,我們就有了一個簡單的計算圖,它是下面這個樣子的:


image.png

下面我們讓計算圖再復(fù)雜一點,我們再加入變量 z:
注意,.data 可以返回一個 Variable 所包裹的 Tensor

z = torch.mean(y * y)  #也可以進行復(fù)合運算,比如求均值mean
z.data #.data屬性可以返回z所包裹的tensor

現(xiàn)在我們的計算圖是這個樣子的:


image.png

backward 可以實施反向傳播算法,并計算所有計算圖上葉子節(jié)點(沒有子節(jié)點)的導(dǎo)數(shù)(梯度)信息。
注意,由于 z 和 y 都不是葉子節(jié)點,所以都沒有梯度信息

z.backward() #梯度反向傳播
print(z.grad) # 無梯度信息
print(y.grad) # 無梯度信息
print(x.grad)

在下面的例子中,會讓矩陣 x 反復(fù)作用在向量 s 上,系統(tǒng)會自動記錄中間的依賴關(guān)系和長路徑。

s = Variable(torch.FloatTensor([[0.01, 0.02]]), requires_grad = True) #創(chuàng)建一個1*2的Variable(1維向量)
x = Variable(torch.ones(2, 2), requires_grad = True) #創(chuàng)建一個2*2的矩陣型Variable
for i in range(10):
    s = s.mm(x)  #反復(fù)用s乘以x(矩陣乘法),注意s始終是1*2的Variable
z = torch.mean(s) #對s中的各個元素求均值,得到一個1*1的scalar(標(biāo)量,即1*1張量)

然后我們得到了一個復(fù)雜的“深度”計算圖:


image.png
z.backward() #在具有很長的依賴路徑的計算圖上用反向傳播算法計算葉節(jié)點的梯度
print(x.grad)  #x作為葉節(jié)點可以獲得這部分梯度信息
print(s.grad)  #s不是葉節(jié)點,沒有梯度信息

如果大家覺得去理解這個計算圖的葉子結(jié)點很困難,這也沒有關(guān)系。
因為我們研究的主題是深度學(xué)習(xí),PyTorch 框架會自動搭建好計算圖的。

利用PyTorch實現(xiàn)簡單的線性回歸算法

準(zhǔn)備數(shù)據(jù)

下面使用 PyTorch 實現(xiàn)一個簡單的線性回歸算法。線性回歸是機器學(xué)習(xí)中最基礎(chǔ)和簡單的算法,你可以將它視為深度學(xué)習(xí)界的 Hello World。如果不了解線性回歸,你可以簡單的理解為:訓(xùn)練一條直線,讓這條直線擬合一些數(shù)據(jù)點的趨勢。大部分的同學(xué)在高中或者大學(xué)肯定接觸過線性回歸,所以我在這里也不做過多的介紹了。首先生成一些樣本點作為原始數(shù)據(jù)。這些原始“數(shù)據(jù)點”就是直線需要擬合的對象。

# linspace可以生成0-100之間的均勻的100個數(shù)字
x = Variable(torch.linspace(0, 100).type(torch.FloatTensor)) 

# 隨機生成100個滿足標(biāo)準(zhǔn)正態(tài)分布的隨機數(shù),均值為0,方差為1.
# 將這個數(shù)字乘以10,標(biāo)準(zhǔn)方差變?yōu)?0
rand = Variable(torch.randn(100)) * 10 

# 將x和rand相加,得到偽造的標(biāo)簽數(shù)據(jù)y。
# 所以(x,y)應(yīng)能近似地落在y=x這條直線上
y = x + rand 

將生成的原始數(shù)據(jù)點畫在圖上,用視覺觀察下數(shù)據(jù)點的“趨勢”。

import matplotlib.pyplot as plt #導(dǎo)入畫圖的程序包
%matplotlib inline

plt.figure(figsize=(10,8)) #設(shè)定繪制窗口大小為10*8 inch
# 繪制數(shù)據(jù),考慮到x和y都是Variable,
# 需要用data獲取它們包裹的Tensor,并專成numpy
plt.plot(x.data.numpy(), y.data.numpy(), 'o') 
plt.xlabel('X') #添加X軸的標(biāo)注
plt.ylabel('Y') #添加Y軸的標(biāo)注
plt.show() #將圖形畫在下面

所有的數(shù)據(jù)點以視覺可見的增勢增長。下面我們構(gòu)造模型,來擬合這些數(shù)據(jù)點。

構(gòu)造模型 計算損失函數(shù)

我們要使用一條直線去擬合若干個點的走勢,那在數(shù)學(xué)上怎么表示這條直線哪?這個大家在高中應(yīng)該都學(xué)過,可以用 ax+b 來表示一條直線。因為這條直線是由參數(shù) a 和 b 控制的,所以模型就是要“學(xué)習(xí)”出這兩個參數(shù)。那么下面首先建立變量,隨機初始化用于線性擬合的參數(shù) a 和 b。

#創(chuàng)建a變量,并隨機賦值初始化
a = Variable(torch.rand(1), requires_grad = True) 
#創(chuàng)建b變量,并隨機賦值初始化
b = Variable(torch.rand(1), requires_grad = True) 
print('Initial parameters:', [a, b])

在當(dāng)前的模型中,這兩個參數(shù)的初始值無關(guān)緊要,因為下面會通過 1000 次的訓(xùn)練,來反復(fù)修正這兩個參數(shù)。在下面的代碼中,需要注意 expand_as 和 mul 的使用。首先,a 的維度為 1,x 是維度為 1001 的 Tensor,這兩者不能直接相乘,因為維度不同。所以,先要將 a 升維成 11 的 Tensor。這就好比將原本在直線上的點被升維到了二維平面上,同時直線仍然在二維平面中。

image.png

learning_rate = 0.0001 #設(shè)置學(xué)習(xí)率
for i in range(1000):
    ### 下面這三行代碼非常重要,這部分代碼,清空存儲在變量a,b中的梯度信息,
    ### 以免在backward的過程中會反復(fù)不停地累加
    #如果a和b的梯度都不是空
    if (a.grad is not None) and (b.grad is not None):  
        a.grad.data.zero_() #清空a的數(shù)值
        b.grad.data.zero_() #清空b的數(shù)值
    #計算在當(dāng)前a、b條件下的模型預(yù)測數(shù)值
    predictions = a.expand_as(x) * x + b.expand_as(x)  
    #通過與標(biāo)簽數(shù)據(jù)y比較,計算誤差
    loss = torch.mean((predictions - y) ** 2) 
    print('loss:', loss.data.numpy())
    loss.backward() #對損失函數(shù)進行梯度反傳
    #利用上一步計算中得到的a的梯度信息更新a中的data數(shù)值
    a.data.add_(- learning_rate * a.grad.data)
    #利用上一步計算中得到的b的梯度信息更新b中的data數(shù)值
    b.data.add_(- learning_rate * b.grad.data) 

從打印出的損失中我們可以觀察到損失一直在下降。我們現(xiàn)在可以把直線繪制出來,看看這條直線是什么樣子。

x_data = x.data.numpy() # 獲得x包裹的數(shù)據(jù)
plt.figure(figsize = (10, 7)) #設(shè)定繪圖窗口大小
xplot, = plt.plot(x_data, y.data.numpy(), 'o') # 繪制原始數(shù)據(jù)
yplot, = plt.plot(x_data, a.data.numpy() * x_data + b.data.numpy())  #繪制擬合數(shù)據(jù)
plt.xlabel('X') #更改坐標(biāo)軸標(biāo)注
plt.ylabel('Y') #更改坐標(biāo)軸標(biāo)注
str1 = str(a.data.numpy()[0]) + 'x +' + str(b.data.numpy()[0]) #圖例信息
plt.legend([xplot, yplot],['Data', str1]) #繪制圖例
plt.show()

可以觀察到直線已經(jīng)擬合了點的走勢。你可以修改訓(xùn)練次數(shù),把訓(xùn)練次數(shù)改的很小,或者增加 10 倍,看看線性擬合會有什么變化。

測試模型

雖然模型很簡單,但我們?nèi)砸M行測試的流程。因為“準(zhǔn)備數(shù)據(jù)”、“模型設(shè)計”、“訓(xùn)練”、“測試”是完成深度學(xué)習(xí)任務(wù)的基本套路。

x_test = Variable(torch.FloatTensor([1, 2, 10, 100, 1000])) #隨便選擇一些點1,2,……,1000
predictions = a.expand_as(x_test) * x_test + b.expand_as(x_test) #計算模型的預(yù)測結(jié)果
predictions #輸出
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容