1.神经网络的代价函数
神经网络可同时用于解决分类问题和回归问题,对于不同的问题会在输出层后,加上不同的变换函数。一般来说,回归问题使用恒等函数,分类问题使用sigmoid或softmax函数。而不同的变换函数,也对应不同的代价函数。
神经网络解决回归问题
在使用神经网络解决回归问题时,会在输出层后,加上恒等函数,不会对输入值做任何修改。如果神经网络只预测一个回归值,那么使用的代价函数和线性回归中的代价函数一样,都是均方误差函数MSE。如果线性回归需要预测多个回归值,则需要将每个回归目标的均方误差计算出来,然后相加得到总误差。
总的代价函数:
m:样本个数
n:目标个数
:真实值
:预测值
i:第i个样本
j:第j个目标
:计算单独每个样本的n个目标的真实值和预测值的平方差
:将m个样本对应的误差加到一起
神经网络解决分类问题
当使用神经网络解决分类问题时,最后一层的每个神经元都会对应一个类型,每个神经元的输出通过变换函数转换为类别对应的概率。如果每个类别之间是互斥的,就将神经元的输出值,输入到softmax函数中,将其转化为这几个互斥的类别对应的概率()。
另一种情况是多标签分类,每个类别之间互不打扰,相互独立,这时要将神经元的输出值,分别输入到sigmoid函数中,经过sigmoid函数的计算,可以得到这些不同类别的概率,它们是多个无关联的、0-1之间的实数。
代价函数
在解决分类问题时,一般使用交叉熵损失函数。对于互斥的分类问题,神经网络会使用与softmax回归形式完全相同的交叉熵损失函数。对于多标签分类问题,神经网络使用与逻辑回归形式相似的交叉熵损失函数。
互斥的多分类问题:
多标签分类问题:
2.小批量梯度下降算法
梯度下降算法有三种常见的形式:批量梯度下降、随机梯度下降和小批量梯度下降
- 批量梯度下降:
每次迭代中,批量梯度下降算法都会基于所有的训练样本,计算损失函数的梯度,因此可以得到一条平滑的收敛曲线。训练数据:100个样本,迭代轮数50,在每一轮迭代中都会一起使用这100个样本,计算整个训练集的梯度,更新模型参数,所以总更新次数:50次
- 随机梯度下降:
会在一轮完整的迭代过程中,遍历整个训练集,但是每次更新只基于一个样本计算梯度,这样会得到一条震荡的收敛曲线。训练数据:100个样本,迭代轮数50,每一轮迭代会遍历这100个样本,每次汇集孙某一个样本的梯度,更新模型参数,所以总更新次数:100*50=5000
- 小批量梯度下降:
结合批量梯度下降和随机梯度下降的优点,每次迭代会从训练集中,随机选择一个小批量,计算梯度,更新模型。训练数据:100个样本,迭代轮数50,小批量大小20,在每一轮迭代中会有5次小批量的迭代,所以总更新次数:(100/20)*50=250
优点 | 缺点 | |
批量梯度下降 | 每次迭代会使用整个训练集计算梯度,可以得到准确的梯度方向 | 如果数据集非常大时,就导致每次迭代的速度都非常慢,计算成本就会很高 |
随机梯度下降 | 每次只用一个样本训练,所以迭代速度会非常快,迭代具有震荡属性,可以跳出局部最优解 | 更新的方向会不稳定,可能永远都不会真正的收敛 |
小批量梯度下降 | 结合随机梯度下降的高效性和批量梯度下降的稳定性,它比随机梯度下降有更稳定的收敛,同时又比批量梯度下降计算的更快 |
方法 | 是否稳定 | 迭代速度 | 局部最优解 |
批量梯度下降 | 稳定 | 慢 | 可能停留 |
随机梯度下降 | 不稳定 | 快 | 可跳出 |
小批量梯度下降 | 较稳定 | 较快 | 可跳出 |
小批量梯度下降算法的实现
1.小批量数据的准备
import numpy as np
import matplotlib.pyplot as plt
import torch
from torch.utils.data import DataLoader
from torch.utils.data import TensorDataset
#设置一个固定的随机种子,确保每次运行得到相同的数据
np.random.seed(0)
#随机生成100个横坐标x,范围在0-2
x=2*np.random.rand(100,1)
#生成带有噪音的纵坐标y,数据基本分布在y=2x+3附近
y=3+2*x+np.random.randn(100,1)*0.5
plt.scatter(x, y,marker='x',color='red')#将训练数据x、y转为张量
x=torch.from_numpy(x).float()
y=torch.from_numpy(y).float()#使用TensorDataset,将x和y组成训练集
dataset=TensorDataset(x,y)
#使用DataLoader,构造随机的小批量数据
dataloader=DataLoader(dataset,#使用一个小批量的数据规模为20batch_size=20,#随机打乱数据的顺序shuffle=True)
print("dataloader len=%d"%(len(dataloader)))
for index,(data,label) in enumerate(dataloader):print("index = %d num=%d"%(index,len(data )))
x=2*np.random.rand(100,1)
np.random.rand(100,1)
:使用NumPy
的rand
函数生成一个形状为 (100,1)(100,1)(100,1) 的随机数组,里面的数值均匀分布在 000 到 111 之间。
2 * np.random.rand(100,1)
:将生成的数组乘以 2,使得数值范围扩展到 000 到 222 之间。
y=3+2*x+np.random.randn(100,1)*0.5
np.random.randn(100,1) * 0.5
:生成形状为 (100,1)(100,1)(100,1) 的随机噪声项,使y
中的值带有一些波动。np.random.randn(100,1)
使用标准正态分布(均值为 0,标准差为 1)生成随机数,将其乘以0.5
缩小标准差,使得噪声更小、波动更平滑。
np.random.rand
生成的随机数均匀分布在 [0,1),而np.random.randn
生成的随机数服从标准正态分布,中心在 0。
DataLoader(dataset, batch_size=20, shuffle=True)
:
DataLoader
是PyTorch
中的一个类,用于将数据集分成小批量并进行迭代读取。这样可以有效地处理大量数据而不必一次性全部加载到内存中,特别是在训练深度学习模型时非常重要。
dataset
:
- 这是要加载的数据集,即前面用
TensorDataset(x, y)
定义的dataset
。DataLoader
将使用该数据集来加载数据。
batch_size=20
:
- 设定每个小批量的大小为 20,意味着每次加载器会返回 20 个样本。
- 在训练过程中,小批量(batch)数据能够加速模型的梯度计算,同时可以让模型在批量数据的平均梯度上进行更新,有助于稳定训练。
shuffle=True
:
- 设置
shuffle=True
会在每个 epoch(轮次)开始前打乱数据集的顺序,以避免模型训练受样本顺序的影响,有助于提升模型的泛化能力。- 在训练时,数据的随机性能够帮助模型更好地学习到数据的真实分布,防止过拟合。
w = torch.randn(1, requires_grad=True)
初始化了模型的权重参数
w
,并启用了自动求导功能(requires_grad=True
),表示w
将在训练过程中计算其梯度。 尽管我们在定义时启用了requires_grad=True
,只是告诉PyTorch
这个张量(比如w
)需要计算和记录梯度,但这并不会主动计算出梯度。实际上,梯度计算只在调用loss.backward()
时才会触发
dataloader len=5
index = 0 num=20
index = 1 num=20
index = 2 num=20
index = 3 num=20
index = 4 num=20
2.小批量梯度下降算法迭代
#带迭代的参数w和b
w=torch.randn(1,requires_grad=True)
b=torch.randn(1,requires_grad=True)
#进入模型的迭代循环
for epoch in range(1,51):#迭代轮数#在一个迭代轮次中,以小批量的方式,使用dataloader对数据进行遍历#batch_idx表示当前遍历的批次#data和label表示这个批次的训练数据和标记for batch_idx,(data,label) in enumerate(dataloader):h=x*w+b#计算当前直线的预测值,保存到h#计算预测值h和真实值y之间的均方误差,保存到loss中loss=torch.mean((h-y)**2)loss.backward()#计算代价loss关于参数w和b的偏导数#进行梯度下降,沿着梯度下降的反方向,更新w和b的值w.data-=0.01*w.grad.datab.data-=0.01*b.grad.data#清空张量w和b中的梯度信息,为下一次迭代做准备w.grad.zero_()b.grad.zero_()#每次迭代,都打印当前迭代的轮数epoch#数据的批次batch_idx和loss损失值print("epoch (%d) batch (%d) loss=%.3lf"%(epoch,batch_idx,loss.item()))
for batch_idx, (data, label) in enumerate(dataloader):
enumerate(dataloader)
是一个迭代器,它会遍历dataloader
并在每次迭代时返回当前批次的索引batch_idx
和该批次的数据(data, label)
。dataloader
是由DataLoader
创建的对象,负责将数据集dataset
分割为小批量,以便模型可以逐批次读取数据进行训练
3.图像绘制
#打印w和b的值,并绘制直线
print('w=%.3lf,b=%.3lf'%(w.item(),b.item()))
w=w.item()
b=b.item()
x=np.linspace(0,2,100)
h=w*x+b
plt.plot(x,h)
plt.show()
4.结果分析
每次运行代码时返回的 w
和 b
不一样,主要是由于以下几个原因:
- 随机初始化:
w = torch.randn(1, requires_grad=True) b = torch.randn(1, requires_grad=True)
这里的 torch.randn
会从标准正态分布(均值 0,标准差 1)中随机生成 w
和 b
的初始值。每次运行代码时,w
和 b
的初始值通常是不同的,这会导致训练过程中的更新路径不同,从而影响最终的值。
- 数据中的随机噪声
y = 3 + 2 * x + np.random.randn(100, 1) * 0.5
虽然设置了 np.random.seed(0)
,使得每次运行生成的 x
和 y
都相同,但在模型初始化时 w
和 b
是随机的,每次不同的初始 w
和 b
会影响模型在不同批次上的更新路径,进而影响训练结果。
- 小批量数据的随机顺序:
dataloader = DataLoader(dataset, batch_size=20, shuffle=True)
DataLoader
的 shuffle=True
参数会在每个 epoch 开始时随机打乱数据的顺序。每次运行代码时,小批量数据的顺序会不同,因此参数更新的路径也会不同。