本文基于书籍《深度学习框架PyTorch:入门与实践》的对应代码也是PyTorch入门指南和教程. 并没有完全照抄源章节,而是实现了一些自己的实验之后添加了自己的理解,同时选择性删除了已经在新版pytorch中弃用的variable部分及其他.
Tensor
# from __future__ import print_function
#该句语句是python2的概念,那么python3对于python2就是future了,
#也就是说,在python2的环境下,超前使用python3的print函数
import torch as t
t.__version__
'1.4.0+cu92'
#只是分配了空间,未初始化
x = t.Tensor
print(x)
x = t.Tensor(5,3)
print(x)
x = t.Tensor([[2,2],[2,2]])
x
<class 'torch.Tensor'>
tensor([[4.2246e-39, 1.0286e-38, 1.0653e-38],
[1.0194e-38, 8.4490e-39, 1.0469e-38],
[9.3674e-39, 9.9184e-39, 8.7245e-39],
[9.2755e-39, 8.9082e-39, 9.9184e-39],
[8.4490e-39, 9.6429e-39, 1.0653e-38]])
tensor([[2., 2.],
[2., 2.]])
print(x.size())
x.size()[1],x.size(1) # 查看列的个数, 两种写法等价
#torch.Size 是tuple对象的子类,因此它支持tuple的所有操作,如x.size()[0]
torch.Size([2, 2])
(2, 2)
#加:
#1,
print(x)
x = t.rand(5,3)
print(x)
# 使用[0,1]均匀分布随机初始化二维数组 rand参数是size
y = t.rand(1,3)
print(x+y)
#2,
print(t.add(x, y))
#3,
result = t.Tensor(5, 3) # 预先分配空间
t.add(x, y, out=result) # 输入到result
result
tensor([[0.6806, 0.1663, 0.7670],
[0.8551, 0.4156, 0.0987],
[0.9882, 0.0415, 0.3336],
[0.7140, 0.8380, 0.1365],
[0.7197, 0.9418, 0.9951]])
tensor([[0.7685, 0.6577, 0.0177],
[0.9424, 0.9072, 0.8444],
[0.3546, 0.7298, 0.4140],
[0.8316, 0.7530, 0.6691],
[0.0165, 0.8941, 0.1688]])
tensor([[1.4669, 1.0402, 0.9156],
[1.6409, 1.2897, 1.7423],
[1.0531, 1.1124, 1.3119],
[1.5301, 1.1355, 1.5670],
[0.7149, 1.2766, 1.0666]])
tensor([[1.4669, 1.0402, 0.9156],
[1.6409, 1.2897, 1.7423],
[1.0531, 1.1124, 1.3119],
[1.5301, 1.1355, 1.5670],
[0.7149, 1.2766, 1.0666]])
tensor([[1.4669, 1.0402, 0.9156],
[1.6409, 1.2897, 1.7423],
[1.0531, 1.1124, 1.3119],
[1.5301, 1.1355, 1.5670],
[0.7149, 1.2766, 1.0666]])
注意,函数名后面带下划线_ 的函数会修改Tensor本身。例如,x.add_(y)和x.t_()会改变 x,但x.add(y)和x.t()返回一个新的Tensor, 而x不变。
如不熟悉,初学不推荐使用
# Tensor的(切片)选取操作与Numpy类似
x[:, 1]
Tensor和Numpy的数组之间的
1,互操作非常容易且快速。对于Tensor不支持的操作,可以先转为Numpy数组处理,之后再转回Tensor。
2,对象共享内存,如果其中一个变了,另外一个也会随之改变>>>>所以很快
a = t.ones(9) #全为1, 九个值的Tensor
print(a)
b = a.numpy() #此处未导入numpy包,这是Tensor对象的函数, .numpy()
b
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1.])
array([1., 1., 1., 1., 1., 1., 1., 1., 1.], dtype=float32)
b = t.from_numpy(b)#变换回Tensor用 t.from_numpy(*)
print(b)
b.add_(1) # 以`_`结尾的函数会修改自身
print(b) # Tensor和Numpy共享内存
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1.])
tensor([2., 2., 2., 2., 2., 2., 2., 2., 2.])
torch.tensor
x = t.tensor([5,3]) # 新建一个包含 5, 3 两个元素的tensor
scalar = t.tensor(3)
scalar
tensor(3)
old_x = x
new_x = old_x.clone()
new_x[0] = 1111
old_x, new_x
(tensor([5, 3]), tensor([1111, 3]))
需要注意的是,t.tensor()或者tensor.clone()总是会进行数据拷贝,新tensor和原来的数据不再共享内存。所以如果你想共享内存的话,建议使用torch.from_numpy()或者tensor.detach()来新建一个tensor, 二者共享内存。
#尝试失败1
new2_x = old_x.detach()
new2_x = 2222 #小问题:不索引,不成立,被认定为赋值,换成了一个整型的数据
old_x, new2_x
(tensor([ 5, 2222]), 2222)
#尝试失败2
new2_x = t.from_numpy(old_x) #from_numoy 只能接收numpy
new2_x = 2222
old_x, new2_x
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-127-b7f3851ca223> in <module>
1 #尝试失败2
----> 2 new2_x = t.from_numpy(old_x) #from_numoy 只能接收numpy
3 new2_x = 2222
4 old_x, new2_x
TypeError: expected np.ndarray (got Tensor)
new2_x = old_x.detach()
new2_x[0] = 2222
old_x, new2_x
(tensor([2222, 3]), tensor([2222, 3]))
x = t.rand(5,3)
y = t.rand(5,3)
#cuda方法将Tensor放在GPU上运算,贼快
# 在不支持CUDA的机器下,下一步还是在CPU上运行
device = t.device("cuda:0" if t.cuda.is_available() else "cpu")
x = x.to(device)
y = y.to(x.device)
z = x+y
此处可能发现GPU运算的速度并未提升太多,这是因为x和y太小且运算也较为简单,而且将数据从内存转移到显存还需要花费额外的开销。GPU的优势需在大规模数据和复杂运算下才能体现出来。
auto grad
深度学习的算法本质上是通过反向传播求导数,而PyTorch的autograd模块则实现了此功能。在Tensor上的所有操作,autograd都能为它们自动提供微分,避免了手动计算导数的复杂过程。
要想使得Tensor使用autograd功能,只需要设置tensor.requries_grad=True.
# 为tensor设置 requires_grad 标识,代表着需要求导数
# pytorch 会自动调用autograd 记录操作
x = t.ones(2, 2, requires_grad=True)
# 上一步等价于
# x = t.ones(2,2)
# x.requires_grad = True
x
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
y = x.sum()
y
tensor(4., grad_fn=<SumBackward0>)
y.grad_fn #???????
<SumBackward0 at 0x21817a97f60>
y.backward() #反向传播,计算梯度
x.grad #每个值的梯度都为1
tensor([[1., 1.],
[1., 1.]])
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需把梯度清零。
y.backward()
print(x.grad)
y.backward()
print(x.grad)
tensor([[2., 2.],
[2., 2.]])
tensor([[3., 3.],
[3., 3.]])
# 以下划线结束的函数是inplace操作,会修改自身的值,就像add_
x.grad.data.zero_()
tensor([[0., 0.],
[0., 0.]])
y.backward()
x.grad
tensor([[1., 1.],
[1., 1.]])
如果是jupyter notebook,可以上面俩段代码来回试试玩
网络结构
Autograd实现了反向传播功能,但是直接用来写深度学习的代码在很多情况下还是稍显复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于 Autograd之上,可用来定义和运行神经网络。nn.Module是nn中最重要的类,可把它看成是一个网络的封装,包含网络各层定义以及forward方法,调用forward(input)方法,可返回前向传播的结果。
#Lenet 模型示例
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module): # 必须基础nn.Module
def __init__(self): # nn.Module子类的函数必须在构造函数中执行父类的构造函数
super(Net,self).__init__() # 也可以写成nn.Module.__init__(self) 推荐前一种
# 卷积层参数: input_channel_size output_channel_size kernel_size
#卷积层 # '1'表示输入图片为单通道, '6'表示输出通道数,'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 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(self.fc2(x))
s = self.fc3(x)
return x
#没写backward,这是继承nn.Modlue,他的子类会autograd,自动实现
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)
)
#网络的可学习参数通过net.parameters()返回,
#net.named_parameters可同时返回可学习的参数及名称。
params = list(net.parameters())
print(len(params))
for name,parameters in net.named_parameters():
print(name,':',parameters.size())
#forward函数输入输出都是Tensor
10
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 = t.randn(1,1,32,32) #randn的n是一般化的意思,
out = net(input)
out.size
<function Tensor.size>
net.zero_grad() # 所有参数的梯度清零
out.backward(t.ones(1,84)) # 反向传播
torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。但如果只想输入一个样本,则用 **input.unsqueeze(0)**将batch_size设为1。例如 nn.Conv2d 输入必须是4维的,形如 Samples × nChannels × Height × Width。可将nSample设为1,即1 × nChannels × Height × Width.
损失函数 & 优化器
#
output = net(input)
target = t.arange(0,84).view(1,84).float()
#创建损失函数和优化器,不过已经有很好用的nn与optim包帮我们建好了
criterion = nn.MSELoss()
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr = 0.01)
optimizer.zero_grad #与net.zero_grad() 效果一样,参数是网络的返回值嘛
#计算损失
loss = criterion(output, target) # loss是个scalar, scalar就算0-dim的Tensor
print(loss)
#反向传播
loss.backward()
#更新参数
optimizer.step()
tensor(2305.7666, grad_fn=<MseLossBackward>)
在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
手动实现如下:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)# inplace 减法
torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,更便于使用,因此大多数时候并不需要手动写上述代码。
数据加载
深度学习中数据加载及预处理 很烦
PyTorch提供了一些可极大简化和加快数据处理流程的工具
对于常用的数据集,PyTorch也提供了封装好的接口供用户快速调用,这些数据集主要保存在torchvison中
torchvision实现了常用的图像数据加载功能,例如Imagenet、CIFAR10、MNIST等,以及常用的数据转换操作,这极大地方便了数据加载,并且代码具有可重用性。
自查表
Tensor 与 Numpy 区别?
俩者互换的函数,以及归属
加法运算
Tensor创建 随机 1 Tensor矩阵
torch.size是___对象的子类
提醒: 如何查看Tensor列数
inplace()操作有什么不同
反向传播函数:?
反向传播又为什么要清零
cuda的操作简单但也要记住呀,而且多显卡并行DataParallel也要会用,而且看那么多csdn讲cuda加速的,虽然还没有服务器用cuda显卡加速,但我估计会有很多坑等着.
定义网络结构咯,卷积层参数是啥呀,
forward函数和init有什么区别呀
nn.Module()很重要哟
好像也没啥,但就算理论最坚实的部分✌
有很多backward不需要你干,记住Tensor创建时用设成自动求导为 True 就行
损失函数和优化器也不需要,但要记得咋用
数据加载,先找 torchvision 玩玩吧,文件夹导入图片的也不难,反正有DataLoader()
时间: 2020-11-11 torch版本: 1.4.0 参考教程: https://github.com/caoyang13/pytorch-book/tree/2db9c662d745d30951ab882c570d049d413caf45