本主题主要梳理损失函数,并同时使用损失函数实现逻辑回归。本主题内容结构:
1. 逻辑回归模型;
2. 逻辑回归Torch实现;
3. 损失函数介绍;
逻辑回归模型
- 逻辑回归与线性回归是有区别的,只要用来解决分类的问题,逻辑回归把从二分类基本模型分析,通过概率的方式来区分两个类:
- 属于A的概率,属于B的概率,哪个可能概率大就属于哪一类。
决策模型
-
逻辑回归的决策模型,使用的还是是线性模型:
-
为了从形式上接近概率,采用了一个概率密度函数(逻辑分布密度函数)
-
从而决策模型为:
损失模型
- 逻辑回归中,损失函数是:
-
- 上述公式被称为交叉熵(Cross Entropy)
- 其中
-
逻辑回归PyTorch实现
采用PyTorch实现,我们还是使用梯度下降方法实现。
-
首先认识下损失函数的表示。
- 损失函数的表达与决策函数有关。
-
数据集:
- 采用鸢尾花数据集
决策函数的表示
- 线性函数在torch中已经定义:linear函数;
- sigmoid函数在torch中已经定义:sigmoid函数;
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100个数据样本
y = torch.Tensor(target[0:100]).view(100,1) # 形状与x线性运算后的形状一样
w = torch.randn(1, 4) # 注意形状(linear会自动转置)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
print(y_.shape)
sy_ = torch.sigmoid(y_)
print(sy_.shape, y.shape)
torch.Size([100, 1])
torch.Size([100, 1]) torch.Size([100, 1])
损失函数的表示
- 逻辑回归使用的损失函数是交叉熵,在Torch中也封装了一个函数:
- 其他损失函数后面专门用一节作为主题介绍。
binary_cross_entropy函数
- 对数损失函数,没有做逻辑分布函数(sigmoid)运算
torch.nn.functional.binary_cross_entropy(input, target, weight=None, size_average=None, reduce=None, reduction='mean')
- 参数说明:
- input:就是计算出来的y_
- target:就是原来数据集中标签y
- reduction: 损失的计算方式:
- 均值
- 求和
binary_cross_entropy_with_logits函数
- 自动做逻辑分布函数(sigmoid函数)运算。
torch.nn.functional.binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
损失函数的例子
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100个数据样本
y = torch.Tensor(target[0:100]).view(100, 1) # 形状与x线性运算后的形状一样
w = torch.randn(1, 4) # 注意形状(linear会自动转置)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)
loss_mean = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="mean") # 多一个运算,除以样本个数
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="sum")
print(loss_sum)
# 自己手工做sigmoid运算
loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean") # 多一个运算,除以样本个数
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="sum")
print(loss_sum)
tensor(0.3298)
tensor(32.9785)
tensor(0.3298)
tensor(32.9785)
逻辑回归分类实现
- 下面的实现方法,没有使用随机梯度,实现的核心关键是:
- 迭代梯度计算
- 数据预测分类
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[50:150]) # 取前面100个数据样本 (前面50与后面的两个50是可分的,后面两个50线性可分性差点)
y = torch.Tensor(target[0:100]).float().view(100, 1) # 形状与x线性运算后的形状一样
w = torch.randn(1, 4)
b = torch.randn(1)
w.requires_grad = True # 可训练(x,y是不需要训练的)
b.requires_grad = True
epoch = 10000
learn_rate = 0.01
for n in range(epoch):
# forward:决策模型表示
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b) # 计算线性输出
sy_ = torch.sigmoid(y_) # 计算逻辑分布运算(输出的值可以作为概率使用)
# loss:损失函数表示
loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean")
# backward:计算梯度
loss_mean.backward()
# 更新梯度
with torch.autograd.no_grad(): # 关闭梯度计算跟踪
w -= learn_rate * w.grad # 更新权重梯度
w.grad.zero_() # 清空本次计算的梯度(因为梯度是累加计算,不清空就累加)
b -= learn_rate * b.grad # 更新偏置项梯度
b.grad.zero_()
# 观察训练过程中的损失下降,与训练集预测的准确性
if n % 1000 == 0:
print(F"误差损失值:{loss_mean:10.6f},", end="")
sy_[sy_ > 0.5] = 1
sy_[sy_ <= 0.5] = 0
correct_rate = (sy_ == y).float().mean() # 逻辑值在Torch不给计算平均值,所以需要转换为float类型
print(F"\t准确率为:{correct_rate*100: 8.2f}%")
print("训练完毕!") # 下面输出的结果与sklearn与tensorflow训练的结果一致
误差损失值: 8.051266, 准确率为: 50.00%
误差损失值: 0.370522, 准确率为: 95.00%
误差损失值: 0.295042, 准确率为: 95.00%
误差损失值: 0.252245, 准确率为: 95.00%
误差损失值: 0.224739, 准确率为: 96.00%
误差损失值: 0.205526, 准确率为: 96.00%
误差损失值: 0.191302, 准确率为: 96.00%
误差损失值: 0.180313, 准确率为: 97.00%
误差损失值: 0.171543, 准确率为: 97.00%
误差损失值: 0.164363, 准确率为: 97.00%
训练完毕!
损失函数
-
torch提供的损失函数有:
- binary_cross_entropy
- binary_cross_entropy_with_logits
- poisson_nll_loss
- cosine_embedding_loss
- cross_entropy
- ctc_loss
- hinge_embedding_loss
- kl_div
- l1_loss
- mse_loss
- margin_ranking_loss
- multilabel_margin_loss
- multilabel_soft_margin_loss
- multi_margin_loss
- nll_loss
- smooth_l1_loss
- soft_margin_loss
- triplet_margin_loss
这些函数都有标准的数学公式,每个函数的使用方式都一样,下面直接列出公式:
binary_cross_entropy与binary_cross_entropy_with_logits函数
- 这两个函数的本质是一样的,区别在于带后缀的with_logits函数对input数据多一个sigmoid操作。
- sigmoid函数也称logits函数。
- 主要用于二分类,比如典型的逻辑回归,例子见上面;
poisson_nll_loss函数
- 泊松负对数似然损失(Poisson negative log likelihood loss)
-
泊松分布的函数为:
-
- 表示单位时间内随机事件发生的次数;
- 泊松分布的期望与方差都为;
- 是的阶乘;
-
-
泊松分布与二项分布的关系:
- 泊松分布是由二项分布推导而来,当二项分布的n很大,p很小的时候,泊松分布可以作为二项分布的近似,这时。
函数定义:
torch.nn.functional.poisson_nll_loss(input, target, log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
-
参数:
- log_input:逻辑值,用来设置是否对输入做exp指数运算:
- False:
- True: :其中eps是无穷小量,用来防止input为0的情况。
- full:是否添加Stirling近似项
- log_input:逻辑值,用来设置是否对输入做exp指数运算:
-
损失函数计算公式:
- target数据服从泊松分布;
例子代码
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100个数据样本
y = torch.Tensor(target[0:100]).view(100, 1) # 形状与x线性运算后的形状一样
w = torch.randn(1, 4) # 注意形状(linear会自动转置)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)
loss = torch.nn.functional.poisson_nll_loss(sy_, y) # 默认均值:比较常采用
print(loss)
# 手工计算的效果(log_input = True)
loss_manual = sy_.exp() - y * sy_
loss_manual = loss_manual.mean()
print(loss_manual)
tensor(1.0067)
tensor(1.0067)
cosine_embedding_loss函数
用来度量两个向量是否相似。主要用于半监督学习与学习非线性嵌入。
函数定义:
torch.nn.functional.cosine_embedding_loss(
input1, # 向量1
input2, # 向量2
target, # 标签
margin=0, size_average=None, reduce=None, reduction='mean') → Tensor
- 计算公式:
-
- 其中:是向量夹角的余弦。
- margin的取值范围
[-1, 1]
-
nll_loss与cross_entropy函数
负对数似然损失函数。用来做C个类别的分类损失函数。
-
实际上这两个函数本质是一样的。
- cross_entropy多做了log_softmax运算
函数定义:
torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
-
参数说明:
- weight是一个1-D的张量(Tensor),张量的长度与类别数相同,用来加权每个分类的类别。
- input:是2-D数据(N,C):N表示数量,C表示分类类别;
- target:是1-D数据,长度为N即可。
-
提示:
- 因为是多分类问题,所以对于输出的结果应该是one-hot,比如2的one-hot就是
[0,0,1,0,0,0]
,假设最大标签是5。 - 所以target必须是LongTensor类型的张量。
- 因为是多分类问题,所以对于输出的结果应该是one-hot,比如2的one-hot就是
例子代码
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).long() # (150)
w = torch.randn(3, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(3) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_.log_softmax(dim=1)
# print(sy_.shape)
loss = torch.nn.functional.nll_loss(y_, y) # 默认均值:比较常采用
print(loss)
loss = torch.nn.functional.nll_loss(sy_, y) #
print(loss)
# cross_entropy交叉熵函数(多运算了一个sigmoid运算)
loss_mamual = torch.nn.functional.cross_entropy(y_, y) # nll_loss就是cross_entrop编码,自动采用softmax的one-hotb
print(loss_mamual)
tensor(-9.1227)
tensor(7.1684)
tensor(7.1684)
cross_entropy的补充
-
cross_entropy函数的计算公式是:
-
下面就是cross_entropy的手工实现函数:
- 只使用了一个样本测试,类别是5个类别。
import torch
import math
data_input = torch.FloatTensor([[1.0, 2.0, 3.0, 4.0, 5.0]]) # 计算的y,
data_target = torch.LongTensor([2]) # 改下标不能超过上面的维数-1,这是损失函数的计算过程决定的
loss_out = torch.nn.functional.cross_entropy(data_input, data_target)
print("输入的数据集:", data_input)
print("输出的数据集:", data_target)
print("cross_entropy函数输出的结果:", loss_out)
# 下面是交叉熵函数的手工计算过程
result_1 = 0.0
# 计算第一部分:−𝑥[𝑡𝑎𝑟𝑔𝑒𝑡]
for row in range(data_input.size()[0]): # 行循环(表示样本与对应的标签),这里其实是1
result_1 -= data_input[row][data_target[row]]
result_2 = 0.0
# 计算第二部分:∑𝑒𝑥[𝑖]
for row in range(data_input.size()[0]): # 行循环(表示样本与对应的标签),这里其实是1
for col in range(data_input.size()[1]):
result_2 += math.exp(data_input[row][col])
# 最终的结果
print("手工计算结果:", result_1 + math.log(result_2)) #
输入的数据集: tensor([[1., 2., 3., 4., 5.]])
输出的数据集: tensor([2])
cross_entropy函数输出的结果: tensor(2.4519)
手工计算结果: tensor(2.4519)
nll_loss函数的补充
-
nll_loss函数名字叫负对数似然函数,实际上根本没有做任何对数运算,其计算公式如下:
直接把x当成对数概率,并最终取
x[target]
作为这个类别的损失,最后的损失就是所有样本的损失。
-
注意:
- 一般nll_loss会与log_softmax一起使用,本质也就等于cross_entropy损失函数。
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).long() # (150)
w = torch.randn(3, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(3) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
# sy _ = y_.log_softmax(dim=1)
sy_ = y_
loss = torch.nn.functional.nll_loss(sy_, y)
print(loss)
# 手工计算
y_one = torch.nn.functional.one_hot(y).float() # 做了个单热编码,方便矩阵运算,否则就要取下标。
re = sy_ * y_one
# re = re.log()
re = -re
re =re.sum(dim=1)
print(re.mean())
tensor(0.0533)
tensor(0.0533)
mse_loss损失函数
-
最直观的损失函数:均方差损失,计算公式如下:
函数说明:
torch.nn.functional.mse_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
- 例子代码
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).view(150,1) # (150)
w = torch.randn(1, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
loss = torch.nn.functional.mse_loss(y_, y)
print(loss)
# 手工计算
loss_manual = ((y -y_) ** 2).mean()
print(loss_manual)
tensor(14.2841)
tensor(14.2841)
l1_loss函数
-
这个函数从字面上理解,应该是L1范数度量的距离误差,与均方差损失函数属于同一性质的损失函数。函数公式为:
函数的定义
torch.nn.functional.l1_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
- 使用例子
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).view(150, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_.sigmoid()
# sy_ = y_
loss = torch.nn.functional.l1_loss(sy_, y) #
print(loss)
# 手工计算
loss_manual = (y - sy_).abs().mean()
print(loss_manual)
tensor(0.6954)
tensor(0.6954)
kl_div函数
-
The
Kullback-Leibler divergence
_ Loss.也称KL距离,一种不同于几何距离的度量方式,用来度量两个概率的差异的距离。
相对熵(relative entropy),又被称为Kullback-Leibler散度(Kullback-Leibler divergence)或信息散度(information divergence),是两个概率分布(probability distribution)间差异的非对称性度量 。
- > 在在信息理论中,相对熵等价于两个概率分布的信息熵(Shannon entropy)的差值。
- > 相对熵是一些优化算法,例如最大期望算法(Expectation-Maximization algorithm, EM)的损失函数 。此时参与计算的一个概率分布为真实分布,另一个为理论(拟合)分布,相对熵表示使用理论分布拟合真实分布时产生的信息损耗 。
-
计算公式:
信息熵:
散度:
-
Torch中封装的公式:
- :target=0的情况总体看成0,只考虑target为1的情况
函数定义
torch.nn.functional.kl_div(input, target, size_average=None, reduce=None, reduction='mean')
-
参数说明:
- reduction参数:batchmean最后的均值使用batch_size,mean使用输出的个数;
- 注意batch_size与输出总数是有差别的。如果是(N,1)维度,则没有差别。
- reduction参数:batchmean最后的均值使用batch_size,mean使用输出的个数;
例子代码
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[:100]) # (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_
loss = torch.nn.functional.kl_div(sy_, y, reduction="batchmean")
print(loss)
# 手工计算(本质与nll_loss函数一样:nll_loss支持多类)
loss_manual = - y * (sy_)
print(loss_manual.mean())
tensor(-0.9451)
tensor(-0.9451)
hinge_embedding_loss函数
-
用来测试两个输入的数据是否相似。
- y的取值为-1或者1
函数定义
torch.nn.functional.hinge_embedding_loss(input, target, margin=1.0, size_average=None, reduce=None, reduction='mean') → Tensor
-
函数公式:
例子代码:
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
target[target == 0] = -1
x = torch.Tensor(data[:100]) # (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1) # w,b是可训练的,就是需要求导或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_
# print(y_)
# sy_ = y_.sigmoid()
loss = torch.nn.functional.hinge_embedding_loss(sy_, y, reduction="mean") # 默认均值:比较常采用(多一个sigmoid运算)
print(loss)
# 手工计算(本质与nll_loss函数一样:nll_loss支持多类)
loss_manual[0:50] = 1 - sy_[0:50]
loss_manual[loss_manual< 0] = 0
loss_manual[50:100] = sy_[50:100]
# loss_manual[loss_manual <0] = 0
print(loss_manual.mean())
tensor(0.3959)
tensor(0.3959)
其他损失函数
- 其他损失函数是基于多分类与其他目的的变种函数,这些损失函数在特定的需求理解会更加容易。
- 比如:soft_margin_loss是基于SVM的软距离提出的一种损失优化方法。