第一章 深度学习与PyTorch
1.1 机器学习
在人工智能领域,机器学习是实现人工智能的一个分支,也是其中发展最快的一个分支。
简单来说,机器学习是计算机程序如何随着经验的积累而提高性能,使系统自我完善的过程。
根据应用场景和学习方式的不同,可以简单分为三类:
无监督学习 unsupervised learning
无监督学习不需要提前知道数据集的类别标签,通常运用场景为聚类和降维。
半监督学习 semi-supervised learning
半监督学习介于二者之间,利用极少的有标签数据和大量无标签数据进行学习,同过学习得到的经验对无标签的数据进行预测。
有监督学习 supervised learning
主要特征是数据集具备标签数据。
1.2 深度学习
深度学习是一种机器学习方法,与传统的机器学习方法相同,可以根据输入的数据进行分类或回归。但随着数据量的增加,传统机器学习方法表现不尽如人意,而此时利用更深的网络挖掘数据信息的方法----深度学习表现出了优异的性能,2010年后,更是迎来了爆发式增长。
在20世纪60~70年代,神经生理学家发现猫的视觉皮层中有两种细胞,一种是简单细胞,他们对图像中的细节信息更加敏感,如边缘、角点。另一种是复杂细胞,能够分析处理图像的空间不变性,如旋转、放缩、远近等图像。学者们根据这点提出了卷积神经网络,在图像处理方面取得了较大的成果。
事实上,在当时未能解决梯度消失问题前,基于深度卷积神经网络的算法并没有引起人们的重视,在分层训练过程中,本应用于修正模型参数的误差随着层数的增加指数递减,这就导致了模型训练效果低下。
尽管深度算法在2000年前就被使用,并且受到了SVM的压制,但其发展并未停止,1992年多层网络提出,通过无监督学习训练深度网络中的每一层,再通过反向传播算法调优。在这一模型中,神经网络的每一层都代表观测变量的一种压缩表示,而这一表示又被传入到下一层网络。
进入21世纪后,随着数据量的积累和计算机性能的提升,神经网络终于迎来了新一轮的春天。深度学习算法和传统的机器学习算法相比,其最大的特点是端到端的学习,在进行学习之前无需进行特征提取等操作,可以通过深层的网络结构自动从原始图像中提取有用的特征。而传统的机器学习过程则需要更多的人工干预,其稳定性较弱。
Pytorch
一.Pytorch是什么?
Pytorch是torch的python版本,是由Facebook 开源的神经网络框架,专门针对 GPU 加速的深度神经网络(DNN)编程。Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor )库,在机器学习和其他数学密集型应用有广泛应用。与Tensorflow的静态计算图不同,pytorch的计算图是动态 的,可以根据计算需要实时改变计算图 。但由于Torch语言采用 Lua,导致在国内一直很小众,并逐渐被支持 Python 的 Tensorflow 抢走用户。作为经典机器学习库 Torch 的端口,PyTorch 为 Python 语言使用者提供了舒适的写代码选择。
二、Pytorch优势
自动求导
例如:
1 2 3 4 5 6 7 8 9 10 11 12 import torchx=torch.tensor(1. ,) a=torch.tensor(1. ,requires_grad=True ) b=torch.tensor(2. ,requires_grad=True ) c=torch.tensor(3. ,requires_grad=True ) y=a**2 *x+b*x+c print ('before' ,a.grad,b.grad,c.grad)grads=torch.autograd.grad(y,[a,b,c]) print ('after' ,grads[0 ],grads[1 ],grads[2 ])
GPU加速计算(可用于numpy的加速)
常用API
简洁
不同于TF,PyTorch的设计遵照tensor->variable(autograd)->nn.Module三个由低到高的抽象层次,分别代表高维数组(张量)、自动求导(变量)和神经网络(层/模块)。PT的源码只有TF的十分钟之一左右,更少的抽象、更直观的设计使得PT十分容易阅读。
速度
易用
线性回归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 def error_rate (a,b,points ): total=0 for i in range (0 ,len (points)): x=points[i][0 ] y=points[i][1 ] total+=(x*a+b-y)**2 return total/float (len (points)) def step_gradient (a_cur,b_cur,points,lr ): b_gradient=0 a_gradient=0 size=float (len (points)) total=0 for i in range (0 ,len (points)): x=points[i][0 ] y=points[i][1 ] a_gradient+=-2 *(y-(a_cur*x+b_cur))*x/size b_gradient+=-2 *(y-(a_cur*x+b_cur))/size total += (y-(a_cur*x+b_cur)) ** 2 return [a_cur-a_gradient*lr,b_cur-b_gradient*lr,total/size] def linear_regressing (points,start_a,start_b,learningrate,iterations ): a=start_a b=start_b time=0 for i in range (iterations): a,b,tem=step_gradient(a,b,points,learningrate) time+=1 if time%10 ==0 : print (tem) return [a,b] import randomimport matplotlib.pyplot as pltpoint=[] for i in range (50 ): t = random.randint(0 , 100 ) point.append([t,t*2 +6 +random.randrange(0 ,10 )]) plt.figure(figsize=[20 ,8 ],dpi=300 ) for i in point: plt.plot(i[0 ],i[1 ],'o' ) a,b=linear_regressing(point,2 ,6 ,0.0002 ,10000 ) new_x=[i for i in range (0 ,101 ) if i%10 ==0 ] new_y=[i*a+b for i in new_x] plt.plot(new_x,new_y) print (a,b)plt.show()
Torch基本操作
1.Torch数据类型
dim: 维度,如2
shape: 长度/形状,如[2,2]
1.1标量Scalar
常见于loss评估
1 2 3 4 5 6 7 8 9 10 11 torch.tensor(1. ) a=torch.tensor(2 ,2 ) a.shape len (a.shape) list (a.shape)a.item()
1.2向量Vector
常见于bias和linear_input
1 2 3 4 5 6 7 8 9 torch.tensor([1.1 ,2.2 ]) torch.FloatTensro(n) data=np.ones(2 ) torch.from_numpy(data)
1.3张量Tensor
实际上在torch0.4之后都叫张量了
1 2 3 4 5 6 7 8 9 10 a=torch.randn(1 ,2 ,3 ) a.numel() a.dim()
Tensor的数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 a=torch.randn(2 ,3 ) a.type () type (a)isinstance (a,torch.FloatTensor)isinstance (data,torch.cuda.DoubleTensor)data=data.cuda data=data.float () data=data.int ()
类型表
注意,Torch没有string类型,可以通过one-hot处理,或是语义关系采用embedded中的word2vec。
1.4创建Tensor
我们可以从numpy中获取
1 2 a=np.ndarray([1 ,2 ,3 ,4 ,5 ]) a=torch.from_numpy(a)
可以从list中获取
1 2 torch.tensor([2 ,3 ]) torch.FloatTensor([2. ,3. ])
注意torch.tensor是接收现有数据,而torch.Tensor是接收数据维度(当然也可以接收现实数据)
1 2 torch.FloatTensor(2 ,3 ) torch.FloatTensro([2 ,3 ])
生成未初始化 的数据
设置默认数据类型
1 2 torch.set_default_tensor_type(torch.DoubleTensor)
生成分布 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 a=torch.rand(3 ,3 ) torch.rand_like(a) torch.randint(1 ,10 ,[3 ,3 ]) torch.randn(3 ,3 ) torch.normal(mean=torch.full([10 ],0 ),std=torch.arange(1 ,0 ,-0.1 ))
全部赋值
1 2 3 4 torch.full([2 ,3 ],7 ) torch.full([],7 )
等差数列
1 2 3 torch.arange(0 ,10 ) torch.arange(0 ,10 ,2 )
等分生成
1 2 3 4 5 6 torch.linspace(0 ,10 ,steps=4 ) torch.logspace(0 ,-1 ,steps=10 )
规则矩阵 生成
1 2 3 4 5 6 7 8 9 torch.eye(3 ) torch.zeros(3 ,3 ) torch.ones_like(a)
随机打散
1 2 3 4 5 6 7 8 9 10 torch.randperm(10 ) a=torch.rand(2 ,3 ) b=torch.rand(2 ,2 ) idx=torch.randperm(2 ) a[idx] b[idx]
2.Tensor索引与切片
tensor的切片索引非常大胆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 a=torch.rand(4 ,3 ,28 ,28 ) a[0 ].shape a[0 ,0 ].shape a[0 ,0 ,2 ,4 ] a[:2 ].shape a[:2 ,1 :,:,:].shape a[:2 ,-1 ,:,:].shape a[:,:,0 :28 :2 ,0 :15 :3 ].shape a[:,:,::2 ,::2 ].shape torch.index_select(dim,[]) torch.index_select(0 ,[1 ,2 ]) a.index_select(2 ,torch.arange(8 )).shape a[...].shape a[0 ,...].shape a[0 :,...,::2 ] x=torch.randn(3 ,4 ) mask=x.ge(0.5 ) torch.masked_select(x,mask) scr=torch.tensor([[4 ,3 ,5 ], [6 ,7 ,8 ]]) torch.take(scr,torch.tensor[[0 ,2 ,-1 ]])
3.维度变化
3.1 view与reshape
view与reshape效果完全一致
1 2 3 a=torch.randn(4 ,1 ,28 ,28 ) a.view(4 ,28 *28 )
3.2 squeeze与unsqueeze
压缩与维度增加
unsqueeze可以在指定位置上插入新的维度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 a.shape a.unsqueeze(0 ).shape a.unsqueeze(4 ).shape b=torch.rand(32 ) f=torch.rand(4 ,32 ,14 ,14 ) b=b.unsqueeze(1 ).unsqueeze(2 ).unsqueeze(0 ) b.shape
squeeze则是删减维度
1 2 3 4 5 6 7 8 9 a.shape a.squeeze().shape a.squeeze(0 ).shape
3.3 expand 与 repeat
expand 并没有主动复制数据,而repeat则会copy数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 a=torch.rand(4 ,32 ,14 ,14 ) b.shape b.expand(4 ,32 ,14 ,14 ).shape b.expande(-1 ,32 ,-1 ,-1 ).shape b.repeat(4 ,32 ,1 ,1 ).shape
3.4 转置
.t
1 2 3 a=torch.randn(3 ,4 ) a.t()
transpose
1 2 3 4 5 6 7 al=a.transpose(1 ,3 ).contiguous().view(4 ,3 *32 *32 ).view(4 ,32 ,32 ,3 ).transpose(1 ,3 )
permute
4. 自动扩展
核心思想 :
若前方无维度,则新增维度
将不足的维度扩展到需求维度
有则相加,无则补全
注意,expand不能自动增加维度,所以需要先用unqueeze增加维度
1 b.unsqueeze[0].unsqueeze[-1].expand_as(A)
能够自动扩展的情况:
需要添加的数据符合最后一维数量
添加的数据维度与需要处理的数据维度相同
5. 拼接与分割
5.1 cat
1 2 3 4 5 6 a=torch.rand(4 ,32 ,8 ) b=torch.rand(5 ,32 ,8 ) torch.cat([a,b],dim=0 ).shape
5.2 stack
stack会在需要合并的维度前新添加一个维度(等于上半部分是A,下半部分是B),且要求维度完全一致。
1 2 3 4 5 a=torch.randn([32 ,32 ,16 ]) b=torch.randn([32 ,32 ,16 ]) torch.stack([a,b],dim=0 ).shape
5.3 split
split是按长度分割
1 2 3 4 5 6 7 8 9 a=torch.rand(4 ,32 ,8 ) t=a.split(7 ,dim=1 ) t[4 ].shape a.split([15 ,1 ,16 ],dim=1 )
5.4 Chunk
chunk是等距分割
6.Tensor运算
加法
1 2 3 4 5 a=torch.rand(3 ,4 ) b=torch.rand(4 ) a+b torch.add(a,b)
减法
乘法
除法
矩阵相乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 torch.mm(a,b) torch.matmul(a,b) a@b a=torch.rand(4 ,784 ) x=torch.rand(4 ,784 ) w=torch.rand(512 ,784 ) (x@w.t()).shape
幂运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 a=torch.full([2 ,2 ],3 ) a.pow (2 ) a**2 a.sqrt() a**0.5 a=torch.exp(torch.ones(2 ,2 )) torch.log(a) torch.ceil() torch.floor() torch.round () torch.trunc() torch.frac()
梯度裁剪
1 2 torch.clamp(10 ) torch.clamp(min ,max )
7.统计API
范数norm
L0范数 :向量中非零元素的数目
L1范数 :向量中各个元素的绝对值之和,也称稀疏规则算子
L2范数 :向量中各个元素的平方求和后开根号。其回归称为“岭回归 ”,也称权值衰减
L2范数可以防止过拟合,提高模型泛化能力
1 2 3 tensor.norm(1 ) tensor.norm(2 ) tensor.norm(1 ,dim=1 )
统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 tensor.min () tensor.max () tensor.mean() tensor.prod() tensor.sum () tensor.argmax() tensor.argmin() tensor.topk(3 ,dim=1 ,largest=False ) tensor.kthvalue(4 ,dim=1 ) a.shape a.max (dim=1 ) a.max (dim=1 ,keepdim=True )
比较
返回的都是逻辑矩阵
1 2 3 4 5 > >= < <= != == torch.eq(a,b)
8.高级操作
三元运算符
torch.where(condition,x,y)->Tensor
o u t : x i i f c o n d i t i o n i e l s e y i out: x_i\quad if \quad condition_i \quad else \quad y_i o u t : x i i f co n d i t i o n i e l se y i
1 2 3 4 cond=torch.rand([2 ,2 ]) a=torch.full([2 ,2 ],0 ) b=torch.full([2 ,2 ],1 ) torch.where(cond>0.5 ,a,b)
查表
torch.gather(input,dim,index,out=None)
在input中的dim维寻找符合index的值
随机梯度下降
1.梯度
导数:某一方向上的变化率
偏微分:指定方向的变化率
梯度:偏微分向量[所有方向的变化率向量]
梯度具有方向 和大小
那么,所谓梯度下降 ,就是沿着梯度变化最大 (也就是函数本身变化最快)的方向前进。这样在满足一定学习率的情况下,能够到达局部极小值点 。
鞍点 :某一方向达到了局部最小,但另一方向处于局部最大
影响搜索结果的因素:
1 2 3 4 5 6 torch.autograd.grad(equation,[parameter]) equation.backward() parameter.grad()
2.激活函数
2.1Sigmoid
科学家发现动物的神经元存在某一阈值 ,当输入刺激小于该阈值时,不会引起神经元变化。于是,学者们借鉴该思想,设置了激活函数。
激活函数是不连续的,不可以求导。为了解决单层神经元激活函数不可导的情况,科学家提出了平滑连续 的激活函数曲线,即Sigmoid ,也叫Logistic 。
其导数性质能够直接通过本身求得,十分方便,能将数据范围压缩到[0,1]
sigmoid会出现梯度离散现象,即长时间的梯度不更新
1 2 a=torch.linspace(-100 ,100 ,10 ) torch.sigmoid(a)
2.2Tanh
常见于RNN网络
取值为[-1,1]
2.3Rectified Linear Unit
减少梯度离散和梯度爆炸,保证梯度连续
1 2 3 torch.relu(a) import torch.nn.function as FF.relu(a)
2.4LeakyReLU
1 nn.LeakyReLU(inplace=True )
3.LOSS
MSE
均方根误差
torch.norm(y-pred,2).pow(2)
F.mse_loss(x,y)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #### **Softmax** + 常用于分类任务 + 输入是对于类别强度值,输出是概率 + 柔性最大传输网络能够保证概率之和为1 + 属于金字塔效应函数 + 每个节点对每个输入都有偏导 + ```python a=torch.rand(3,requires_grad=True) from torch.nn import functional as F p=F.softmax(a,dim=0) torch.autograd.grad(p[1],[a],retain_graph=True) torch.autograd.grad(p[2],[a],retain_graph=True)
注意torch是动态图,每次都需要更新,backward时得格外注意,若是想要不更新,可以使用retain_graph参数。求导只能对scalar类型进行求导。
Cross Entropy
置信度的度量,更高的不确定性
E n t r o p y = − ∑ i P ( i ) l o g P ( i ) Entropy=-\sum_iP(i)logP(i)
E n t ro p y = − i ∑ P ( i ) l o g P ( i )
1 2 a=torch.full([4 ],1 /4. ) -(a*torch.log2(a)).sum ()
衡量两个分布的不稳定
H ( p , q ) = − ∑ p ( x ) l o g q ( x ) H ( p , q ) = H ( p ) + D K L ( p ∣ q ) H(p,q)=-\sum p(x)logq(x)
\\
H(p,q)=H(p)+D_{KL}(p|q)
H ( p , q ) = − ∑ p ( x ) l o g q ( x ) H ( p , q ) = H ( p ) + D K L ( p ∣ q )
KL即分布离散度
优化目标即让两个分布越来越接近,也就是KL最小
一个好的模型应该让不确定性降到最低
于是,优化就是判断
m i n . C E min.\quad CE
min . CE
例如一个二分类案例
第一步是将所有预测概率(即P(cat)、P(dog))与实际概率Q(cat)、Q(dog)求交叉熵
y表示预测概率,p表示实际概率
1 2 3 4 5 6 7 8 9 10 11 x=torch.randn(1 ,784 ) w=torch.randn(10 ,784 ) logits=x@w.t() F.cross_entrop(logits,torch.tensor([3 ])) pre=F.softmax(logits,dim=1 ) pred_log=torch.log(pre) F.nll_loss(pred_log,torch.tensor([3 ]))
1.感知机
感知机(perceptron)是二类分类的线性分类模型 ,其输入为实例的特征向量,输出为实例的类别。
单层感知机
输入层 :第0层,其元素表示x n 0 x_n^0 x n 0 ,0表示第1层,n表示所在节点次序
隐含层 :第1层,其元素表示为W i j 1 W_{ij}^1 W ij 1 ,1表示第1层,i表示上一层节点,j表示当前层连接节点
激活层 :其元素表示为O 1 1 O_1^1 O 1 1 ,为经过激活后的元素。
损失层 :E层,表示loss
发现其导数仅跟神经元节点输出和输入层有关。
1 2 3 4 5 6 7 8 9 10 11 12 x=torch.randn(1 ,10 ) w=torch.randn(1 ,10 ,requires_grad=True ) o=torch.sigmoid(x@w.t()) loss=F.mse_loss(torch.ones(1 ,1 ),o) loss.backward() w.grad
多层感知机
1 2 3 4 5 6 7 8 9 10 11 12 x=torch.randn(1 ,10 ) w=torch.randn(2 ,10 ,requires_grad=True ) o=torch.sigmoid(x@w.t()) loss=F.mse_loss(torch.ones(1 ,2 ),o) loss.backward() w.grad
2.链式法则
3.MLP反向传播推导
Multi-Layer Perceptron 多层感知机
4.函数优化实例
target
实现绘制该函数覆盖图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def himmelblau (x ): return (x[0 ]**2 +x[1 ]-11 )**2 +(x[0 ]+x[1 ]**2 -7 )**2 import numpy as npimport matplotlib.pyplot as pltx=np.arange(-6 ,6 ,0.1 ) y=np.arange(-6 ,6 ,0.1 ) X,Y=np.meshgrid(x,y) print ("X,Y maps: " ,X.shape,"before :" ,x.shape)Z=himmelblau([X,Y]) fig=plt.figure("HIMMELBLAU" ) ax=fig.gca(projection='3d' ) ax.plot_surface(X,Y,Z) ax.view_init(60 ,-30 ) ax.set_xlabel('x' ) ax.set_ylabel('y' ) plt.show()
利用随机梯度下降优化
注意优化目标不再是LOSS,而是函数本身,求导对象是x,即δ P r e δ x \frac{\delta P_{re}}{\delta x} δ x δ P re
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 x=torch.tensor([0. ,0. ],requires_grad=True ) optimizer=torch.optim.Adam([x],lr=1e-3 ) for i in range (20000 ): pred=himmelblau(x) optimizer.zero_grad() pred.backward() optimizer.step() if i % 2000 ==0 : print ("step {}: x={},f(x)={}" .format (i,x.tolist(),pred.item()))
神经网络
1.逻辑回归
分类问题
问题一:当权重改变时,梯度很可能不变!因为准确率不变!
问题二:梯度不连续!【从0.6直接变成0.8】
逻辑回归
本质上更像分类
使用了sigmoid函数,使用MSE评估回归,Cross Entropy评估分类
在判断输出值与阈值的距离,用以评估二分类,并通过MSE评估输出效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import torchfrom torchvision import datasets,transformsfrom torch.utils.data import DataLoaderfrom torch.nn import functional as Fbatch_size=200 lr=0.01 epochs=10 train_loader=DataLoader( datasets.MNIST('../data' ,train=True ,download=True ,transform= transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307 ),(0.3081 ,)) ])), batch_size=batch_size,shuffle=True ) test_loader=DataLoader( datasets.MNIST('../data' ,train=False ,transform= transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307 ),(0.3081 ,)) ])), batch_size=batch_size,shuffle=True ) w1=torch.randn(200 ,784 ,requires_grad=True ) b1=torch.zeros(200 ,requires_grad=True ) w2=torch.randn(200 ,200 ,requires_grad=True ) b2=torch.zeros(200 ,requires_grad=True ) w3=torch.randn(10 ,200 ,requires_grad=True ) b3=torch.zeros(10 ,requires_grad=True ) def forward (x ): x=x@w1.t()+b1 x=F.relu(x) x=x@w2.t()+b2 x=F.relu(x) x=x@w3.t()+b3 x=F.relu(x) return x optimizer=torch.optim.SGD([w1,b1,w2,b2,w3,b3],lr=1e-3 ) criteon=torch.nn.CrossEntropyLoss() for epoch in range (10000 ): for batch_idx,(data,target) in enumerate (train_loader): data=data.view(-1 ,28 *28 ) logits=forward(data) loss=criteon(logits,target) optimizer.zero_grad() loss.backward() optimizer.step() if batch_idx % 100 ==0 : print ("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss:{:.6f}" . format (epoch,batch_idx*len (data),len (train_loader.dataset), 100. *batch_idx/len (train_loader),loss.item()))
发现陷入了局部最小值,本次并没有做初始化,如果做了初始化就能大大降低损失。
2.全连接层
1 2 3 4 5 6 7 8 9 x=torch.tensor([1 ,784 ]) layer1=nn.Linear(784 :in ,200 :out) layer2=nn.Linear(200 ,200 ) layer3=nn.Linear(200 ,10 ) x=layer3(layer2(layer1(x))) x=layer1(x) x=F.relu(x,inplace=True )
如何创建一个层?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MLP (nn.Module): def __init__ (self ): super (MLP,self).__init__() self.model=nn.Sequential( nn.Linear(784 ,200 ), nn.ReLU(inplace=True ), nn.Linear(200 ,200 ), nn.ReLU(inplace=True ), nn.Linear(200 ,10 ), nn.ReLU(inplace=True ), ) def forward (self,x ): return self.model(x)
pyTorch有两种风格的API,一种是class风格的API,如nn.ReLU,一种是函数风格的API,如F.relu()
类风格的API需要构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 net=MLP() optimizer=torch.optim.SGD(net.parameters(),lr=1e-3 ) criteon=torch.nn.CrossEntropyLoss() for epoch in range (10000 ): for batch_idx,(data,target) in enumerate (train_loader): data=data.view(-1 ,28 *28 ) logits=net(data) loss=criteon(logits,target) optimizer.zero_grad() loss.backward() optimizer.step() if batch_idx % 100 ==0 : print ("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss:{:.6f}" . format (epoch,batch_idx*len (data),len (train_loader.dataset), 100. *batch_idx/len (train_loader),loss.item()))
3.GPU加速
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 device=torch.device("cuda:0" ) net=MLP().to(device) optimizer=optim.SGD(net.paameters(),lr=lr) criteon=nn.CrossEntropyLoss().to(device) for e in range (epochs): for batch_idx,(data,target) in enumerate (train_loader): data=data.view(-1 ,28 *28 ) data,target=data.to(device),target.cuda()
精度验证
1 2 3 4 5 6 7 8 9 logits=torch.rand(4 ,10 ) pred=F.softmax(logits,dim=1 ) pred_label=pred.argmax(dim=1 ) label=torch.tensor([9 ,2 ,3 ,4 ]) correct=torch.eq(pred_label,label) correct.sum ().float ().item()/4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 test_loss=0 correct=0 for data,target in test_loader: data=data.view(-1 ,28 *28 ) data,target=data.to(device),target.cuda() logits=net(data) test_loss+=criteon(logits,target).item() pred=logits.argmax(dim=1 ) correct+=pred.eq(target).float ().sum ().item() test_loss/=len (test_loader.dataset)
可视化交互
TensorboardX
visdom
相较于TBX读取的numpy,VD能直接跟Tensor进行交互(当然只能是img),且不会大量存放监听文件。
本质上是一个外部的服务器,需要配合命令。
python -m visdom.server
于是就能在8097端口找到visdom。
如何使用?
单线追踪
1 2 3 4 5 6 7 8 9 10 11 12 13 from visdom import Visdomviz=Visdom() viz.line([0. ],[0. ],win='train_loss' ,opts=dict (title="train loss" )) viz.line([loss.item()],[global_step],win="train_loss" ,update="append" )
多线追踪
1 2 3 4 5 6 7 8 from visdom import Visdomviz=Visdom() viz.line([0.0 ,0.0 ],[0. ],win='test' ,opts=dict (title="train loss&acc." ,legend=['loss' ,'acc.' ])) viz.line([[test_loss,correct/len (test_loader.dataset)]],[global_step],win="test" ,update="append" )
可视化
1 2 3 4 from visdom import Visdomviz=Visdom() viz.images(data.view(-1 ,1 ,28 ,28 ),win='x' ) viz.text(str (pred.detach().cpu().numpy()),win='pred' ,opts=dict (title='pred' ))
4.过拟合
5.交叉验证
如何划分train-val-test
对train 人为切割:
1 2 3 4 5 6 7 8 9 10 train_db,val_db=torch.utils.data.random_split(train_db,[50000 ,10000 ]) train_loader=torch.utils.data.DataLoader( train_db, batch_size=batch_size, shuffle=True )
关于批训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import torchimport torch.utils.data as Data torch.manual_seed(1 ) BATCH_SIZE = 8 x = torch.linspace(1 , 15 , 15 ) y = torch.linspace(15 , 1 , 15 ) torch_dataset = Data.TensorDataset(x, y) loader = Data.DataLoader( dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True , num_workers=2 , ) def show_batch (): for epoch in range (3 ): for step, (batch_x, batch_y) in enumerate (loader): print ('Epoch: ' , epoch, '| Step: ' , step, '| batch x: ' , batch_x.numpy(), '| batch y: ' , batch_y.numpy()) if __name__ == '__main__' : show_batch() ''' (1)每次训练5个数据,打乱数据;每进行一次完整的训练需要进行3个训练步骤: Epoch: 0 | Step: 0 | batch x: [10. 12. 9. 5. 1.] | batch y: [ 6. 4. 7. 11. 15.] Epoch: 0 | Step: 1 | batch x: [ 7. 15. 8. 13. 3.] | batch y: [ 9. 1. 8. 3. 13.] Epoch: 0 | Step: 2 | batch x: [ 2. 6. 14. 4. 11.] | batch y: [14. 10. 2. 12. 5.] Epoch: 1 | Step: 0 | batch x: [ 3. 10. 8. 13. 2.] | batch y: [13. 6. 8. 3. 14.] Epoch: 1 | Step: 1 | batch x: [ 5. 4. 12. 14. 1.] | batch y: [11. 12. 4. 2. 15.] Epoch: 1 | Step: 2 | batch x: [15. 9. 11. 6. 7.] | batch y: [ 1. 7. 5. 10. 9.] Epoch: 2 | Step: 0 | batch x: [ 8. 7. 3. 10. 12.] | batch y: [ 8. 9. 13. 6. 4.] Epoch: 2 | Step: 1 | batch x: [ 6. 13. 9. 4. 15.] | batch y: [10. 3. 7. 12. 1.] Epoch: 2 | Step: 2 | batch x: [14. 2. 5. 1. 11.] | batch y: [ 2. 14. 11. 15. 5.] (2)每次训练8个数据,打乱数据;每进行一次完整的训练需要进行2个训练步骤,一次8个数据,一次7个数据: Epoch: 0 | Step: 0 | batch x: [10. 12. 9. 5. 1. 7. 15. 8.] | batch y: [ 6. 4. 7. 11. 15. 9. 1. 8.] Epoch: 0 | Step: 1 | batch x: [13. 3. 2. 6. 14. 4. 11.] | batch y: [ 3. 13. 14. 10. 2. 12. 5.] Epoch: 1 | Step: 0 | batch x: [ 3. 10. 8. 13. 2. 5. 4. 12.] | batch y: [13. 6. 8. 3. 14. 11. 12. 4.] Epoch: 1 | Step: 1 | batch x: [14. 1. 15. 9. 11. 6. 7.] | batch y: [ 2. 15. 1. 7. 5. 10. 9.] Epoch: 2 | Step: 0 | batch x: [ 8. 7. 3. 10. 12. 6. 13. 9.] | batch y: [ 8. 9. 13. 6. 4. 10. 3. 7.] Epoch: 2 | Step: 1 | batch x: [ 4. 15. 14. 2. 5. 1. 11.] | batch y: [12. 1. 2. 14. 11. 15. 5.] '''
6.正则化
当范数接近于0时,模型的复杂度会降低。可以有效处理过拟合。
是一个weight_decay
在学习时,如果数据提供的特征有些影响模型的复杂度或这个特征数据异常特别多,那么应该尽量减少(甚至删除某个特征的影响),这就是正则化
L2正则化:
作用:可以使得其中的一些权重w都很小,削弱某个特征的影响
优点:越小的参数说明模型越简单,越简单的模型越不容易发生过拟合
Ridge回归
作用:可以将一些w直接设置为0,直接删除该特征的影响
LASSO回归
如何添加一个L2正则化呢?
1 2 3 4 5 6 7 device=torch.device("cuda:0" ) net=MLP().to(device) optimizer=optim.SGD(net.parameters(),lr=learning_rate,weight_decay=0.01 ) criteon=nn.CrossEntropyLoss().to(device)
对于L1正则化
1 2 3 4 5 6 7 regularization_loss=0 for param in model.parameters(): regularization_loss+=torch.sum (torch.abs (param)) classify_loss=criteon(logits,target) loss=classify_loss+0.01 *regularization_loss
7.动量
momentum
如何实现动量捏
1 2 3 optimizer=torch.optim.SGD(model.parameter(),args.lr,momentum=args.momentum,weight_decay=args.weight_decay) scheduler=ReduceLROnPlateau(optimizer,'min' )
部分优化器如Adam内置动量属性了,不能额外设置
learning-rate decay
学习率如何选择是一个比较困难的过程,那么我们为什么不考虑让他自动衰减呢?
如上图所示,发生斜率爆炸的情况是因为改变了学习率,可能之前的过程都是在双峰之间打转,而改变了学习率马上就能找到新的谷底。
1 2 3 4 5 6 7 8 9 10 11 12 optimizer=torch.optim.SGD(model.parameter(),args.lr,momentum=args.momentum,weight_decay=args.weight_decay) scheduler=ReduceLROnPlateau(optimizer,'min' ) for epoch in xrange(args.start_epoch,args.epochs): train(train_loader,model,criterion,optimizer,epoch) result_avg,loss_val=validate(val_loader,model,criterion,epoch) scheduler.step(loss_val)
1 2 3 4 5 6 7 8 scheduler=StepLR(optimizer,step_size=30 ,gamma=0.1 ) for epoch in range (100 ): shceduler.step() train(...) validate(...)
Early Stopping
有概率断掉某些层
如何添加dropout?
我们在任意一层添加即可
1 2 3 4 5 net_dropped=torch.nn.Sequential( torch.nn.Linear(784 ,200 ), torch.nn.Dropout(0.5 ), torch.nn.ReLU() )
当然,在测试的时候,我们需要把断掉的层添加回去
1 2 3 4 5 6 7 8 9 10 for epoch in range (epochs): net_dropped.train() for batch_idx,(data,target) in enumerate (train_loader): ... net_dropped.eval () test_loss=0 correct=0 for data,target in test_loader: ...
stochastic
x − − − > f ( x ) N ( 0 , x ) x--->f(x) ~~ N(0,x) x − − − > f ( x ) N ( 0 , x )
也就是说并不是完全符合映射关系,而是符合某一分布
为了节省内存,并不对全部数据做梯度,而是对少量banch数据做梯度
卷积神经网络
多卷积核
注意Kernel的多通道运算最后还需要求和
每一次卷积观察的特征都不同,但根据经验公式可以发现,基本上是由局部到全局的变化。
使用Pytorch实现Convolution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 layer=nn.Conv2d(1 ,3 ,kernel_size=3 ,stride=1 ,padding=0 ) x=torch.rand(1 ,1 ,28 ,28 ) out=layer.forward(x) layer=nn.Conv2d(1 ,3 ,kernel_size=3 ,stride=1 ,padding=1 ) out=layer.forward(x) layer=nn.Conv2d(1 ,3 ,kernel_size=3 ,stride=2 ,padding=1 ) out=layer.forward(x) out=layer(x) layer.weight layer.bias
池化层
降采样Downsample
最大池化Max pooling
通过一个移动窗口对图像进行处理,从而达到降采样的效果
1 2 3 4 5 6 7 8 9 10 x=out layer=nn.MaxPool2d(2 ,stride=2 ) out=layer(x) out=F.avg_pool3d(x,2 ,stride=2 )
上采样
1 2 3 x=out out=F.interpolate(x,scale_factor=2 ,mode='nearest' )
Batch Normal
为什么要batch normal
1 2 normalize=transforms.Normalize(mean=[0.485 ,0.456 ,0.406 ], std=[0.229 ,0.224 ,0.225 ])
对于一个维度为[6,3,28,28]的均值,一般是[3]大小,统计的是六张图片每个通道的均值。
1 2 3 4 5 6 x=torch.rand(100 ,16 ,784 ) layer=nn.BatchNorm1d(16 ) out=layer(x) layer.running_mean layer.running_var
而对于2d数据:
1 2 3 4 5 6 7 8 x.shape layer=nn.BatchNorm2d(16 ) out=layer(x) layer.weight layer.bias
BatchNorm的优势:
经典卷积神经网络