Tensor
tensor 是Pytorch重要的数据结构 ,可以认为是一个高纬的数组,它是一个(标量),一维数组(向量),二维矩阵(矩阵)以及更高维度的数组, Tesnor 跟numpy 的 ndarrays类似。
from __feature__ import print_function
import torch as t
#构建一个(5,3) 的矩阵,只是分配了空间,未初始化
x = Tensor(5,3)
print(x)
#使用0-1均匀分布随机初始化二维数组
x = t.rand(5,3)
print(x)
out:tensor([[8.4490e-39, 9.6429e-39, 9.2755e-39],
[1.0286e-38, 9.0919e-39, 8.9082e-39],
[9.2755e-39, 8.4490e-39, 1.0194e-38],
[9.0919e-39, 8.4490e-39, 9.6429e-39],
[1.0653e-38, 9.6429e-39, 1.0745e-38]])
tensor([[ 0.0292, 0.5167, 0.9883],
[ 0.2294, 0.1510, 0.5729],
[ 0.6362, 0.2311, 0.9526],
[ 0.6667, 0.8822, 0.3827],
[ 0.3663, 0.3416, 0.4670]])
print(x.size()) #查看x 的形状
x.size()[1] #查看列数 与下面的方法等价
x.size(1) #两者是相等的 torch.Size 是tuple对象的子类,它支持x.size()[1]
Tensor 的加法
y = t.rand(5,3)
#加法的一种写法
x+y
#第二种加法的写法
t.add(x,y)
#第三种加法的写法
result = t.Tensor(5,3) 先定义
t.add(x,y,out = result)
result
普通的加法并不会改变y的值
print("开始的y")
print(y)
y.add(x)
print("相加后的y")
print(y)
#第二种加法y的结果
y.add_(x)
print(yu)
y的结果变了
** 注意,函数名后面带下划线_ 的函数会修改Tensor本身。例如,x.add_(y)和x.t_()会改变 x,但x.add(y)和x.t()返回一个新的Tensor, 而x不变。
Tensor的选取过程跟numpy 类似
x[:,1] #所有行的1列
Tensor和Numpy的数组之间的互操作非常容易且快速。对于Tensor不支持的操作,可以先转为Numpy数组处理,之后再转回Tensor。
import numpy
import tensor as t
a = t.one(5) #创建一个全系的tensor
print(a)
b = a.numpy() #Tensor --> Numpy
print(b)
c = t.from_numpy(a) # Numpy -- > Tensor
Numpy与Tensor 共享内存,意味着一个发生改变另一个也会发生改变
b.add_(1)
print(a) #out:[2. 2. 2. 2. 2.]
print(b) #out:tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
Tensor 可以通过.cuda方法转化成GPU的Tensor 从而享受Tensor的加速运算
if t.cuda.is_available():
x = x.cuda()
y = y.,cuda()
x + y
CUDA 带来的速度提升 需要有大量的数据才能体现出来
Autograd 自动微分
深度学习的算法本质上是通过反向传播求导数,而PyTorch的Autograd模块则实现了此功能。在Tensor上的所有操作,Autograd都能为它们自动提供微分,避免了手动计算导数的复杂过程。
autograd.Variable是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor有的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度
Variable主要包含三个属性。
data:保存Variable所包含的Tensor
grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。
grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度
使用Tensor 创建一个Variable
from torch.autograd import Variable
x = Variable(t.ones(2,2),requires_grad = True
out:tensor([[1., 1.],
[1., 1.]], requires_grad=True)
y = x.sum()
print(y)
out:tensor(4., grad_fn=)
y.grad_fn
<SumBackward0 at 0x7fc14824b860>
y.backward() 反向传播,计算梯度
# y = sum(x) = x[0][0] + x[0][1] + x[1][0] + x[1][0]+x[1][1]
x.grad
#tensor([[1., 1.],
#[1., 1.]])
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需把梯度清零。
y.backgrad()
x.grad()
y.backward()
x.grad
out: tensor([[2., 2.],
[2., 2.]])
Variable containing:
3 3
3 3
[torch.FloatTensor of size 2x2]
#以下划线结束的就是 inplace操作
x.grad.data.zero_()
y.backward()
x.grad
out :Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]
定义一个网络
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__ (self):
super(Net,self).__init__()
#nn.Module子类 必须在构造函数中执行父类函数
#等价于 nn.Module.__init__()
# 卷积层'1'表示通道数量,6表示输出的通道数,5表示5个卷积核
self.conv1 = nn.Conv2d(1,6,5)
self.conv2 = nn.Conv2d(6,16,5)
#全连接层 y = wx + b
self.fc1 = nn.Linear(15*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)
#reshape “-1”表示 自适应
x = x.view(x.size()[0],-1)
x = F.relu(self.fc1(x))
x = F.relu(slef.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net)
out: 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=375, 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函数,backward函数就会自动被实现(利用Autograd)。在forward 函数中可使用任何Variable支持的函数,还可以使用if、for循环、print、log等Python语法,写法和标准的Python写法一致。网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
params = list(net.parameters())
print(len(params))
out:10
for name,parameters in net.named_parameters():
print(name,':',parameters.size())
out:
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])
input = Variable(t.randn(1, 1, 32, 32))
out = net(input)
out.size()
net.zero_grad() # 所有参数的梯度清零
out.backward(Variable(t.ones(1,10))) # 反向传播 需要放入一个维度相同的
torch.Size([1, 10])
损失函数
nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
output = net(input)
target = Variable(t.arange(0,10))
criterion = nn.MSELoss()
loss = criterion(output, target)
loss
Variable containing:
28.5536
[torch.FloatTensor of size 1]
loss的计算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
当调用loss.backward()时,该图会动态生成并自动微分,也即会自动计算图中参数(Parameter)的导数。
# 运行.backward,观察调用之前和调用之后的grad
net.zero_grad() # 把net中所有可学习参数的梯度清零
print('反向传播之前 conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向传播之后 conv1.bias的梯度')
print(net.conv1.bias.grad)
反向传播之前 conv1.bias的梯度
Variable containing:
0
0
0
0
0
0
[torch.FloatTensor of size 6]
反向传播之后 conv1.bias的梯度
Variable containing:
1.00000e-02 *
-4.2109
-2.7638
-5.8431
1.3761
-2.4141
-1.2015
[torch.FloatTensor of size 6]
在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)# inplace 减法
import torch.optim as optim
#新建一个优化器,指定要调整的参数和学习率
optimizer = optim.SGD(net.parameters(), lr = 0.01)
# 在训练过程中
# 先梯度清零(与net.zero_grad()效果一样)
optimizer.zero_grad()
# 计算损失
output = net(input)
loss = criterion(output, target)
#反向传播
loss.backward()
#更新参数
optimizer.step()