Pytorch 神經(jīng)網(wǎng)絡(luò)基礎(chǔ)
1.1 Pytorch & Numpy
1.1.1 用Torch還是Numpy
Torch 自稱為神經(jīng)網(wǎng)絡(luò)界的 Numpy, 因?yàn)樗軐?torch 產(chǎn)生的 tensor 放在 GPU 中加速運(yùn)算 (前提是你有合適的 GPU), 就像 Numpy 會(huì)把 array 放在 CPU 中加速運(yùn)算. 所以神經(jīng)網(wǎng)絡(luò)的話, 當(dāng)然是用 Torch 的 tensor 形式數(shù)據(jù)最好咯. 就像 Tensorflow 當(dāng)中的 tensor 一樣.
當(dāng)然, 我們對(duì) Numpy 還是愛不釋手的, 因?yàn)槲覀兲?xí)慣 numpy 的形式了. 不過 torch 看出來我們的喜愛, 他把 torch 做的和 numpy 能很好的兼容. 比如這樣就能自由地轉(zhuǎn)換 numpy array 和 torch tensor 了:
numpy array轉(zhuǎn)換成 torch tensor:torch.from_numpy(np_data)
torch tensor轉(zhuǎn)換成 numpy array:torch_data.numpy()
import torch
import numpy as np
np_data = np.arange(6).reshape((2, 3))
torch_data = torch.from_numpy(np_data)
tensor2array = torch_data.numpy()
print(
'\nnumpy array:', np_data, # [[0 1 2], [3 4 5]]
'\ntorch tensor:', torch_data, # 0 1 2 \n 3 4 5 [torch.LongTensor of size 2x3]
'\ntensor to array:', tensor2array, # [[0 1 2], [3 4 5]]
)
1.1.2 Torch 中的數(shù)學(xué)運(yùn)算
其實(shí) torch 中 tensor 的運(yùn)算和 numpy array 的如出一轍, 我們就以對(duì)比的形式來看. 如果想了解 torch 中其它更多有用的運(yùn)算符, 可以參考Pytorch中文手冊(cè).
- abs 絕對(duì)值計(jì)算
data = [-1, -2, 1, 2]
tensor = torch.FloatTensor(data) # 轉(zhuǎn)換成32位浮點(diǎn) tensor
print(
'\nabs',
'\nnumpy: ', np.abs(data), # [1 2 1 2]
'\ntorch: ', torch.abs(tensor) # [1 2 1 2]
)
- sin 三角函數(shù)
print(
'\nsin',
'\nnumpy: ', np.sin(data), # [-0.84147098 -0.90929743 0.84147098 0.90929743]
'\ntorch: ', torch.sin(tensor) # [-0.8415 -0.9093 0.8415 0.9093]
)
- mean 均值
print(
'\nmean',
'\nnumpy: ', np.mean(data), # 0.0
'\ntorch: ', torch.mean(tensor) # 0.0
)
除了簡(jiǎn)單的計(jì)算, 矩陣運(yùn)算才是神經(jīng)網(wǎng)絡(luò)中最重要的部分. 所以我們展示下矩陣的乘法. 注意一下包含了一個(gè) numpy 中可行, 但是 torch 中不可行的方式.
- matrix multiplication 矩陣點(diǎn)乘
# matrix multiplication 矩陣點(diǎn)乘
data = [[1,2], [3,4]]
tensor = torch.FloatTensor(data) # 轉(zhuǎn)換成32位浮點(diǎn) tensor
# correct method
print(
'\nmatrix multiplication (matmul)',
'\nnumpy: ', np.matmul(data, data), # [[7, 10], [15, 22]]
'\ntorch: ', torch.mm(tensor, tensor) # [[7, 10], [15, 22]]
)
# !!!! 下面是錯(cuò)誤的方法 !!!!
data = np.array(data)
print(
'\nmatrix multiplication (dot)',
'\nnumpy: ', data.dot(data), # [[7, 10], [15, 22]] 在numpy 中可行
'\ntorch: ', tensor.dot(tensor) # torch.dot只能處理一維數(shù)組
)
1.2 變量 Variable
1.2.1 什么是Variable
在 Torch 中的 Variable 就是一個(gè)存放會(huì)變化的值的地理位置. 里面的值會(huì)不停的變化. 就像一個(gè)裝雞蛋的籃子, 雞蛋數(shù)會(huì)不停變動(dòng). 那誰是里面的雞蛋呢, 自然就是 Torch 的 Tensor 咯. 如果用一個(gè) Variable 進(jìn)行計(jì)算, 那返回的也是一個(gè)同類型的 Variable.
我們定義一個(gè) Variable:
import torch
from torch.autograd import Variable # torch 中 Variable 模塊
# 先生雞蛋
tensor = torch.FloatTensor([[1,2],[3,4]])
# 把雞蛋放到籃子里, requires_grad是參不參與誤差反向傳播, 要不要計(jì)算梯度
variable = Variable(tensor, requires_grad=True)
print(tensor)
"""
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable)
"""
Variable containing:
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
1.2.2 Variable 計(jì)算 梯度
我們?cè)賹?duì)比一下 tensor 的計(jì)算和 variable 的計(jì)算.
t_out = torch.mean(tensor*tensor) # x^2
v_out = torch.mean(variable*variable) # x^2
print(t_out)
print(v_out) # 7.5
到目前為止, 我們看不出什么不同, 但是時(shí)刻記住, Variable 計(jì)算時(shí), 它在背景幕布后面一步步默默地搭建著一個(gè)龐大的系統(tǒng), 叫做計(jì)算圖(computational graph). 這個(gè)圖是用來干嘛的? 原來是將所有的計(jì)算步驟 (節(jié)點(diǎn)) 都連接起來, 最后進(jìn)行誤差反向傳遞的時(shí)候, 一次性將所有 variable 里面的修改幅度 (梯度) 都計(jì)算出來, 而 tensor 就沒有這個(gè)能力啦.
v_out = torch.mean(variable*variable)
就是在計(jì)算圖中添加的一個(gè)計(jì)算步驟, 計(jì)算誤差反向傳遞的時(shí)候有他一份功勞, 我們就來舉個(gè)例子:
v_out.backward() # 模擬 v_out 的誤差反向傳遞
# 下面兩步看不懂沒關(guān)系, 只要知道 Variable 是計(jì)算圖的一部分, 可以用來傳遞誤差就好.
# v_out = 1/4 * sum(variable*variable) 這是計(jì)算圖中的 v_out 計(jì)算步驟
# 針對(duì)于 v_out 的梯度就是, d(v_out)/d(variable) = 1/4*2*variable = variable/2
print(variable.grad) # 初始 Variable 的梯度
'''
0.5000 1.0000
1.5000 2.0000
'''
1.2.3 獲取 Variable 里面的數(shù)據(jù)
直接print(variable)
只會(huì)輸出 Variable 形式的數(shù)據(jù), 在很多時(shí)候是用不了的(比如想要用 plt 畫圖), 所以我們要轉(zhuǎn)換一下, 將它變成 tensor 形式.
print(variable) # Variable 形式
"""
Variable containing:
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable.data) # tensor 形式
"""
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable.data.numpy()) # numpy 形式
"""
[[ 1. 2.]
[ 3. 4.]]
"""
1.3 Torch中的激勵(lì)函數(shù)
Torch 中的激勵(lì)函數(shù)有很多, 不過我們平時(shí)要用到的就這幾個(gè). relu, sigmoid, tanh, softplus. 那我們就看看他們各自長(zhǎng)什么樣啦.
import torch
import torch.nn.functional as F # 激勵(lì)函數(shù)都在這
from torch.autograd import Variable
# 做一些假數(shù)據(jù)來觀看圖像
x = torch.linspace(-5, 5, 200) # x data (tensor), shape=(100, 1)
x = Variable(x)
接著就是做生成不同的激勵(lì)函數(shù)數(shù)據(jù):
x_np = x.data.numpy() # 換成 numpy array, 出圖時(shí)用
# 幾種常用的 激勵(lì)函數(shù)
y_relu = F.relu(x).data.numpy()
y_sigmoid = F.sigmoid(x).data.numpy()
y_tanh = F.tanh(x).data.numpy()
y_softplus = F.softplus(x).data.numpy()
# y_softmax = F.softmax(x) softmax 比較特殊, 不能直接顯示, 不過他是關(guān)于概率的, 用于分類
接著我們開始畫圖, 畫圖的代碼也在下面: import matplotlib.pyplot as plt
plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')
plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')
plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')
plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')
plt.show()
建造第一個(gè)神經(jīng)網(wǎng)絡(luò)
2.1 關(guān)系擬合
本節(jié)會(huì)來見證神經(jīng)網(wǎng)絡(luò)是如何通過簡(jiǎn)單的形式將一群數(shù)據(jù)用一條線條來表示. 或者說, 是如何在數(shù)據(jù)當(dāng)中找到他們的關(guān)系, 然后用神經(jīng)網(wǎng)絡(luò)模型來建立一個(gè)可以代表他們關(guān)系的線條.
2.1.1 建立數(shù)據(jù)集
我們創(chuàng)建一些假數(shù)據(jù)來模擬真實(shí)的情況. 比如一個(gè)一元二次函數(shù): y = a * x^2 + b, 我們給 y 數(shù)據(jù)加上一點(diǎn)噪聲來更加真實(shí)的展示它.
import torch
from torch.autograd import Variable
import matplotlib.pyplot as plt
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
# 用 Variable 來修飾這些數(shù)據(jù) tensor
x, y = torch.autograd.Variable(x), Variable(y)
# 畫圖
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()
2.1.2 建立神經(jīng)網(wǎng)絡(luò)
建立一個(gè)神經(jīng)網(wǎng)絡(luò)我們可以直接運(yùn)用 torch 中的體系. 先定義所有的層屬性(__init__()
), 然后再一層層搭建(forward(x)
)層于層的關(guān)系鏈接. 建立關(guān)系的時(shí)候, 我們會(huì)用到激勵(lì)函數(shù).
import torch
import torch.nn.functional as F # 激勵(lì)函數(shù)都在這
class Net(torch.nn.Module): # 繼承 torch 的 Module
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 繼承 __init__ 功能
# 定義每層用什么樣的形式
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隱藏層線性輸出
self.predict = torch.nn.Linear(n_hidden, n_output) # 輸出層線性輸出
def forward(self, x): # 這同時(shí)也是 Module 中的 forward 功能
# 正向傳播輸入值, 神經(jīng)網(wǎng)絡(luò)分析出輸出值
x = F.relu(self.hidden(x)) # 激勵(lì)函數(shù)(隱藏層的線性值)
x = self.predict(x) # 輸出值
return x
net = Net(n_feature=1, n_hidden=10, n_output=1)
print(net) # net 的結(jié)構(gòu)
"""
Net (
(hidden): Linear (1 -> 10)
(predict): Linear (10 -> 1)
)
2.1.3 訓(xùn)練網(wǎng)絡(luò)
訓(xùn)練的步驟很簡(jiǎn)單, 如下:
# optimizer 是訓(xùn)練的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.5) # 傳入 net 的所有參數(shù), 學(xué)習(xí)率
loss_func = torch.nn.MSELoss() # 預(yù)測(cè)值和真實(shí)值的誤差計(jì)算公式 (均方差)
for t in range(100):
prediction = net(x) # 喂給 net 訓(xùn)練數(shù)據(jù) x, 輸出預(yù)測(cè)值
loss = loss_func(prediction, y) # 計(jì)算兩者的誤差
optimizer.zero_grad() # 清空上一步的殘余更新參數(shù)值
loss.backward() # 誤差反向傳播, 計(jì)算參數(shù)更新值
optimizer.step() # 將參數(shù)更新值施加到 net 的 parameters 上
2.1.4 可視化訓(xùn)練過程
為了可視化整個(gè)訓(xùn)練的過程, 更好的理解是如何訓(xùn)練, 我們?nèi)缦虏僮?
import matplotlib.pyplot as plt
plt.ion() # 畫圖
plt.show()
for t in range(100):
...
loss.backward()
optimizer.step()
# 接著上面來
if t % 5 == 0:
# plot and show learning process
plt.cla()
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % loss.data[0], fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
可視化效果如下:2.2 區(qū)分類型(分類)
這次我們也是用最簡(jiǎn)單的途徑來看看神經(jīng)網(wǎng)絡(luò)是怎么進(jìn)行事物的分類.
2.2.1 建立數(shù)據(jù)集
我們創(chuàng)建一些假數(shù)據(jù)來模擬真實(shí)的情況. 比如兩個(gè)二項(xiàng)分布的數(shù)據(jù), 不過他們的均值都不一樣.
import torch
from torch.autograd import Variable
import matplotlib.pyplot as plt
# 假數(shù)據(jù)
n_data = torch.ones(100, 2) # 數(shù)據(jù)的基本形態(tài)
x0 = torch.normal(2*n_data, 1) # 類型0 x data (tensor), shape=(100, 2)
y0 = torch.zeros(100) # 類型0 y data (tensor), shape=(100, 1)
x1 = torch.normal(-2*n_data, 1) # 類型1 x data (tensor), shape=(100, 1)
y1 = torch.ones(100) # 類型1 y data (tensor), shape=(100, 1)
# 注意 x, y 數(shù)據(jù)的數(shù)據(jù)形式是一定要像下面一樣 (torch.cat 是在合并數(shù)據(jù))
x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # FloatTensor = 32-bit floating
y = torch.cat((y0, y1), ).type(torch.LongTensor) # LongTensor = 64-bit integer
# torch 只能在 Variable 上訓(xùn)練, 所以把它們變成 Variable
x, y = Variable(x), Variable(y)
# 畫圖
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()
2.2.2 建立神經(jīng)網(wǎng)絡(luò)
建立一個(gè)神經(jīng)網(wǎng)絡(luò)我們可以直接運(yùn)用 torch 中的體系. 先定義所有的層屬性(init()), 然后再一層層搭建(forward(x))層于層的關(guān)系鏈接. 這個(gè)和我們?cè)谇懊?regression 的時(shí)候的神經(jīng)網(wǎng)絡(luò)基本沒差. 建立關(guān)系的時(shí)候, 我們會(huì)用到激勵(lì)函數(shù)霎肯。
import torch
import torch.nn.functional as F # 激勵(lì)函數(shù)都在這
class Net(torch.nn.Module): # 繼承 torch 的 Module
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 繼承 __init__ 功能
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隱藏層線性輸出
self.out = torch.nn.Linear(n_hidden, n_output) # 輸出層線性輸出
def forward(self, x):
# 正向傳播輸入值, 神經(jīng)網(wǎng)絡(luò)分析出輸出值
x = F.relu(self.hidden(x)) # 激勵(lì)函數(shù)(隱藏層的線性值)
x = self.out(x) # 輸出值, 但是這個(gè)不是預(yù)測(cè)值, 預(yù)測(cè)值還需要再另外計(jì)算
return x
net = Net(n_feature=2, n_hidden=10, n_output=2) # 幾個(gè)類別就幾個(gè) output
print(net) # net 的結(jié)構(gòu)
"""
Net (
(hidden): Linear (2 -> 10)
(out): Linear (10 -> 2)
)
"""
2.2.3 訓(xùn)練網(wǎng)絡(luò)
訓(xùn)練的步驟很簡(jiǎn)單, 如下:
# optimizer 是訓(xùn)練的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.02) # 傳入 net 的所有參數(shù), 學(xué)習(xí)率
# 算誤差的時(shí)候, 注意真實(shí)值!不是! one-hot 形式的, 而是1D Tensor, (batch,)
# 但是預(yù)測(cè)值是2D tensor (batch, n_classes)
loss_func = torch.nn.CrossEntropyLoss()
for t in range(100):
out = net(x) # 喂給 net 訓(xùn)練數(shù)據(jù) x, 輸出分析值
loss = loss_func(out, y) # 計(jì)算兩者的誤差
optimizer.zero_grad() # 清空上一步的殘余更新參數(shù)值
loss.backward() # 誤差反向傳播, 計(jì)算參數(shù)更新值
optimizer.step() # 將參數(shù)更新值施加到 net 的 parameters 上
2.2.4 可視化訓(xùn)練過程
為了可視化整個(gè)訓(xùn)練的過程, 更好的理解是如何訓(xùn)練, 我們?nèi)缦虏僮?
import matplotlib.pyplot as plt
plt.ion() # 畫圖
plt.show()
for t in range(100):
...
loss.backward()
optimizer.step()
# 接著上面來
if t % 2 == 0:
plt.cla()
# 過了一道 softmax 的激勵(lì)函數(shù)后的最大概率才是預(yù)測(cè)值
prediction = torch.max(F.softmax(out, dim=1), 1)[1]
pred_y = prediction.data.numpy().squeeze()
target_y = y.data.numpy()
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
accuracy = sum(pred_y == target_y)/200 # 預(yù)測(cè)中有多少和真實(shí)值一樣
plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff() # 停止畫圖
plt.show()
可視化效果如下:2.3 快速搭建法
Torch 中提供了很多方便的途徑, 同樣是神經(jīng)網(wǎng)絡(luò), 能快則快, 我們看看如何用更簡(jiǎn)單的方式搭建同樣的回歸神經(jīng)網(wǎng)絡(luò)。
2.3.1 快速搭建
我們先看看之前寫神經(jīng)網(wǎng)絡(luò)時(shí)用到的步驟. 我們用 net1
代表這種方式搭建的神經(jīng)網(wǎng)絡(luò).
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.predict = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.predict(x)
return x
net1 = Net(1, 10, 1) # 這是我們用這種方式搭建的 net1
我們用 class 繼承了一個(gè) torch 中的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu), 然后對(duì)其進(jìn)行了修改, 不過還有更快的一招, 用一句話就概括了上面所有的內(nèi)容!
net2 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
我們?cè)賹?duì)比一下兩者的結(jié)構(gòu):
print(net1)
"""
Net(
(hidden): Linear(in_features=1, out_features=10, bias=True)
(predict): Linear(in_features=10, out_features=1, bias=True)
)
"""
print(net2)
"""
Sequential(
(0): Linear(in_features=1, out_features=10, bias=True)
(1): ReLU()
(2): Linear(in_features=10, out_features=1, bias=True)
)
"""
我們會(huì)發(fā)現(xiàn) net2
多顯示了一些內(nèi)容, 這是為什么呢? 原來他把激勵(lì)函數(shù)也一同納入進(jìn)去了, 但是 net1
中, 激勵(lì)函數(shù)實(shí)際上是在 forward() 功能中才被調(diào)用的. 這也就說明了, 相比 net2
, net1
的好處就是, 你可以根據(jù)你的個(gè)人需要更加個(gè)性化你自己的前向傳播過程, 比如(RNN). 不過如果你不需要七七八八的過程, 相信 net2
這種形式更適合你.
2.4 保存提取
訓(xùn)練好了一個(gè)模型, 我們當(dāng)然想要保存它, 留到下次要用的時(shí)候直接提取直接用, 這就是這節(jié)的內(nèi)容啦. 我們用回歸的神經(jīng)網(wǎng)絡(luò)舉例實(shí)現(xiàn)保存提取.
2.4.1 保存
我們快速地建造數(shù)據(jù), 搭建網(wǎng)絡(luò):
torch.manual_seed(1) # reproducible
# 假數(shù)據(jù)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
x, y = Variable(x, requires_grad=False), Variable(y, requires_grad=False)
def save():
# 建網(wǎng)絡(luò)
net1 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
optimizer = torch.optim.SGD(net1.parameters(), lr=0.5)
loss_func = torch.nn.MSELoss()
# 訓(xùn)練
for t in range(100):
prediction = net1(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
接下來我們有兩種途徑來保存:
torch.save(net1, 'net.pkl') # 保存整個(gè)網(wǎng)絡(luò)
torch.save(net1.state_dict(), 'net_params.pkl') # 只保存網(wǎng)絡(luò)中的參數(shù) (速度快, 占內(nèi)存少)
2.4.2 提取網(wǎng)絡(luò)
這種方式將會(huì)提取整個(gè)神經(jīng)網(wǎng)絡(luò), 網(wǎng)絡(luò)大的時(shí)候可能會(huì)比較慢.
def restore_net():
# restore entire net1 to net2
net2 = torch.load('net.pkl')
prediction = net2(x)
2.4.3 只提取網(wǎng)絡(luò)參數(shù)
這種方式將會(huì)提取所有的參數(shù), 然后再放到你的新建網(wǎng)絡(luò)中.
def restore_params():
# 新建 net3
net3 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
# 將保存的參數(shù)復(fù)制到 net3
net3.load_state_dict(torch.load('net_params.pkl'))
prediction = net3(x)
2.4.4 顯示結(jié)果
調(diào)用上面建立的幾個(gè)功能, 然后出圖.
# 保存 net1 (1. 整個(gè)網(wǎng)絡(luò), 2. 只有參數(shù))
save()
# 提取整個(gè)網(wǎng)絡(luò)
restore_net()
# 提取網(wǎng)絡(luò)參數(shù), 復(fù)制到新網(wǎng)絡(luò)
restore_params()
這樣我們就能看出三個(gè)網(wǎng)絡(luò)完全一模一樣啦榛斯!
2.5 批訓(xùn)練
Torch 中提供了一種幫你整理你的數(shù)據(jù)結(jié)構(gòu)的好東西, 叫做 DataLoader, 我們能用它來包裝自己的數(shù)據(jù), 進(jìn)行批訓(xùn)練.
2.5.1 DataLoader
DataLoader 是 torch 給你用來包裝你的數(shù)據(jù)的工具. 所以你要講自己的 (numpy array 或其他) 數(shù)據(jù)形式裝換成 Tensor, 然后再放進(jìn)這個(gè)包裝器中. 使用 DataLoader 有什么好處呢? 就是他們幫你有效地迭代數(shù)據(jù), 舉例:
import torch
import torch.utils.data as Data
torch.manual_seed(1) # reproducible
BATCH_SIZE = 5 # 批訓(xùn)練的數(shù)據(jù)個(gè)數(shù)
x = torch.linspace(1, 10, 10) # x data (torch tensor)
y = torch.linspace(10, 1, 10) # y data (torch tensor)
# 先轉(zhuǎn)換成 torch 能識(shí)別的 Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)
# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # mini batch size
shuffle=True, # 要不要打亂數(shù)據(jù) (打亂比較好)
num_workers=2, # 多線程來讀數(shù)據(jù)
)
for epoch in range(3): # 訓(xùn)練所有!整套!數(shù)據(jù) 3 次
for step, (batch_x, batch_y) in enumerate(loader): # 每一步 loader 釋放一小批數(shù)據(jù)用來學(xué)習(xí)
# 假設(shè)這里就是你訓(xùn)練的地方...
# 打出來一些數(shù)據(jù)
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())
"""
Epoch: 0 | Step: 0 | batch x: [ 6. 7. 2. 3. 1.] | batch y: [ 5. 4. 9. 8. 10.]
Epoch: 0 | Step: 1 | batch x: [ 9. 10. 4. 8. 5.] | batch y: [ 2. 1. 7. 3. 6.]
Epoch: 1 | Step: 0 | batch x: [ 3. 4. 2. 9. 10.] | batch y: [ 8. 7. 9. 2. 1.]
Epoch: 1 | Step: 1 | batch x: [ 1. 7. 8. 5. 6.] | batch y: [ 10. 4. 3. 6. 5.]
Epoch: 2 | Step: 0 | batch x: [ 3. 9. 2. 6. 7.] | batch y: [ 8. 2. 9. 5. 4.]
Epoch: 2 | Step: 1 | batch x: [ 10. 4. 8. 1. 5.] | batch y: [ 1. 7. 3. 10. 6.]
"""
可以看出, 每步都導(dǎo)出了5個(gè)數(shù)據(jù)進(jìn)行學(xué)習(xí). 然后每個(gè) epoch 的導(dǎo)出數(shù)據(jù)都是先打亂了以后再導(dǎo)出.
真正方便的還不是這點(diǎn). 如果我們改變一下 BATCH_SIZE = 8
, 這樣我們就知道, step=0
會(huì)導(dǎo)出8個(gè)數(shù)據(jù), 但是, step=1 時(shí)數(shù)據(jù)庫中的數(shù)據(jù)不夠 8個(gè), 這時(shí)怎么辦呢:
BATCH_SIZE = 8 # 批訓(xùn)練的數(shù)據(jù)個(gè)數(shù)
...
for ...:
for ...:
...
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())
"""
Epoch: 0 | Step: 0 | batch x: [ 6. 7. 2. 3. 1. 9. 10. 4.] | batch y: [ 5. 4. 9. 8. 10. 2. 1. 7.]
Epoch: 0 | Step: 1 | batch x: [ 8. 5.] | batch y: [ 3. 6.]
Epoch: 1 | Step: 0 | batch x: [ 3. 4. 2. 9. 10. 1. 7. 8.] | batch y: [ 8. 7. 9. 2. 1. 10. 4. 3.]
Epoch: 1 | Step: 1 | batch x: [ 5. 6.] | batch y: [ 6. 5.]
Epoch: 2 | Step: 0 | batch x: [ 3. 9. 2. 6. 7. 10. 4. 8.] | batch y: [ 8. 2. 9. 5. 4. 1. 7. 3.]
Epoch: 2 | Step: 1 | batch x: [ 1. 5.] | batch y: [ 10. 6.]
"""
這時(shí), 在 step=1 就只給你返回這個(gè) epoch 中剩下的數(shù)據(jù)就好了.
2.6 加速神經(jīng)網(wǎng)絡(luò)訓(xùn)練
加速你的神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程包括以下幾種模式:
- Stochastic Gradient Descent (SGD)
- Momentum
- AdaGrad
- RMSProp
- Adam
具體算法講解待后續(xù)補(bǔ)充观游,也可網(wǎng)上搜索相關(guān)資料。
2.7 Optimizer 優(yōu)化器
這節(jié)內(nèi)容主要是用 Torch 實(shí)踐上一小節(jié)中提到的幾種優(yōu)化器驮俗,下圖就是這節(jié)內(nèi)容對(duì)比各種優(yōu)化器的效果:2.7.1 偽數(shù)據(jù)
為了對(duì)比各種優(yōu)化器的效果, 我們需要有一些數(shù)據(jù), 今天我們還是自己編一些偽數(shù)據(jù), 這批數(shù)據(jù)是這樣的:2.7.2 每個(gè)優(yōu)化器優(yōu)化一個(gè)神經(jīng)網(wǎng)絡(luò)
為了對(duì)比每一種優(yōu)化器, 我們給他們各自創(chuàng)建一個(gè)神經(jīng)網(wǎng)絡(luò), 但這個(gè)神經(jīng)網(wǎng)絡(luò)都來自同一個(gè) Net 形式.
# 默認(rèn)的 network 形式
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(1, 20) # hidden layer
self.predict = torch.nn.Linear(20, 1) # output layer
def forward(self, x):
x = F.relu(self.hidden(x)) # activation function for hidden layer
x = self.predict(x) # linear output
return x
# 為每個(gè)優(yōu)化器創(chuàng)建一個(gè) net
net_SGD = Net()
net_Momentum = Net()
net_RMSprop = Net()
net_Adam = Net()
nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]
2.7.3 優(yōu)化器 Optimizer
接下來在創(chuàng)建不同的優(yōu)化器, 用來訓(xùn)練不同的網(wǎng)絡(luò). 并創(chuàng)建一個(gè) loss_func
用來計(jì)算誤差. 我們用幾種常見的優(yōu)化器, SGD, Momentum, RMSprop, Adam.
# different optimizers
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]
loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []] # 記錄 training 時(shí)不同神經(jīng)網(wǎng)絡(luò)的 loss
2.7.3 優(yōu)化器 Optimizer
接下來在創(chuàng)建不同的優(yōu)化器, 用來訓(xùn)練不同的網(wǎng)絡(luò). 并創(chuàng)建一個(gè) loss_func
用來計(jì)算誤差. 我們用幾種常見的優(yōu)化器, SGD, Momentum, RMSprop, Adam.
# different optimizers
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]
loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []] # 記錄 training 時(shí)不同神經(jīng)網(wǎng)絡(luò)的 loss
2.7.4 訓(xùn)練懂缕、出圖
接下來訓(xùn)練和 loss 畫圖.
# 訓(xùn)練
for epoch in range(EPOCH):
print('Epoch: ', epoch)
for step, (batch_x, batch_y) in enumerate(loader):
b_x = Variable(batch_x) # 務(wù)必要用 Variable 包一下
b_y = Variable(batch_y)
# 對(duì)每個(gè)優(yōu)化器, 優(yōu)化屬于他的神經(jīng)網(wǎng)絡(luò)
for net, opt, l_his in zip(nets, optimizers, losses_his):
output = net(b_x) # get output for every net
loss = loss_func(output, b_y) # compute loss for every net
opt.zero_grad() # clear gradients for next train
loss.backward() # backpropagation, compute gradients
opt.step() # apply gradients
l_his.append(loss.data[0]) # loss recoder
# 出圖
labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(losses_his):
plt.plot(l_his, label=labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0, 0.2))
plt.show()
SGD 是最普通的優(yōu)化器, 也可以說沒有加速效果, 而 Momentum 是 SGD 的改良版, 它加入了動(dòng)量原則. 后面的 RMSprop 又是 Momentum 的升級(jí)版. 而 Adam 又是 RMSprop 的升級(jí)版. 不過從這個(gè)結(jié)果中我們看到, Adam 的效果似乎比 RMSprop 要差一點(diǎn). 所以說并不是越先進(jìn)的優(yōu)化器, 結(jié)果越佳. 我們?cè)谧约旱脑囼?yàn)中可以嘗試不同的優(yōu)化器, 找到那個(gè)最適合你數(shù)據(jù)/網(wǎng)絡(luò)的優(yōu)化器.
高級(jí)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)
3.1 卷積神經(jīng)網(wǎng)絡(luò) CNN
這一節(jié),我們一步一步做一個(gè)分析手寫數(shù)字的 CNN王凑。下面是一個(gè) CNN 最后一層的學(xué)習(xí)過程, 我們先可視化看看:[站外圖片上傳中...(image-f40379-1521963744043)]
3.1.1 MNIST手寫數(shù)據(jù)
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as Data
import torchvision # 數(shù)據(jù)庫模塊
import matplotlib.pyplot as plt
torch.manual_seed(1) # reproducible
# Hyper Parameters
EPOCH = 1 # 訓(xùn)練整批數(shù)據(jù)多少次, 為了節(jié)約時(shí)間, 我們只訓(xùn)練一次
BATCH_SIZE = 50
LR = 0.001 # 學(xué)習(xí)率
DOWNLOAD_MNIST = True # 如果你已經(jīng)下載好了mnist數(shù)據(jù)就寫上 Fasle
# Mnist 手寫數(shù)字
train_data = torchvision.datasets.MNIST(
root='./mnist/', # 保存或者提取位置
train=True, # this is training data
transform=torchvision.transforms.ToTensor(), # 轉(zhuǎn)換 PIL.Image or numpy.ndarray 成
# torch.FloatTensor (C x H x W), 訓(xùn)練的時(shí)候 normalize 成 [0.0, 1.0] 區(qū)間
download=DOWNLOAD_MNIST, # 沒下載就下載, 下載了就不用再下了
)
# plot one example
print(train_data.train_data.size()) # (60000, 28, 28)
print(train_data.train_labels.size()) # (60000)
plt.imshow(train_data.train_data[0].numpy(), cmap='gray')
plt.title('%i' % train_data.train_labels[0])
plt.show()
黑色的地方的值都是0, 白色的地方值大于0.
同樣, 我們除了訓(xùn)練數(shù)據(jù), 還給一些測(cè)試數(shù)據(jù), 測(cè)試看看它有沒有訓(xùn)練好.
test_data = torchvision.datasets.MNIST(root='./mnist/', train=False)
# 批訓(xùn)練 50samples, 1 channel, 28x28 (50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# 為了節(jié)約時(shí)間, 我們測(cè)試時(shí)只測(cè)試前2000個(gè)
test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1), volatile=True).type(torch.FloatTensor)[:2000]/255. # shape from (2000, 28, 28) to (2000, 1, 28, 28), value in range(0,1)
test_y = test_data.test_labels[:2000]
3.1.2 CNN模型
和以前一樣, 我們用一個(gè) class 來建立 CNN 模型. 這個(gè) CNN 整體流程是:
卷積(Conv2d) -> 激勵(lì)函數(shù)(ReLU) -> 池化, 向下采樣 (MaxPooling) -> 再來一遍 -> 展平多維的卷積成的特征圖 -> 接入全連接層 (Linear) -> 輸出
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Sequential( # input shape (1, 28, 28)
nn.Conv2d(
in_channels=1, # input height
out_channels=16, # n_filters
kernel_size=5, # filter size
stride=1, # filter movement/step
padding=2, # 如果想要 con2d 出來的圖片長(zhǎng)寬沒有變化, padding=(kernel_size-1)/2 當(dāng) stride=1
), # output shape (16, 28, 28)
nn.ReLU(), # activation
nn.MaxPool2d(kernel_size=2), # 在 2x2 空間里向下采樣, output shape (16, 14, 14)
)
self.conv2 = nn.Sequential( # input shape (16, 14, 14)
nn.Conv2d(16, 32, 5, 1, 2), # output shape (32, 14, 14)
nn.ReLU(), # activation
nn.MaxPool2d(2), # output shape (32, 7, 7)
)
self.out = nn.Linear(32 * 7 * 7, 10) # fully connected layer, output 10 classes
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size(0), -1) # 展平多維的卷積圖成 (batch_size, 32 * 7 * 7)
output = self.out(x)
return output
cnn = CNN()
print(cnn) # net architecture
"""
CNN (
(conv1): Sequential (
(0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU ()
(2): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
)
(conv2): Sequential (
(0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU ()
(2): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
)
(out): Linear (1568 -> 10)
)
"""
3.1.3 訓(xùn)練
下面我們開始訓(xùn)練, 將 x
y
都用 Variable
包起來, 然后放入 cnn 中計(jì)算 output, 最后再計(jì)算誤差.
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR) # optimize all cnn parameters
loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted
# training and testing
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader): # 分配 batch data, normalize x when iterate train_loader
b_x = Variable(x) # batch x
b_y = Variable(y) # batch y
output = cnn(b_x) # cnn output
loss = loss_func(output, b_y) # cross entropy loss
optimizer.zero_grad() # clear gradients for this training step
loss.backward() # backpropagation, compute gradients
optimizer.step() # apply gradients
if step % 50 == 0:
test_output, last_layer = cnn(test_x)
pred_y = torch.max(test_output, 1)[1].data.squeeze()
accuracy = sum(pred_y == test_y) / float(test_y.size(0))
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data[0], '| test accuracy: %.2f' % accuracy)
if HAS_SK:
# Visualization of trained flatten layer (T-SNE)
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
plot_only = 500
low_dim_embs = tsne.fit_transform(last_layer.data.numpy()[:plot_only, :])
labels = test_y.numpy()[:plot_only]
plot_with_labels(low_dim_embs, labels)
"""
...
Epoch: 0 | train loss: 0.0306 | test accuracy: 0.97
Epoch: 0 | train loss: 0.0147 | test accuracy: 0.98
Epoch: 0 | train loss: 0.0427 | test accuracy: 0.98
Epoch: 0 | train loss: 0.0078 | test accuracy: 0.98
"""
最后我們?cè)賮砣?0個(gè)數(shù)據(jù), 看看預(yù)測(cè)的值到底對(duì)不對(duì):
test_output = cnn(test_x[:10])
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
print(pred_y, 'prediction number')
print(test_y[:10].numpy(), 'real number')
"""
[7 2 1 0 4 1 4 9 5 9] prediction number
[7 2 1 0 4 1 4 9 5 9] real number
"""
3.1.4 可視化訓(xùn)練
這是做完視頻后突然想要補(bǔ)充的內(nèi)容, 因?yàn)榭梢暬梢詭椭斫? 所以還是有必要提一下. 可視化的代碼主要是用 matplotlib 和 sklearn 來完成的, 因?yàn)槠渲形覀冇玫搅?T-SNE 的降維手段, 將高維的 CNN 最后一層輸出結(jié)果可視化, 也就是 CNN forward 代碼中的 x = x.view(x.size(0), -1)
這一個(gè)結(jié)果.