基本概念
Tensor
tensor是的含義是張量,簡單的理解可以將其當成三維矩陣,pytorch中的張量是對數據的一種封裝,也是數據結構中最核心的部分之一。對于pytorch中的張量,數組可能是更好的理解方法。
Tensor的定義
- 直接定義矩陣,使用
torch.Tensor(shape)
方法定義未初始化的張量,使用torch.rand(shape)
或torch.randn(shape)
定義隨機張量
import torch as pt
x = pt.Tensor(2,4)
print(x)
# 1.00000e-23 *
# 0.0000 0.0000 1.2028 0.0000
# 0.0000 0.0000 1.1517 0.0000
# [torch.FloatTensor of size 2x4]
x = pt.rand(5,3)
# 0.7609 0.5925 0.5840
# 0.1949 0.6366 0.3763
# 0.1802 0.8529 0.9373
# 0.6013 0.9685 0.9945
# 0.6555 0.1740 0.9884
# [torch.FloatTensor of size 5x3]
print(x,x.size()[0])
# 5
y = pt.rand(5,3)
- 從numpy 中定義tensor,使用
torch.from_numpy(ndarray)
的方法,需要注意的是,這種情況下numpy矩陣和tensor會“綁定”,即修改任何一個的值,另一個的值也會發生變化
a = np.ones(5)
b = pt.from_numpy(a)
print(a,b)
#[ 1. 1. 1. 1. 1.]
#1
#1
#1
#1
#1
#[torch.DoubleTensor of size 5]
Tensor的基本操作
Tensor和numpy中的ndarray相似,可以完成加減乘除等運算,常見的操作方法通常為Tensor.操作(參數)
或torch.操作(參數,out=輸出tensor)
,以加法為例
result = pt.Tensor(5,3)
test = pt.add(x,y,out=result)
# print(result,test)
y.add_(x)
兩種方法test = pt.add(x,y,out=result)
和y.add_(x)
都是相加,前者是相加后將結果交給一個新的Tensorresult
,而后者可以理解為y
自加x
Tensor還可以轉換為numpy的對象ndarray,可以使用Tensor.numpy()
獲得與Tensor
綁定的ndarray對象,修改Tensor時,ndarray對象也發生變化
a = pt.ones(5)
b = a.numpy()
# print(a,b)
a.add_(1)
# print(a,b)
使用GPU加速
使用Tensor = Tensor.cuda()
的方法可以講Tensor
放到GPU上,通常的運算不支持從CPU到GPU的變換,因此若要在GPU上進行網絡運算,網絡聲明完成后也要調用網絡和輸入的.cuda()
方法將網絡和輸入放在GPU上
a,b = pt.Tensor(2,2),pt.Tensor(2,2)
a = a.cuda()
b = b.cuda()
Variable
Variable正向傳播
Variable與TensorFlow中的Variable一樣,是構建神經網絡和訓練的核心類,使用Variable可以構建計算圖,并在圖中計算結果(正向傳播)和微分(反向傳播),Variable的一些運算符重載過,因此可以直接使用+-*/
運算符
x = Variable(pt.ones(2,2),requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
# Variable containing:
#27
#[torch.FloatTensor of size 1]
Variable反向傳播
以上構建了一個計算圖并計算了out
的值,完成前向傳播,使用out.backward()
可以執行反向傳播,就是計算微分。
out.backward()
print(x.grad)
#Variable containing:
# 4.5000 4.5000
# 4.5000 4.5000
#[torch.FloatTensor of size 2x2]
網絡構建
網絡結構構建
class Net(nn.Module):
"""docstring for Net"""
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1,6,5)# input channel,6 output channel,5x5
self.conv2 = nn.Conv2d(6,16,5)
self.fc1 = nn.Linear(16*5*5,120)#input 16*5*5,output120
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)) #input,poolcore shape
x = F.max_pool2d(F.relu(self.conv2(x)), 2) #(2,2) => 2 because 2=2
x = x.view(-1, self.num_flat_features(x)) #reshape
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self,x):
size = x.size()[1:] #remove batch size
num_f = 1
for s in size:
num_f *= s
return num_f
以上為構建一個簡單的CNN的例子,其中
-
nn
來自import torch.nn as nn
這其中封裝各種各樣的網絡層 -
F
來自import torch.nn.functional as F
,這其中封裝了各種各樣的神經網絡需要使用的函數
在網絡結構中
-
nn.Linear(input_size,output_size)
為線性連接層,為MLP的線性部分
-nn.Conv2d(input_channel,output_channel,shape)
表示卷積核
函數中
-
F.max_pool2d(input,core_shape)
為池化層 -
F.relu(input)
為ReLu激活函數
另外,Variable.view()
為變形函數,其中的-1
表示不關心batch,而函數self.num_flat_features(x)
是為了獲得x
的元素數量,這一步直接將x
拍扁成向量
網絡的前向傳播
定義網絡后(以上的類)后,聲明后可以直接調用
net = Net().cuda()
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 (400 -> 120)
# (fc2): Linear (120 -> 84)
# (fc3): Linear (84 -> 10)
#)
其中.cuda()
是將整個網絡放到GPU上,如果直接使用net = Net()
網絡位置在CPU上,將無法使用GPU加速。
定義網絡后,直接傳入輸入即可完成前向傳播
net.zero_grad() # Zero the gradient buffers of all parameters
inputdata = Variable(pt.randn(1,1,32,32)) #?(1,1,32,32) nSamples x nChannels x Height x Width
inputdata = inputdata.cuda()
# 1-batch 1-input channel 32,32
# print(inputdata)
# print(inputdata.unsqueeze(0)) #[torch.FloatTensor of size 1x1x1x32x32]
out = net(inputdata)
print(out)
其中net.zero_grad()
是為了清除梯度,起類似于初始化的作用。pytorch要求數據與網絡的位置相同,因此若是網絡聲明在GPU上,數據也必須要GPU上加速。
網絡的反向傳播(權值更新)
網絡的反向傳播可以直接使用預先定義的代價函數的.backward()
方法實現
net.zero_grad()
print(net.conv1.bias.grad)
loss.backward()
print(net.conv1.bias.grad)
在更新權值的時候,可以手動指定更新的方法
for f in net.parameters():
f.data.sub_(f.grad.data * 0.01)
其中:
- net.parameters()是個生成器,可以遍歷net中的所有參數
- f.grad.data為輸出(代價函數)到這一參數的梯度
除了手動制定,也可以從import torch.optim as optim
中調用優化器
optimizer = optim.SGD(net.parameters(),lr=0.01)
optimizer.zero_grad()
out = net(inputdata)
loss = criterion(out,target)
loss.backward()
optimizer.step()
其中criterion()
為代價函數,loss
為代價函數的輸出值,optimizer.step()
為調用一次優化
代價函數
代價函數表示當前結果距離期望輸出的“距離”,torch.nn
封裝了一些代價函數,可以在訓練的時候直接調用
target = Variable(pt.arange(1,11)).cuda()
# print(target)
criterion = nn.MSELoss().cuda()
loss = criterion(out,target) #out shape = 1*10 target shape = 10
這里調用的代價函數是MSELoss()
平方平均函數
分類網絡搭建,訓練與測試
分類網絡數據準備
教程提供的范例的訓練集是CIFAR10數據集,該數據集提供了10種不同類型的圖片,引入代碼如下圖
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
這部分僅僅是下載并提供數據集,不必深究,需要注意的是從testloader
中獲得數據即可
分類網絡搭建
分類網絡搭建使用兩層conv+pool后接3層mlp層的結構,是個基本的卷積神經網絡,構建類如下
class Net(nn.Module):
"""docstring for Net"""
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3,6,5)
self.pool = nn.MaxPool2d(2,2)
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 = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1,16*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net().cuda()
這里將組件的定義放在了構造函數中,而將網絡前饋部分放在了單獨的forward()
函數中。另外,使用net = Net().cuda()
將網絡放在了GPU上
分類網絡的訓練
分類網絡的訓練需要定義優化器和代價函數,剩下的就是將數據丟進神經網絡中了,代碼如下
criterion = nn.CrossEntropyLoss()
#聲明使用交叉熵函數作為代價函數
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
#聲明使用學習率0.001的SGD優化器
for epoch in range(2):
running_loss = 0
for i,data in enumerate(trainloader,0):
inputs,labels = data
inputs,labels = Variable(inputs).cuda(),Variable(labels).cuda()
#獲得數據并將其放在GPU上
optimizer.zero_grad()
#初始化梯度
outputs = net(inputs)
#前饋
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
#反饋計算梯度并更新權值
running_loss += loss.data[0]
if i % 200 == 0:
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0
#打印平均代價函數值
print('Finished Training')
總結一下,該部分代碼總共做了以下幾件事
- 定義優化器與代價函數
- 執行網絡訓練
執行網絡訓練部分,每次迭代包括以下操作
- 獲取batch數據并將其放在GPU上
2.初始化梯度
3.執行前饋計算代價函數
4.執行反饋計算梯度并更新權值
分類網絡的測試
網絡測試部分就是將所有的訓練數據再投入網絡中訓練一次,看真實結果與預測結果是否相同,代碼如下
corret,total = 0,0
for images,labels in testloader:
images = images.cuda()
labels = labels.cuda()
outputs = net(Variable(images))
_,predicted = torch.max(outputs.data,1)
total += labels.size(0)
corret += (predicted == labels).sum()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * corret / total))
前饋得到預測結果后,使用_,predicted = torch.max(outputs.data,1)
在第一維看取出最大的數(丟棄)和最大數的位置(保留)后再與label相比即可進行測試