第五周学习周报
- 摘要
- Abstract
- 机器学习——神经网络训练不起来怎么办?
- 1. 局部最小点和鞍点
- 1.1 Momentum(动量)
- 1.2 Local minima和saddle point谁出现的情况更多?
- 2. 批次(Batch)
- 2.1 Small Batch v.s. Large Batch
- Pytorch学习——Transforms
- 1. Transforms的结构以及用法
- 2. 常见的Transforms
- 2.1 python中_call_的用法
- 2.2 Normalize()的使用
- 2.3 Resize()的使用
- 2.4 RandomCrop()用法
- 总结
摘要
这一周继续进行了机器学习,主要学习了如何处理神经网络训练不起来的情况,其中深入了解了局部最小点和鞍点与动量以及批次的概念以及细节内容,此外还对Pytorch进行了学习,学习了Transforms的结构以及用法还了解了常见的Transforms。
Abstract
This week, I continued to engage in machine learning, mainly learning how to handle situations where neural networks cannot be trained. I delved into the concepts and details of local minima, saddle points, momentum, and batches. Additionally, I learned about Pytorch, the structure and usage of Transformers, and common Transformers.
机器学习——神经网络训练不起来怎么办?
1. 局部最小点和鞍点
继续上一周的问题,我们来探讨一下Optimization失败要怎么做
我们使用optimization时,使用的是gradient descent算法,因此optimization没做好,很大一部分原因是我在gradient decent没做好。
所以当我们的在optimization的时候,loss不再下降时候,证明是我们的gradient以及无限接近于0了,我们会认为这时候就到达我们之前提到的local minima。
但是,当我们达到一个critical point(临界点)时,不是只有local minima的gradient是0,还有其他可能会让gradient是零,比如:
saddle point(鞍点),这时候gradient是0,但其不是local minima也不是local maxima
鞍点(Saddle point)在微分方程中,沿着某一方向是稳定的,另一条方向是不稳定的奇点,叫做鞍点。
在泛函中,既不是极大值点也不是极小值点的临界点,叫做鞍点。
在矩阵中,一个数在所在行中是最大值,在所在列中是最小值,则被称为鞍点。
在物理上要广泛一些,指在一个方向是极大值,另一个方向是极小值的点。
如下图所示:saddle point的图像,前后是高的,左右是低的,像是一个马鞍,所以称之为鞍点。
那我们怎么判断到底是卡在local minima 还是 saddle point呢?
因为走到local minima 我们就无路可走了,但是到saddle point我们还是有办法让其变得更低。
要解决上述问题,我们需要知道Loss function的形状,但是Loss function的形状是不容易显现出来的,我们可以根据其周围的点来确认。
我们在学习高等数学的时候,有接触到一个叫做**Taylor Series (泰勒级数)**的东西,其可以近似表示一个函数
泰勒级数(英语:Taylor series)用无限项连加式——级数来表示一个函数
这些相加的项由函数在某一点的导数求得。
通过函数在自变量零点的导数求得的泰勒级数又叫做麦克劳林级数。
一元函数的泰勒级数其表达式为:
∑ n − 0 ∞ f ( n ) ( x 0 ) n ! ( x − x 0 ) n = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 + ⋯ + f ( n ) ( x 0 ) n ! ( x − x 0 ) n + ⋯ {\color{red}\sum_{n-0}^{\infty} \frac{f^{(n)}\left(x_{0}\right)}{n!}\left(x-x_{0}\right)^{n}=f\left(x_{0}\right)+f^{\prime}\left(x_{0}\right)\left(x-x_{0}\right)+\frac{f^{\prime \prime}\left(x_{0}\right)}{2!}\left(x-x_{0}\right)^{2}+\cdots+\frac{f^{(n)}\left(x_{0}\right)}{n!}\left(x-x_{0}\right)^{n}+\cdots} n−0∑∞n!f(n)(x0)(x−x0)n=f(x0)+f′(x0)(x−x0)+2!f′′(x0)(x−x0)2+⋯+n!f(n)(x0)(x−x0)n+⋯
同理可得,多元函数的泰勒表达式为:
f
(
x
1
,
x
2
,
…
,
x
n
)
=
f
(
x
k
1
,
x
k
2
,
…
,
x
k
n
)
+
∑
i
=
1
n
(
x
i
−
x
k
i
)
f
x
i
′
(
x
k
1
,
x
k
2
,
…
,
x
k
n
)
+
1
2
!
∑
i
,
j
=
1
n
(
x
i
−
x
k
i
)
(
x
j
−
x
k
j
)
f
i
j
′
′
(
x
k
1
,
x
k
2
,
…
,
x
k
n
)
+
o
n
{\color{Red} f(x^1,x^2,\ldots,x^n)=f(x^1_k,x^2_k,\ldots,x^n_k)+\sum_{i=1}^n(x^i-x_k^i)f'_{x^i}(x^1_k,x^2_k,\ldots,x^n_k)\\+\frac1{2!}\sum_{i,j=1}^n(x^i-x_k^i)(x^j-x_k^j)f''_{ij}(x^1_k,x^2_k,\ldots,x^n_k)\\+o^n}
f(x1,x2,…,xn)=f(xk1,xk2,…,xkn)+i=1∑n(xi−xki)fxi′(xk1,xk2,…,xkn)+2!1i,j=1∑n(xi−xki)(xj−xkj)fij′′(xk1,xk2,…,xkn)+on
把上述公式写成矩阵的形式为:
f
(
x
)
=
f
(
x
k
)
+
[
∇
f
(
x
k
)
]
T
(
x
−
x
k
)
+
1
2
!
[
x
−
x
k
]
T
H
(
x
k
)
[
x
−
x
k
]
+
o
n
{\color{Red} f(\mathbf x) = f(\mathbf x_k)+[\nabla f(\mathbf x_k)]^T(\mathbf x-\mathbf x_k)+\frac1{2!}[\mathbf x-\mathbf x_k]^TH(\mathbf x_k)[\mathbf x-\mathbf x_k]+o^n}
f(x)=f(xk)+[∇f(xk)]T(x−xk)+2!1[x−xk]TH(xk)[x−xk]+on
其中
H
(
x
k
)
=
[
∂
2
f
(
x
k
)
∂
x
1
2
∂
2
f
(
x
k
)
∂
x
1
∂
x
2
⋯
∂
2
f
(
x
k
)
∂
x
1
∂
x
n
∂
2
f
(
x
k
)
∂
x
2
∂
x
1
∂
2
f
(
x
k
)
∂
x
2
2
⋯
∂
2
f
(
x
k
)
∂
x
2
∂
x
n
⋮
⋮
⋱
⋮
∂
2
f
(
x
k
)
∂
x
n
∂
x
1
∂
2
f
(
x
k
)
∂
x
n
∂
x
2
⋯
∂
2
f
(
x
k
)
∂
x
n
2
]
{\color{Red} H(\mathbf x_k)=\left[\begin{matrix}\frac{\partial^2f(x_k)}{\partial x_1^2} & \frac{\partial^2f(x_k)}{\partial x_1\partial x_2} & \cdots & \frac{\partial^2f(x_k)}{\partial x_1\partial x_n} \\\frac{\partial^2f(x_k)}{\partial x_2 \partial x_1} & \frac{\partial^2f(x_k)}{\partial x_2^2} & \cdots & \frac{\partial^2f(x_k)}{\partial x_2\partial x_n} \\\vdots & \vdots & \ddots & \vdots \\\frac{\partial^2f(x_k)}{\partial x_n\partial x_1} & \frac{\partial^2f(x_k)}{\partial x_n\partial x_2} & \cdots & \frac{\partial^2f(x_k)}{\partial x_n^2} \\\end{matrix}\right]}
H(xk)=
∂x12∂2f(xk)∂x2∂x1∂2f(xk)⋮∂xn∂x1∂2f(xk)∂x1∂x2∂2f(xk)∂x22∂2f(xk)⋮∂xn∂x2∂2f(xk)⋯⋯⋱⋯∂x1∂xn∂2f(xk)∂x2∂xn∂2f(xk)⋮∂xn2∂2f(xk)
所以我们可以使用Taylor Series Approximate(泰勒级数近似)来完成L(θ)的表达:
L ( θ ) ≈ L ( θ ′ ) + ( θ − θ ′ ) T g + 1 2 ( θ − θ ′ ) T H ( θ − θ ′ ) {\color{Red}L(\boldsymbol{\theta}) \approx L\left(\boldsymbol{\theta}^{\prime}\right)+\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{T} g+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{T} H\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right) } L(θ)≈L(θ′)+(θ−θ′)Tg+21(θ−θ′)TH(θ−θ′)
其中g是梯度,vector(向量);H是黑塞矩阵,matrix(矩阵)
黑塞矩阵(Hessian Matrix),是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率。
黑塞矩阵常用于牛顿法解决优化问题,利用黑塞矩阵可判定多元函数的极值问题。
在工程实际问题的优化设计中,所列的目标函数往往很复杂,为了使问题简化,常常将目标函数在某点邻域展开成泰勒多项式来逼近原函数,此时函数在某点泰勒展开式的矩阵形式中会涉及到黑塞矩阵。
如下图所示,可以看到,L(θ’)加上后面的部分会逼近L(θ)
所以,当我们达到一个critical point(临界点)时,意味着我们的gradient为0,所以绿色的一项就不见了,只剩下红色这一项。
此时,L(θ)就近似为 (注意:是在critical point的前提下) :
L ( θ ) ≈ L ( θ ′ ) + 1 2 ( θ − θ ′ ) T H ( θ − θ ′ ) {\color{Red} L(\boldsymbol{\theta}) \approx L\left(\boldsymbol{\theta}^{\prime}\right)+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{T} H\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right) } L(θ)≈L(θ′)+21(θ−θ′)TH(θ−θ′)
然后根据红色的一项,我们就可以大概了解critical point目前所处的地势如何
接下来,我们研究一下红色这一项里面是如何进行判断的
接下来,我们用举例说明:
假设这里有一个非常简单的model,每一层只与对于的w做乘法,然后作为下层的输入
只有一个训练资料,是x = ,ŷ=1
于是我们得到的图像如下图所示(越红越大,越蓝越小)
图中有以下critical point:
中间的是saddle point(因为其向右斜两侧变小,向左斜两侧变大)
其余的point都是local minima
那么在我们不知道的前提下,随便给定一个critical point,我们如何判断其是local minima还是local maxima 还是 saddle point呢?
如下:
可以看到当w₁=0、w₂=0时候,就是鞍点
当我们求出saddle point时,应该感到高兴,因为我们有方法让Loss变得更低了。
那么要怎么降低我们的Loss呢?
解释如下:
所以承接上述例子,特征值<0的特征向量可得:
但是现实很少人使用这个方法,因为计算量实在是太大了。
1.1 Momentum(动量)
momentum,这也是另外一个有可能可以对抗saddle point或local minima的技术。
这个momentum是怎么运作的呢?
可以想象成在物理的世界里面,假设error surface就是真正的斜坡,而我们的参数是一个球,把球从斜波上滚下来。如果是gradient descent,他走到local就停住了或者走到saddle point就停住了,
但是在物理的世界里面,一个球会这样子吗?
一个球如果从高处滚下来,就算滚到saddle point。如果有惯性,它从左边滚下来,因为惯性的关系,它还是会继续往右走,甚至它走到一个local minima。
如果今天它的动量够大的话。还是会继续往右走,甚至翻过这个小坡,然后继续往右走。
如下图所示:
那所以在物理的世界里面,一个球从高处滚下来的时候,它并不会被或local minima卡住或者不一定会被或saddle minima卡住。
我们有没有办法运用这样子的概念到里面呢?
我们先回顾一下之前常用的Gradient Descent
我们计算一下gradient,然后计算完这个gradient以后,我们跟我们往gradient的反方向去update参数,我们到了新的参数以后再计算一次,规定再往gradient的反方向再update一次参数。一直这样下去。
如下图:
如果加上Momentum的话会怎么样呢?
加上Momentum后,现在不只往gradient的反方向去移动参数
而是gradient的反方向加上前一步移动的方向,两者加起来的结果去调整去移动
1、 找一个初始的参数(θ⁰),然后假设前一步的参数的update量(m)就设为0
2、 计算g⁰即计算gradient的方向,它是gradient的方向加上前一步的方向,不过因为前一步呢,正好是零,现在是刚初始的时候,,所以这个方向呢,跟原来的一般的gradient一样的。
3、 但从第二步开始有加上momentum以后。就不太一样了,从第二步开始,我们计算之g¹外话要计算m¹
m
1
=
λ
m
0
−
η
g
0
{\color{red}m^1 = λm^0 - ηg^0}
m1=λm0−ηg0
然后再用m¹与θ⁰算出θ¹,m¹与θ⁰方向不同就折中
θ
1
=
θ
0
+
m
1
{\color{red}θ^1 = θ^0 + m^1}
θ1=θ0+m1
以此类推,反复进行这个过程,一直更新θ
如下图所示(不同颜色箭头,表示不同变量方向,虚线表示momentum上一步的方向)
再回到刚刚的物理例子来理解Gradient Descent + Momentum的过程:
1、我们从开头的地方开始update参数,根据gradient的方向告诉我们,应该往右update参数,那现在没有前一次update的方向,所以我们就完全按照gradient给我们的指示往右移动参数,那我们的参数就往右移动了一点。
2、到第二个点的地方gradient变得很小。但前一步是往右移动的,我们把前一步的方向用虚线来表示放在这个地方。我们把之前gradient告诉我们要走的方向跟前一步,移动的方向加起来得到往右走的方向。
3、那再往右走,走到一个local minima。照理说,走到local minima一般gradient descent就无法向前走了,因为已经没有这个gradient的方向,那走到saddle point也一样,没有gradient的方向,已经无法向前走了。**但没有关系,如果有momentum的话,你还是有办法继续走下去。**因为momentum不是只看gradient,gradient就算是零,你还有前一步的方向。
4、前一步的方向告诉我们,向右走,我们就继续向右走。甚至你走到这种地方gradient告诉你,应该要往左走了,但是假设你前一步的影响力比gradient要大的话,你还是有可能继续往右走甚至翻过一个小丘,搞不好就可以走到更好的local minima。
这个就是momentum有可能带来的好处
1.2 Local minima和saddle point谁出现的情况更多?
我们都知道降维打击这个词,在高维生物看来,低维生物看到几乎完美的东西,实际上是漏洞百出的,如下图所示
二维来说就是一个local minima,但是三维看起来就是一个saddle point
像下面的假山中,其实在三维看来就是很大local minima的,但在更高维度就说不定了,我们也无法查看四维的东西
在我们的深度学习中,维度动不动就是上千、上万维度,既然维度那么多,那么肯定没有太多的local minima,事实也的确如此。
如下图中:
Minimal Ratio是表示eigen value正的数目占eigen value总数的比率
在这个图上越往右代表我们的critical point,越像local minima。但是他们都没有真的变成local minima,就算是在最极端的状况一半的case,我们的eigen value是负的,这一半的case eigen value是正的,最大的Minimal Ratio也不过零点五到零点六间而已。
所以我们大部分遇到的都是saddle point,这也是optimization的意义
2. 批次(Batch)
我们在算微分的时候,不是真的对所有θ算出来做微分,而是把所有的data分成一个一个的batch,我们每次在update参数的时候,我们是拿一个Batch的资料出来算个loss、算gradient update参数。再拿另外一个Batch的资料再算loss与gradient再update参数,以此类推。
然后把所有的batch资料都看过一次之后称之为一次epoch,每达到一次epoch就要进行一次shuffle
epoch(时期;新纪元;新时代;阶段)
表示: 所有的数据送入网络中, 完成了一次前向计算 + 反向传播的过程。
shuffle(洗牌、混乱)
shuffle在机器学习与深度学习中代表的意思是,将训练模型的数据集进行打乱操作。
如下图所示:
2.1 Small Batch v.s. Large Batch
那为什么要用Batch呢?
我们可以对用了batch和没用batch进行对比
左边的图就是没有用batch,因为base size直接设的跟训练资料一样多。这种状况没使用batch的意思。而那右边的图batch size = 1
这是两个最极端的状况
①我们先来看左边的图:
因为没有用batch,我们有20笔训练资料,我们的model必须把20笔训练资料都看完,才能够计算loss,才能够计算gradien。
②然后看右边的图:
**如果batch size=1的话,代表我们每次update参数的时候,我们只要看一笔资料。**说明我们
**只需要拿一笔资料出来算loss,我们就可以update我们的参数。**如果总共有20笔资料的话,那在每在一个里面,我们的参数会update二十次。
通过图像对比我们可以看到,左边的图片是比较简单的,右边的图片是比较noisy(杂乱的)
这是因为左边是通过一次蓄力并释放的操作,而右边是通过多次蓄力并并每一次蓄力后释放的操作。
可以这样理解:左边的方法的优点就是这一步走的是稳的,那右边这个方法它的缺点就是它每一步走的是不稳的。
那左边和右边谁更好呢?
看起来左边的方法跟右边的方法,他们各自都有擅长跟不擅长的东西。
左边。是蓄力时间长,但是威力比较大。右边技能冷却时间短,但是它是比较不准的。
看起来各自有各自的优缺点。
但是,考虑到平行运算之后,Batch size 大的未必就比Bact size小的时间长多少
如下图中,有实验表面更大的Batch Size反而会不会花费很长的时间,其时间几乎是差不多的。
MNIST(Modified National Institute of Standards and Technology database)
是一个大型手写数字数据库,广泛用于训练和测试机器学习模型,特别是在计算机视觉领域。
因为在实际上做运算的时候,我们有GPU做平行运算。所以这1000笔资料是平行处理的。
这1000笔资料所花的时间并不是1笔资料的1000倍。
当然GPU也是有极限的,当我们的Batch Size非常大的时候,还是会随着Batch Size的增长而增长。
当的batch size增加到一万乃至增加到六万的时候。就会发现gpu要算完一个batch,把这个batch里面的资料都拿出来算Loss,再进而算gradient主要耗费的时间确实有随着batch size的增加。
假设我们的训练资料只有六万笔
那base size = 1,就要六万个update才能跑完一个epoch。
如果今天是base size = 1000,你要60个update才能跑完一个epoch
假设今天一个base size = 1和一个base size = 1000,要算规定的时间根本差不多。那六万次update跟60次update比起来,它的时间的差距量就非常可观了。
右边的图是跑完一个完整的epoch需要花的时间
发现左边的图跟右边的图它的趋势正好是相反的。
所以如果我们看右边图的话,大的batch size反而是较有效率的。
实际上在有考虑平行运算的时候,一个epoch大的batch花的时间反而是比较少的。
所以这边我们如果要比较这个大小的差异的话,看起来直接用技能时间冷却的长短并不是一个精确的描述。看起来batch size大的并没有比较吃亏,甚至还占到优势了。
那我们可能会想到那大的batch的劣势消失了,那难这样看起来大的base应该比较好
但事实真的是这样吗?
神奇的地方是。noisy的gradient反而可以帮助training!!!这又是一个反直觉的事情
如下图:
横轴代表的batch size是从左到右越来越大,纵轴代表的是正确率,越上面正确率越高越好。
从结果可以看到,同一个模型中,batch size越大,training的结果也是越差的。
为什么有noisy的gradient反而会在training的时候更好呢?
解释如下:
①左图中:
假设在update你的参数的时候,你就是沿着一个loss function来update参数,那进行update参数的时候,到一个local minima,走到一个critical point,显然就停下来了,如果不进行特殊的操作,那用gradient descent的方法,你就没有办法再更新你的参数。
②右图中:
假如你有用batch的话,会发生什么事呢?
因为我们每次是挑一个batch出来,算它的loss,等于每一次update你的参数的时候,用的loss function都是略有差异的。
比如,选到第一个batch的时候,你是用loss function 1来算gradient。选到第二个batch的时候,你是用loss function 2来算gradient。
假设用loss function 1算gradient的时候,发现gradient = 0卡住了,但在loss function 2的不一定会卡住。
所以loss function 1卡住了,没关系,换下一个batch来loss function 再算gradient ,还是有办法training model的,使得Loss变小的
还有一个更神奇的事情,其实小的batch也对testing有帮助。
假设有一些方法,努力的调大的batch的learning rate
然后把大的batch跟小的batch在training的效果一样好。
实验结果会发现,小的batch居然在testing的时候会是比较好的
如下图:
training的时候SB与LB都很好,但是在testing的时候就,SB反而会更好
SB = Small Batch Size = 256
LB = Large Batch Size = 0.1*数据集数量
在上图中,training的时候都很好,testing的时候,小的batch差,那这就代表产生了overfitting(过拟合)
那为什么会有这样子的现象呢?
在这篇实验文章里面也给出了一个解释,假设这个是我们的可能有很多个local minima,这些local minima它们的loss都很低,它们loss可能都趋近于零,但是这个local minima还是有好minima跟坏minima之分
什么叫做好minima跟坏minima呢?
如下图所示:
通常认为,如果一个local minima,它在一个峡谷里面,它就是坏的minima。minima在一个平原上,它就是好的minima。
为什么会有这样的差异呢?
假设现在training跟testing中间有一个mismatch,training的loss跟testing的loss,他们那个function不一样
为什么不一样呢?
1、有可能是本来你training跟testing的distribution就不一样。
2、可能是因为training跟testing你的自己都是从sample的data算出来的,也许training跟testing sample到的data不一样导致的。
假设training跟testing的差距就是把training的loss这个方向往右平移一点。
对这个在一个平原里面minima来说,它的training跟testing上面的结果不会差太多,只差了一点。
但是对右边这个在峡谷里面minima来说,就可以天差地远了
而很说法都说,大的batch size会让我们的minima倾向于走到峡谷里面,而小的batch size的minima倾向于让我们走到盆地里面
因为一个很小的峡谷没有办法困住小的batch size。
如果峡谷很小,batch size小的话,一下就跳过去,之后如果有一个非常宽的盆地。它才会停下来。
那对于大的batch size,反正它就是顺着gradient去update,然后它就很有可能走到一个小的峡谷里面。
如下图,是一个SB与LB的比较总结图:
从一个epoch需要的时间来看,LB其实是占到优势的。
小的batch你会update方向比较noisy,大的batch update方向比较稳定。
但是noisy的update方向反而在optimization的时候会占到优势,而且在testing的时候。也会占到优势。
他们各自有他们擅长的地方,
所以batch size作为一个hyper parameter(超参数,需要人为调控),这个hyper parameter是需要去调制好的。
Pytorch学习——Transforms
1. Transforms的结构以及用法
from torchvision import transforms
ctrl+左键 点击 transforms 进入transform.py 后我们可以看到其结构
上图中的结构我们看到:比喻Transforms就是一个工具包,而每一个Class就是一个工具
点开Class,我们就可以看到里面的方法。
点击Class会挑战到py文件代码相应的界面,在这里可以看到Class的说明以及要如何使用这个Class的注解
比如我们点开Compose,可以看到其作用就是把transforms组合到一起。
图示如下:
在例子中,我们可以看见:
首先将调用transforms.CenterCrop对图片剪裁
然后调用transforms.PILToTensor对PIL图像进行一个转化,使其可以在tensor中使用
最后转换图像的时间格式
然后输出
我们最常用的类就是ToTensor,其功能就是把PIL图像或者numpy格式的图像转化为tensor。
python的用法-》tensor的数据类型
通过transforms.Tensor去看两个问题
1、transforms该如何被使用(python)
2、为什么我们需要Tensor数据类型
首先来回答第一个问题:
1、transforms该如何被使用(python)
引入PIL的的image后,我们复制相对路径,打开图片
from PIL import Image
最后,print查看图片的格式是PIL类型的,有尺寸和逻辑地址等信息
输入如下代码,按ctrl + 鼠标左键,看看transforms.ToTensor()中是如何使用的
tensor_train = transforms.ToTensor()
然后输入如下代码
from PIL import Image
from torchvision import transforms
# 相对路径
img_path = "Dataset/train/ants_image/0013035.jpg"
img = Image.open(img_path)
tensor_train = transforms.ToTensor()
tensor_image = tensor_train()
所以我们把 img 传入进去
tensor_image = tensor_train(img)
最后print出来,可以看到其格式以及转化为tensor类型的
totensor里面的方法可以理解为具体工具的工具
所以我们可以把流程图具体完善为如下:
接下来回答第二个问题:
2、为什么我们需要Tensor数据类型
把以下代码复制进console中
from PIL import Image
from torchvision import transforms
# 相对路径
img_path = "Dataset/train/ants_image/0013035.jpg"
img = Image.open(img_path)
tensor_train = transforms.ToTensor()
tensor_image = tensor_train(img)
我们可以利用控制台看到PIL的img详细信息,如下:
然后我们再看看tesor的img类型
可以看到其实就是封装了一些神经网络参数(CNN)的格式(因为我们训练就需要用到这些参数,所以一定会用到)
接下我们使用cv2完成(需要pip 安装opencv-python)
输入如下代码:
import cv2
cv_img = cv2.imread(img_path)
可以看到cv2处理后的图片是numpy类型的,前面Totensor也支持numpy类型的转换。
结合上一周学习的tensorboard,我们引入SummaryWriter,输入如下代码
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
# python的用法-》tensor的数据类型
# 通过transforms.Tensor去看两个问题
# 1、transforms该如何被使用(python)
# 2、为什么我们需要Tensor数据类型
# 相对路径
img_path = "Dataset/train/ants_image/0013035.jpg"
img = Image.open(img_path)
write = SummaryWriter("logs")
tensor_trans = transforms.ToTensor()
tensor_image = tensor_trans(img)
write.add_image("Tensor_img", tensor_image)
write.close()
在Terminal运行之后,可以看到图片已经成功上传到Tensorboard了。(与上一周的区别就是这一周引用了Transforms的工具包,简化了图片的处理与输入)
tensorboard --logdir=logs --port=6007
2. 常见的Transforms
我们使用Transforms通常只关注三个点:输入、输出、作用。
此外还有三个需要注意的函数,如:
PIL使用Image.open()
tensor使用的是ToTensor()
narrays矩阵主要是通过opencv里的cv.imread()
接下通过案例来学习常见的Transforms
首先,先创建一个images的文件夹,放入自己喜欢的图片,然后再创建一个名为UsefulTransforms的python文件。
然后我们去到transforms.py中找到compose看看其实如何使用的,可以看到有一个call函数,不止是这个类有,几乎全部的类都有_call_,那么这个到底是什么东西呢?
2.1 python中_call_的用法
为了更深入的了解_call_,我们创建一个例子来解释
创建一个文件夹为test,在test里面创建一个CallTest来理解_call_。
输入如下代码:
#定义类
class Person:
def __call__(self, name):
print("__call__" + "Hello" + name)
def hello(self, name):
print("Hello" + name)
#创建实例
person = Person()
person("zhangsan")
person.hello("lisi")
输出结果如下:
可以看到,我们使用call函数的用法,可以直接用对象然后传参调用_call_,而不需要用 对象名.方法 的形式去调用。
我们使用ctrl + p也可以看到,其让我们传入的参数是name,证明可以直接传参调用。
2.2 Normalize()的使用
Normalize(标准化)
PyTorch中的transforms.Normalize主要用于对图像数据进行标准化处理。
标准化是一种常见的数据预处理步骤,旨在减少数据集中的变量之间的差异性,使得每个特征的平均值为0,标准差为1,或者将数据变换到[-1,1]之间。
在PyTorch中,transforms.Normalize通过减去均值(mean)并除以标准差(std)来实现这一目的。
这种处理有助于提高模型的训练效率和准确性。
具体来说,transforms.Normalize的作用是将图像数据归一化到[-1,1]之间。
这一过程通过计算图像数据的均值和标准差,然后对每个像素值进行相应的减均值和除标准差的操作实现。这样做的好处包括:
加快模型收敛速度:通过将数据归一化,可以使得数据的分布更加接近标准正态分布,从而使得模型在训练过程中更容易找到最优解,加快收敛速度。
提高模型泛化能力:标准化有助于模型更好地学习数据的内在规律,从而提高模型的泛化能力,即在训练数据之外的数据上也能取得较好的预测效果。
output[channel] = (input[channel] - mean[channel]) / std[channel]
其中mean是均值、std是标准差
主要的运算就是:输出通道 = (输入通道 - 平均通道)/ 标准差
如下图:
假如我们的input范围是[0,1]
那么我们的result就是[-1,1]
然后我们用代码去实现:
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
img = Image.open('images/machine_learning.png')
print(img)
# ToTensor
trans_tensor = transforms.ToTensor()
img_tensor = trans_tensor(img)
writer.add_image("ToTensor", img_tensor)
# Normalize
# 先看看标准化前的tensor第一层第一行第一列的数值
print(img_tensor[0][0][0])
# 进行标准化
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
# 需要输入tensor数据类型,所以传入img_tensor
img_norm = trans_norm(img_tensor)
# 标准化后的数值
print(img_norm[0][0][0])
writer.add_image("Normalize", img_norm)
writer.close()
然后再打开Tensorboard看看图片的变化
注意:输入不同的mean和std会产生不一样的图像
tensorboard --logdir=logs --port=6007
2.3 Resize()的使用
Resize就是给一个一个输入图片的尺寸
我们可以给定一个宽度和高度,然后会就重新定义图片的尺寸,如果只有一个值,就会进行等比例的缩放。
输入如下代码,使用writer.add_image()前,要注意使用ToTensor进行格式的转换:
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
img = Image.open('images/machine_learning.png')
print(img)
# --------------------------------------------------
# ToTensor的使用
trans_tensor = transforms.ToTensor()
img_tensor = trans_tensor(img)
writer.add_image("ToTensor", img_tensor)
# --------------------------------------------------
# Normalize的使用
# 先看看标准化前的tensor第一层第一行第一列的数值
print(img_tensor[0][0][0])
# 进行标准化
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
# 需要输入tensor数据类型,所以传入img_tensor
img_norm = trans_norm(img_tensor)
# 标准化后的数值
print(img_norm[0][0][0])
writer.add_image("Normalize", img_norm)
# --------------------------------------------------
# Resize的使用
print(img.size)
# 变换图片为512*512大小
trans_resize = transforms.Resize((512, 512))
# imgPIL -> Resize -> img_resizePIL
img_resize = trans_resize(img)
print(img_resize)
# img_resizePIL -> ToTensor -> img_resizeTensor(转化为Tensor类型),相当于覆盖掉了原来的img_resize
img_resize = trans_tensor(img_resize)
writer.add_image("Resize", img_resize)
# --------------------------------------------------
writer.close()
可以看到尺寸已经改变:
在Tensorboard上可以明显看到图像变化:
接下来我们结合Compose一起使用Resize的第二种用法–缩放用法
代码如下:
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
img = Image.open('images/machine_learning.png')
print(img)
# --------------------------------------------------
# ToTensor的使用
trans_toTensor = transforms.ToTensor()
img_tensor = trans_toTensor(img)
writer.add_image("ToTensor", img_tensor)
# --------------------------------------------------
# Normalize的使用
# 先看看标准化前的tensor第一层第一行第一列的数值
print(img_tensor[0][0][0])
# 进行标准化
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
# 需要输入tensor数据类型,所以传入img_tensor
img_norm = trans_norm(img_tensor)
# 标准化后的数值
print(img_norm[0][0][0])
writer.add_image("Normalize", img_norm)
# --------------------------------------------------
# Resize的使用
print(img.size)
# 变换图片为512*512大小
trans_resize = transforms.Resize((512, 512))
# imgPIL -> Resize -> img_resizePIL
img_resize = trans_resize(img)
print(img_resize)
# img_resizePIL -> ToTensor -> img_resizeTensor(转化为Tensor类型),相当于覆盖掉了原来的img_resize
img_resize = trans_toTensor(img_resize)
writer.add_image("Resize", img_resize)
# Compose resize用法
trans_resize_2 = transforms.Resize(512)
# compose结合了Resize和ToTensor,trans_resize_2的输出是trans_toTensor的输入
trans_compose = transforms.Compose([trans_resize_2, trans_toTensor])
img_resize_2 = trans_compose(img)
writer.add_image("Resize", img_resize_2, 2)
# --------------------------------------------------
writer.close()
结果可以看到step 1(512*512)与step 2(512)的不同
2.4 RandomCrop()用法
RandomCrop是随机裁剪,其用法跟Resize差不多,都是给定宽高,或者一个值,然后对图像进行随机裁剪
接下来通过代码,深度理解:
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
img = Image.open('images/machine_learning.png')
print(img)
# --------------------------------------------------
# ToTensor的使用
trans_toTensor = transforms.ToTensor()
img_tensor = trans_toTensor(img)
writer.add_image("ToTensor", img_tensor)
# --------------------------------------------------
# Normalize的使用
# 先看看标准化前的tensor第一层第一行第一列的数值
print(img_tensor[0][0][0])
# 进行标准化
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
# 需要输入tensor数据类型,所以传入img_tensor
img_norm = trans_norm(img_tensor)
# 标准化后的数值
print(img_norm[0][0][0])
writer.add_image("Normalize", img_norm)
# --------------------------------------------------
# Resize的使用
print(img.size)
# 变换图片为512*512大小
trans_resize = transforms.Resize((512, 512))
# imgPIL -> Resize -> img_resizePIL
img_resize = trans_resize(img)
print(img_resize)
# img_resizePIL -> ToTensor -> img_resizeTensor(转化为Tensor类型),相当于覆盖掉了原来的img_resize
img_resize = trans_toTensor(img_resize)
writer.add_image("Resize", img_resize)
# Compose resize用法
trans_resize_2 = transforms.Resize(512)
# compose结合了Resize和ToTensor,trans_resize_2的输出是trans_toTensor的输入
trans_compose = transforms.Compose([trans_resize_2, trans_toTensor])
img_resize_2 = trans_compose(img)
writer.add_image("Resize", img_resize_2, 2)
# --------------------------------------------------
# RandomCrop用法
trans_random = transforms.RandomCrop(256)
trans_compose_2 = transforms.Compose([trans_random, trans_toTensor])
# 进行十次随机裁剪
for i in range(10):
img_crop = trans_compose_2(img)
writer.add_image("RandomCrop", img_crop, i)
# --------------------------------------------------
writer.close()
可以看到每个一step都是进行随机裁剪的
以上就是我们在代码实战中常用的Transforms工具。
总结:
1、使用Transforms,一定要学会观看官方给出的注释,搞清楚输入是什么类型,输出是什么类型。
2、可以进入Class工具,先看看_init_需要什么参数(有默认值的一般不用改),然后再去注释里找到Args(参数),看看这个我们需要输入的参数要什么样的格式。
3、如果不知道输出是什么数据类型,可以使用print() 或者 print(type())打印出来看,也可以在debug中查看
总结
这一周继续对李宏毅的机器学习视频进行了进一步的学习,了解了局部最小点和鞍点的概念,其中学会了如何用高维泰勒级数近似表示一个L(θ),还了解了如何处理saddle point的方式,比如,第一就是通过泰勒级数的最后一项配合算出Hessian的特征值与特征向量完成;第二就是通过momentum物理的方式处理。然后还学习了batch(批次)的概念,了解了SB与LB各自的优劣势,以及其特点。此外还对Pytorch进行了学习,主要学习了Transforms的结构以及用法,Transforms实际上就是一个工具包,里面的一个个Class就是一个个工具,Class里面的方法就是具体工具的工具,可以通过流程图简单的理解。然后其中最重要的方法就是ToTensor,在PIL或者cv2引入图片后,再调用方法让其转换为Tensor,最后还了解了常见的Transforms,使用Transforms,一定要学会观看官方给出的注释,搞清楚输入是什么类型,输出是什么类型。可以进入Class工具,先看看_init_需要什么参数(有默认值的一般不用改),然后再去注释里找到Args(参数),看看这个我们需要输入的参数要什么样的格式,如果不知道输出是什么数据类型,可以使用print() 或者 print(type())打印出来看,也可以在debug中查看。
下一周计划继续进行机器学习的里理论知识学习,以及加快对Pytorch的学习,争取早日进入代码实战。