← 返回文章列表

损失函数详解及多分类二分类回归应用

神经网络搭建和参数计算 构建神经网络模型 计算和查看模型参数 损失函数 什么是损失函数 分类任务损失函数 多分类任务损失函数 二分 …

神经网络搭建和参数计算

构建神经网络模型

import torch
import torch.nn as nn  # 线性模型和初始化方法


# todo:1-创建类继承 nn.module类
class ModelDemo(nn.Module):
    # todo:2-定义__init__构造方法, 构建神经网络
    def __init__(self):
        # todo:2-1 调用父类的__init__方法
        super().__init__()
        # todo:2-2 创建隐藏层和输出层  定义属性
        # in_features: 输入特征数(上一层神经元个数)
        # out_features: 输出特征数(当前层神经元个数)
        self.linear1 = nn.Linear(in_features=3, out_features=3)
        self.linear2 = nn.Linear(in_features=3, out_features=2)
        self.output = nn.Linear(in_features=2, out_features=2)
        # todo:2-3 对隐藏层进行参数初始化
        # self.linear1.weight: 在类的内部调用对象属性
        nn.init.xavier_normal_(tensor=self.linear1.weight)
        nn.init.zeros_(tensor=self.linear1.bias)
        nn.init.kaiming_normal_(tensor=self.linear2.weight, nonlinearity='relu')
        nn.init.zeros_(tensor=self.linear2.bias)

    # todo:3-定义前向传播方法 forward(方法名固定) 得到预测y值
    def forward(self, x):  # x->输入样本的特征值
        # todo:3-1 第一层计算 加权求和值计算  激活值计算
        x = torch.sigmoid(input=self.linear1(x))
        # todo:3-2 第二层计算
        x = torch.relu(input=self.linear2(x))
        # todo:3-3 输出层计算  假设多分类问题
        # dim=-1: 按行计算, 一个样本一个样本算
        x = torch.softmax(input=self.output(x), dim=-1)
        # 返回预测值
        return x


if __name__ == '__main__':
    # 创建神经网络模型对象
    my_model = ModelDemo()

计算和查看模型参数

# 创建模型预测函数
def train():
    # todo:1-创建神经网络模型对象
    my_model = ModelDemo()
    print('my_model->', my_model)
    # todo:2-构造数据集样本, 随机生成
    data = torch.randn(size=(5, 3))
    print('data->', data)
    print('data.shape->', data.shape)
    # todo:3-调用神经网络模型对象进行模型训练
    output = my_model(data)
    print('output->', output)
    print('output.shape->', output.shape)
    # todo:4-计算和查看模型参数
    print(('====================计算和查看模型参数==================='))
    # input_size: 样本的特征数
    # batch_size: 批量训练的样本数
    # summary(model=my_model, input_size=(3,), batch_size=5)
    summary(model=my_model, input_size=(5, 3))
    for name, param in my_model.named_parameters():
        print('name->', name)
        print('param->', param)


if __name__ == '__main__':
    train()

损失函数

什么是损失函数

  • 衡量模型参数质量的函数(评估模型)
  • 对比模型预测值和真实值差异
  • 指导通过梯度下降法计算梯度, 进行参数更新 loss.backward()

分类任务损失函数

多分类任务损失函数

  • 适用于多分类问题
  • 计算出损失值后续结合梯度下降法和反向传播算法, 进行梯度更新
  • nn.CrossEntropyLoss()
# 适用于多分类
import torch
import torch.nn as nn


def dm01():
    # 手动创建样本的真实y值
    # y_true = torch.tensor(data=[[0, 1, 0], [1, 0, 0]], dtype=torch.float32)
    y_true = torch.tensor(data=[1, 2])
    print('y_true->', y_true.dtype)
    # 手动创建样本的预测y值 -> 模型预测值
    y_pred = torch.tensor(data=[[0.1, 0.8, 0.1], [0.7, 0.2, 0.1]], requires_grad=True, dtype=torch.float32)
    # 创建多分类交叉熵损失对象
    # reduction:损失值计算的方式, 默认mean 平均损失值
    criterion = nn.CrossEntropyLoss(reduction='sum')
    # 调用损失对象计算损失值
    # 预测y  真实y
    loss = criterion(y_pred, y_true)
    print('loss->', loss)


if __name__ == '__main__':
    dm01()

二分类任务损失函数

  • 适用于二分类问题
  • nn.BCEloss()
# 适用于二分类
import torch
import torch.nn as nn


