PyTorch学习笔记(1)- tensor、autograd、神经网络

1 tensor

【创建tensor】
  • a = t.Tensor(2, 3):指定tensor形状
  • a = t.Tensor((2, 3)):创建一个元素为2和3的tensor
  • x = t.Tensor([[1,2],[3,4]]):用list数据创建tensor
  • b.tolist():把tensor转为list
  • c = t.Tensor(b_size):创建一个和b形状一样的tensor,系统不会马上分配空间,会计算剩余内存是否够用,使用到tensor时才会分配
  • t.ones(2, 3):形状为2×3的全为1的tensor
  • t.zeros(2, 3):形状为2×3的全为0的tensor
  • t.arange(1, 6, 2):从1到6,步长为2
  • t.linspace(1, 10, 3):从1到10,均匀切成3份
  • x = t.rand(5, 3):均匀随机分布
  • t.randn(2, 3, device=t.device('cpu')):标准随机分布
  • t.randperm(5):长度为5的随机排列
  • t.eye(2, 3, dtype=t.int):对角线为1,不要求行列数一致
  • torch.tensor:在0.4版本新增的一个新版本的创建tensor方法,使用方法和参数几乎和np.array完全一致,如scalar = t.tensor(3.14159) vector = t.tensor([1, 2])tensor = t.Tensor(1,2)empty_tensor = t.tensor([])
【tensor操作】

