一、感知机
感知机(perceptron)是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取+1和-1二值。感知机对应于输入空间(特征空间)中将实例划分为正负两类的分离超平面,属于判别模型。感知机学习旨在求出将训练数据集线性划分的分离超平面,为此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式。感知机预测是用学习得到的感知机模型对新的输入实例进行分类。感知机1957年由Rosenblatt提出,是神经网络与支持向量机的基础。
定义(感知机):
假设输入空间(特征空间)是,输出空间是Y={+1,-1},输入x∈X 表示实例的特征向量,对应于输入空间(特征空间)的点,输出
表示实例的类别,由输入空间到输出空间的函数称为感知机:
f(x)=sign(w·x+b)
模型参数: wx, 内积,权值向量w,偏置b,
超平面解释:
只有一个变量,空间为一维,超平面是一个点;
有两个变量,空间为二维,超平面是一条线;
有三个变量,空间为三维,超平面是一个二维面;
…
是一个n维空间的超平面S,其中w是超平面的法向量,b是超平面的截距,这个超平面将特征空间划分成两个部分,位于两部分的点分别被分为正负两类,所以超平面S被称为分离超平面.
感知机的参数可以自动调整,无需人工繁琐的进行一步步的调试。假设二分类问题中,样本的特征为,,标签为Y∈{0,1},那么感知机对该样本的预测输出为
罗森布拉特利用生物中的负反馈调节机制来调整感知机的参数。对于该样本,感知机收到的反馈为,其参数根据反馈进行更新。
η
η
其中,η是学习率,如果感知机的预测正确,即,其收到的反馈为0,参数不更新,如果感知机预测为0,但样本的真实标签为1,感知机收到的反馈为-1,说明其预测的结果整体偏大,需要将权重和偏置下调;如果感知机预测值为1,真实标签为0,需要将权重和偏置上调。然而,感知机模型也存在致命缺陷,它只能处理线性问题。
二、隐含层与多层感知机
为了突破只能解决线性问题带来的困境,有位学者提出了把多个单层感知机组合起来,采用前馈结构,即将神经元分为不同的层,每一层只与其前后相邻层的神经元连接,层内以及间隔一层以上的神经元之间没有连接。这样,我们可以将网络的结构分为直接接受输入的输入层,中间进行处理的隐含层,以及最终给出结果的输出层。将多个单层感知机按照前馈结构组合起来,就形成了多层感知机。
隐含层
1.定义与位置:隐含层是多层感知机中位于输入层和输出层之间的一层或多层神经元。它不直接与外界交互,主要用于对输入数据进行内部处理和特征提取。
2.功能:隐含层通过对输入数据进行非线性变换,增加了神经网络的表达能力,使其能够处理更复杂的函数关系和模式。
3.神经元数量:隐含层中神经元的数量是一个超参数,需要根据具体问题进行调整。神经元数量过多可能导致过拟合,而过少则可能导致欠拟合。
多层感知机
1.定义与结构:多层感知机是一种包含至少一个隐含层的前馈神经网络,由输入层、隐含层和输出层组成。每层神经元与下一层神经元全连接,信号从输入层依次传递到输出层。
2.工作原理:输入层接收原始数据,隐含层对数据进行非线性变换,输出层产生最终的预测结果。多层感知机通过反向传播算法进行训练,调整网络的权重和偏置,以最小化损失函数。
3.应用领域:多层感知机广泛应用于图像识别、语音识别、自然语言处理等众多领域,是深度学习的基础模型之一。
两者关系
1.隐含层是多层感知机的核心组成部分,决定了多层感知机的表达能力和性能。
2.多层感知机通过增加隐含层的数量和神经元数量,可以提高模型的复杂度和精度,但也增加了训练的难度和计算成本。
为了使多层感知机模型的激活函数具有更好的拟合能力,我们用到了以下几种的非线性的激活函数。
逻辑斯谛函数(sigmoid):
函数定义:
导数:
双曲正切(tanh)函数:
函数定义:
导数:
线性整流单元(ReLU):
函数定义:
导数:
三、反向传播
反向传播(Backpropagation)是一种用于训练人工神经网络的常见方法,特别是在多层前馈神经网络中。以下是其详细介绍:
工作原理
1.前向传播:输入数据通过网络的输入层进入,经过一系列的隐藏层,在每层中进行线性变换(通过权重和输入值的矩阵乘法)和非线性变换(通过激活函数),最终在输出层产生预测结果。
2.计算损失:将预测结果与真实标签进行比较,计算出损失函数的值,以衡量预测的误差。
3.反向传播误差:从输出层开始,反向计算每个神经元的误差对网络中所有权重的影响,即计算损失函数对每个权重的梯度。这是通过链式法则进行的,从输出层向输入层逐层计算梯度。
4.权重更新:根据计算出的梯度,使用优化算法(如梯度下降法)来更新网络中的权重,以最小化损失函数。权重的更新公式为:,其中w_{ij}是第i层到第j层的权重,\eta是学习率,E是损失函数。
数学基础
1.链式法则:如果y = g(x), z = f(y),那么。在神经网络中,用于计算复合函数的导数,即从输出层到输入层逐层计算误差对权重的导数。
2.梯度下降法:通过不断调整权重,使损失函数沿着梯度的反方向下降,以找到损失函数的最小值。学习率\eta控制着每次更新的步长,过大可能导致振荡,过小则可能导致收敛过慢。
特点
1.高效性:反向传播算法通过一次前向传播和一次反向传播,就可以计算出网络中所有权重的梯度,大大提高了计算效率。
2.普遍性:适用于各种类型的神经网络和损失函数,只要激活函数是可微的。
3.可扩展性:可以方便地应用于具有多个隐藏层的深度神经网络,通过逐层计算梯度来更新权重。
应用领域
1.图像识别:用于训练卷积神经网络(CNN),以识别图像中的物体、场景等。
2.语音识别:在语音识别系统中,反向传播算法用于训练神经网络,以提高语音识别的准确率。
3.自然语言处理:在文本分类、机器翻译等任务中,反向传播算法用于训练循环神经网络(RNN)和长短时记忆网络(LSTM)等。
4.其他领域:如推荐系统、数据挖掘、金融预测等,反向传播算法也被广泛应用于各种预测和分类任务中。
四、多层感知机的实现
随机生成数据集的Python代码如下:
import numpy as np
import pandas as pd
# 生成数据
def generate_data(size):
feature1 = np.random.uniform(low=-2, high=2, size=size)
feature2 = np.random.uniform(low=-1, high=1, size=size)
# 根据简单规则生成label(这里假设如果feature1大于feature2,label为0,否则为1)
label = np.where(feature1 > feature2, 0, 1)
data = {
'feature1': feature1,
'feature2': feature2,
'label': label
}
return pd.DataFrame(data)
# 生成1000个样本的数据集
data = generate_data(1000)
data.to_csv('xor_dataset.csv', index=False)
实现多层感知机的两种方式(动手实现和PyTorch库实现)的Python代码如下:
print('动手实现多层感知机')
import numpy as np
import matplotlib.pyplot as plt
# 基类
class Layer:
# 前向传播函数,根据输入x计算该层的输出y
def forward(self, x):
raise NotImplementedError
# 反向传播函数,输入上一层回传的梯度grad,输出当前层的梯度
def backward(self, grad):
raise NotImplementedError
# 更新函数,用于更新当前层的参数
def update(self, learning_rate):
pass
# 线性层(全连接层),参数W和b,输入与输出关系为y = Wx + b,相当于将前后的神经元两两都连接起来
class Linear(Layer):
def __init__(self, num_in, num_out, use_bias=True):
self.num_in = num_in # 输入维度
self.num_in = num_in
self.num_out = num_out # 输出维度
self.use_bias = use_bias # 是否添加偏置
# 参数的初始化非常重要
# 如果把参数默认设置为0,会导致Wx = 0,后续计算失去意义
# 我们直接用正态分布来初始化参数
self.W = np.random.normal(loc=0, scale=1.0, size=(num_in, num_out))
if use_bias: # 初始化偏置
self.b = np.zeros((1, num_out))
def forward(self, x):
# 前向传播y = Wx + b
# x的维度为(batch_size, num_in)
self.x = x
self.y = x @ self.W # y的维度为(batch_size, num_out)
if self.use_bias:
self.y += self.b
return self.y
def backward(self, grad):
# 反向传播,按照链式法则计算
# grad的维度为(batch_size, num_out)
# 梯度要对batch_size取平均值
# grad_W的维度与W相同,为(num_in, num_out)
self.grad_W = self.x.T @ grad / grad.shape[0]
if self.use_bias:
# grad_b的维度与b相同,为(1, num_out)
self.grad_b = np.mean(grad, axis=0, keepdims=True)
# 前向传播的grad的维度为(batch_size, num_in)
grad = grad @ self.W.T
return grad
def update(self, learning_rate):
# 更新参数以完成梯度下降
self.W -= learning_rate * self.grad_W
if self.use_bias:
self.b -= learning_rate * self.grad_b
class Identity(Layer):
# 单位函数
def forward(self, x):
return x
def backward(self, grad):
return grad
class Sigmoid(Layer):
# 逻辑斯谛函数
def forward(self, x):
self.x = x
self.y = 1 / (1 + np.exp(-x))
return self.y
def backward(self, grad):
return grad * self.y * (1 - self.y)
class Tanh(Layer):
# tanh函数
def forward(self, x):
self.x = x
self.y = np.tanh(x)
return self.y
def backward(self, grad):
return grad * (1 - self.y ** 2)
class ReLU(Layer):
# ReLU函数
def forward(self, x):
self.x = x
self.y = np.maximum(x, 0)
return self.y
def backward(self, grad):
return grad * (self.x >= 0)
# 存储所有激活函数和对应名称,方便索引
activation_dict = {
'identity': Identity,
'sigmoid': Sigmoid,
'tanh': Tanh,
'relu': ReLU
}
# 将全连接层和激活函数层依次拼接起来,得到一个简单的MLP了
class MLP:
def __init__(
self,
layer_sizes, # 包含每层大小的list
use_bias=True,
activation='relu',
out_activation='identity'
):
self.layers = []
num_in = layer_sizes[0]
for num_out in layer_sizes[1: -1]:
# 添加全连接层
self.layers.append(Linear(num_in, num_out, use_bias))
# 添加激活函数
self.layers.append(activation_dict[activation]())
num_in = num_out
# 由于输出需要满足的一些任务的一些要求,例如二分类任务,需要输出值范围为[0,1]的概率值
# 因此最后一层通常做特殊处理
self.layers.append(Linear(num_in, layer_sizes[-1], use_bias))
self.layers.append(activation_dict[out_activation]())
def forward(self, x):
# 前向传播,将输入依次通过每一层
for layer in self.layers:
x = layer.forward(x)
return x
def backward(self, grad):
# 反向传播,grad为损失函数对输出的梯度,将该梯度依次回传,得到每一层参数的梯度
for layer in reversed(self.layers):
grad = layer.backward(grad)
def update(self, learning_rate):
# 更新每一层的参数
for layer in self.layers:
layer.update(learning_rate)
# 设置超参数
num_epochs = 1000
learning_rate = 0.1
batch_size = 128
eps = 1e-7 # 用于防止除以0,log(0)等数学问题
# 创建一个层大小依次为[2, 4, 1]的多层感知机
# 对于二分类问题,我们用sigmoid作为输出层的激活函数,使其输出值范围为[0,1]
mlp = MLP(layer_sizes=[2, 4, 1], use_bias=True, out_activation='sigmoid')
# 导入数据集
data = np.loadtxt('xor_dataset.csv', skiprows=1, delimiter=',')
print('数据集大小:', len(data))
print(data[:5])
# 划分训练集与测试集
ratio = 0.8
split = int(ratio * len(data))
np.random.seed(0)
data = np.random.permutation(data)
x_train, y_train = data[:split, :2], data[:split, -1].reshape(-1, 1)
x_test, y_test = data[split:, :2], data[split:, -1].reshape(-1, 1)
# 训练过程
losses = []
test_losses = []
test_accs = []
for epoch in range(num_epochs):
# 我们实现的MLP支持批量输入,因此采用SGD算法
st = 0
loss = 0.0
while True:
ed = min(st + batch_size, len(x_train))
if st >= ed:
break
# 取出batch
x = x_train[st:ed]
y = y_train[st:ed]
# 计算MLP的预测
y_pred = mlp.forward(x)
# 计算梯度
grad = (y_pred - y) / (y_pred * (1 - y_pred) + eps)
# 反向传播
mlp.backward(grad)
# 更新参数
mlp.update(learning_rate)
# 计算交叉熵损失
train_loss = np.sum(-y * np.log(y_pred + eps) \
- (1 - y) * np.log(1 - y_pred + eps))
loss += train_loss
st += batch_size
losses.append(loss / len(x_train))
# 计算测试集上的交叉熵和准确率
y_pred = mlp.forward(x_test)
test_loss = np.sum(-y_test * np.log(y_pred + eps) \
- (1 - y_test) * np.log(1 - y_pred + eps)) / len(x_test)
test_acc = np.sum(np.round(y_pred) == y_test) / len(x_test)
test_losses.append(test_loss)
test_accs.append(test_acc)
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()
print('用PyTorch库实现多层感知机')
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()
程序运行结果如下:
动手实现多层感知机的图像
用PyTorch库实现多层感知机的图像