def dm01():
    # 手动创建样本的真实y值
    y_true = torch.tensor(data=[0, 1, 0], dtype=torch.float32)
    print('y_true->', y_true.dtype)
    # 手动创建样本的预测y值 -> 模型预测值
    # 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
    y_pred = torch.tensor(data=[0.6901, 0.5459, 0.2469], requires_grad=True, dtype=torch.float32)
    # 创建多分类交叉熵损失对象
    # reduction:损失值计算的方式, 默认mean 平均损失值
    criterion = nn.BCELoss()
    # 调用损失对象计算损失值
    # 预测y  真实y
    loss = criterion(y_pred, y_true)
    print('loss->', loss)
    print('loss->', loss.requires_grad)


if __name__ == '__main__':
    dm01()

回归任务损失函数

MAE回归损失函数

  • loss=(|y真实-y预测| + …)/n
  • 导数为-1或1, 0点位置不可导
# 适用于回归任务
# MAE:导数为-1/1 0点位置不可导,一般取0作为导数
import torch
import torch.nn as nn


def dm01():
    # 手动创建样本的真实y值
    y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
    print('y_true->', y_true.dtype)
    # 手动创建样本的预测y值 -> 模型预测值
    # 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
    y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
    # 创建回归任务MAE损失对象
    # reduction:损失值计算的方式, 默认mean 平均损失值
    criterion = nn.L1Loss()
    # 调用损失对象计算损失值
    # 预测y  真实y
    loss = criterion(y_pred, y_true)
    print('loss->', loss)
    print('loss->', loss.requires_grad)


if __name__ == '__main__':
    dm01()

MSE损失函数

  • 数据集中有异常(噪声)样本, 影响结果, 误差放大, 容易产生梯度爆炸
  • 任意位置可导, 越接近底部, 导数越小, 符合梯度下降套路, 可以找到最小值
# 适用于回归任务, 如果数据集有异常样本, 放大误差, 有可能导致梯度爆炸
# MSE: 任意位置都可导, 越接近底部, 导数越小
import torch
import torch.nn as nn


def dm01():
    # 手动创建样本的真实y值
    y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
    print('y_true->', y_true.dtype)
    # 手动创建样本的预测y值 -> 模型预测值
    # 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
    y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
    # 创建回归任务MAE损失对象
    # reduction:损失值计算的方式, 默认mean 平均损失值
    criterion = nn.MSELoss()
    # 调用损失对象计算损失值
    # 预测y  真实y
    loss = criterion(y_pred, y_true)
    print('loss->', loss)
    print('loss->', loss.requires_grad)


if __name__ == '__main__':
    dm01()

Smooth L1损失函数

  • 中和MAE和MSE, w小于-1或w大于1使用MAE, w在[-1,1]范围内使用MSE
  • 不受异常值影响,不容易产生梯度爆炸,在任意位置可导,可以找到最小值
  • nn.SmoothL1()
# 适用于回归任务, 不受异常值影响
# smooth l1: 任意位置都可导, 越接近底部, 导数越小
import torch
import torch.nn as nn


def dm01():
    # 手动创建样本的真实y值
    y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
    print('y_true->', y_true.dtype)
    # 手动创建样本的预测y值 -> 模型预测值
    # 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
    y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
    # 创建回归任务smoothl1损失对象
    # reduction:损失值计算的方式, 默认mean 平均损失值
    criterion = nn.SmoothL1Loss()
    # 调用损失对象计算损失值
    # 预测y  真实y
    loss = criterion(y_pred, y_true)
    print('loss->', loss)
    print('loss->', loss.requires_grad)


if __name__ == '__main__':
    dm01()

神经网络优化方法

梯度下降算法回顾

  • W = W - lr * grad

  • 梯度下降算法是一种寻找最优网络参数的策略, 计算每次损失值对应的梯度, 进行参数更新

    • BGD, 使用全部样本计算梯度, 计算量大
    • SGD, 使用全部样本中随机一个样本计算梯度, 梯度可能不合理
    • Min-Batch, 使用一批样本计算梯度, 计算量相对小, 梯度更加合理

反向传播算法(BP算法)

  • 反向传播就是算当前损失值和参数的导数(梯度), 需要借助梯度连乘的方式
  • 结合梯度下降算法更新参数 W1 = W0 - LR * 梯度 -> W0和LR已知, 梯度根据BP算法计算出

梯度下降优化方法

避免遇到鞍点以及局部最小值情况

指数移动加权平均

  • S100 = 0.1*Y100 + 0.1*0.9*Y99 + 0.1*0.9*0.9*Y98 + ....

  • β值越大, 受当前时刻真实值越小, 一般0.9

动量算法Momentum

(v_0 = 0)。(\gamma) 控制历史梯度的保留比重。

# 动量法计算梯度实际上计算的是当前时刻的指数移动加权平均梯度值
import torch
from torch import optim


