2024-11-29 学习人工智能的Day33 BP算法和神经网络小实验
七、BP 算法(误差反向传播)
BP(Back Propagation)算法是用于训练多层神经网络的核心方法。它结合前向传播和反向传播,通过链式求导计算每个权重和偏置的梯度,并更新这些参数以最小化损失函数。
1. 算法基本步骤
-
前向传播:
-
数据从输入层传到隐藏层,再到输出层,计算预测值。
-
公式:
z ( l ) = W ( l ) ⋅ a ( l − 1 ) + b ( l ) z ( l ) = W ( l ) ⋅ a ( l − 1 ) + b ( l ) z ( l ) = W ( l ) ⋅ a ( l − 1 ) + b ( l ) a ( l ) = σ ( z ( l ) ) a ( l ) = σ ( z ( l ) ) a ( l ) = σ ( z ( l ) ) z(l)=W(l)⋅a(l−1)+b(l)z^{(l)} = W^{(l)} \cdot a^{(l-1)} + b^{(l)}z(l)=W(l)⋅a(l−1)+b(l) a(l)=σ(z(l))a^{(l)} = \sigma(z^{(l)})a(l)=σ(z(l)) z(l)=W(l)⋅a(l−1)+b(l)z(l)=W(l)⋅a(l−1)+b(l)z(l)=W(l)⋅a(l−1)+b(l)a(l)=σ(z(l))a(l)=σ(z(l))a(l)=σ(z(l))
-
-
计算损失:
- 使用损失函数计算预测值和真实值的误差(如均方误差、交叉熵损失)。
-
反向传播:
-
通过链式法则计算损失函数相对于每个权重和偏置的梯度。
-
梯度计算公式:
δ ( l ) = ( δ ( l + 1 ) W ( l + 1 ) ) ⊙ σ ′ ( z ( l ) ) δ ( l ) = ( δ ( l + 1 ) W ( l + 1 ) ) ⊙ σ ′ ( z ( l ) ) δ ( l ) = ( δ ( l + 1 ) W ( l + 1 ) ) ⊙ σ ′ ( z ( l ) ) δ(l)=(δ(l+1)W(l+1))⊙σ′(z(l))\delta^{(l)} = (\delta^{(l+1)} W^{(l+1)}) \odot \sigma'(z^{(l)})δ(l)=(δ(l+1)W(l+1))⊙σ′(z(l)) δ(l)=(δ(l+1)W(l+1))⊙σ′(z(l))δ(l)=(δ(l+1)W(l+1))⊙σ′(z(l))δ(l)=(δ(l+1)W(l+1))⊙σ′(z(l))
-
-
更新参数:
- 使用梯度下降或其他优化算法更新参数:
W ( l ) ← W ( l ) − η ∂ L ∂ W ( l ) , b ( l ) ← b ( l ) − η ∂ L ∂ b ( l ) W ( l ) ← W ( l ) − η ∂ L ∂ W ( l ) , b ( l ) ← b ( l ) − η ∂ L ∂ b ( l ) W ( l ) ← W ( l ) − η ∂ W ( l ) ∂ L , b ( l ) ← b ( l ) − η ∂ b ( l ) ∂ L W(l)←W(l)−η∂L∂W(l),b(l)←b(l)−η∂L∂b(l)W^{(l)} \gets W^{(l)} - \eta \frac{\partial L}{\partial W^{(l)}}, \quad b^{(l)} \gets b^{(l)} - \eta \frac{\partial L}{\partial b^{(l)}}W(l)←W(l)−η∂W(l)∂L,b(l)←b(l)−η∂b(l)∂L W(l)←W(l)−η∂L∂W(l),b(l)←b(l)−η∂L∂b(l)W(l)←W(l)−η∂W(l)∂L,b(l)←b(l)−η∂b(l)∂LW(l)←W(l)−η∂W(l)∂L,b(l)←b(l)−η∂b(l)∂L
需要关注的是,当进行循环反向传播时,优化器中的梯度会不断的叠加,所以需要对梯度进行清空。
- 使用梯度下降或其他优化算法更新参数:
2. 链式法则的应用
-
反向传播利用链式法则逐层计算梯度,从输出层逐层传播到输入层。
-
例子:复合函数
f ( x ) = 11 + e − ( w x + b ) f ( x ) = 1 1 + e − ( w x + b ) f ( x ) = 1 + e − ( w x + b ) 1 f(x)=11+e−(wx+b)f(x) = \frac{1}{1 + e^{-(wx + b)}}f(x)=1+e−(wx+b)1 f(x)=11+e−(wx+b)f(x)=1+e−(wx+b)1f(x)=1+e−(wx+b)1
的梯度可分解为每层的导数相乘。
3. BP 算法的优势
- 高效计算梯度,适合深度神经网络。
- 结合优化算法(如 SGD、Adam),能有效降低损失,提升模型性能。
总结:BP 算法是深度学习的基础,通过前向传播和反向传播实现参数的自动优化,适用于多种类型的神经网络。
实例代码:
import torch
import torch.nn as nn
import torch.optim as optim
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.linear1 = nn.Linear(2, 2)
self.linear2 = nn.Linear(2, 2)
# 网络参数初始化
self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
self.linear1.bias.data = torch.tensor([0.35, 0.35])
self.linear2.bias.data = torch.tensor([0.60, 0.60])
def forward(self, x):
x = self.linear1(x)
x = torch.sigmoid(x)
x = self.linear2(x)
x = torch.sigmoid(x)
return x
if __name__ == "__main__":
inputs = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
# 获得网络输出值
net = Net()
output = net(inputs)
# 计算误差
loss = torch.sum((output - target) ** 2) / 2
# 优化方法
optimizer = optim.SGD(net.parameters(), lr=0.5)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
print(net.linear1.weight.grad.data)
print(net.linear2.weight.grad.data)
#更新梯度
optimizer.step()
# 打印更新后的网络参数
print(net.state_dict())
八、优化算法
优化算法用于调整网络参数(权重和偏置),以最小化损失函数。
1. 梯度下降法(Gradient Descent)
-
核心思想:沿着损失函数梯度下降的方向更新参数。
-
参数更新公式:
θ = θ − η ∂ L ∂ θ θ = θ − η ∂ L ∂ θ θ = θ − η ∂ θ ∂ L θ=θ−η∂L∂θ\theta = \theta - \eta \frac{\partial L}{\partial \theta}θ=θ−η∂θ∂L θ=θ−η∂L∂θθ=θ−η∂θ∂Lθ=θ−η∂θ∂L- η:学习率,控制步长。
2. 改进的梯度下降方法
- 指数加权平均:参考各数值,并且各数值的权重都不同,距离越远的数字对平均数计算的贡献就越小(权重较小),距离越近则对平均数的计算贡献就越大(权重越大)。
示例:
import torch
import matplotlib.pyplot as plt
ELEMENT_NUMBER = 30
# 1. 实际平均温度
def test01():
# 固定随机数种子
torch.manual_seed(0)
# 产生30天的随机温度
temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10
print(temperature)
# 绘制平均温度
days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
plt.plot(days, temperature, color='r')
plt.scatter(days, temperature)
plt.show()
# 2. 指数加权平均温度
def test02(beta=0.9):
# 固定随机数种子
torch.manual_seed(0)
# 产生30天的随机温度
temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10
print(temperature)
exp_weight_avg = []
for idx, temp in enumerate(temperature):
# 第一个元素的的 EWA 值等于自身
if idx == 0:
exp_weight_avg.append(temp)
continue
# 第二个元素的 EWA 值等于上一个 EWA 乘以 β + 当前气温乘以 (1-β)
new_temp = exp_weight_avg[-1] * beta + (1 - beta) * temp
exp_weight_avg.append(new_temp)
days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
plt.plot(days, exp_weight_avg, color='r')
plt.scatter(days, temperature)
plt.show()
if __name__ == '__main__':
test01()
test02(0.5)
test02(0.9)
- Momentum(动量法):加速收敛,避免震荡。
v t = β v t − 1 + ( 1 − β ) ∇ θ L , θ = θ − η v t v t = β v t − 1 + ( 1 − β ) ∇ θ L , θ = θ − η v t v t = β v t − 1 + ( 1 − β ) ∇ θ L , θ = θ − η v t vt=βvt−1+(1−β)∇θL,θ=θ−ηvtv_t = \beta v_{t-1} + (1-\beta)\nabla_\theta L, \quad \theta = \theta - \eta v_tvt=βvt−1+(1−β)∇θL,θ=θ−ηvt vt=βvt−1+(1−β)∇θL,θ=θ−ηvtvt=βvt−1+(1−β)∇θL,θ=θ−ηvtvt=βvt−1+(1−β)∇θL,θ=θ−ηvt
API:
optimizer = optim.SGD(model.parameters(), lr=0.6, momentum=0.9) # 学习率和动量值可以根据实际情况调整,momentum 参数指定了动量系数,默认为0。动量系数通常设置为 0 到0.5 之间的一个值,但也可以根据具体的应用场景调整
-
Adam:
-
结合动量法和自适应学习率,参数更新平稳。
-
更新公式:
m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ L m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ L m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ L v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ θ L ) 2 v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ θ L ) 2 v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ θ L ) 2 θ = θ − η m t v t + ϵ θ = θ − η m t v t + ϵ θ = θ − v t + ϵ η m t mt=β1mt−1+(1−β1)∇θLm_t = \beta_1 m_{t-1} + (1-\beta_1)\nabla_\theta Lmt=β1mt−1+(1−β1)∇θL vt=β2vt−1+(1−β2)(∇θL)2v_t = \beta_2 v_{t-1} + (1-\beta_2)(\nabla_\theta L)^2vt=β2vt−1+(1−β2)(∇θL)2 θ=θ−ηmtvt+ϵ\theta = \theta - \frac{\eta m_t}{\sqrt{v_t} + \epsilon}θ=θ−vt+ϵηmt mt=β1mt−1+(1−β1)∇θLmt=β1mt−1+(1−β1)∇θLmt=β1mt−1+(1−β1)∇θLvt=β2vt−1+(1−β2)(∇θL)2vt=β2vt−1+(1−β2)(∇θL)2vt=β2vt−1+(1−β2)(∇θL)2θ=θ−ηmtvt+ϵθ=θ−vt+ϵηmtθ=θ−vt+ϵηmt
-
API:
optimizer = optim.Adagrad(model.parameters(), lr=0.9) # 设置学习率
总结:优化算法选择需视任务和模型而定,SGD 适合小数据集,Adam 是通用选择。
九、正则化(Regularization)
正则化方法用于防止模型过拟合,提高泛化能力。
1. L1 和 L2 正则化
-
L1 正则化:
L = L l o s s + λ ∑ ∣ w i ∣ L = L loss + λ ∑ ∣ w i ∣ L = L l o s s + λ ∑ ∣ w i ∣ L=Lloss+λ∑∣wi∣L = L_{\text{loss}} + \lambda \sum |w_i|L=Lloss+λ∑∣wi∣ L=Lloss+λ∑∣wi∣L=Lloss+λ∑∣wi∣L=Lloss+λ∑∣wi∣- 增强稀疏性,适合特征选择。
示例代码:
def train(): # 模型构建 # 损失函数 # 优化器 # 输入数据 # 预测 # 计算损失:L1 正则化项并将其加入到总损失中 l1_lambda = 0.001 l1_norm = sum(p.abs().sum() for p in model.parameters()) loss = criterion(output, target)+l1_lambda*l1_norm print(loss) # 梯度清零 # 反向传播 # 进行权重参数更新 # 打印更新之后的权重参数 # 保存模型权重参数
-
L2 正则化(权重衰减):
L = L l o s s + λ ∑ w i 2 L = L loss + λ ∑ w i 2 L = L l o s s + λ ∑ w i 2 L=Lloss+λ∑wi2L = L_{\text{loss}} + \lambda \sum w_i^2L=Lloss+λ∑wi2 L=Lloss+λ∑wi2L=Lloss+λ∑wi2L=Lloss+λ∑wi2- 防止权重过大,适合连续值预测。
实例代码如下:
import torch.optim as optim optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.001) # L2 正则化,weight_decay就是L2正则化前面的参数λ
2. Dropout
- 随机丢弃部分神经元,避免过度依赖特定特征,提高模型鲁棒性。
实例代码:
import torch
import torch.nn as nn
def dropout():
dropout = nn.Dropout(p=0.5)
x = torch.randint(0, 10, (5, 6), dtype=torch.float)
print(x)
# 开始dropout
print(dropout(x))
if __name__ == "__main__":
dropout()
3. 数据增强
- 对训练数据进行旋转、翻转、裁剪等操作,增加数据多样性,提升泛化能力。
总结:正则化是深度学习防止过拟合的重要工具,常与其他方法结合使用。
十、迁移学习
迁移学习是将预训练模型的知识应用于新任务。
1. 核心思想
- 使用在大规模数据集(如 ImageNet)上训练的模型作为基础,在新数据集上微调。
2. 方法
- 冻结层:固定预训练模型的部分权重,仅训练新加入的层。
- 微调:对所有层进行训练,但设置较低学习率。
总结:迁移学习有效减少训练时间,适合小数据集和资源受限的场景。
学习示例:手机信息预测项目
import time
import torch
import pandas as pd
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader,Dataset,TensorDataset
from sklearn.preprocessing import StandardScaler
# 1.数据
def phone_data_set(path):
data = pd.read_csv(path)
# 抽离特征和目标数据
x = data.iloc[:,:-1]
y = data.iloc[:,-1]
transfer = StandardScaler()
x = transfer.fit_transform(x.values)
x = torch.tensor(x,dtype=torch.float32)
y = torch.tensor(y.values,dtype=torch.int64)
# 数据集划分
x_train,x_test,y_train,y_test = train_test_split(x,y,train_size=0.8,random_state=66,shuffle=True,stratify=y)
return x_train,x_test,y_train,y_test
class my_phone_data_loader(Dataset):
def __init__(self,x,y):
self.x = x
self.y = y
def __len__(self):
return len(self.x)
def __getitem__(self, index):
return self.x[index],self.y[index]
def data_loader(x_train,y_train):
# data = my_phone_data_loader(x_train,y_train)
data = TensorDataset(x_train,y_train)
data_loader = DataLoader(data,batch_size=16,shuffle=True)
return data_loader
# 2.模型(构建和初始化参数)
class Model(torch.nn.Module):
def __init__(self,input_features,out_features):
super(Model,self).__init__()
self.linear1 = torch.nn.Linear(input_features,128)
self.activation = torch.nn.LeakyReLU()
self.linear2 = torch.nn.Linear(128,256)
self.activation2 = torch.nn.LeakyReLU()
self.out = torch.nn.Linear(256,out_features)
self.activation3 = torch.nn.Softmax()
def forward(self,input_data):
l1 = self.linear1(input_data)
o1 = self.activation(l1)
l2 = self.linear2(o1)
o2 = self.activation(l2)
o3 = self.out(o2)
y_pred = self.activation3(o3)
return y_pred
# def forward(input_data):
x_train,x_test,y_train,y_test = phone_data_set('./data/手机价格预测.csv')
# 训练
def train():
# 加载数据
data_loader_ = data_loader(x_train,y_train)
y_feature = torch.unique(y_train).shape[0]
model = Model(x_train.shape[1],y_feature)
# 初始化模型参数
# 默认是初始化过的
torch.nn.init.kaiming_normal_(model.linear1.weight,nonlinearity='leaky_relu')
torch.nn.init.kaiming_normal_(model.linear2.weight,nonlinearity='leaky_relu')
# 3.损失函数
loss_fn = torch.nn.CrossEntropyLoss()# 虽然分类的结果也可以用均方误差来计算,但一般用交叉熵(因为计算出来梯度更大)
# 4.优化器
optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
# 5.训练(batch)
# 定义训练参数
epochs = 100
for epoch in range(epochs):
loss_total = 0
count = 0
start_time = time.time()
for x,y in data_loader_:
count +=1
# 生成预测值
y_pred = model(x)
# 损失函数
loss = loss_fn(y_pred,y)
# 梯度清零
optimizer.zero_grad()
# 迭代
loss.backward()
# 梯度更新
optimizer.step()
loss_total +=loss
end_time = time.time()
print(f'epoch{epoch},loss{loss_total/count},time{end_time-start_time}')
torch.save(model.state_dict(),"./model/model.pth")
# 6.测试
# 7.保存
# 8.加载
# 9.前向传播
# 评估函数
def test():
# 加载数据
data = data_loader(x_test,y_test)
# 加载模型
y_feature = torch.unique(y_train).shape[0]
model = Model(x_test.shape[1],y_feature)
state_dict = torch.load('./model/model.pth',map_location='cpu')
model.load_state_dict(state_dict)
total = 0
for x,y in data:
y_pred = model(x)
y_pred = torch.argmax(y_pred,dim=1)
total += torch.sum(y_pred==y)
print(f"精准度:{total/len(x_test)}")
if __name__ == '__main__':
train()
test()
总括性总结
从 BP 算法到正则化、优化和迁移学习,这些内容共同构成深度学习模型训练的重要组成部分:
- BP 提供了高效的梯度计算方法。
- 优化算法确保模型快速收敛。
- 正则化方法提高模型的泛化能力。
- 迁移学习为模型提供快速适配新任务的能力。