【NLP 5、深度学习的基本原理】

目录

一、梯度下降算法

[1.引例 ------ 找极小值问题](#1.引例 —— 找极小值问题)

目标:

方法:

2.梯度

例:

3.求解目标

为什么损失函数越小越好

4.梯度下降法

代码实现

5.细节问题

6.梯度爆炸和梯度消失

梯度爆炸

梯度消失

7.过拟合和欠拟合

欠拟合(Underfitting)

定义:

原因:

表现:

过拟合(Overfitting)

定义:

原因:

表现:

解决方法

解决欠拟合:

解决过拟合:

[二、网络结构组件 ------ 线性层(全连接层)](#二、网络结构组件 —— 线性层(全连接层))

定义网络结构的方法

[① init初始化:](#① init初始化:)

[② forward函数,前向计算](#② forward函数,前向计算)

举例:

nn.Linear函数:

DNN深层神经网络组件

[三、网络结构组件 ------ 激活函数(非线性函数)](#三、网络结构组件 —— 激活函数(非线性函数))

1.Sigmoid函数

函数特点:

公式:

[2.tanh函数 ------ 双曲正切函数](#2.tanh函数 —— 双曲正切函数)

公式:

3.Relu函数

公式:

4.Gelu函数

公式:

5.Softmax

作用:

公式:

6.激活函数在线性层上的使用

[7.Softmax函数 和 Sigmoid函数的区别](#7.Softmax函数 和 Sigmoid函数的区别)

区别:

[四、网络结构组件 ------ 损失函数](#四、网络结构组件 —— 损失函数)

[1.均方差 MSE mean square error](#1.均方差 MSE mean square error)

调用:nn.MSE

[2.交叉熵 Cross Entropy](#2.交叉熵 Cross Entropy)

公式:

例:

手动实现交叉熵的计算

3.交叉熵与均方差损失函数的适用范围

五、基于pytorch框架编写训练模型

1.设计模型

[2.前向传递(Forward Pass)](#2.前向传递(Forward Pass))

[3.准备训练数据 ------ 随机生成](#3.准备训练数据 —— 随机生成)

[4. 将训练数据整理为张量](#4. 将训练数据整理为张量)

5.⭐模型训练

[① 配置参数](#① 配置参数)

[② 建立模型](#② 建立模型)

[③ 选择优化器](#③ 选择优化器)

[④ 读取训练集](#④ 读取训练集)

[⑤ 训练过程](#⑤ 训练过程)

6.模型评估函数evaluate

7.模型训练好后如何使用

[① 模型训练好后保存模型](#① 模型训练好后保存模型)

[② 训练好的模型编写预测函数](#② 训练好的模型编写预测函数)

[③ 用模型做预测](#③ 用模型做预测)

[④ 证明预测准确性](#④ 证明预测准确性)


全力以赴,终有绽放的那天

------ 24.12.5

一、梯度下降算法

1.引例 ------ 找极小值问题

导数的正负衡量了函数在某一点的增减性

目标:

找到合适的x值,使得f(x)最小

方法:

1.任取一点x,计算在这一点的导数值f(x)

2.根据导数的正负,决定x应当调大还是调小

3.迭代进行1、2步,直到x不再变化(或变化极小)

原函数为 y = x^2

导函数为 y = 2*x

在 x = -1 这个点,导数值为 -2,该点导数为负数,说明在这一点,如果x增大,y会减小,所以f(x)最小值的点应当在-1的右侧(大于-1)

原函数为 y = x^2

导函数为 y = 2*x

在 x = 1 这个点,导数值为 2,该点导数为正数,说明在这一点,如果x增大,y会增大,所以f(x)最小值的点应当在1的左侧(小于1)

通过梯度寻找最小值会遇到局部最小值的问题


2.梯度

梯度: 可以理解为多元函数的导数,意义与导数基本一致

例:


3.求解目标

找到一个模型权重,使得损失函数最小(相对足够小),即预测值与真实值差异足够小

为什么损失函数越小越好

① 损失函数越小,模型越好

② 我们学习的目标是损失函数最小化

③ 模型权重影响损失函数值

④ 通过梯度下降法来找到最优权重


4.梯度下降法

梯度下降法也是基于求导,通过对每一点的求导计算其继续是增长还是下降,以此来判断该点是否是近似最优解(也就是增长/减少达到近似稳定)

根据梯度,更新权重

学习率控制步长(权重更新的幅度 ),学习率应该根据不同模型进行调整

梯度(导数值)也可以控制移动的步长大小

全局最优解不一定成立,很难寻找到最优解,但一定要寻找相对最优解,只要模型最终可用就行

梯度下降法的公式既受到导数的控制又受到学习率的控制


代码实现

Yp = [func(i) for i in X]

列表推导式(List Comprehension),创建一个新的列表 Yp。它的基本逻辑是遍历可迭代对象 X 中的每一个元素 i,将其作为参数传递给函数 func,然后把 func(i) 的返回结果依次收集起来,最终组成一个新的列表 Yp

python 复制代码
import matplotlib.pyplot as pyplot
import math
import sys

# 生成数据
# X = [0.01 * x for x in range(100)]
# Y = [2*x**2 + 3*x + 4 for x in X]
# print(X)
# print(Y)
# pyplot.scatter(X, Y, color='red')
# pyplot.show()
# input()

# 看作这是一百个样本
X = [0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18,
     0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35000000000000003,
     0.36, 0.37, 0.38, 0.39, 0.4, 0.41000000000000003, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47000000000000003, 0.48, 0.49,
     0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.5700000000000001, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64, 0.65, 0.66,
     0.67, 0.68, 0.6900000000000001, 0.7000000000000001, 0.71, 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79, 0.8,
     0.81, 0.8200000000000001, 0.8300000000000001, 0.84, 0.85,
     0.86, 0.87, 0.88, 0.89, 0.9, 0.91, 0.92, 0.93, 0.9400000000000001, 0.9500000000000001, 0.96, 0.97, 0.98, 0.99]

Y = [4.0, 4.0302, 4.0608, 4.0918, 4.1232, 4.155, 4.1872, 4.2198, 4.2528, 4.2862, 4.32, 4.3542, 4.3888, 4.4238, 4.4592,
     4.495, 4.5312, 4.5678, 4.6048, 4.6422, 4.68, 4.7181999999999995, 4.7568, 4.7958, 4.8352, 4.875, 4.9152000000000005,
     4.9558, 4.9968, 5.0382, 5.08, 5.122199999999999, 5.1648, 5.2078, 5.2512, 5.295, 5.3392, 5.3838, 5.4288, 5.4742,
     5.5200000000000005, 5.5662, 5.6128, 5.6598, 5.7072, 5.755, 5.8032, 5.851800000000001, 5.9008, 5.9502, 6.0, 6.0502,
     6.1008, 6.1518, 6.203200000000001, 6.255000000000001, 6.3072, 6.3598, 6.4128, 6.4662, 6.52, 6.5742, 6.6288, 6.6838,
     6.7392, 6.795, 6.8512, 6.9078, 6.9648, 7.022200000000001, 7.08, 7.138199999999999, 7.1968, 7.2558, 7.3152, 7.375,
     7.4352, 7.4958, 7.5568, 7.6182, 7.680000000000001, 7.7422, 7.8048, 7.867800000000001, 7.9312, 7.994999999999999,
     8.0592, 8.1238, 8.1888, 8.2542, 8.32, 8.3862, 8.4528, 8.5198, 8.587200000000001, 8.655000000000001, 8.7232, 8.7918,
     8.8608, 8.9302]

# 随机选定一个模型结构
def func(x):
    y_pred = w1 * x ** 2 + w2 * x + w3
    return y_pred

# 权重随机初始化
w1, w2, w3 = 1, 0, -1

# 拟定一个损失函数 均方差损失函数
def loss(y_pred, y_true):
    # y_pred = w1 * x ** 2 + w2 * x + w3
    return (y_pred - y_true) ** 2

# 学习率设置
lr = 0.1

# batch size 一次处理的样本数量
batch_size = 20

# 训练过程
for epoch in range(1000):
    epoch_loss = 0  # 损失值
    grad_w1 = 0 # w1的梯度
    grad_w2 = 0 # w2的梯度
    grad_w3 = 0 # w3的梯度
    counter = 0 # 计数
    # 在 Python 中, zip()是一个内置函数,它用于将多个可迭代对象(如列表、元组等)中对应的元素打包成一个个元组,
    # 然后返回一个由这些元组组成的可迭代对象(通常是一个 zip 对象)。
    for x, y_true in zip(X, Y):
        y_pred = func(x)
        epoch_loss += loss(y_pred, y_true)
        counter += 1
        # 梯度计算,对损失函数求导,链式法则
        # y_pred = w1 * x ** 2 + w2 * x + w3
        # loss(y_pred, y_true) = (y_pred - y_true) ** 2
        grad_w1 += 2 * (y_pred - y_true) * x ** 2
        grad_w2 += 2 * (y_pred - y_true) * x
        grad_w3 += 2 * (y_pred - y_true)
        if counter == batch_size:
            # 权重更新 新的权重 = 旧的权重 - 学习率 × 批次训练梯度和 / 每一批次的训练大小
            w1 = w1 - lr * grad_w1 / batch_size  # sgd
            w2 = w2 - lr * grad_w2 / batch_size
            w3 = w3 - lr * grad_w3 / batch_size
            counter = 0
            grad_w1 = 0
            grad_w2 = 0
            grad_w3 = 0
    # 每一轮批次训练后的损失值的平均值
    epoch_loss /= len(X)
    print("第%d轮, loss %f" % (epoch, epoch_loss))
    # 当损失函数低于某一特定数时,训练就可以停止
    if epoch_loss < 0.00001:
        break

print(f"训练后权重:w1:{w1} w2:{w2} w3:{w3}")

# 使用训练后模型输出预测值
Yp = [func(i) for i in X]

# 预测值与真实值比对数据分布,可视化表示
pyplot.scatter(X, Y, color="red")
pyplot.scatter(X, Yp)
pyplot.show()

求出的训练后参数权重值与真实参数值接近


5.细节问题

原函数是什么不会影响优化流程,模型的选择与其原函数无关,函数本质上只是一个假设,我们只是根据数据来寻找它们之间的规律。原函数是什么是会影响最终的准确率。模型函数决定了表达能力的极限

原函数是什么只是会影响到最终的上限,而我们只能近似的寻找一个当前函数下相对精确的接近分布值,也许结果不对,但是预测也有意义,可以显示大致的趋势

学习率的大小会影响训练速率(训练到达最终结果的轮数)

模型权重的随机初始化要在一定的范围内,在不同的框架内范围不同

不同框架训练的权重是不同的,不宜过大

batch_size指用多少样本,来更新一次权重,batch_size过大会迭代速度变慢,产生资源问题

batch_size在大模型中设置的标准是将显存占满而不爆掉,在小模型时batch_size可调节

梯度控制方向,学习率控制步长

一段样本数据中的梯度累加,以防其中有错误数据带来的影响

简单任务而言,batch_size小的时候反而训练会更快

学习率 × 梯度可以控制步长权重

lr 学习率由大致范围来控制学习率,模型越大学习率越小

项目中,权重参数的多少设置取决于需要挖掘规律的难易,

不同任务的模型函数一般不要求一定,可以随机选择,只是预测结果的准确率不同

batch_size根据具体问题进行选择

loss损失函数有常用的计算公式

梯度值过大,会使权重参数过大,进而会使原函数过大

学习率的确定在大小模型中会有一定范围,大模型的学习率相对更小

梯度的计算与损失函数有关,因为梯度其实就是损失函数对三个模型权重求偏导

权重就是模型函数中除了x和y的部分


6.梯度爆炸和梯度消失

梯度爆炸

在神经网络训练过程中,梯度爆炸是指计算得到的梯度(用于更新模型参数的梯度值)变得非常大,使得模型参数在更新时出现极大的变化,从而导致训练过程不稳定甚至无法收敛。

梯度消失

与梯度爆炸相反,梯度消失是指在神经网络的反向传播过程中,梯度(用于更新模型参数的梯度值)变得非常小,几乎接近于零,导致模型参数更新缓慢甚至停止更新。


7.过拟合和欠拟合

欠拟合(Underfitting)

定义:

欠拟合是指模型没有很好地捕捉到数据中的模式和关系,在训练数据上的表现就很差。例如,在一个回归问题中,模型的拟合曲线可能是一条简单的直线,但数据实际呈现出更复杂的曲线关系,导致模型无法准确地预测数据的真实值。

原因:

**① 模型复杂度不够:**如使用线性模型来拟合一个本质上是非线性的数据。

**② 特征数量不足:**没有足够的信息来构建一个有效的模型。

表现:

在训练集上的损失(如均方误差等)较高,模型预测值和真实值之间的差距较大。

模型在训练集和测试集上的性能都比较差,而且随着新数据的增加,性能也不会有明显的改善。


过拟合(Overfitting)

定义:

过拟合是指模型过度学习了训练数据中的细节和噪声,导致在训练数据上表现很好,但在新的、未见过的数据(测试数据)上表现不佳。模型变得过于复杂,它记住了训练数据的具体特征,而不是学习到数据背后的一般规律。

原因:

① 模型复杂度太高: 例如在一个分类问题中,使用了一个非常复杂的神经网络,其参数数量远远超过了数据所能支持的合理范围,导致模型可以拟合训练数据中的每一个细节,包括噪声。

② 训练数据不足: 模型在有限的数据中过度挖掘信息。如果数据量较小,模型很容易记住所有的数据模式,当遇到新的数据时,由于没有学习到通用的规律,无法很好地进行预测。

表现:

在训练集上损失很低,模型几乎可以完美地预测训练数据中的样本。

在测试集上损失很高,模型的泛化能力差。


解决方法

解决欠拟合:

**① 增加模型的复杂度:**如使用更高阶的多项式模型、增加神经网络的层数或神经元数量等。

**② 增加更多的特征:**通过特征工程获取更多与目标变量相关的有效信息。

解决过拟合:

**① 降低模型复杂度:**如对神经网络进行剪枝(减少神经元或连接)、采用正则化方法(如L1和L2正则化)来限制模型参数的大小。

**② 增加训练数据量:**通过数据扩充(如在图像数据中进行旋转、翻转等操作)来提供更多的样本。 - 使用交叉验证等技术,更好地评估模型的泛化能力,调整模型超参数以找到合适的模型复杂度。


二、网络结构组件 ------ 线性层(全连接层)

线性层,又称全连接层

计算公式y = w * x + b(b可有可无,w固定是一个矩阵,形状通过输入数据决定)

wb是参与训练的参数

w 的维度决定了隐含层输出的维度,一般称为隐单元个数(hidden size),如果公式中存在b,则b的维度与输出维度相同

定义网络结构的方法

① init初始化:

声明网络模型中有哪几层

② forward函数,前向计算

定义网络的使用方式,声明模型计算过程

举例:

输入:x(维度1×3) 维度需要一致

隐含层1:w(维度3×5) ------> x × w:1 × 5

隐含层2:w(维度5×2) ------> x × w × w:1 × 2

一个线性层的拟合能力会比较差,线性层可以堆叠多层,但是输入输出的维度需要保持相一致

python 复制代码
    # 前向计算
    # 定义网络的使用方式
    def forward(self, x):
        # 第一层代入隐含层1公式,参数x指传入的数据,返回得到的结果x
        x = self.layer1(x)   #shape: (batch_size, input_size) -> (batch_size, hidden_size1)
        x = self.sig(x)
        # 将隐含层1返回的结果赋值给x,将x再传入隐含层2,根据公式得到y的预测值
        y_pred = self.layer2(x) #shape: (batch_size, hidden_size1) -> (batch_size, hidden_size2)
        y_pred = self.relu(y_pred)
        return y_pred

nn.Linear函数:

**nn.Linear:**pytorch中的一个线性层函数

in_features:输入的维度

out_features:输出的维度

bias:模型公式中需不需要参数 b(默认是true)


DNN深层神经网络组件

Pytorch网络的定义分为两层

第一层声明需要用到哪些层

第二层forward函数定义网络的使用方式

np.dot()函数,矩阵内积运算

python 复制代码
#coding:utf8

import torch
import torch.nn as nn
import numpy as np

"""
numpy手动实现模拟一个线性层
"""

#搭建一个2个线性层的神经网络模型
#每层都是线性层
class TorchModel(nn.Module):    # 继承Pytorch中的基类nn.Module
    # 初始化环节
    def __init__(self, input_size, hidden_size1, hidden_size2):
        # input_size: 输入的特征维度,比如这里是3
        # hidden_size1: 隐含层1(中间层)的维度,比如这里是5
        # hidden_size2: 隐含层2(最后一层)的维度,比如这里是2
        super(TorchModel, self).__init__()
        # 隐含层1公式:wx + b , w:3 * 5
        self.layer1 = nn.Linear(input_size, hidden_size1)
        # 隐含层2公式:wx + b , w:5 * 2
        self.layer2 = nn.Linear(hidden_size1, hidden_size2)

    # 前向计算
    # 定义网络的使用方式
    def forward(self, x):
        # 第一层代入隐含层1公式,参数x指传入的数据,返回得到的结果x
        x = self.layer1(x)   #shape: (batch_size, input_size) -> (batch_size, hidden_size1)
        # 将隐含层1返回的结果赋值给x,将x再传入隐含层2,根据公式得到y的预测值
        y_pred = self.layer2(x) #shape: (batch_size, hidden_size1) -> (batch_size, hidden_size2)
        return y_pred

# 自定义模型
class DiyModel:
    def __init__(self, w1, b1, w2, b2):
        self.w1 = w1
        self.b1 = b1
        self.w2 = w2
        self.b2 = b2

    def forward(self, x):
        hidden = np.dot(x, self.w1.T) + self.b1 # 1*5
        y_pred = np.dot(hidden, self.w2.T) + self.b2 # 1*2
        return y_pred

#随便准备一个网络输入
x = np.array([[3.1, 1.3, 1.2],
              [2.1, 1.3, 13]])
#建立torch模型
torch_model = TorchModel(3, 5, 2)

print("建立的torch模型:")
print(torch_model.state_dict())

print("-----------")
#打印模型权重,权重为随机初始化
torch_model_w1 = torch_model.state_dict()["layer1.weight"].numpy()
torch_model_b1 = torch_model.state_dict()["layer1.bias"].numpy()
torch_model_w2 = torch_model.state_dict()["layer2.weight"].numpy()
torch_model_b2 = torch_model.state_dict()["layer2.bias"].numpy()
print(torch_model_w1, "torch w1 权重")
print(torch_model_b1, "torch b1 权重")
print("-----------")
print(torch_model_w2, "torch w2 权重")
print(torch_model_b2, "torch b2 权重")
print("-----------")

#使用torch模型做预测
torch_x = torch.FloatTensor(x)  # 将矩阵转化为张量
y_pred = torch_model.forward(torch_x)
print("torch模型预测结果:", y_pred)

# #把torch模型权重拿过来自己实现计算过程
diy_model = DiyModel(torch_model_w1, torch_model_b1, torch_model_w2, torch_model_b2)
# 用自己定义的模型来预测作对比
y_pred_diy = diy_model.forward(np.array(x))
print("diy模型预测结果:", y_pred_diy)

三、网络结构组件 ------ 激活函数(非线性函数)

很多情况下,神经网络只有线性层表达能力会不足,拟合能力弱

为模型添加非线性因素(与线性函数结合在一起),使模型具有拟合非线性函数的能力

无激活函数时 y = w1(w2(w3 * x + b3) + b2) + b1 仍然是线性函数

1.Sigmoid函数

函数特点:

任意的输入经过sigmoid函数后,其取值都会变为0、1之间

公式:

sigmoid 是一个非线性函数,将其和线性函数的线性层组合在一起,可使得线性函数的表达能力增强;可以拟合更复杂的规律;使得输出变为0、1之间(归一化)

* sigmoid 函数的导函数等于其 自身 * (1 - 自身)


2.tanh函数 ------ 双曲正切函数

公式:


3.Relu函数

分段函数,小于0的部分取值为0,大于0的部分取值为其自身

线性函数:求导后导数为0

公式:


4.Gelu函数

公式:


5.Softmax

作用:

向量归一化

公式:

e在数学和许多编程语言中,exp函数是指数函数,它通常表示以自然常数e为底的指数运算。即 y = exp(x) 等价于 y = e^x。


6.激活函数在线性层上的使用

python 复制代码
#搭建一个2个线性层的神经网络模型
#每层都是线性层
class TorchModel(nn.Module):    # 继承Pytorch中的基类nn.Module
    # 初始化环节
    def __init__(self, input_size, hidden_size1, hidden_size2):
        # input_size: 输入的特征维度,比如这里是3
        # hidden_size1: 隐含层1(中间层)的维度,比如这里是5
        # hidden_size2: 隐含层2(最后一层)的维度,比如这里是2
        super(TorchModel, self).__init__()
        # 隐含层1公式:wx + b , w:3 * 5
        self.layer1 = nn.Linear(input_size, hidden_size1)
        self.sig = nn.Sigmoid()
        # 隐含层2公式:wx + b , w:5 * 2
        self.layer2 = nn.Linear(hidden_size1, hidden_size2)
        self.relu = nn.ReLU()

    # 前向计算
    # 定义网络的使用方式
    def forward(self, x):
        # 第一层代入隐含层1公式,参数x指传入的数据,返回得到的结果x
        x = self.layer1(x)   #shape: (batch_size, input_size) -> (batch_size, hidden_size1)
        # 将第一层线性层经过模型的值返回,让其继续过激活函数simoid激活函数
        x = self.sig(x)
        # 将隐含层1返回的结果赋值给x,将x再传入隐含层2,根据公式得到y的预测值
        y_pred = self.layer2(x) #shape: (batch_size, hidden_size1) -> (batch_size, hidden_size2)
        # 将第二层线性层经过模型的值返回,让其继续过激活函数Relu激活函数
        y_pred = self.relu(y_pred)
        return y_pred

7.Softmax函数 和 Sigmoid函数的区别

Sigmoid函数保证输入的每个值都在0,1之间;主要运用于二分类问题

Softmax函数保证输入的所有值之和为1;主要运用于多分类问题

Softmax归一化函数,调用方式①:nn.Softmax()、调用方式②:torch.softmax

sigmoid归一化函数,调用方式①:nn.Sigmoid()、调用方式②:torch.sigmoid

区别:

torch.sigmoid() 是一个函数,nn.Softmax是一个类,二者在模型定义时都不需要参数

torch.Softmax() 是一个类,需要两个参数:输入张量和维度,实例化时,必须传递 维度dim 参数来指定 softmax 操作的维度

input: 输入张量

dim: 指定在哪个维度上应用 softmax 函数。这是一个必需的参数,没有默认值

dtype(可选): 指定输出的数据类型。如果没有指定,输出将具有与输入相同的数据类型

out(可选): 指定输出张量。如果没有指定,将创建一个新的张量来存储输出

nn.Softmax() 是一个 torch.nn 模块中的类,而不是一个函数,必须有一个参数,实例化Softmax时,传入维度dim就行


四、网络结构组件 ------ 损失函数

1.均方差 MSE mean square error

调用:nn.MSE

两个数相减,再求平方

对均方差再做开根号,可以得到根方差

Xn:y_pred

Yn:y_true

一般可以求平均(mean),有时也会求和(sum)


2.交叉熵 Cross Entropy

常用于分类任务,只适用于多选一的情况,因为所有类别已给出,所以正确的类别肯定在已知类别中,我们只需保留正确的类别上的概率分布即可

交叉熵公式作为损失函数使用的前提 是模型输出的概率值必须在(0,1)之间,各概率相加和为1(Softmax函数)

分类任务中,网络输出经常是所有类别上的概率分布

做一个分类任务,在每一种可能的概率分布是多少,看在哪一种可能上概率分布最大,则它应该属于哪一类

例:[0.1, 0.4, 0.5],则该样本应该属于第三类0.5

公式:

交叉熵只有正确的那类和这类对应的概率分布,错误类别上预测概率失效,事实上只有正确类别上预测的概率有效

公式中的负号是将log在(0,1)区间上的概率值求出,让概率值取正

p(x)logq(x)代表准确率,准确率在负区间越接近于0,则代表准确率越高,取负后代表损失值越低,算出的交叉熵H越接近于0,即损失值越小,越接近真实值

Softmax函数进行归一化可以保证交叉熵的使用条件,即样本X不大于1,所以交叉熵与Softmax函数常搭配使用

Torch框架中,Softmax函数是交叉熵的一部分,使用交叉熵之前,默认使用一次Softmax函数进行归一化

例:

损失函数loss:

假设一个三分类任务,某样本的正确标签是第一类,则p = [1, 0, 0],模型预测值假设为[0.5, 0.4, 0.1],则交叉熵计算如下:

手动实现交叉熵的计算

交叉熵在神经网络中的使用 : nn.CrossEntropyLoss()

python 复制代码
import torch
import torch.nn as nn
import numpy as np

'''
手动实现交叉熵的计算
'''

#使用torch计算交叉熵
ce_loss = nn.CrossEntropyLoss()

#假设有3个样本(Batch_size = 3),每个预测都在做3分类
pred = torch.FloatTensor([[1.3, 0.1, 0.3],
                          [0.9, 1.2, 0.9],
                          [0.5, 1.4, 0.2]]) #n * class_num

#正确的类别分别为1,2,0
target = torch.LongTensor([1,2,0])

'''
等效类别划分语句:one-hot:
target = torch.FloatTensor([[0,1,0],
                           [0,0,1],
                           [1,0,0]])
'''

# 使用交叉熵计算损失函数
loss = ce_loss(pred, target)
print("torch输出交叉熵:", loss)

#手动实现交叉熵
def cross_entropy(pred, target):
    batch_size, class_num = pred.shape
    pred = softmax(pred)
    target = to_one_hot(target, pred.shape)
    entropy = - np.sum(target * np.log(pred), axis=1)
    return sum(entropy) / batch_size

#实现softmax函数 进行归一化
def softmax(matrix):
    # exp函数是e的指数函数,exp(x) = e ^ x,x为任意实数或复数
    return np.exp(matrix) / np.sum(np.exp(matrix), axis=1, keepdims=True)

#验证softmax函数
# print(torch.softmax(pred, dim=1))
# print(softmax(pred.numpy()))


#将输入转化为onehot矩阵型
def to_one_hot(target, shape):
    one_hot_target = np.zeros(shape)
    for i, t in enumerate(target):
        one_hot_target[i][t] = 1
    return one_hot_target

print("手动实现交叉熵:", cross_entropy(pred.numpy(), target.numpy()))

3.交叉熵与均方差损失函数的适用范围

交叉熵处理多选一任务

均方差适合回归任务------即预测值为一个概率值的问题

loss损失函数是我们优化的目标,我们要让loss函数,也就是损失值尽可能减小,使预测值与真实值之间差距尽可能小

而不同的损失函数只是恰好符合使模型的损失函数尽可能小的条件


五、基于pytorch框架编写训练模型

实现一个自行构造的找规律(机器学习)任务

**规律:**x是一个5维向量,如果第1个数 > 第5个数,则为正样本,反之为负样本

1.设计模型

① 用一个线性层(输入维度5,输出维度1)作为第一层模型函数

② 定义一个激活函数sigmoid

③ 设计一个loss函数均方差损失函数mse_loss

python 复制代码
class TorchModel(nn.Module):
    def __init__(self, input_size):
        super(TorchModel, self).__init__()
        self.linear = nn.Linear(input_size, 1)  # 线性层
        self.activation = nn.Sigmoid()    # 激活函数 或写为torch.sigmoid
        self.loss = nn.functional.mse_loss  # loss函数采用均方差损失

2.前向传递(Forward Pass)

参数列表中加一个可选项y,如果不传参数y,y默认为None,则让样本x先过线性层,再过激活层,得到预测值y_pred,如果传入参数y,则通过均方差损失函数计算损失值loss

python 复制代码
    # 当输入真实标签,返回loss值;无真实标签,返回预测值
    def forward(self, x, y=None):
        x = self.linear(x)  # (batch_size, input_size) -> (batch_size, 1)
        y_pred = self.activation(x)  # (batch_size, 1) -> (batch_size, 1)
        if y is not None:
            return self.loss(y_pred, y)  # 预测值和真实值计算损失
        else:
            return y_pred  # 输出预测结果

3.准备训练数据 ------ 随机生成

生成一个样本,样本的生成方法,代表了我们要学习的规律

随机生成一个5维向量,如果第一个值大于第五个值,认为是正样本,反之为负样本

python 复制代码
class TorchModel(nn.Module):
    # 生成一个样本, 样本的生成方法,代表了我们要学习的规律
    # 随机生成一个5维向量,如果第一个值大于第五个值,认为是正样本,反之为负样本
    def build_sample():
        x = np.random.random(5)
        if x[0] > x[4]:
            return x, 1
        else:
            return x, 0

4. 将训练数据整理为张量

随机生成一批样本,正负样本均匀生成

注: 如果是标量,要转化为张量,则需要在标量外加一个"[]",如果是向量,转化为张量则不需要加"[]"

python 复制代码
# 随机生成一批样本
# 正负样本均匀生成
    def build_dataset(total_sample_num):
        X = []
        Y = []
        for i in range(total_sample_num):
            x, y = build_sample()
            X.append(x)
            Y.append([y])
            X_array = np.array(X)
            Y_array = np.array(Y)
        return torch.FloatTensor(X_array), torch.FloatTensor(Y_array)

5.⭐模型训练

① 配置参数

配置模型训练中需要用到的参数(训练轮次、每轮次训练样本数、学习率、样本总数、输入向量维度)

② 建立模型

调用定义的模型

③ 选择优化器

通过计算的梯度选择优化器公式,对模型参数权重进行优化

④ 读取训练集

从构造的训练数据集上读取训练数据,对模型进行训练

⑤ 训练过程

**Ⅰ、**model.train():设置训练模式

**Ⅱ、**对训练集样本开始循环训练(循环取出训练数据)

**Ⅲ、**根据模型函数和损失函数的定义计算模型损失

**Ⅳ、**计算梯度

**Ⅴ、**通过梯度用优化器更新权重

**Ⅵ、**计算完一轮训练数据后梯度进行归零,下一轮重新计算

python 复制代码
def main():
    # 配置参数
    epoch_num = 20  # 训练轮数
    batch_size = 20  # 每次训练样本个数
    train_sample = 5000  # 每轮训练总共训练的样本总数
    input_size = 5  # 输入向量维度
    learning_rate = 0.001  # 学习率
    # 建立模型
    model = TorchModel(input_size)
    # 选择优化器 model.parameters()    指定要优化哪个模型的参数
    optim = torch.optim.Adam(model.parameters(), lr=learning_rate)
    log = []
    # 创建训练集,正常任务是读取训练集
    train_x, train_y = build_dataset(train_sample)
    # 训练过程
    for epoch in range(epoch_num):
        model.train()
        watch_loss = []
        for batch_index in range(train_sample // batch_size):    
            x = train_x[batch_index * batch_size : (batch_index + 1) * batch_size]
            y = train_y[batch_index * batch_size : (batch_index + 1) * batch_size]
            loss = model(x, y)  # 计算loss  model.forward(x,y)
            loss.backward()  # 计算梯度
            optim.step()  # 更新权重
            optim.zero_grad()  # 梯度归零
            watch_loss.append(loss.item())
        print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))
        acc = evaluate(model)  # 测试本轮模型结果
        log.append([acc, float(np.mean(watch_loss))])
    # 保存模型
    torch.save(model.state_dict(), "model.bin")
    # 画图
    print(log)
    plt.plot(range(len(log)), [l[0] for l in log], label="acc")  # 画acc曲线
    plt.plot(range(len(log)), [l[1] for l in log], label="loss")  # 画loss曲线
    plt.legend()
    plt.show()
    return

6.模型评估函数evaluate

evaluate相当于测试部分,用来测试每轮模型预测的精确率

model.eval(),声明模型框架在这个函数中不做训练

指定模型测试数量

生成测试样本(读取测试样本)

声明测试函数中计算梯度:with torch.no_grad()

模型中,不传入参数已知样本y,生成预测样本

模型预测值与样本真实值进行比较,判断预测的正确与否

计算样本预测正确率

python 复制代码
# 测试代码
# 用来测试每轮模型的准确率
def evaluate(model):
    model.eval()
    test_sample_num = 100
    x, y = build_dataset(test_sample_num)
    print("本次预测集中共有%d个正样本,%d个负样本" % (sum(y), test_sample_num - sum(y)))
    correct, wrong = 0, 0
    with torch.no_grad():
        y_pred = model(x)  # 模型预测 model.forward(x)
        for y_p, y_t in zip(y_pred, y):  # 与真实标签进行对比
            if float(y_p) < 0.5 and int(y_t) == 0:
                correct += 1  # 负样本判断正确
            elif float(y_p) >= 0.5 and int(y_t) == 1:
                correct += 1  # 正样本判断正确
            else:
                wrong += 1
    print("正确预测个数:%d, 正确率:%f" % (correct, correct / (correct + wrong)))
    return correct / (correct + wrong)

7.模型训练好后如何使用

① 模型训练好后保存模型

python 复制代码
    # 保存模型
    torch.save(model.state_dict(), "model.bin")

② 训练好的模型编写预测函数

**Ⅰ、模型初始化:**model = TorchModel(input_size)

**Ⅱ、按照路径读取模型:**torch.load(model_path)

Ⅲ、加载训练好的权重: model.load_state_dict(torch.load(model_path))

python 复制代码
# 使用训练好的模型做预测
def predict(model_path, input_vec):
    input_size = 5
    model = TorchModel(input_size)
    model.load_state_dict(torch.load(model_path))  # 加载训练好的权重
    print(model.state_dict())

    model.eval()  # 测试模式
    with torch.no_grad():  # 不计算梯度
        result = model.forward(torch.FloatTensor(input_vec))  # 模型预测
    for vec, res in zip(input_vec, result):
        print("输入:%s, 预测类别:%d, 概率值:%f" % (vec, round(float(res)), res))  # 打印结果

③ 用模型做预测

python 复制代码
if __name__ == "__main__":
    test_vec = [[0.97889086,0.15229675,0.31082123,0.03504317,0.88920843],
                [0.74963533,0.5524256,0.95758807,0.95520434,0.84890681],
                [0.00797868,0.67482528,0.13625847,0.34675372,0.19871392],
                [0.09349776,0.59416669,0.92579291,0.41567412,0.1358894]]
    predict("model.bin", test_vec)

④ 证明预测准确性

相关推荐
桂月二二29 分钟前
构建现代化的AI流水线:从数据处理到模型部署
人工智能
梦星辰.31 分钟前
大型模型运行过程概述
人工智能
CodeJourney.31 分钟前
PyTorch不同优化器比较
人工智能·pytorch·算法·能源
小强的进阶之路32 分钟前
AI Agent 赛道:投资新宠还是潜力陷阱?
人工智能
qq_2739002334 分钟前
pytorch张量列表索引和多维度张量索引比较
人工智能·pytorch·深度学习
qq_2739002342 分钟前
pytorch 计算图中的叶子节点介绍
人工智能·pytorch·python
小舞O_o1 小时前
RP2K:一个面向细粒度图像的大规模零售商品数据集
人工智能·pytorch·python·分类·数据集
深蓝海拓1 小时前
基于深度学习的视觉检测小项目(六) 项目的信号和变量的规划
pytorch·深度学习·yolo·视觉检测·pyqt
hao_wujing1 小时前
以图像识别为例,关于卷积神经网络(CNN)的直观解释
人工智能·神经网络·cnn