def dm01():
	# todo: 1-初始化权重参数
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
    # 自定义损失函数, 实际工作中调用不同任务的损失函数, 交叉熵损失/MSE损失...
	loss = ((w ** 2) / 2.0).sum()
	# todo: 2-创建优化器函数对象 SGD->动量法
	# momentum: 动量法, 一般0.9或0.99
	optimizer = optim.SGD([w], lr=0.01, momentum=0.9)
	# todo: 3-计算梯度值
	optimizer.zero_grad()
	loss.sum().backward()
	# todo: 4-更新权重参数 梯度更新
	optimizer.step()
	print('w.grad->', w.grad)
    # 第二次计算
    loss = ((w ** 2) / 2.0).sum()
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    print('w.grad->', w.grad)


if __name__ == '__main__':
	dm01()

AdaGrad

  • 随着训练次数增加调整学习率, 开始学习率大, 后续学习率越来越小
# adagrad优化方法调整学习率, 随着训练次数增加, 学习率越来越小, 一开始的学习率比较大
import torch
from torch import optim


def dm01():
	# todo: 1-初始化权重参数
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
	loss = ((w ** 2) / 2.0).sum()
	# todo: 2-创建优化器函数对象 Adagrad
	optimizer = optim.Adagrad([w], lr=0.01)
	# todo: 3-计算梯度值
	optimizer.zero_grad()
	loss.sum().backward()
	# todo: 4-更新权重参数 梯度更新
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第二次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第三次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)


if __name__ == '__main__':
	dm01()

RMSProp

  • 对adagrap优化, 使用指数移动加权平均梯度代替累加梯度
# rmsprop优化方法调整学习率, 对adagrad方法进行优化, 使用指数移动加权平均梯度代替历史累加梯度
# 避免学习率过快减小, 导致后续模型收敛速度慢
import torch
from torch import optim


def dm01():
	# todo: 1-初始化权重参数
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
	loss = ((w ** 2) / 2.0).sum()
	# todo: 2-创建优化器函数对象  RMSprop
	optimizer = optim.RMSprop([w], lr=0.01, alpha=0.9)
	# todo: 3-计算梯度值
	optimizer.zero_grad()
	loss.sum().backward()
	# todo: 4-更新权重参数 梯度更新
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第二次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第三次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)


if __name__ == '__main__':
	dm01()

Adam

  • 修正梯度和学习率
# adam既优化学习率又优化梯度  adam=rmsprop+momentum
import torch
from torch import optim


def dm01():
	# todo: 1-初始化权重参数
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
	loss = ((w ** 2) / 2.0).sum()
	# todo: 2-创建优化器函数对象
	# betas: 两个β值, 接收元组类型
	optimizer = optim.Adam([w], lr=0.01, betas=(0.9, 0.99))
	# todo: 3-计算梯度值
	optimizer.zero_grad()
	loss.sum().backward()
	# todo: 4-更新权重参数 梯度更新
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第二次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)
	# 第三次计算
	loss = ((w ** 2) / 2.0).sum()
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()
	print('w.grad->', w.grad, w)


if __name__ == '__main__':
	dm01()

如何选择梯度下降优化方法

  • SGD和momentum适用于简单任务或浅层神经网络模型
  • Adam适用于复杂任务或数据量大
  • AdaGrad和RMSprop适用于文本数据处理 nlp

学习率衰减优化方法

为什么进行学习率优化

  • 根据模型训练次数, 手动调整学习率, 使模型前期收敛速度快, 后期收敛速度慢

等间隔学习率衰减

# 等间隔: 指定训练次数后修改学习率  lr=lr*gamma
import torch
from torch import optim
import matplotlib.pyplot as plt


def dm01():
    # todo: 1-初始化参数
    # lr epoch iteration
    lr = 0.1
    epoch = 200
    iteration = 10
    # todo: 2-创建数据集
    # y_true x w
    y_true = torch.tensor([0])
    x = torch.tensor([1.0], dtype=torch.float32)
    w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
    # todo: 3-创建优化器对象 动量法
    optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9)
    # todo: 4-创建等间隔学习率衰减对象
    # optimizer: 优化器对象
    # step_size: 间隔, 指定训练次数后修改学习率
    # gamma: 衰减系数 默认0.1
    scheduer = optim.lr_scheduler.StepLR(optimizer=optimizer, step_size=50, gamma=0.5)
    # todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr
    lr_list, epoch_list = [], []
    # todo: 6-循环遍历训练次数
    for i in range(epoch):
        # todo: 7-获取每次训练的次数和lr保存到列表中
        # scheduer.get_last_lr(): 获取最后lr
        lr_list.append(scheduer.get_last_lr())
        epoch_list.append(i)
        # todo: 8-循环遍历, batch计算
        for batch in range(iteration):
            # 先算预测y值 wx, 计算损失值 (wx-y_true)**2
            y_pred = w * x
            loss = (y_pred - y_true) ** 2
            # 梯度清零
            optimizer.zero_grad()
            # 梯度计算
            loss.backward()
            # 参数更新
            optimizer.step()
        # todo: 9-更新下一次训练的学习率
        scheduer.step()
    print('lr_list->', lr_list)

    plt.plot(epoch_list, lr_list)
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()


