目录
一、梯度下降算法
1.引例 —— 找极小值问题
目标:
方法:
2.梯度
例:
3.求解目标
为什么损失函数越小越好
4.梯度下降法
代码实现
5.细节问题
6.梯度爆炸和梯度消失
梯度爆炸
梯度消失
7.过拟合和欠拟合
欠拟合(Underfitting)
定义:
原因:
表现:
过拟合(Overfitting)
定义:
原因:
表现:
解决方法
解决欠拟合:
解决过拟合:
二、网络结构组件 —— 线性层(全连接层)
定义网络结构的方法
① init初始化:
② forward函数,前向计算
举例:
nn.Linear函数:
DNN深层神经网络组件
三、网络结构组件 —— 激活函数(非线性函数)
1.Sigmoid函数
函数特点:
公式:
2.tanh函数 —— 双曲正切函数
公式:
3.Relu函数
公式:
4.Gelu函数
公式:
5.Softmax
作用:
公式:
6.激活函数在线性层上的使用
7.Softmax函数 和 Sigmoid函数的区别
区别:
四、网络结构组件 —— 损失函数
1.均方差 MSE mean square error
调用:nn.MSE
2.交叉熵 Cross Entropy
公式:
例:
手动实现交叉熵的计算
3.交叉熵与均方差损失函数的适用范围
五、基于pytorch框架编写训练模型
1.设计模型
2.前向传递(Forward Pass)
3.准备训练数据 —— 随机生成
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
。
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固定是一个矩阵,形状通过输入数据决定)
w 和 b 是参与训练的参数
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
一个线性层的拟合能力会比较差,线性层可以堆叠多层,但是输入输出的维度需要保持相一致
# 前向计算
# 定义网络的使用方式
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()函数,矩阵内积运算
#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.激活函数在线性层上的使用
#搭建一个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()
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
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
# 当输入真实标签,返回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维向量,如果第一个值大于第五个值,认为是正样本,反之为负样本
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. 将训练数据整理为张量
随机生成一批样本,正负样本均匀生成
注: 如果是标量,要转化为张量,则需要在标量外加一个“[]”,如果是向量,转化为张量则不需要加"[]"
# 随机生成一批样本
# 正负样本均匀生成
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():设置训练模式
Ⅱ、对训练集样本开始循环训练(循环取出训练数据)
Ⅲ、根据模型函数和损失函数的定义计算模型损失
Ⅳ、计算梯度
Ⅴ、通过梯度用优化器更新权重
Ⅵ、计算完一轮训练数据后梯度进行归零,下一轮重新计算
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,生成预测样本
⑥ 模型预测值与样本真实值进行比较,判断预测的正确与否
⑦ 计算样本预测正确率
# 测试代码
# 用来测试每轮模型的准确率
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.模型训练好后如何使用
① 模型训练好后保存模型
# 保存模型
torch.save(model.state_dict(), "model.bin")
② 训练好的模型编写预测函数
Ⅰ、模型初始化:model = TorchModel(input_size)
Ⅱ、按照路径读取模型:torch.load(model_path)
Ⅲ、加载训练好的权重:model.load_state_dict(torch.load(model_path))
# 使用训练好的模型做预测
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)) # 打印结果
③ 用模型做预测
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)