TORCH02-03:Torch的损失函数与逻辑回归实现

本主题主要梳理损失函数,并同时使用损失函数实现逻辑回归。本主题内容结构:
  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)

逻辑回归分类实现

  • 下面的实现方法,没有使用随机梯度,实现的核心关键是:
    1. 迭代梯度计算
    2. 数据预测分类
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提供的损失函数有:

    1. binary_cross_entropy
    2. binary_cross_entropy_with_logits
    3. poisson_nll_loss
    4. cosine_embedding_loss
    5. cross_entropy
    6. ctc_loss
    7. hinge_embedding_loss
    8. kl_div
    9. l1_loss
    10. mse_loss
    11. margin_ranking_loss
    12. multilabel_margin_loss
    13. multilabel_soft_margin_loss
    14. multi_margin_loss
    15. nll_loss
    16. smooth_l1_loss
    17. soft_margin_loss
    18. 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近似项
  • 损失函数计算公式:

    • 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类型的张量。
  • 例子代码

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)维度,则没有差别。
  • 例子代码

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的软距离提出的一种损失优化方法。

你可能感兴趣的:(TORCH02-03:Torch的损失函数与逻辑回归实现)