if __name__ == '__main__':
    dm01()

指定间隔学习率衰减

  • 前期模型训练设置比较多次数学习率更大, 中期设置相对少训练次数学习率小一些, 后期设置更少训练次数学习率更小一些
# 指定间隔: 通过步长列表指定训练次数后修改学习率  lr=lr*gamma
import torch
from torch import optim
import matplotlib.pyplot as plt


def dm01():
	# todo: 1-初始化参数
	# lr epoch iteration
	lr = 0.1
	epoch = 200
	iteration = 10
	# todo: 2-创建数据集
	# y_true x w
	y_true = torch.tensor([0])
	x = torch.tensor([1.0], dtype=torch.float32)
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
	# todo: 3-创建优化器对象 动量法
	optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9)
	# todo: 4-创建等间隔学习率衰减对象
	# optimizer: 优化器对象
	# milestones: 指定间隔列表, 指定训练次数后修改学习率
	# gamma: 衰减系数 默认0.1
	scheduer = optim.lr_scheduler.MultiStepLR(optimizer=optimizer, milestones=[50, 100, 160], gamma=0.5, last_epoch=-1)
	# todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr
	lr_list, epoch_list = [], []
	# todo: 6-循环遍历训练次数
	for i in range(epoch):
		# todo: 7-获取每次训练的次数和lr保存到列表中
		# scheduer.get_last_lr(): 获取最后lr
		lr_list.append(scheduer.get_last_lr())
		epoch_list.append(i)
		# todo: 8-循环遍历, batch计算
		for batch in range(iteration):
			# 先算预测y值 wx, 计算损失值 (wx-y_true)**2
			y_pred = w * x
			loss = (y_pred - y_true) ** 2
			# 梯度清零
			optimizer.zero_grad()
			# 梯度计算
			loss.backward()
			# 参数更新
			optimizer.step()
		# todo: 9-更新下一次训练的学习率
		scheduer.step()
	print('lr_list->', lr_list)

	plt.plot(epoch_list, lr_list)
	plt.xlabel("Epoch")
	plt.ylabel("Learning rate")
	plt.legend()
	plt.show()


if __name__ == '__main__':
	dm01()

按指数学习率衰减

  • 更符合梯度下降规律, 前期下降快, 中期缓慢, 后期更慢
# 指数间隔: 前期学习率衰减快, 中期慢, 后期更慢  lr=lr * gamma**epoch
import torch
from torch import optim
import matplotlib.pyplot as plt


def dm01():
	# todo: 1-初始化参数
	# lr epoch iteration
	lr = 0.1
	epoch = 200
	iteration = 10
	# todo: 2-创建数据集
	# y_true x w
	y_true = torch.tensor([0])
	x = torch.tensor([1.0], dtype=torch.float32)
	w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
	# todo: 3-创建优化器对象 动量法
	optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9)
	# todo: 4-创建等间隔学习率衰减对象
	# optimizer: 优化器对象
	# gamma: 衰减系数 设置大一些, 初始指数大
	scheduer = optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=0.9)
	# todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr
	lr_list, epoch_list = [], []
	# todo: 6-循环遍历训练次数
	for i in range(epoch):
		# todo: 7-获取每次训练的次数和lr保存到列表中
		# scheduer.get_last_lr(): 获取最后lr
		lr_list.append(scheduer.get_last_lr())
		epoch_list.append(i)
		# todo: 8-循环遍历, batch计算
		for batch in range(iteration):
			# 先算预测y值 wx, 计算损失值 (wx-y_true)**2
			y_pred = w * x
			loss = (y_pred - y_true) ** 2
			# 梯度清零
			optimizer.zero_grad()
			# 梯度计算
			loss.backward()
			# 参数更新
			optimizer.step()
		# todo: 9-更新下一次训练的学习率
		scheduer.step()
	print('lr_list->', lr_list)

	plt.plot(epoch_list, lr_list)
	plt.xlabel("Epoch")
	plt.ylabel("Learning rate")
	plt.legend()
	plt.show()


if __name__ == '__main__':
	dm01()

如何选择学习率衰减方法

  • 优先选择指数学习率衰减方法
  • 根据经验设置间隔选择指定间隔学习率衰减方法
  • 简单模型选择等间隔学习率衰减方法