影响找到全局极小值点的几个主要的影响因素:
1)初始点的设置
2)学习率的设置,可能会影响收敛的精度与速度
ps : 处于一个全局极小值或者是鞍点的状态时,往往会影响实验效果
import torch import torch.nn.functional as F import matplotlib.pyplot as plt
# Jupyter notebook 中的魔法命令 %matplotlib inline # 在下面的代码模块里面都可以不用写 plt.show() %config InlineBackend.figure_format = "png" # 设置矢量图的方式来提高图片显示质量 # 常用的设置图片分辨率的有以下几个: # %config InlineBackend.figure_format = “png" # %config InlineBackend.figure_format = “svg" # %config InlineBackend.figure_format = “retina"
1.常见的激活函数与梯度
Sign函数
# 使用函数的运行速度较慢 # def Single_layer(x): # if x <= 0: return 0 # else: return 1 X = torch.linspace(-10,10,100) Y = torch.tensor([0 if x<=0 else 1 for x in X]) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$sign$')
([<matplotlib.lines.Line2D at 0x296e3813c88>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$sign$'))
Sigmoid函数
X = torch.linspace(-3,3,100) Y = F.sigmoid(X) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$sigmoid$')
([<matplotlib.lines.Line2D at 0x296e1e1d1c8>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$sigmoid$'))
由此可见,sigmoid函数的特性,可以根据现有的值相乘以下便可以完成导数的求解情况。而且其是连续光滑的,解决了sign函数不能求导的情况。但是,在正无穷或者是负无穷的情况中,导数接近为0。而根据梯度下降法,这会导致梯度长时间得不到更新,这种情况称为梯度弥散现象
Tanh函数
X = torch.linspace(-3,3,100) Y = F.tanh(X) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$tanh$')
([<matplotlib.lines.Line2D at 0x296e3903688>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$tanh$'))
ReLU函数
优点,导数计算起来十分的简单,不会放大也不会缩小,梯度会保持变,所以很大情况下会减小出现梯度弥散或者是梯度爆炸的情况
X = torch.linspace(-3,3,100) Y = F.relu(X) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$ReLU$')
([<matplotlib.lines.Line2D at 0x296e3f2e6c8>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$ReLU$'))
Softmax函数
Leaky ReLU函数
Leaky ReLU函数是在ReLU函数上面做出一个改进,用于对于ReLU函数函数来说,当x<0的时候,其梯度是为0的,而这种情况也可能会导致梯度长时间得不到更新,所以Leaky ReLU函数会在x<0的时候做一个小小的偏移,让其有一个比较小的梯度
X = torch.arange(-10,10,0.1) Y = F.leaky_relu(X) plt.xticks(range(-10,10,1)) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$Leaky ReLU$')
([<matplotlib.lines.Line2D at 0x1c1caaff9c8>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$Leaky ReLU$'))
SELU函数
同样的,SeLU函数也是ReLU函数的改进,对于x<0的情况,SELU函数是一个指数函数,也就是相当于是两个函数的拼接
X = torch.arange(-10,10,0.1) Y = F.selu(X) #cplt.xticks(range(-10,10,1)) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$SeLU$')
([<matplotlib.lines.Line2D at 0x1c1cacac2c8>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$SeLU$'))
Softplus函数
Softplus函数的表达式为
其也是在ReLU函数上面作了一个改进,使得在x=0的时候使得函数比较的平滑一些,如下图所示:
X = torch.arange(-10,10,0.1) Y = F.softplus(X) plt.plot(X,Y),plt.xlabel('X_value'),plt.ylabel('Y_value'),plt.title('$Softplus$')
([<matplotlib.lines.Line2D at 0x1c1cabedc48>], Text(0.5, 0, 'X_value'), Text(0, 0.5, 'Y_value'), Text(0.5, 1.0, '$Softplus$'))
2.常见的Loss与梯度
Mean Squared Error均方误差
# 代码表示为 torch.norm(y-pred,2).pow(2)
1)torch.autograd.grad的使用
# 现在使用torch.autograd.grad实现求导的过程 # torch.autograd.grad的使用方法 # 现在假设需要解决的任务方程是简单的一元函数:y = xw + b # 所以其loss function为 loss = ∑[(xw + b) - y]**2 # 第一个例子: x = torch.tensor(5) # 设置x为5 w = torch.full([1],10.) # 设置斜率w为10 b = torch.tensor(2) # 设置bios为2 y = torch.rand(1)+50 # 设置真实值为50附近的随机数,输出为50.6296 loss = F.mse_loss(x*w+b,y) # 输出结果为: tensor([1.8779])) # 现在进行验证一下:(5*10+2-50.6296)**2=1.8779 # 对此时的w进行求导,但是出出现一个报错 ## torch.autograd.grad(loss,[w]) # RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn # 表示w此时是不需要求导,错误原因是一开始的w没有设置好导数信息,所以pytorch自动标注了w不需要求导 # 修改的方法是,可以使用requires_grad_函数更正标注 w.requires_grad_() # RuntimeError: only Tensors of floating point dtype can require gradients # 又出现了一个错误,表示w需要是浮点数值,现在重新将w修改为w = torch.full([1],10.) # 此时想再来求导,但是还是出现了错误 # torch.autograd.grad(loss,[w]) # RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn # 出现错误的原因是:前一步已经更新了w,但pytorch的这个动态图还是没有更新,所以需要重新更新一个图 loss = F.mse_loss(x*w+b,y) # 此时输出下数值:w,y,loss # (tensor([10.], requires_grad=True), # tensor([50.5761]), # tensor(1.8779, grad_fn=<MseLossBackward>), # 完成这个过程之后,就可以对w进行求导了 torch.autograd.grad(loss,[w]) # 输出为(tensor([13.7037]),) # 验证一下 # 2*5*(5*10+2-50.6296)=13.7037 与输出结果一致 # 补充: # 以上过程进行中出现的问题,也就是w不能丘梯度的问题,其实可以从开始初始化的时候直接进行解决 w = torch.full([1],10.).requires_grad_() w = torch.tensor(10.,requires_grad=True) # 以上的两个写法是等价的 # 第二个例子: # 同时可以对多个未知量进行求导 x = torch.tensor(5) # 设置x为5 w = torch.full([1],10.).requires_grad_() # 设置斜率w为10 b = torch.tensor(2.).requires_grad_() # 设置bios为2 y = torch.rand(1)+50 # 设置真实值为50附近的随机数,输出为50.6296 loss = F.mse_loss(x*w+b,y) # autograd.grad的函数中如果对多个未知量进行求导,需要设置参数retain_graph=True,否则相关的结果内存会被释放 dw = torch.autograd.grad(loss,[w],retain_graph=True) db = torch.autograd.grad(loss,[b]) print(y,x*w+b-y,loss) # 输出为:tensor([50.6393]) tensor([1.3607], grad_fn=<SubBackward0>) tensor(1.8514, grad_fn=<MseLossBackward>) dw,db # 输出为:((tensor([13.6066]),), (tensor(2.7213),)) 验证结果是正确的
(tensor([13.7037]),)
2)loss.backward的使用
# 还是以这个例子为例,其实backward函数会自动的对所有的参数进行求导,但是不会输出结果而是将结果保存在每一个未知量x.grad中 x = torch.tensor(5) # 设置x为5 w = torch.full([1],10.).requires_grad_() # 设置斜率w为10 b = torch.tensor(2.).requires_grad_() # 设置bios为2 y = torch.rand(1)+50 # 设置真实值为50附近的随机数,输出为50.6296 loss = F.mse_loss(x*w+b,y) print("loss:",loss,"[(x*w+b)-y]**2:",((x*w+b)-y)**2) # 输出为:loss: tensor(3.1575, grad_fn=<MseLossBackward>) [(x*w+b)-y]**2: tensor([3.1575], grad_fn=<PowBackward0>) # 使用backward自动求导函数 loss.backward() w.grad,b.grad # 输出为:(tensor([17.7694]), tensor(3.5539)) # 验证一下 loss = F.mse_loss(x*w+b,y) dw = torch.autograd.grad(loss,[w],retain_graph=True) db = torch.autograd.grad(loss,[b],retain_graph=True) dw,db # ((tensor([17.7694]),), (tensor(3.5539),)) # 可见,两种方法的输出结果一样
loss: tensor(3.1575, grad_fn=<MseLossBackward>) [(x*w+b)-y]**2: tensor([3.1575], grad_fn=<PowBackward0>)
3)Softmax的使用
a = torch.rand(3,requires_grad=True) # 输出为:tensor([0.2001, 0.6048, 0.0787], requires_grad=True) p = F.softmax(a,dim=0) # 输出为:tensor([0.2954, 0.4429, 0.2617], grad_fn=<SoftmaxBackward>) # 预测值: # tensor([0.2954, 0.4429, 0.2617], grad_fn=<SoftmaxBackward>) # (-0.13083266, 0.24673958999999998, -0.11590693) torch.autograd.grad(p[1],[a]) # 输出为:(tensor([-0.1308, 0.2467, -0.1159]),),符合预测值
tensor([0.2001, 0.6048, 0.0787], requires_grad=True)
Cross Entropy Loss交叉熵损失
相对熵(relative entropy)就是KL散度(Kullback–Leibler divergence),用于衡量两个概率分布之间的差异。
对于两个概率分布p(x)和q(x) ,其相对熵的计算公式为:
需要注意:p(x)和q(x)在公式中的地位不是相等的,所以:
相对熵的特点,是只有p(x)=q(x)时,其值为0。若p(x)和q(x)略有差异,其值就会大于0。其证明利用了负对数函数(-lnx)是严格凸函数(strictly convex function)的性质。
相对熵公式的前半部分就是交叉熵(cross entropy)。既
- 相对熵
- 交叉熵
若p(x) 是数据的真实概率分布,q(x)是由数据计算得到的概率分布。机器学习的目的就是希望p(x)尽可能地逼近甚至等于p(x),从而使得相对熵接近最小值0. 由于真实的概率分布是固定的,相对熵公式的后半部分就成了一个常数。其后半部分为:
那么相对熵达到最小值的时候,也意味着交叉熵达到了最小值。对q(x)的优化就等效于求交叉熵的最小值。另外,对交叉熵求最小值,也等效于求最大似然估计(maximum likelihood estimation)。
1)cross_entropy的使用
x = torch.randn(1,28*28) w = torch.randn(10,28*28) # 矩阵相乘 logit = x@w.t() # logit.shape为:torch.Size([1, 10]) # 直接求出交叉熵 F.cross_entropy(logit,torch.tensor([3])) # 输出为:tensor(17.2760)
2)nll_loss的使用
nll_loss函数其实就是最大似然函数
x = torch.randn(1,28*28) w = torch.randn(10,28*28) # 矩阵相乘 logit = x@w.t() # softmax 处理,即将 input 转换成概率分布的形式 pred = F.softmax(logit , dim=1) # softmax 处理处理后取对数 pred = torch.log(pred) # 最大似然计算,其中pred是预测值,torch.tensor([3])是真是值也就是label值,这位置是不能换的 F.nll_loss(pred,torch.tensor([3]))
输出结果同上面一样,所以实际上,可以认为:
CrossEntropyLoss = log_sofrmax + NLLLoss
实际意义为:
在对图片进行分类时,通常会一次性输入n张图片,最后得出一个n*c的tensor,c是此次分类的类别数:比如:n=10,c=2,表示一共10张图片,类别为2类;接下来对每一行使用softmax函数,得到每张图片每个类别的概率分布:结果如下:可以看出来,每一行的值加起来和为1
然后再对给概率分布取对数:输出如下:
最后,CrossEntropyLoss的结果就是将这10个图像输出中与label对应值拿出来,累加再对结果取负值。先假设label如下所示:
则最后结果为:
loss = -(-0.0178 + -0.3859 + -0.8744 + / -1.3443 + -0.3392 + -2.7229 + -0.2614 + / -0.2736 + -1.3477 + -2.0514) / 10 = 0.96186
使用代码来计算可以表示为:
print(torch.nn.CrossEntropyLosss(output, label)))