赞
踩
深度学习(deep learning)是机器学习的分支,是一种以人工神经网络为架构,对数据进行特征学习的算法。
从特征提取的角度出发:
从数据量的角度出发:
图像识别
自然语言处理技术
语音技术
目前企业中常见的深度学习框架有很多,TensorFlow, Caffe2, Keras, Theano, PyTorch, Chainer, DyNet, MXNet, and CNTK等等
其中tensorflow和Kears是google出品的,使用者很多,但是其语法晦涩而且和python的语法不尽相同,对于入门玩家而言上手难度较高。
所以在之后的课程中我们会使用facebook出的PyTorch,PyTorch的使用和python的语法相同,整个操作类似Numpy的操作,并且 PyTorch使用的是动态计算,会让代码的调试变的更加简单
人工神经网络(Artificial Neural Network,ANN),简称神经网络(Neural Network,NN)或类神经网络,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型,用于对函数进行估计或近似。
和其他机器学习方法一样,神经网络已经被用于解决各种各样的问题,例如机器视觉和语音识别。这些问题都是很难被传统基于规则的编程所解决的。
在生物神经网络中,每个神经元与其他神经元相连,当它“兴奋”时,就会向相连的神经元发送化学物质,从而改变这些神经元内的电位;如果某神经元的电位超过了一个“阈值”,那么它就会被激活,即“兴奋”起来,向其他神经元发送化学物质。
1943 年,McCulloch 和 Pitts 将上述情形抽象为上图所示的简单模型,这就是一直沿用至今的 M-P 神经元模型。把许多这样的神经元按一定的层次结构连接起来,就得到了神经网络。
一个简单的神经元如下图所示,
其中:
使用数学公式表示就是:
t
=
f
(
W
T
A
+
b
)
t = f(W^TA+b)
t=f(WTA+b)
可见,一个神经元的功能是求得输入向量与权向量的内积后,经一个非线性传递函数得到一个标量结果。
是最基本的神经元网络形式,由有限个神经元构成,所有神经元的输入向量都是同一个向量。由于每一个神经元都会产生一个标量结果,所以单层神经元的输出是一个向量,向量的维数等于神经元的数目。
示意图如下:
感知机由两层神经网络组成,输入层接收外界输入信号后传递给输出层(输出+1正例,-1反例),输出层是 M-P 神经元
其中从 w 0 , w 1 ⋯ w n w_0,w_1\cdots w_n w0,w1⋯wn都表示权重
感知机的作用:
把一个n维向量空间用一个超平面分割成两部分,给定一个输入向量,超平面可以判断出这个向量位于超平面的哪一边,得到输入时正类或者是反类,对应到2维空间就是一条直线把一个平面分为两个部分。
多层神经网络就是由单层神经网络进行叠加之后得到的,所以就形成了层的概念,常见的多层神经网络有如下结构:
示意图如下:
全连接层
全连接层:当前一层和前一层每个神经元相互链接,我们称当前这一层为全连接层。
思考:假设第N-1层有m个神经元,第N层有n个神经元,当第N层是全连接层的时候,则N-1和N层之间有1,这些参数可以如何表示?
从上图可以看出,所谓的全连接层就是在前一层的输出的基础上进行一次 Y = W x + b Y=Wx+b Y=Wx+b的变化(不考虑激活函数的情况下就是一次线性变化,所谓线性变化就是平移(+b)和缩放的组合(*w))
在前面的神经元的介绍过程中我们提到了激活函数,那么他到底是干什么的呢?
假设我们有这样一组数据,三角形和四边形,需要把他们分为两类
通过不带激活函数的感知机模型我们可以划出一条线, 把平面分割开
假设我们确定了参数w和b之后,那么带入需要预测的数据,如果y>0,我们认为这个点在直线的右边,也就是正类(三角形),否则是在左边(四边形)
但是可以看出,三角形和四边形是没有办法通过直线分开的,那么这个时候该怎么办?
可以考虑使用多层神经网络来进行尝试,比如在前面的感知机模型中再增加一层
对上图中的等式进行合并,我们可以得到:
y
=
(
w
1
−
11
w
2
−
1
+
⋯
)
x
1
+
(
w
1
−
21
w
2
−
1
+
⋯
)
x
2
+
(
w
2
−
1
+
⋯
)
b
1
−
1
y = (w_{1-11}w_{2-1}+\cdots)x_1+(w_{1-21}w_{2-1}+\cdots)x_2 + (w_{2-1}+\cdots)b_{1-1}
y=(w1−11w2−1+⋯)x1+(w1−21w2−1+⋯)x2+(w2−1+⋯)b1−1
上式括号中的都为w参数,和公式
y
=
w
1
x
1
+
w
2
x
2
+
b
y = w_1x_1 + w_2x_2 +b
y=w1x1+w2x2+b完全相同,依然只能够绘制出直线
所以可以发现,即使是多层神经网络,相比于前面的感知机,没有任何的改进。
但是如果此时,我们在前面感知机的基础上加上非线性的激活函数之后,输出的结果就不在是一条直线
如上图,右边是sigmoid函数,对感知机的结果,通过sigmoid函数进行处理
如果给定合适的参数w和b,就可以得到合适的曲线,能够完成对最开始问题的非线性分割
所以激活函数很重要的一个作用就是增加模型的非线性分割能力
常见的激活函数有:
看图可知:
激活函数的作用除了前面说的增加模型的非线性分割能力外,还有
一个男孩想要找一个女朋友,于是实现了一个女友判定机,随着年龄的增长,他的判定机也一直在变化
14岁的时候:
无数次碰壁之后,男孩意识到追到女孩的可能性和颜值一样重要,于是修改了判定机:
在15岁的时候终于找到呢女朋友,但是一顿时间后他发现有各种难以忍受的习惯,最终决定分手。一段空窗期中,他发现找女朋友很复杂,需要更多的条件才能够帮助他找到女朋友,于是在25岁的时候,他再次修改了判定机:
在更新了女友判定机之后,问题又来了,很多指标不能够很好的量化,如何颜值,什么样的叫做颜值高,什么样的叫做性格好等等,为了解决这个问题,他又更新了判定机,最终得到超级女友判定机
上述的超级女友判定机其实就是神经网络,它能够接受基础的输入,通过隐藏层的线性的和非线性的变化最终的到输出
通过上面例子,希望大家能够理解深度学习的思想:
输出的最原始、最基本的数据,通过模型来进行特征工程,进行更加高级特征的学习,然后通过传入的数据来确定合适的参数,让模型去更好的拟合数据。
这个过程可以理解为盲人摸象,多个人一起摸,把摸到的结果乘上合适的权重,进行合适的变化,让他和目标值趋近一致。整个过程只需要输入基础的数据,程序自动寻找合适的参数。
Pytorch是一款facebook发布的深度学习框架,由其易用性,友好性,深受广大用户青睐。
安装地址介绍:https://pytorch.org/get-started/locally/
带GPU安装步骤:
conda install pytorch torchvision cudatoolkit=9.0 -c pytorch
不带GPU安装步骤
conda install pytorch-cpu torchvision-cpu -c pytorch
安装之后打开ipython
输入:
In [1]:import torch
In [2]: torch.__version__
Out[2]: '1.X.1'
注意:安装模块的时候安装的是pytorch
,但是在代码中都是使用torch
下载地址
安装
pip install E:\torch-1.4.0+cpu-cp36-cp36m-win_amd64.whl --user
安装成功
import torch
torch.__version__
张量是一个统称,其中包含很多类型:
解决 jupyter 中 import torch 'no module named torch’错误
使用python中的列表或者序列创建tensor
torch.tensor([[1., -1.], [1., -1.]])
tensor([[ 1.0000, -1.0000],
[ 1.0000, -1.0000]])
使用numpy中的数组创建tensor
torch.tensor(np.array([[1, 2, 3], [4, 5, 6]]))
tensor([[ 1, 2, 3],
[ 4, 5, 6]])
使用torch的api创建tensor
torch.empty(3,4)
创建3行4列的空的tensor,会用无用数据进行填充
torch.ones([3,4])
创建3行4列的全为1的tensor
torch.zeros([3,4])
创建3行4列的全为0的tensor
torch.rand([3,4])
创建3行4列的随机值的tensor,随机值的区间是[0, 1)
>>> torch.rand(2, 3)
tensor([[ 0.8237, 0.5781, 0.6879],
[ 0.3816, 0.7249, 0.0998]])
torch.randint(low=0,high=10,size=[3,4])
创建3行4列的随机整数的tensor,随机值的区间是[low, high)
>>> torch.randint(3, 10, (2, 2))
tensor([[4, 5],
[6, 7]])
torch.randn([3,4])
创建3行4列的随机数的tensor,随机值的分布式均值为0,方差为1
获取tensor中的数据(当tensor中只有一个元素可用,获取该元素的值):tensor.item()
In [10]: a = torch.tensor(np.arange(1))
In [11]: a
Out[11]: tensor([0])
In [12]: a.item()
Out[12]: 0
转化为numpy数组
In [55]: z.numpy()
Out[55]:
array([[-2.5871205],
[ 7.3690367],
[-2.4918075]], dtype=float32)
获取形状:tensor.size()
(size中可以传入参数,获取某一维度的数值,索引从0开始,-1表示倒数第一个)
In [72]: x
Out[72]:
tensor([[ 1, 2],
[ 3, 4],
[ 5, 10]], dtype=torch.int32)
In [73]: x.size()
Out[73]: torch.Size([3, 2])
形状改变:tensor.view((3,4))
。类似numpy中的reshape,是一种浅拷贝,仅仅是形状发生改变(view()中若参数为-1,表示根据确定的一维,自动设置另一维度数值)
In [76]: x.view(2,3)
Out[76]:
tensor([[ 1, 2, 3],
[ 4, 5, 10]], dtype=torch.int32)
获取阶数:tensor.dim()
In [77]: x.dim()
Out[77]: 2
获取最大值:tensor.max()
In [78]: x.max()
Out[78]: tensor(10, dtype=torch.int32)
转置:tensor.t()
In [79]: x.t()
Out[79]:
tensor([[ 1, 3, 5],
[ 2, 4, 10]], dtype=torch.int32)
tensor[1,3]
获取tensor中第一行第三列的值
transpose(a,b) 交换某两维度的值
permute()用法类似transpose(),但是permute()针对多维度,而tranpose()最多操作2维度
permute()和transpose()区别
tensor[1,3]=100
对tensor中第一行第三列的位置进行赋值100
tensor的切片
In [101]: x
Out[101]:
tensor([[1.6437, 1.9439, 1.5393],
[1.3491, 1.9575, 1.0552],
[1.5106, 1.0123, 1.0961],
[1.4382, 1.5939, 1.5012],
[1.5267, 1.4858, 1.4007]])
In [102]: x[:,1]
Out[102]: tensor([1.9439, 1.9575, 1.0123, 1.5939, 1.4858])
tensor中的数据类型非常多,常见类型如下:
上图中的Tensor types表示这种type的tensor是其实例
获取tensor的数据类型:tensor.dtype
In [80]: x.dtype
Out[80]: torch.int32
创建数据的时候指定类型(torch.Tensor()创建时候,无法使用dtype指定形状;torch.tensor()相反)
In [88]: torch.ones([2,3],dtype=torch.float32)
Out[88]:
tensor([[9.1167e+18, 0.0000e+00, 7.8796e+15],
[8.3097e-43, 0.0000e+00, -0.0000e+00]])
类型的修改
In [17]: a
Out[17]: tensor([1, 2], dtype=torch.int32)
In [18]: a.type(torch.float)
Out[18]: tensor([1., 2.])
In [19]: a.double()
Out[19]: tensor([1., 2.], dtype=torch.float64)
tensor和tensor相加
In [94]: x = x.new_ones(5, 3, dtype=torch.float) In [95]: y = torch.rand(5, 3) In [96]: x+y Out[96]: tensor([[1.6437, 1.9439, 1.5393], [1.3491, 1.9575, 1.0552], [1.5106, 1.0123, 1.0961], [1.4382, 1.5939, 1.5012], [1.5267, 1.4858, 1.4007]]) In [98]: torch.add(x,y) Out[98]: tensor([[1.6437, 1.9439, 1.5393], [1.3491, 1.9575, 1.0552], [1.5106, 1.0123, 1.0961], [1.4382, 1.5939, 1.5012], [1.5267, 1.4858, 1.4007]]) In [99]: x.add(y) Out[99]: tensor([[1.6437, 1.9439, 1.5393], [1.3491, 1.9575, 1.0552], [1.5106, 1.0123, 1.0961], [1.4382, 1.5939, 1.5012], [1.5267, 1.4858, 1.4007]]) In [100]: x.add_(y) #带下划线的方法会对x进行就地修改 Out[100]: tensor([[1.6437, 1.9439, 1.5393], [1.3491, 1.9575, 1.0552], [1.5106, 1.0123, 1.0961], [1.4382, 1.5939, 1.5012], [1.5267, 1.4858, 1.4007]]) In [101]: x #x发生改变 Out[101]: tensor([[1.6437, 1.9439, 1.5393], [1.3491, 1.9575, 1.0552], [1.5106, 1.0123, 1.0961], [1.4382, 1.5939, 1.5012], [1.5267, 1.4858, 1.4007]])
注意:带下划线的方法(比如:add_
)会对tensor进行就地修改
tensor和数字操作
In [97]: x +10
Out[97]:
tensor([[11., 11., 11.],
[11., 11., 11.],
[11., 11., 11.],
[11., 11., 11.],
[11., 11., 11.]])
CUDA中的tensor
CUDA(Compute Unified Device Architecture),是NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。
torch.cuda
这个模块增加了对CUDA tensor的支持,能够在cpu和gpu上使用相同的方法操作tensor
通过.to
方法能够把一个tensor转移到另外一个设备(比如从CPU转到GPU)
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 'cuda:0' 0 表示多块GPU存在时,指定第0块GPU进行运算
if torch.cuda.is_available():
device = torch.device("cuda") # cuda device对象
y = torch.ones_like(x, device=device) # 创建一个在cuda上的tensor
x = x.to(device) # 使用方法把x转为cuda 的tensor
z = x + y
print(z)
print(z.to("cpu", torch.double)) # .to方法也能够同时设置类型
>>tensor([1.9806], device='cuda:0')
>>tensor([1.9806], dtype=torch.float64)
通过前面的学习,可以发现torch的各种操作几乎和numpy一样
梯度:是一个向量,导数+变化最快的方向(学习的前进方向)
回顾机器学习
收集数据 x x x ,构建机器学习模型 f f f,得到 f ( x , w ) = Y p r e d i c t f(x,w) = Y_{predict} f(x,w)=Ypredict
判断模型好坏的方法:
目标:通过调整(学习)参数 w w w,尽可能的降低 l o s s loss loss,那么我们该如何调整 w w w呢?
随机选择一个起始点 w 0 w_0 w0,通过调整 w 0 w_0 w0,让loss函数取到最小值
w w w的更新方法:
其中:
总结:梯度就是多元函数参数的变化趋势(参数学习的方向),只有一个自变量时称为导数
多项式求导数: f ( x ) = x 5 f(x) = x^5 f(x)=x5 , f ′ ( x ) = 5 x ( 5 − 1 ) f^{'}(x) = 5x^{(5-1)} f′(x)=5x(5−1)
基本运算求导: f ( x ) = x y f(x) = xy f(x)=xy , f ′ ( x ) = y f^{'}(x) = y f′(x)=y
指数求导: f ( x ) = 5 e x f(x) = 5e^x f(x)=5ex , f ′ ( x ) = 5 e x f^{'}(x) = 5e^x f′(x)=5ex
对数求导: f ( x ) = 5 l n x f(x) = 5lnx f(x)=5lnx , f ′ ( x ) = 5 x f^{'}(x) = \frac{5}{x} f′(x)=x5,ln 表示log以e为底的对数
导数的微分形式:
那么:如何求 f ( x ) = ( 1 + e − x ) − 1 f(x) = (1+e^{-x})^{-1} f(x)=(1+e−x)−1 的导数呢?那就可以使用
f ( x ) = ( 1 + e − x ) − 1 f(x) = (1+e^{-x})^{-1} f(x)=(1+e−x)−1 ==> f ( a ) = a − 1 , a ( b ) = ( 1 + b ) , b ( c ) = e c , c ( x ) = − x f(a) = a^{-1},a(b) = (1+b),b(c) = e^c,c(x) = -x f(a)=a−1,a(b)=(1+b),b(c)=ec,c(x)=−x
则有:
一元函数,即有一个自变量。类似 f ( x ) f(x) f(x)
多元函数,即有多个自变量。类似 f ( x , y , z ) , 三 个 自 变 量 x , y , z f(x,y,z),三个自变量x,y,z f(x,y,z),三个自变量x,y,z
多元函数求偏导过程中:对某一个自变量求导,其他自变量当做常量即可
例1:
例2:
例3:
练习:
已知
J
(
a
,
b
,
c
)
=
3
(
a
+
b
c
)
,
令
u
=
a
+
v
,
v
=
b
c
J(a,b,c) = 3(a+bc),令u=a+v,v = bc
J(a,b,c)=3(a+bc),令u=a+v,v=bc,求a,b,c各自的偏导数。
计算图:通过图的方式来描述函数的图形
在上面的练习中, J ( a , b , c ) = 3 ( a + b c ) , 令 u = a + v , v = b c J(a,b,c) = 3(a+bc),令u=a+v,v = bc J(a,b,c)=3(a+bc),令u=a+v,v=bc,把它绘制成计算图可以表示为:
绘制成为计算图之后,可以清楚的看到向前计算的过程
之后,对每个节点求偏导可有:
那么反向传播的过程就是一个上图的从右往左的过程,自变量
a
,
b
,
c
a,b,c
a,b,c各自的偏导就是连线上的梯度的乘积:
w 1 , w 2 , . . . . w n w1,w2,....wn w1,w2,....wn表示网络第n层权重
w n [ i , j ] w_n[i,j] wn[i,j]表示第n层第i个神经元,连接到第n+1层第j个神经元的权重。
其中:
问题:那么此时 w 1 [ 1 , 2 ] w_1[1,2] w1[1,2]的偏导该如何求解呢?
通过观察,发现从 o u t out out 到 w 1 [ 1 , 2 ] w_1[1,2] w1[1,2]的来连接线有两条
结果如下:
d
o
u
t
d
W
1
[
1
,
2
]
=
x
1
∗
f
′
(
a
2
)
∗
(
W
2
[
2
,
1
]
∗
f
′
(
b
1
)
∗
W
3
[
1
,
1
]
∗
∇
o
u
t
+
W
2
[
2
,
2
]
∗
f
′
(
b
2
)
∗
W
3
[
2
,
1
]
∗
∇
o
u
t
)
\frac{dout}{dW_1[1,2]} = x1*f^{'}(a2)*(W_2[2,1]*f^{'}(b1)*W_3[1,1]*\nabla out +W_2[2,2]*f^{'}(b2)*W_3[2,1]*\nabla out)
dW1[1,2]dout=x1∗f′(a2)∗(W2[2,1]∗f′(b1)∗W3[1,1]∗∇out+W2[2,2]∗f′(b2)∗W3[2,1]∗∇out)
公式分为两部分:
但是这样做,当模型很大的时候,计算量非常大
所以反向传播的思想就是对其中的某一个参数单独求梯度,之后更新,如下图所示:
计算过程如下
更新参数之后,继续反向传播
计算过程如下:
继续反向传播
计算过程如下:
通用的描述如下
∇
w
i
,
j
l
=
f
(
a
i
l
)
∗
∇
a
j
i
+
1
∇
a
i
l
=
f
′
(
a
i
l
)
∗
(
∑
j
=
1
m
w
i
,
j
∗
∇
a
j
l
+
1
)
\nabla w^{l}_{i,j} = f(a^l_i)* \nabla a^{i+1}_{j}\\ \nabla a^{l}_i = f'(a^l_i)*(\sum_{j=1}^{m}w_{i,j}*\nabla a_j^{l+1})
∇wi,jl=f(ail)∗∇aji+1∇ail=f′(ail)∗(j=1∑mwi,j∗∇ajl+1)
对于pytorch中的一个tensor,如果设置它的属性 .requires_grad
为True
,那么它将会追踪对于该张量的所有操作。或者可以理解为,这个tensor是一个参数,后续会被计算梯度,更新该参数。
假设有以下条件(1/4表示求均值,xi中有4个数),使用torch完成其向前计算的过程
如果x为参数,需要对其进行梯度的计算和更新
那么,在最开始随机设置x的值的过程中,需要设置他的requires_grad属性为True,其默认值为False
import torch x = torch.ones(2, 2, requires_grad=True) #初始化参数x并设置requires_grad=True用来追踪其计算历史 print(x) #tensor([[1., 1.], # [1., 1.]], requires_grad=True) y = x+2 print(y) #tensor([[3., 3.], # [3., 3.]], grad_fn=<AddBackward0>) z = y*y*3 #平方x3 print(z) #tensor([[27., 27.], # [27., 27.]], grad_fn=<MulBackward0>) out = z.mean() #求均值 print(out) #tensor(27., grad_fn=<MeanBackward0>)
从上述代码可以看出:
grad_fn
属性,用来记录做过的操作
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad) #False
a.requires_grad_(True) #就地修改
print(a.requires_grad) #True
b = (a * a).sum()
print(b.grad_fn) # <SumBackward0 object at 0x4e2b14345d21>
with torch.no_gard():
c = (a * a).sum() #tensor(151.6830),此时c没有gard_fn
print(c.requires_grad) #False
注意:
为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():
中。在评估模型时特别有用,因为模型可能具有requires_grad = True
的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。
对于1.1 中的out而言,我们可以使用backward
方法来进行反向传播,计算梯度
out.backward()
,此时便能够求出导数
d
o
u
t
d
x
\frac{d out}{dx}
dxdout,调用x.gard
能够获取导数值
得到
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
因为:
d
(
O
)
d
(
x
i
)
=
3
2
(
x
i
+
2
)
\frac{d(O)}{d(x_i)} = \frac{3}{2}(x_i+2)
d(xi)d(O)=23(xi+2)
在
x
i
x_i
xi等于1时其值为4.5
注意:在输出为一个标量的情况下,我们可以调用输出tensor
的backword()
方法,但是在数据是一个向量的时候,调用backward()
的时候还需要传入其他参数。
很多时候我们的损失函数都是一个标量,所以这里就不再介绍损失为向量的情况。
loss.backward()
就是根据损失函数,对参数(requires_grad=True)的去计算他的梯度,并且把它累加保存到x.gard
,此时还并未更新其梯度(因此每次反向传播之前,需要将梯度设置为0)
注意点:
tensor.data
:
在tensor的require_grad=False,tensor.data和tensor等价
require_grad=True时,tensor.data仅仅是获取tensor中的数据内容,不带grad等属性
tensor.numpy()
:
require_grad=True
不能够直接转换,需要使用tensor.detach().numpy()
,实现对tensor中数据的深拷贝,将tensor转换为ndarry类型下面,我们使用一个自定义的数据,来使用torch实现一个简单的线性回归
假设我们的基础模型就是y = wx+b
,其中w和b均为参数,我们使用y = 3x+0.8
来构造数据x、y,所以最后通过模型应该能够得出w和b应该分别接近3和0.8
import torch import numpy as np from matplotlib import pyplot as plt #1. 准备数据 y = 3x+0.8,准备参数 x = torch.rand([50]) y = 3*x + 0.8 w = torch.rand(1,requires_grad=True) b = torch.rand(1,requires_grad=True) def loss_fn(y,y_predict): loss = (y_predict-y).pow(2).mean() for i in [w,b]: #每次反向传播前把梯度置为0 if i.grad is not None: i.grad.data.zero_() # [i.grad.data.zero_() for i in [w,b] if i.grad is not None] loss.backward() return loss.data def optimize(learning_rate): # print(w.grad.data,w.data,b.data) w.data -= learning_rate* w.grad.data b.data -= learning_rate* b.grad.data for i in range(3000): #2. 计算预测值 y_predict = x*w + b #3.计算损失,把参数的梯度置为0,进行反向传播 loss = loss_fn(y,y_predict) if i%500 == 0: print(i,loss) #4. 更新参数w和b optimize(0.01) # 绘制图形,观察训练结束的预测值和真实值 predict = x*w + b #使用训练后的w和b计算预测值 plt.scatter(x.data.numpy(), y.data.numpy(),c = "r") plt.plot(x.data.numpy(), predict.data.numpy()) plt.show() print("w",w) print("b",b)
图形效果如下:
打印w和b,可有
w tensor([2.9280], requires_grad=True)
b tensor([0.8372], requires_grad=True)
可知,w和b已经非常接近原来的预设的3和0.8
注意:
ValueError: only one element tensors can be converted to Python scalars
在前一部分,我们自己实现了通过torch的相关方法完成反向传播和参数更新,在pytorch中预设了一些更加灵活简单的对象,让我们来构造模型、定义损失,优化损失等
那么接下来,我们一起来了解一下其中常用的API
nn.Module
nn.Modul
是torch.nn
提供的一个类,是pytorch中我们自定义网络
的一个基类,在这个类中定义了很多有用的方法,让我们在继承这个类定义网络的时候非常简单
当我们自定义网络的时候,有两个方法需要特别注意:
__init__
需要调用super
方法,继承父类的属性和方法farward
方法必须实现,用来定义我们的网络的向前计算的过程用前面的y = wx+b
的模型举例如下:
from torch import nn
class Lr(nn.Module):
def __init__(self):
super(Lr, self).__init__() #继承父类init的参数
self.linear = nn.Linear(1, 1) # Linear(输入特征形状,输出特征形状即列数)
# forward 完成一次前向计算过程
def forward(self, x):
out = self.linear(x)
return out
注意:
nn.Linear
为torch预定义好的线性模型,也被称为全链接层,传入的参数为输入的数量,输出的数量(in_features, out_features),是不算(batch_size的列数)nn.Module
定义了__call__
方法,实现的就是调用forward
方法,即Lr
的实例,能够直接被传入参数调用,实际上调用的是forward
方法并传入参数# 实例化模型
model = Lr()
# 传入数据,计算结果
predict = model(x)
优化器(optimizer
),可以理解为torch为我们封装的用来进行更新参数的方法,比如常见的随机梯度下降(stochastic gradient descent,SGD
)
优化器类都是由torch.optim
提供的,例如
torch.optim.SGD(参数,学习率)
torch.optim.Adam(参数,学习率)
注意:
model.parameters()
来获取,获取模型中所有requires_grad=True
的参数示例如下:
optimizer = optim.SGD(model.parameters(), lr=1e-3) #1. 实例化
optimizer.zero_grad() #2. 梯度置为0
loss.backward() #3. 计算梯度
optimizer.step() #4. 更新参数的值
前面的例子是一个回归问题,torch中也预测了很多损失函数
nn.MSELoss()
,常用于回归问题nn.CrossEntropyLoss()
,常用于分类问题使用方法:
model = Lr() #1. 实例化模型
criterion = nn.MSELoss() #2. 实例化损失函数
optimizer = optim.SGD(model.parameters(), lr=1e-3) #3. 实例化优化器类
for i in range(100):
y_predict = model(x_true) #4. 向前计算预测值
loss = criterion(y_true,y_predict) #5. 调用损失函数传入真实值和预测值,得到损失结果
optimizer.zero_grad() #5. 当前循环参数梯度置为0
loss.backward() #6. 计算梯度
optimizer.step() #7. 更新参数的值
import torch from torch import nn from torch import optim import numpy as np from matplotlib import pyplot as plt # 1. 定义数据 x = torch.rand([50,1]) y = x*3 + 0.8 #2 .定义模型 class Lr(nn.Module): def __init__(self): super(Lr,self).__init__() self.linear = nn.Linear(1,1) def forward(self, x): out = self.linear(x) return out # 2. 实例化模型,loss,和优化器 model = Lr() criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=1e-3) #3. 训练模型 for i in range(30000): out = model(x) #3.1 获取预测值 loss = criterion(y,out) #3.2 计算损失 optimizer.zero_grad() #3.3 梯度归零 loss.backward() #3.4 计算梯度 optimizer.step() # 3.5 更新梯度 if (i+1) % 20 == 0: print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data)) #4. 模型评估 # 或者model.train(False),model.train属性默认为True表示该模型用于训练,False等同于model.eval() model.eval() #设置模型为评估模式,即预测模式 predict = model(x) predict = predict.data.numpy() plt.scatter(x.data.numpy(),y.data.numpy(),c="r") plt.plot(x.data.numpy(),predict) plt.show()
输出如下:
注意:
model.eval()
表示设置模型为评估模式,即预测模式
model.train(mode=True)
表示设置模型为训练模式
在当前的线性回归中,上述并无区别
但是在其他的一些模型中,训练的参数和预测的参数会不相同,到时候就需要具体告诉程序我们是在进行训练还是预测,比如模型中存在Dropout,BatchNorm的时候
当模型太大,或者参数太多的情况下,为了加快训练速度,经常会使用GPU来进行训练
此时我们的代码需要稍作调整:
判断GPU是否可用torch.cuda.is_available()
torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
>>device(type='cuda', index=0) #使用gpu
>>device(type='cpu') #使用cpu
把模型参数和input数据转化为cuda的支持类型
model.to(device)
x_true.to(device)
在GPU上计算结果也为cuda的数据类型,需要转化为numpy或者torch的cpu的tensor类型
predict = predict.cpu().detach().numpy()
detach()
的效果和data的相似,但是detach()
是深拷贝,data是取值,是浅拷贝
修改之后的代码如下:
import torch from torch import nn from torch import optim import numpy as np from matplotlib import pyplot as plt import time # 1. 定义数据 x = torch.rand([50,1]) y = x*3 + 0.8 #2 .定义模型 class Lr(nn.Module): def __init__(self): super(Lr,self).__init__() self.linear = nn.Linear(1,1) def forward(self, x): out = self.linear(x) return out # 2. 实例化模型,loss,和优化器 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") x,y = x.to(device),y.to(device) model = Lr().to(device) criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=1e-3) #3. 训练模型 for i in range(300): out = model(x) loss = criterion(y,out) optimizer.zero_grad() loss.backward() optimizer.step() if (i+1) % 20 == 0: print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data)) #4. 模型评估 model.eval() # predict = model(x) predict = predict.cpu().detach().numpy() #转化为numpy数组 plt.scatter(x.cpu().data.numpy(),y.cpu().data.numpy(),c="r") plt.plot(x.cpu().data.numpy(),predict,) plt.show()
每次迭代都需要把所有样本都送入,这样的好处是每次迭代都顾及了全部的样本,做的是全局最优化,但是有可能达到局部最优。
针对梯度下降算法训练速度过慢的缺点,提出了随机梯度下降算法,随机梯度下降算法算法是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次,在样本量及其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型了。
torch中的api为:torch.optim.SGD()
SGD相对来说要快很多,但是也有存在问题,由于单个样本的训练可能会带来很多噪声,使得SGD并不是每次迭代都向着整体最优化方向,因此在刚开始训练时可能收敛得很快,但是训练一段时间后就会变得很慢。在此基础上又提出了小批量梯度下降法,它是每次从样本中随机抽取一小批进行训练,而不是一组,这样即保证了效果又保证的速度。
mini-batch SGD算法虽然这种算法能够带来很好的训练速度,但是在到达最优点的时候并不能够总是真正到达最优点,而是在最优点附近徘徊。
另一个缺点就是mini-batch SGD需要我们挑选一个合适的学习率,当我们采用小的学习率的时候,会导致网络在训练的时候收敛太慢;当我们采用大的学习率的时候,会导致在训练过程中优化的幅度跳过函数的范围,也就是可能跳过最优点。我们所希望的仅仅是网络在优化的时候网络的损失函数有一个很好的收敛速度同时又不至于摆动幅度太大。
所以Momentum优化器刚好可以解决我们所面临的问题,它主要是基于梯度的移动指数加权平均,对网络的梯度进行平滑处理的,让梯度的摆动幅度变得更小。
(注:t+1的的histroy_gradent 为第t次的gradent)
AdaGrad算法就是将每一个参数的每一次迭代的梯度取平方累加后在开方,用全局学习率除以这个数,作为学习率的动态更新,从而达到自适应学习率的效果
Momentum优化算法中,虽然初步解决了优化中摆动幅度大的问题,为了进一步优化损失函数在更新中存在摆动幅度过大的问题,并且进一步加快函数的收敛速度,RMSProp算法对参数的梯度使用了平方加权平均数。
Adam(Adaptive Moment Estimation)算法是将Momentum算法和RMSProp算法结合起来使用的一种算法,能够达到防止梯度的摆幅多大,同时还能够加开收敛速度
torch中的api为:torch.optim.Adam()
在前面的线性回归模型中,我们使用的数据很少,所以直接把全部数据放到模型中去使用。
但是在深度学习中,数据量通常是都非常多,非常大的,如此大量的数据,不可能一次性的在模型中进行向前的计算和反向传播,经常我们会对整个数据进行随机的打乱顺序,把数据处理成一个个的batch,同时还会对数据进行预处理。
所以,接下来我们来学习pytorch中的数据加载的方法
在torch中提供了数据集的基类torch.utils.data.Dataset
,继承这个基类,我们能够非常快速的实现对数据的加载。
torch.utils.data.Dataset
的源码如下:
class Dataset(object): """An abstract class representing a Dataset. All other datasets should subclass it. All subclasses should override ``__len__``, that provides the size of the dataset, and ``__getitem__``, supporting integer indexing in range from 0 to len(self) exclusive. """ def __getitem__(self, index): raise NotImplementedError def __len__(self): raise NotImplementedError def __add__(self, other): return ConcatDataset([self, other])
可知:我们需要在自定义的数据集类中继承Dataset类,同时还需要实现两个方法:
__len__
方法,能够实现通过全局的len()
方法获取其中的元素个数__getitem__
方法,能够通过传入索引的方式获取数据,例如通过dataset[i]
获取其中的第i
条数据下面通过一个例子来看看如何使用Dataset来加载数据
数据来源:http://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection
数据介绍:SMS Spam Collection是用于骚扰短信识别的经典数据集,完全来自真实短信内容,包括4831条正常短信和747条骚扰短信。正常短信和骚扰短信保存在一个文本文件中。 每行完整记录一条短信内容,每行开头通过ham和spam标识正常短信和骚扰短信
数据实例:
实现如下:
from torch.utils.data import Dataset,DataLoader import pandas as pd data_path = r"data\SMSSpamCollection" class CifarDataset(Dataset): def __init__(self): lines = open(data_path,"r") #对数据进行处理,前4个为label,后面的为短信内容 lines = [[i[:4].strip(),i[4:].strip()] for i in lines] #转化为dataFrame self.df = pd.DataFrame(lines,columns=["label","sms"]) def __getitem__(self, index): single_item = self.df.iloc[index,:] return single_item.values[0],single_item.values[1] def __len__(self): return self.df.shape[0]
之后对Dataset进行实例化,可以跌倒获取其中的数据
d = CifarDataset()
for i in range(len(d)):
print(i,d[i])
输出如下:
....
5571 ('ham', 'Pity, * was in mood for that. So...any other suggestions?')
5572 ('ham', "The guy did some bitching but I acted like i'd be interested in buying something else next week and he gave it to us for free")
5573 ('ham', 'Rofl. Its true to its name')
import torch from torch.utils.data import Dataset data_path = r'E:\data\SMSSpamCollection' # 完成数据集类 class MyDataset(Dataset): def __init__(self): self.lines = open(data_path, errors='ignore').readlines() def __getitem__(self, index): # 获取索引对应位置的一条数据 cur_line = self.lines[index].strip() label = cur_line[:4].strip() content = cur_line[4:].strip() return label, content def __len__(self): return len(self.lines) if __name__ == '__main__': my_dataset = MyDataset() print(my_dataset[1000]) print(len(my_dataset))
使用上述的方法能够进行数据的读取,但是其中还有很多内容没有实现:
multiprocessing
并行加载数据。在pytorch中torch.utils.data.DataLoader
提供了上述的所用方法
DataLoader
的使用方法示例:
from torch.utils.data import DataLoader
dataset = CifarDataset()
data_loader = DataLoader(dataset=dataset,batch_size=10,shuffle=True,num_workers=2)
#遍历,获取其中的每个batch的结果
for index, (label, context) in enumerate(data_loader):
print(index,label,context)
print("*"*100)
其中参数含义:
num_workers
:加载数据的线程数数据迭代器的返回结果如下:
555 ('spam', 'ham', 'spam', 'ham', 'ham', 'ham', 'ham', 'spam', 'ham', 'ham') ('URGENT! We are trying to contact U. Todays draw shows that you have won a £800 prize GUARANTEED. Call 09050003091 from....", 'swhrt how u dey,hope ur ok, tot about u 2day.love n miss.take care.')
***********************************************************************************
556 ('ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'spam') ('He telling not to tell any one. If so treat for me hi hi hi', 'Did u got that persons story', "Don kn....1000 cash prize or a prize worth £5000')
from torch.utils.data import Dataset, DataLoader data_path = r'E:\nlp课件\阶段9-人工智能NLP项目\第三天\代码\data\SMSSpamCollection' class MyDataSet(Dataset): def __init__(self): self.lines = open(data_path, errors='ignore').readlines() def __getitem__(self, index): cur_line = self.lines[index].strip() label = cur_line[:4].strip() content = cur_line[4:].strip() return label, content def __len__(self): return len(self.lines) my_dataset = MyDataSet() data_loader = DataLoader(dataset = my_dataset, batch_size=2, shuffle = True) if __name__ == '__main__': for i in data_loader: print(i)
注意:
len(dataset) = 数据集的样本数
len(dataloader) = math.ceil(样本数/batch_size) 即向上取整
pytorch中自带的数据集由两个上层api提供,分别是torchvision
和torchtext
其中:
torchvision
提供了对图片数据处理相关的api和数据
torchvision.datasets
,例如:torchvision.datasets.MNIST
(手写数字图片数据)torchtext
提供了对文本数据处理相关的API和数据
torchtext.datasets
,例如:torchtext.datasets.IMDB
(电影评论文本数据)下面我们以Mnist手写数字为例,来看看pytorch如何加载其中自带的数据集
使用方法和之前一样:
torchvisoin.datasets
中的数据集类(比如torchvision.datasets.MNIST
),都是继承自Dataset
意味着:直接对torchvision.datasets.MNIST
进行实例化就可以得到Dataset
的实例
但是MNIST API中的参数需要注意一下:
torchvision.datasets.MNIST(root='/files/', train=True, download=True, transform=)
root
参数表示数据存放的位置train:
bool类型,表示是使用训练集的数据还是测试集的数据download:
bool类型,表示是否需要下载数据到root目录transform:
实现的对图片的处理函数数据集的原始地址:http://yann.lecun.com/exdb/mnist/
MNIST是由Yann LeCun
等人提供的免费的图像识别的数据集,其中包括60000个训练样本和10000个测试样本,其中图拍了的尺寸已经进行的标准化的处理,都是黑白的图像,大小为28X28
执行代码,下载数据,观察数据类型:
import torchvision
dataset = torchvision.datasets.MNIST(root="./data",train=True,download=True,transform=None)
print(dataset[0])
下载的数据如下:
代码输出结果如下:
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Processing...
Done!
(<PIL.Image.Image image mode=L size=28x28 at 0x18D303B9C18>, tensor(5))
可以其中数据集返回了两条数据,可以猜测为图片的数据和目标值
返回值的第0个为Image类型,可以调用show() 方法打开,发现为手写数字5
import torchvision
dataset = torchvision.datasets.MNIST(root="./data",train=True,download=True,transform=None)
print(dataset[0])
img = dataset[0][0]
img.show() #打开图片
图片如下:
由上可知:返回值为(图片,目标值)
,这个结果也可以通过观察源码得到
流程:
准备数据集的方法前面已经讲过,但是通过前面的内容可知,调用MNIST返回的结果中图形数据是一个Image对象,需要对其进行处理
为了进行数据的处理,接下来学习torchvision.transfroms
的方法
torchvision.transforms
的图形数据处理方法torchvision.transforms.ToTensor
把一个取值范围是[0,255]
的PIL.Image
或者shape
为(H,W,C)
的numpy.ndarray
,转换成形状为[C,H,W]
其中(H,W,C)
意思为(高,宽,通道数)
,黑白图片的通道数只有1,其中每个像素点的取值为[0,255],彩色图片的通道数为(R,G,B),每个通道的每个像素点的取值为[0,255],三个通道的颜色相互叠加,形成了各种颜色
示例如下:
from torchvision import transforms
import numpy as np
data = np.random.randint(0, 255, size=12)
img = data.reshape(2,2,3)
print(img.shape)
img_tensor = transforms.ToTensor()(img) # 转换成tensor
print(img_tensor)
print(img_tensor.shape)
输出如下:
shape:(2, 2, 3)
img_tensor:tensor([[[215, 171],
[ 34, 12]],
[[229, 87],
[ 15, 237]],
[[ 10, 55],
[ 72, 204]]], dtype=torch.int32)
new shape:torch.Size([3, 2, 2])
注意:
transforms.ToTensor
对象中有__call__
方法,所以可以对其示例能够传入数据获取结果
torchvision.transforms.Normalize(mean, std)
给定均值:mean,shape和图片的通道数相同(指的是每个通道的均值),方差:std,和图片的通道数相同(指的是每个通道的方差),将会把Tensor
规范化处理。
即:Normalized_image=(image-mean)/std
。
例如:
from torchvision import transforms
import numpy as np
import torchvision
data = np.random.randint(0, 255, size=12)
img = data.reshape(2,2,3)
img = transforms.ToTensor()(img) # 转换成tensor
print(img)
print("*"*100)
norm_img = transforms.Normalize((10,10,10), (1,1,1))(img) #进行规范化处理,指定均值为10,标准差为1
print(norm_img)
输出如下:
tensor([[[177, 223], [ 71, 182]], [[153, 120], [173, 33]], [[162, 233], [194, 73]]], dtype=torch.int32) *************************************************************************************** tensor([[[167, 213], [ 61, 172]], [[143, 110], [163, 23]], [[152, 223], [184, 63]]], dtype=torch.int32)
注意:在sklearn中,默认上式中的std和mean为数据每列的std和mean,sklearn会在标准化之前算出每一列的std和mean。
但是在api:Normalize中并没有帮我们计算,所以我们需要手动计算
当mean为全部数据的均值,std为全部数据的std的时候,才是进行了标准化。
如果mean(x)不是全部数据的mean的时候,std(y)也不是的时候,Normalize后的数据分布满足下面的关系
torchvision.transforms.Compose(transforms)
将多个transform
组合起来使用。
例如
transforms.Compose([
torchvision.transforms.ToTensor(), #先转化为Tensor
torchvision.transforms.Normalize(mean,std) #在进行正则化
])
准备训练集
import torchvision
#准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化
#因为MNIST只有一个通道(黑白图片),所以元组中只有一个值
dataset = torchvision.datasets.MNIST('/data', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
]))
#准备数据迭代器
train_dataloader = torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True)
准备测试集
import torchvision
#准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化
#因为MNIST只有一个通道(黑白图片),所以元组中只有一个值
dataset = torchvision.datasets.MNIST('/data', train=False, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
]))
#准备数据迭代器
train_dataloader = torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True)
补充:全连接层:当前一层的神经元和前一层的神经元相互链接,其核心操作就是 y = w x y = wx y=wx,即矩阵的乘法,实现对前一层的数据的变换
模型的构建使用了一个三层的神经网络,其中包括两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果
那么在这个模型中有两个地方需要注意:
前面介绍了激活函数的作用,常用的激活函数为Relu激活函数,他的使用非常简单
Relu激活函数由import torch.nn.functional as F
提供,F.relu(x)
即可对x进行处理
例如:
In [30]: b
Out[30]: tensor([-2, -1, 0, 1, 2])
In [31]: import torch.nn.functional as F
In [32]: F.relu(b)
Out[32]: tensor([0, 0, 0, 1, 2])
[batch_size,1,28,28]
[batch_size,28*28]
,(全连接层是在进行矩阵的乘法操作)[batch_size,28]
,这里的28是个人设定的,你也可以设置为别的[batch_size,10]
,因为手写数字有10个类别构建模型的代码如下:
import torch from torch import nn import torch.nn.functional as F class MnistNet(nn.Module): def __init__(self): super(MnistNet,self).__init__() self.fc1 = nn.Linear(28*28*1,28) #定义Linear的输入和输出的形状 self.fc2 = nn.Linear(28,10) #定义Linear的输入和输出的形状 def forward(self,x): x = x.view(-1,28*28*1) #对数据形状变形,-1表示该位置根据后面的形状自动调整 x = self.fc1(x) #[batch_size,28] x = F.relu(x) #[batch_size,28] x = self.fc2(x) #[batch_size,10]
可以发现:pytorch在构建模型的时候形状上
并不会考虑batch_size
首先,我们需要明确,当前我们手写字体识别的问题是一个多分类的问题,所谓多分类对比的是之前学习的2分类
回顾之前的课程,我们在逻辑回归中,我们使用sigmoid进行计算对数似然损失,来定义我们的2分类的损失。
在2分类中我们有正类和负类,正类的概率为 P ( x ) = 1 1 + e − x = e x 1 + e x P(x) = \frac{1}{1+e^{-x}} = \frac{e^x}{1+e^x} P(x)=1+e−x1=1+exex,那么负类的概率为 1 − P ( x ) 1-P(x) 1−P(x)
将这个结果进行计算对数似然损失 − ∑ y l o g ( P ( x ) ) -\sum y log(P(x)) −∑ylog(P(x))就可以得到最终的损失
那么在多分类的过程中我们应该怎么做呢?
多分类和2分类中唯一的区别是我们不能够再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax函数。
softmax和sigmoid的区别在于我们需要去计算样本属于每个类别的概率,需要计算多次,而sigmoid只需要计算一次
softmax的公式如下:
σ
(
z
)
j
=
e
z
j
∑
k
=
1
K
e
z
K
,
j
=
1
⋯
k
\sigma(z)_j = \frac{e^{z_j}}{\sum^K_{k=1}e^{z_K}} ,j=1 \cdots k
σ(z)j=∑k=1KezKezj,j=1⋯k
例如下图:
假如softmax之前的输出结果是2.3, 4.1, 5.6
,那么经过softmax之后的结果是多少呢?
Y
1
=
e
2.3
e
2.3
+
e
4.1
+
e
5.6
Y
2
=
e
4.1
e
2.3
+
e
4.1
+
e
5.6
Y
3
=
e
5.6
e
2.3
+
e
4.1
+
e
5.6
Y1 = \frac{e^{2.3}}{e^{2.3}+e^{4.1}+e^{5.6}} \\ Y2 = \frac{e^{4.1}}{e^{2.3}+e^{4.1}+e^{5.6}} \\ Y3 = \frac{e^{5.6}}{e^{2.3}+e^{4.1}+e^{5.6}} \\
Y1=e2.3+e4.1+e5.6e2.3Y2=e2.3+e4.1+e5.6e4.1Y3=e2.3+e4.1+e5.6e5.6
对于这个softmax输出的结果,是在[0,1]区间,我们可以把它当做概率
和前面2分类的损失一样,多分类的损失只需要再把这个结果进行对数似然损失的计算即可
即:
最后,会计算每个样本的损失,即上式的平均值
我们把softmax概率传入对数似然损失得到的损失函数称为交叉熵损失
在pytorch中有两种方法实现交叉熵损失
criterion = nn.CrossEntropyLoss()
loss = criterion(input,target)
#1. 对输出值计算softmax和取对数
output = F.log_softmax(x,dim=-1)
注意:dim 需要指定,否则:UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
#2. 使用torch中带权损失
loss = F.nll_loss(output,target)
带权损失定义为: l n = − ∑ w i x i l_n = -\sum w_{i} x_{i} ln=−∑wixi,其实就是把 l o g ( P ) log(P) log(P)作为 x i x_i xi,把真实值Y作为权重
训练的流程:
mnist_net = MnistNet() optimizer = optim.Adam(mnist_net.parameters(),lr= 0.001) def train(epoch): mode = True mnist_net.train(mode=mode) #模型设置为训练模型 train_dataloader = get_dataloader(train=mode) #获取训练数据集 for idx,(data,target) in enumerate(train_dataloader): optimizer.zero_grad() #梯度置为0 output = mnist_net(data) #进行向前计算 loss = F.nll_loss(output,target) #带权损失 loss.backward() #进行反向传播,计算梯度 optimizer.step() #参数更新 if idx % 10 == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, idx * len(data), len(train_dataloader.dataset), 100. * idx / len(train_dataloader), loss.item()))
torch.save(mnist_net.state_dict(),"model/mnist_net.pt") #保存模型参数
torch.save(optimizer.state_dict(), 'results/mnist_optimizer.pt') #保存优化器参数
mnist_net.load_state_dict(torch.load("model/mnist_net.pt"))
optimizer.load_state_dict(torch.load("results/mnist_optimizer.pt"))
评估的过程和训练的过程相似,但是:
torch.max
,返回最大值和最大值的位置[batch_size]
)进行对比,相同表示预测成功def test():
test_loss = 0
correct = 0
mnist_net.eval() #设置模型为评估模式
test_dataloader = get_dataloader(train=False) #获取评估数据集
with torch.no_grad(): #不计算其梯度
for data, target in test_dataloader:
output = mnist_net(data)
test_loss += F.nll_loss(output, target, reduction='sum').item()
pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1]
correct += pred.eq(target.data.view_as(pred)).sum() #预测准备样本数累加
test_loss /= len(test_dataloader.dataset) #计算平均损失
print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
test_loss, correct, len(test_dataloader.dataset),
100. * correct / len(test_dataloader.dataset)))
import torch from torch import nn from torch import optim import torch.nn.functional as F import torchvision train_batch_size = 64 test_batch_size = 1000 img_size = 28 def get_dataloader(train=True): assert isinstance(train,bool),"train 必须是bool类型" #准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化 #因为MNIST只有一个通道(黑白图片),所以元组中只有一个值 dataset = torchvision.datasets.MNIST('/data', train=train, download=True, transform=torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize((0.1307,), (0.3081,)),])) #准备数据迭代器 batch_size = train_batch_size if train else test_batch_size dataloader = torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True) return dataloader class MnistNet(nn.Module): def __init__(self): super(MnistNet,self).__init__() self.fc1 = nn.Linear(28*28*1,28) self.fc2 = nn.Linear(28,10) def forward(self,x): x = x.view(-1,28*28*1) x = self.fc1(x) #[batch_size,28] x = F.relu(x) #[batch_size,28] x = self.fc2(x) #[batch_size,10] # return x return F.log_softmax(x,dim=-1) mnist_net = MnistNet() optimizer = optim.Adam(mnist_net.parameters(),lr= 0.001) # criterion = nn.NLLLoss() # criterion = nn.CrossEntropyLoss() train_loss_list = [] train_count_list = [] def train(epoch): mode = True mnist_net.train(mode=mode) train_dataloader = get_dataloader(train=mode) print(len(train_dataloader.dataset)) print(len(train_dataloader)) for idx,(data,target) in enumerate(train_dataloader): optimizer.zero_grad() output = mnist_net(data) loss = F.nll_loss(output,target) #对数似然损失 loss.backward() optimizer.step() if idx % 10 == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, idx * len(data), len(train_dataloader.dataset), 100. * idx / len(train_dataloader), loss.item())) train_loss_list.append(loss.item()) train_count_list.append(idx*train_batch_size+(epoch-1)*len(train_dataloader)) torch.save(mnist_net.state_dict(),"model/mnist_net.pkl") torch.save(optimizer.state_dict(), 'results/mnist_optimizer.pkl') def test(): test_loss = 0 correct = 0 mnist_net.eval() test_dataloader = get_dataloader(train=False) with torch.no_grad(): for data, target in test_dataloader: output = mnist_net(data) test_loss += F.nll_loss(output, target, reduction='sum').item() pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1] correct += pred.eq(target.data.view_as(pred)).sum() test_loss /= len(test_dataloader.dataset) print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format( test_loss, correct, len(test_dataloader.dataset), 100. * correct / len(test_dataloader.dataset))) if __name__ == '__main__': test() for i in range(5): #模型训练5轮 train(i) test()
""" 使用pytorch 实现手写数字识别 """ import torch import os from torch.utils.data import DataLoader from torchvision.datasets import MNIST from torchvision.transforms import Compose, ToTensor, Normalize import torch.nn as nn import torch.nn.functional as F from torch.optim import Adam import numpy as np BATCH_SIZE = 128 TEST_BATCH_SIZE = 1000 # 1.准备数据集 def get_dataloader(train = True, batch_size = BATCH_SIZE): transform_fn = Compose([ ToTensor(), Normalize(mean = (0.1307,), std = (0.3081, )) # mean,std的形状和通道数相同,彩色3通道,黑白单通道 ]) dataset = MNIST(root = './data', train = train, transform = transform_fn) dataloader = DataLoader(dataset, batch_size = batch_size, shuffle = True) return dataloader # 2.构建模型 class MnistModel(nn.Module): def __init__(self): super(MnistModel, self).__init__() self.fc1 = nn.Linear(1 * 28 * 28, 28) self.fc2 = nn.Linear(28, 10) def forward(self, input): """ :param input: [batch_size, 1 * 28 * 28] :return: """ # 1.修改形状 # input.view([-1, 1 * 28 * 28]) x = input.view([input.size(0), 1 * 28 * 28]) # 2.全连接操作 - 矩阵乘法 x = self.fc1(x) # 3.进行激活函数处理 - 形状不发生变化 x = F.relu(x) # 4.输出层 out = self.fc2(x) return F.log_softmax(out, dim = -1) """实现训练过程""" model = MnistModel() # 优化器 optimizer = Adam(model.parameters(), lr = 0.001) if os.path.exists('./models/model.pkl'): # 模型加载 model.load_state_dict(torch.load('./models/model.pkl')) optimizer.load_state_dict(torch.load('./models/optimizer.pkl')) def train(epoch): # 获取训练数据集 data_loader = get_dataloader() # 迭代 for idx, (input, target) in enumerate(data_loader): optimizer.zero_grad() # 梯度重置 output = model(input) # 调用模型,得到预测值 loss = F.nll_loss(output, target) # 得到损失 loss.backward() # 反向传播更新梯度 optimizer.step() # 更新参数 if idx % 100 == 0: print(epoch, idx, loss.item()) # 模型的保存,每训练100次保存 if idx % 100 == 0: torch.save(model.state_dict(), './models/model.pkl') torch.save(optimizer.state_dict(), './models/optimizer.pkl') def test(): loss_list = list() acc_list = list() # 获取测试集数据 test_dataloader = get_dataloader(train = False, batch_size = TEST_BATCH_SIZE) for idx, (input, target) in enumerate(test_dataloader): # torch.no_grad 包裹的不用进行梯度计算 with torch.no_grad(): output = model(input) # [batch_size, 10] cur_loss = F.nll_loss(output, target) loss_list.append(cur_loss) # 计算准确率,target:[batch_size] pred = output.max(dim = -1)[-1] # dim -- -1为行,0为列 # 计算当前准确率,求预测和真实值的bool,并转换为float求均值 cur_acc = pred.eq(target).float().mean() acc_list.append(cur_acc) print('准确率均值为:%lf,损失均值为:%lf' % (np.mean(acc_list), np.mean((loss_list)))) if __name__ == '__main__': for i in range(3): # epoch = 3,训练3轮 print('第%d轮训练' % (i + 1)) train(i) print('开始评估....') test()
第1轮训练 0 0 0.07957030832767487 0 100 0.10652356594800949 0 200 0.0352938212454319 0 300 0.08590278029441833 0 400 0.06835667788982391 第2轮训练 1 0 0.0978207066655159 1 100 0.10921172052621841 1 200 0.04206729307770729 1 300 0.08663889765739441 1 400 0.08657629042863846 第3轮训练 2 0 0.09602506458759308 2 100 0.03740483522415161 2 200 0.10988372564315796 2 300 0.22913388907909393 2 400 0.08315720409154892 开始评估.... 准确率均值为:0.964000,损失均值为:0.115622 Process finished with exit code 0
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。