接口来说,对tensor操作分为两类:

  1. torch.function(如torch.save
  2. tensor.function(如tensor.view
  • tensor.size():等价于tensor.shape,返回torch.Size对象
  • tensor.view():调整tensor形状,当某一维为1时,自动计算它的大小,和源tensor共享内存,实际应用如需添加或减小某一维度,使用squeezeunsqueeze
  • b.unsqueeze(1):在第1维(下标从0开始)上增加“1”
  • b.unsqueeze(-2):在倒数第2维上增加“1”
  • b.squeeze(0):压缩第0维的“1”
  • b.squeeze():把所有维度为“1”的压缩
  • tensor.resize_():修改tensor的大小,于view不同的是,可以修改tensor的大小,如果超过原大小,会自动分配新的内存空间,如果小于原大小,之前的数据会被保存
  • b.numel():b中元素总个数
【索引操作】
  • a[0]:第0行
  • a[:, 0]:第0列
  • a[0, -1]:第0行最后一个元素,等价于a[0][-1]
  • a[:2]:前两行
  • a[:2, 0:2]:前两行,第0,1列
  • a[None].shape:None类似于np.newaxis,为a新增一个轴,等价于a.view(1, a.shape[0], a.shape[1])
  • a[a>1]:等价于a.mask_select(a>1),选择结果与原tensor不共享内存空间
  • a[t.LongTensor([0, 1])]:第0行和第1行
  • gather把数据从input中按index取出,相反地,scatter_是把取出的数据放回去(就地操作),out = input.gather(dim, index)逆操作为out.scatter_(dim, index)
index = t.LongTensor([[0, 1, 2, 3]]) #对角线元素
a.gather(0, index)
index = t.LongTensor([[3, 2, 1, 0]]).t() #反对角线元素
a.gather(1, index)
index = t.LongTensor([[3,2,1,0]]) #反对角线元素,和上面不同
a.gather(0, index)
index = t.LongTensor([[0,1,2,3],[3,2,1,0]]).t() # 选取两个对角线上的元素
b = a.gather(1, index)
  • 获取某一个元素的值:使用tensor.item()(一个元素的tensor),直接索引操作tensor[idx]得到的还是一个tensor
【tensor类型】
  • 默认是FloatTensor,可通过t.set_default_tensor_type("torch.DoubleTensor")修改默认tensor类型
  • t.set_default_tensor_type('torch.FloatTensor'):恢复之前的默认设置
  • b = a.float():把a转成FloatTensor,等价于b = a.type(t.FloatTensor)
  • 数据类型转换是type(new_type),还有float, long, half方法
  • c = a.type_as(b)
  • torch.*_like(tensora):生成和tensora同样属性的新tensor
  • tensor.new_*(new_shape):新建一个不同形状的tensor
  • a.new(2, 3):等价于torch.DoulbeTensor(2, 3)
  • t.zeros_like(a):全为0,等价于t.zeros(a.shape, dtype = a.dtype, device = a.device)
  • t.rand_like(a):随机数值
  • a.new_ones(4, 5, dype = t.int)
  • a.new_tensor([3, 4])
【逐元素操作】
  • 数学运算:abs, sqrt, div, exp, fmod, log, pow
  • 三角函数:cos, sin, asin, atan2, cosh
  • 取整:ceil, round, floor, trunc
  • 截断:clamp(input, min, max)
  • 激活函数:sigmod, tanh
  • a % 3等价于t.fmod(a, 3)
  • a ** 2等价于t.pow(a, 2)
【归并操作】
  • 包括mean, sum, median, mode(众数),norm(范数),dist(距离),std, var, cumsum(累加),cumprod(累乘)
  • dim指定是在哪个维度上执行操作,如果输入形状是(m, n, k)
    如果指定dim = 0,输出形状(1, n, k)或(n, k)
    如果指定dim = 1,输出形状(m, 1, k)或(m, k)
    如果指定dim = 2,输出形状(m, n, 1)或(m, n)
    size中是否有1取决于参数keepdim,keepdim = True会保留维度1,除了cumsum
【比较】
  • 包括gt(大于),lt(小于),te(大于等于),le(小于等于),eq(等于),ne(不等),topk(最大的k个数),sort(排序),max/min(比较两个tensor最大最小值)
  • t.clamp(a, min = 10):比较a和10较大的元素
【线性代数】
  • trace:矩阵的迹
  • diag:对角线元素
  • triu, tril:矩阵上三角/下三角可指定的偏移量
  • mm, bmm:矩阵乘法
  • addmm, addbmm, addmv, addr, badbmm:矩阵运算
  • t:转置
  • dot, cross:内积/外积
  • inverse:求逆
  • svd:奇异值分解
  • 矩阵转置会导致存储空间不连续,需调用它的.contiguous方法将其转为连续,b = a.t() b = contiguous()
【numpy和tensor转换】
  • tensor不支持的操作可以转为numpy数组处理,之后再转回tensor,共享内存
# Tensor -> Numpy
a = t.ones(5)
b = a.numpy() 
# Numpy -> Tensor
import numpy as np
a = np.ones(5)
b = t.from_numpy(a)
  • 当numpy数据类型和tensor不一样时,数据被复制,不会共享内存
  • 不论输入类型是什么,t.Tensor()tensor.clone()都会数据拷贝,不共享内存
a = np.ones([2,3])
b = t.Tensor(a)
  • 如果想共享内存,使用torch.from_numpy()或者tensor.detach()来新建一个tensor,二者共享内存
【广播法则】
  1. 让所有的输入数组向shape最长的数组看齐,shape不足的部分通过在前面加1补齐;
  2. 两个数组要么在某一维长度一致,要么其中一个为1;
  3. 当输入数组某个维度为1时,计算沿此维度复制扩充成一样的形状

手动广播更直观,不容易出错:

  1. unsqueeze / view / tensor[None]为数据某一维形状补1,实现法则1;
  2. expand/expand_as重复数组,实现法则3;
  3. repeat实现与expand类似的功能,但repeat会把相同数据复制成多份,占用额外空间
a = t.ones(3, 2)
b = t.zeros(2, 3, 1)
a[None].expand(2, 3, 2) + b.expand(2, 3, 2)
【内部结构】

tensor分为头信息区(Tensor)和存储区(Storage),信息区保存tensor的形状(size)、步长(stride)、数据类型(type),存储区决定了tensor中元素数目和内存,不同tensor头信息不同,但可能使用相同storage。绝大多数操作不修改tensor数据,只修改tensor头信息,这样更节省内存,提升了处理速度。

【CPU和GPU tensor之间的转换】
  • tensor.cuda(device_id)tensor.cpu()a = t.randn(3, 4, device = t.device('cuda:1'))
  • tensor.to(device):将device设置成一个可配置的参数device=t.device('cpu')
【保存和加载】
a = a.cuda(1) #转化为GPU1上的tensor
t.save(a,'a.pth')
b = t.load('a.pth', map_location = lamda storage, loc: storage) #存储于CPU
d = t.load('a.pth', map_location = {'cuda:1':'cuda:0'}) #存储于GPU0上
【向量化】

并行计算方式,提高科学计算效率

2 autograd(自动微分)

torch.autograd是为方便用户使用,而专门开发的自动求导引擎,能够根据输入二号前向传播过程自动构建计算图并执行反向传播。

【variable】
  • 创建tensor的时候只需设置tensor.requires_grad=True,代表需要求导数,需要求导(requires_grad)的tensor即为variable
# 方式1
a = t.randn(3, 4, requires_grad = True)
# 方式2
a = t.randn(3, 4).requires_grad()
# 方式3
a = t.randn(3, 4)
a.requires_grad = True
  • variable的grad与data形状一致,variable默认不需要求导,即requires_grad = false,如果一个节点的requires_grad=True,所有依赖它的节点requires_grad都为True
  • variable的volatile属性默认为False,即volatile = false,如果volatile=True,所有依赖它的节点volatile属性都为True。volatile属性为True的节点不会求导,优先级比requires_grad高
x = t.ones(2, 2, requires_grad = True)
【反向传播】
  • 计算图是一种特殊的有向无环图(DAG),用于记录算子与变量之间的关系。PyTorch采用动态图设计,可以很方便查看中间层输出,动态设计计算图结构。
  • 深度学习的算法本质是通过反向传播求导数,autograd会记录生成当前variable的所有操作,建立一个有向无环图,每个变量在图中位置可通过grad_fn属性记录了操作Function,叶子节点的grad_fn为None
  • 叶子节点:对于那些不是任何Function的输出,由用户创建的节点称为叶子节点,其grad_fn为None,叶子节点中需要求导的variable具有AccumulateGrad标识,其梯度累加
  • grad在反向传播过程中累加,每一次反向传播之前需要把梯度清0
x.grad.data.zero_()
  • 反向传播时计算各个variable梯度,只需调用根节点的backward方法,autograd会沿着图反向传播,计算每一个叶子节点的梯度
variable.backward(gradient_variable = None, retain_graph = None, create_graph = None)
  1. gradient_variables:形状与variable一致,对于y.backward(),grad_variables相当于链式法则dz/dx=(dz/dy)*(dy/dx)中的dz/dy,可以是tensor或序列,如果是标量,可以省略,默认为1
  2. retain_graph:反向传播的中间缓存会被清空,为进行多次反向传播,通过指定retain_graph = True不清空反向传播之后的中间缓存
  3. create_graph:对反向传播过程再次构建计算图,可通过backward of backward实现求高阶导数
【variable的grad属性和backward()函数的grad_variables】
  • variable x梯度是目标函数f(x)对x的梯度,df(x)/dx = (df(x)/dx0, df(x)/dx1, … df(x)/dxN),形状和x一致
  • y.backward(grad_variables)中的grad_variables可以看成链式求导的中间结果,相当于链式法则中的dz/dx = dz/dy * dy/dx中的dz/dy,z是目标函数,一般是一个标量,故dz/dy的形状和variable y的状一致
  • z.backward()在一定程度上等价于y.backward(grad_y)z.backward()省略了在grad_variables参数,因为z是一个标量,dz/dz = 1。
【修改tensor数值】
  • 如果想修改tensor数值,但不被autograd记录,可以对tensor.datatensor.detach()
  • 如果修改variable.data将无法使用autograd进行反向传播,除了对参数做初始化,一般不会修改variable.data的值,应避免
【查看变量梯度】

在反向传播过程中非叶子节点梯度计算完被清空,若想查看这些非叶子节点的梯度,使用autograd.grad函数或hook(推荐)

  1. 使用autograd.grad函数
x = t.ones(3, requires_grad = True)
w = t.rand(3, requires_grad=True)
y = x*w
z = y.sum()
# z对y的梯度,隐式调用backward()
t.autograd.grad(z, y)
  1. 使用hook
# hook是一个函数,输入是梯度,不应该有返回值
def variable_hook(grad):
	print('y的梯度:', grad)
	
x = t.ones(3, requires_grad = True)
w = t.rand(3, requires_grad = True)
y = x*w
# 注册hook
hook_handle = y.register_hook(variable_hook)
z = y.sum()
z.backward()
# 除非每次都用Hook,否则用完之后记得移除hook
hook_handle.remove()

【前向传播和反向传播实现】

  1. ctx是context的缩写, 翻译成"上下文; 环境",ctx专门用在静态方法中
  2. 自定义的Function需要继承autograd.Function,没有构造函数__init__forward()backward()都是静态方法
  3. self指的是实例对象;而ctx用在静态方法中, 调用的时候不需要实例化对象, 直接通过类名就可以调用, 所以self在静态方法中没有意义
  4. 自定义的forward()方法和backward()方法的第一个参数必须是ctx; ctx可以保存forward()中的变量,以便在backward()中继续使用,否则前向传播结束后这些对象即被释放
  5. ctx.save_for_backward(a, b)能够保存forward()静态方法中的张量, 从而可以在backward()静态方法中调用
  6. ctx.needs_input_grad是一个元组, 元素是True或者False, 表示forward()中对应的输入是否需要求导, 比如ctx.needs_input_grad[0]指的是下面forwad()代码中indices是否需要求导
  7. backward的输出个数,应与forward的输入个数相同,如果某一输入变量不需要梯度,在backward中返回None;backward函数的输入和forward函数的输出一一对应
from torch.autograd import Function
class MultiplyAdd(Function):
	@staticmethod
	def forward(ctx, w, x, b):
		ctx.save_for_backward(w, x)
		output = w * x + b
		return output
		
	@staticmethod
	def backward(ctx, grad_output):
		w, x = ctx.saved_tensors
		grad_w = grad_output * x
		grad_x = grad_output * w
		grad_b = grad_output * 1
		return grad_w, grad_x, grad_b
x = t.ones(1)
w = t.rand(1, requires_grad = True)
b = t.rand(1, requires_grad = True)
# 开始前向传播
z = MultiplyAdd.apply(w, x, b)
# 开始反向传播
# 方式1
z.backward()
# 方式2 调用MultiplyAdd.backward
z.grad_fn.apply(t.ones(1))

实现了自己的Function后,可以使用gradcheck函数检测实现是否正确。gradcheck通过数值逼近计算梯度,可能具有一定误差,通过控制eps大小可以控制容忍的误差。

class Sigmoid(Function):
	@staticmethod
	def forward(ctx, x):
		output = 1 / (1 + t.exp(-x))
		ctx.save_for_backward(output)
		return output
	
	@staticmethod
	def backward(ctx, grad_output):
		output, = ctx.saved_tensors
		grad_x = output * (1 - output) * grad_output
		return grad_x

# 采用数值逼近方式检验计算梯度的公式对不对
test_input = t.rand(3, 4, requires_grad = True).double()
t.autograd.gradcheck(Sigmoid.apply, (test_input,), eps = 1e-3)
【高阶求导】

之所以forward函数的输入是tensor,而backward函数的输入是variable,是为了实现高阶求导。backward函数的输入输出虽然是variable,但在实际使用时autograd.Function会将输入variable提取为tensor,并将计算结果的tensor封装成variable返回。在backward函数中,之所以要对variable进行操作,是为了能够计算梯度的梯度。

x = t.tensor([5], requires_grad=True,dtype=t.float)
y = x ** 2
grad_x = t.autograd.grad(y, x, create_graph=True)
grad_x # dy/dx = 2 * x
grad_grad_x = t.autograd.grad(grad_x[0],x)
grad_grad_x # 二阶导数 d(2x)/dx = 2

3 神经网络

  • torch.nn:神经网络模块化接口
  • nn.Module:网络各层定义和forward方法
  • nn.functional:和nn.Module类似,纯函数
  • 如果模型中有可学习的参数,用nn.Module,否则既可以用nn.functional也可以用nn.Module:对于激活函数(ReLU, sigmoid, tanh)、池化(MaxPool)等没有可学习参数,可以用nn.functional函数代替,对于卷积、全连接等具有可学习参数的网络使用nn.Module。
3.1 定义网络

(1) 需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在__init__中,如果某一层不具有可学习参数,可放在构造函数中,或在forwad中使用nn.functional代替

from torch.nn import functional as F
class Net(nn.Module)
	def __init__(self)
		super(Net, self).__init__()
		self.conv1 = nn.Conv2d(3, 6, 5)
		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 = F.pool(F.relu(self.conv1(x)), 2)
		x = F.pool(F.relu(self.conv2(x)), 2)
		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

(2) 网络可学习参数通过net.parameters()返回,net.named_parameters()可同时返回可学习的参数及名称
(3) nn.Conv2d输入必须是4维的,形如nSamples×nChannels×Height×Width
self.conv1 = nn.Conv2d(1, 6, 5) '1’输入通道,'6’输出通道,'5’卷积核
(4) torch.nn只支持mini-batches,一次必须是一个batch
(5) view()相当于numpy中的reshape,重新定义矩阵形状,-1代表动态调整这个维度的元素个数
(6) 卷积层:卷积 > 激活 > 池化 input -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss

【全连接层】
import torch as t
from torch import nn

class Linear(nn.Module):
	def __init__(self, in_features, out_features):
		super(Linear, self).__init__()
		self.w = nn.Parameter(t.randn(in_features, out_features))
		self.b = nn.Parameter(t.randn(out_features))
	
	def forward(self, x):
		x = x.mm(self.w)
		return x + self.b.expand_as(x)
  • 构造函数使用super(Linear, self).__init__()(推荐)或nn.Module.__init__(self)
  • 构造函数__init__必须自己定义可学习的参数,并封装为Parameter(一种特殊的Tensor),默认需要求导requires_grad = True
  • forward实现前向传播过程,输入可以是一个或多个tensor
    无需写反向传播函数,nn.Module能利用autograd实现反向传播,比利用Function实现更简单
  • 使用时可以将layer看成数学概念中的函数,layer(input)等价于layers.__call__(input),在__call__函数中调用layer.forward(x),实际尽量使用layer(x)而不是layer.forward(x)
  • 输入输出:nn.linear的输入形状是(N, input_features),输出为(N, output_features),N是batch_size。默认输入不是单个数据,而是一个batch,如果输入只有一个数据,必须调用tensor.unsqueeze(0)tensor[None]将数据伪装成batch_size=1的batch
  • 全连接层对模型影响参数有三个:全连接层的总层数(长度)、单个全连接层的神经元数(宽度)、激活函数
  • 全连接层的意义:(详见https://blog.csdn.net/zfjBIT/article/details/88075569)
    把特征representation整合到一起,输出为一个值,大大减少特征位置对分类带来的影响。连接层实际就是卷积核大小为上层特征大小的卷积运算,卷积后的结果为一个节点,就对应全连接层的一个点。

假设最后一个卷积层的输出为7×7×512,连接此卷积层的全连接层为1×1×4096。如果将这个全连接层转化为卷积层:

  1. 共有4096组滤波器
  2. 每组滤波器含有512个卷积核
  3. 每个卷积核的大小为7×7
  4. 则输出为1×1×4096

若后面再连接一个1×1×4096全连接层。则其对应的转换后的卷积层的参数为:

  1. 共有4096组滤波器
  2. 每组滤波器含有4096个卷积核
  3. 每个卷积核的大小为1×1
  4. 输出为1×1×4096

相当于就是将特征组合起来进行4096个分类分数的计算,得分最高的就是划到的正确的类别。

【卷积层】
from PIL import Image
from torchvision.transform import ToTensor, ToPILImage
to_tensor = ToTensor()  # img -> tensor
to_pil = ToPILImage()
lena = Image.open("imgs/lena.png")

# 输入是一个batch, batch_size = 1
input = to_tensor(lena).unsqueeze(0)

# 锐化卷积核
kernel = t.ones(3, 3)/-9.
kernel[1][1] = 1
conv = nn.Conv2d(1, 1, (3, 3), 1, bias = False)
conv.weight.data = kernel.view(1, 1, 3, 3)
out = conv(input)
to_pil(out.data.squeeze(0))
【池化层】

特殊的卷积层,用来下采样。池化层没有可学习参数,其weight是固定的。

pool = nn.AvgPool2d(2, 2)
out = pool(input)
【BatchNorm】

批规范化层,分为1D、2D和3D。除了标准的BatchNorm之外,还有在风格迁移中常用到的InstanceNorm层。

# 输入batch_size = 2,维度3
input = t.randn(2, 3)
linear = nn.Linear(3, 4)
h = linear(input)

# 4 channel,初始化标准差为4,均值为0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)
bn_out = bn(h)  # 输出的均值、方差
【Dropout】

防止过拟合,分为1D、2D和3D

# 每个元素以0.5的概率舍弃
dropout = nn.Dropout(0.5)
o = dropout(bn_out)
【激活函数】

最常用的激活函数ReLU:数学表达式为ReLU(x) = max(0, x)

relu = nn.ReLU(inplace = True)
input = t.randn(2, 3)
output = relu(input)

ReLU函数有个inplace函数,如果设为True,会把输出直接覆盖到输入中,这样可以节约内存/显存。之所以可以覆盖,是因为在计算ReLU的反向传播时,只需根据输出就能够推算反向传播的梯度。但只有少数的autograd操作支持inplace操作如tensor.sigmoid_(),除非明确知道自己在做什么,否则不要使用inplace操作。

【Sequential/ModuleList】
  • Sequential:前馈神经网络将每层输出作为下一层输入,不用每次都写forward,包含几个子Module,前向传播会把输入层一层接一层传递下去

方式1

net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())

方式2

net2 = nn.Sequential(nn.Conv2d(3, 3, 3), nn.BatchNorm2d(3), nn.ReLU())

方式3

from collections import OrderedDict
net3 = nn.Sequential(OrderedDict([('conv1', nn.Conv2d(3, 3, 3)),('bn1', nn.BatchNorm2d(3)),('relu1', nn.ReLU())]))

可根据名字或序号取出子module:net1.conv, net2[0], net3.conv1

  • ModuleList:一个特殊Module,可以像list使用,但不能直接把输入传给ModuleList
modellist = nn.ModuleList([nn.Linear(3, 4), nn.ReLU(), nn.Linear(4, 2)])
input = t.randn(1, 3)
for model in modellist:
	input = model(input)
# 下面会报错
# output = modelist(input)
  • ParameterList:一个可以包含多个parameter的类list对象,使用方式和ModuleList类似,如果在构造函数__init__中用到list, tuple, dict对象时,考虑是否该用ModuleList或ParameterList代替
3.2 损失函数

损失函数可看作一种特殊的layer,PyTorch将这些损失函数实现为nn.Module的子类,然而实际使用中通常将这些loss function专门提取出来,和主模型互相独立。

  • nn.MSELoss:计算均方误差
  • nn.CrossEntropyLoss:计算交叉熵损失
  • loss.backward():动态生成并自动微分
score = t.randn(3, 2) # batch_size=3,计算每个类别的分数
label = t.Tensor([1, 0, 1]).long() # 三个样本属于1,0,1类,label是LongTensor
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
3.3 优化器

在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(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,都是继承基类在optim.Optimizer,大多数时候不需要手动实现

from torch import optim

#新建一个优化器,使用时指定要调整的参数和学习率
optimizer = optim.SGD(net.parameters(), lr = 0.01)

#训练过程中梯度清0,等价于net.zero_grad()
optimizer.zero_grad()

#计算损失
input = t.randn(1, 3, 32, 32)
output = net(input)
loss = criterion(output, target)

# 反向传播
loss.backward()

# 更新参数
optimizer.step()
  • 为不同子网络设置不同学习率,在finetune中常用,如果对某个参数不指定学习率就用最外层的学习率
optimizer = optim.SGD([{'params': net.features.parameters()},{'params': net.classifier.parameters(), 'lr': 1e-2}],lr = 1e-5)
  • 为两个全连接层设置较大的学习率,其余层的学习率较小
special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
special_layers_params = list(map(id, special_layers.parameters()))
base_params = filter(lambda p : id(p) not in special_layers_params, net.parameters())
  • 调整学习率的两种方法:

方法1 调整学习率,新建一个optimizer

old_lr = 0.1
optimizer1 = optim.SGD([{'params': net.features.parameters()},{'params': net.classifier.parameters(), 'lr': old_lr*0.1}], lr = 1e-5)

方法2 调整学习率,手动decay,保存动量

for param_group in optimizer.param_groups:
	param_group['lr'] *= 0.1
optimizer
3.4 初始化策略

良好的初始化可以使模型更快收敛,nn.Module采取了较合理的初始化策略,也可以自定义初始化,如在使用Parameter时。t.Tensor()返回的是内存中的随机数,可能有极大值,造成训练网络溢出或梯度消失

t.maual_seed(1)  #为CPU/GPU设置种子用于生成随机数,以使得结果是确定的
init.xavier_normal_(linear.weight)
3.5 nn.functional
3.6 nn和autograd的关系

autograd.Functionnn.Module区别:

  1. autograd.Function利用Tensor对autograd技术的拓展,为autograd实现了新的运算op,不仅要实现前向传播还要手动实现反向传播;
  2. nn.Module利用autograd技术,对nn功能进行拓展,实现深度学习中更多层,只需实现前向传播功能,autograd会自动实现反向传播;
  3. nn.functional是一些autograd操作的集合,是经过封装的函数
3.6 nn.Module深入分析
【nn.Module属性】
  1. _parameters:字典,在字典中加入key为’param’,value为对应parameter的item
  2. _modules:子module,通过self.submodel = nn.Linear(3,4)指定的子module会保存于此
  3. _buffers:缓存,如batchnorm使用momentum机制,每次前向传播用到上一次传播的结果
  4. _backward_hooks_forward_hooks:钩子技术,用来提取中间变量
model = VGG()
features = t.Tensor()
def hook(module, input, output):
	features.copy_(output.data)
handle = model.layer8.register_forward_hook(hook)
_ = model(input)
handle.remove() #用完hook后删除
【training】
  • Batch层在训练阶段和测试阶段采取的策略不同,通过判断training决定前向传播策略
  • 对于batchnorm, dropout, instancenorm等在训练和测试阶段行为差距巨大的层,需要在测试时将training值设为False
  • 可以调用model.train()函数,将当前module及其子module中所有training属性设为True,model.eval()函数,将training属性设为False
【构造:__getattr__和__setattr__方法】
  • result = obj.name会调用buildin函数getattr(obj, 'name'),如果该属性找不到,调用obj.__getattr__('name'),返回一个对象属性值
  • obj.name = value会调用buildin函数setattr(obj, 'name', value),如果obj对象实现了在__setattr__方法,setattr会直接调用obj.__setattr__('name','value'),设置一个对象属性值
【保存和加载模型】
#保存模型
t.save(net.state_dict(), 'net.pth') 
#加载已保存的模型
net2 = Net()
net2.load_state_dict(t.load('net.pth'))
【GPU运算】
  • 将模型所有参数转存GPU:model = model.cuda()
  • 将输入数据放置到GPU上:input.cuda()
  • 在多个GPU上进行并行计算

方式1 直接利用多GPU并行计算得出结果

nn.parallel.data_parallel(module, inputs, device_ids = None, output_device = None, dim = 0, module_kwargs = None) 

方式2 返回一个新的module,自动在GPU上进行并行加速

class torch.nn.DataParallel(module, device_ids = None, output_device = None, dim = 0)
  1. device_ids参数指定在哪些GPU上进行优化
  2. output_device指定输出到哪个GPU上

DataParallel并行方式,是将输入一个batch的数据分成多份,分别送到对应GPU计算,各个GPU得到的梯度累加,与Module相关的数据也都以浅复制的方式复制多份。module中属性应是已读的。

4 用CIFAR-10进行神经网络训练

【数据加载】
  • 图片预处理:利用transforms.ToTensor()转化为tensor,再用transforms.Normalize()归一化
  • Dataloader是一个可迭代的对象,将dataset返回的每一条数据拼接成一个batch,提供多线程加速优化和数据打乱等
trainset = tv.datasets.CIFAR10(root = './dataset', train = True, download = True, transform = transform)
trainloader = t.utils.data.DataLoader(trainset, batch_size = 4, shuffle = True, num_workers = 2)  #一个batch返回4张图片
【训练网络】
  • 流程:①输入数据;②前向传播+反向传播;③更新参数
  • 每遍历完一遍数据集称为一个epoch
  • t.set_num_threads:设置pytorch进行CPU多线程并行计算时占用的线程数,在linux和windows平台都有效
  • t.set_printoptions(precision = 10):设置打印tensor时的数值精度和格式
for epoch in range(2): #遍历数据集
	running_loss=0.0
	for i, data in enumerate(trainloader, 0):
		#输入数据
		inputs, labels = data
		#梯度清0
		optimizer.zero_grad()
		#forward+backward
		outputs = net(inputs)
		loss = criterion(outputs, labels)
		loss.backward()
		#更新参数
		optimizer.step()
		#打印log:loss是一个scalar,需要使用loss.item()获取数值
		running_loss += loss.item()
		if i % 2000 == 1999:
			print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
			running_loss = 0.0
print('Finish Training')

遍历dataloader的另一种方法:

dataiter = iter(testloader)
images, labels = dataiter.next() #一个batch返回4张图片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值