感知机
缺点:只能处理线性问题,感知机无法解决异或问题
在这里偏置就像线性模型的常数项,加入偏置模型的表达能力增强,而激活函数就像示性函数,可以模拟神经元的兴奋和抑制,当大于等于0就输出1。
多层感知机MLP
前馈网络的层数是指权重的层数
多个单层感知机按前馈结构,前馈结构就是层只与相邻层连接,不跨越连接,就是多层感知机
激活函数
逻辑斯蒂函数
双曲正切函数
线性整流单元ReLU
一般让所有的隐含层的激活函数相同,输出层的激活函数需根据任务的需求选择,二分类可以选择逻辑斯蒂回归,多分类用softmax函数
MLP相比单层感知机的表达能力提升,关键在于非线性激活函数
可以证明任意一个R上的连续函数都可以由MLP来拟合,而对其非线性的激活函数的形式要求很少,也称作普适逼近定理。
非线性对提升模型的表达能力很重要,其实因为非线性变换相当于提升了数据的维度,维度提升的好处就在于低维数据不可分的问题可以在高维中可分
import torch # PyTorch库
import torch.nn as nn # PyTorch中与神经网络相关的工具
from torch.nn.init import normal_ # 正态分布初始化
torch_activation_dict = {
'identity': lambda x: x,
'sigmoid': torch.sigmoid,
'tanh': torch.tanh,
'relu': torch.relu
}
# 定义MLP类,基于PyTorch的自定义模块通常都继承nn.Module
# 继承后,只需要实现forward函数,进行前向传播
# 反向传播与梯度计算均由PyTorch自动完成
class MLP_torch(nn.Module):
def __init__(
self,
layer_sizes, # 包含每层大小的list
use_bias=True,
activation='relu',
out_activation='identity'
):
super().__init__() # 初始化父类
self.activation = torch_activation_dict[activation]
self.out_activation = torch_activation_dict[out_activation]
self.layers = nn.ModuleList() # ModuleList以列表方式存储PyTorch模块
num_in = layer_sizes[0]
for num_out in layer_sizes[1:]:
# 创建全连接层
self.layers.append(nn.Linear(num_in, num_out, bias=use_bias))
# 正态分布初始化,采用与前面手动实现时相同的方式
normal_(self.layers[-1].weight, std=1.0)
# 偏置项为全0
self.layers[-1].bias.data.fill_(0.0)
num_in = num_out
def forward(self, x):
# 前向传播
# PyTorch可以自行处理batch_size等维度问题
# 我们只需要让输入依次通过每一层即可
for i in range(len(self.layers) - 1):
x = self.layers[i](x)
x = self.activation(x)
# 输出层
x = self.layers[-1](x)
x = self.out_activation(x)
return x
#%%
# 设置超参数
num_epochs = 1000
learning_rate = 0.1
batch_size = 128
eps = 1e-7
torch.manual_seed(0)
# 初始化MLP模型
mlp = MLP_torch(layer_sizes=[2, 4, 1], use_bias=True,
out_activation='sigmoid')
# 定义SGD优化器
opt = torch.optim.SGD(mlp.parameters(), lr=learning_rate)
# 训练过程
losses = []
test_losses = []
test_accs = []
for epoch in range(num_epochs):
st = 0
loss = []
while True:
ed = min(st + batch_size, len(x_train))
if st >= ed:
break
# 取出batch,转为张量
x = torch.tensor(x_train[st: ed],
dtype=torch.float32)
y = torch.tensor(y_train[st: ed],
dtype=torch.float32).reshape(-1, 1)
# 计算MLP的预测
# 调用模型时,PyTorch会自动调用模型的forward方法
# y_pred的维度为(batch_size, layer_sizes[-1])
y_pred = mlp(x)
# 计算交叉熵损失
train_loss = torch.mean(-y * torch.log(y_pred + eps) \
- (1 - y) * torch.log(1 - y_pred + eps))
# 清空梯度
opt.zero_grad()
# 反向传播
train_loss.backward()
# 更新参数
opt.step()
# 记录累加损失,需要先将损失从张量转为numpy格式
loss.append(train_loss.detach().numpy())
st += batch_size
losses.append(np.mean(loss))
# 计算测试集上的交叉熵
# 在不需要梯度的部分,可以用torch.inference_mode()加速计算
with torch.inference_mode():
x = torch.tensor(x_test, dtype=torch.float32)
y = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)
y_pred = mlp(x)
test_loss = torch.sum(-y * torch.log(y_pred + eps) \
- (1 - y) * torch.log(1 - y_pred + eps)) / len(x_test)
test_acc = torch.sum(torch.round(y_pred) == y) / len(x_test)
test_losses.append(test_loss.detach().numpy())
test_accs.append(test_acc.detach().numpy())
print('测试精度:', test_accs[-1])
# 将损失变化进行可视化
plt.figure(figsize=(16, 6))
plt.subplot(121)
plt.plot(losses, color='blue', label='train loss')
plt.plot(test_losses, color='red', ls='--', label='test loss')
plt.xlabel('Step')
plt.ylabel('Loss')
plt.title('Cross-Entropy Loss')
plt.legend()
plt.subplot(122)
plt.plot(test_accs, color='red')
plt.ylim(top=1.0)
plt.xlabel('Step')
plt.ylabel('Accuracy')
plt.title('Test Accuracy')
plt.show()