【AI】感知机与多层感知机
Sunday, August 20, 2023
本文共1589字
4分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/posts/ai%E6%84%9F%E7%9F%A5%E6%9C%BA%E4%B8%8E%E5%A4%9A%E5%B1%82%E6%84%9F%E7%9F%A5%E6%9C%BA/。商业转载请联系作者获得授权,非商业转载请注明出处!
Knowledge speaks, but wisdom listens.
— Jimi Hendrix
感知机
感知机是一个简单的二分类线性分类器,是神经网络和深度学习的基石。它基于一个线性预测函数来进行预测,根据这个预测值再经过一个阈值函数来做二分类决策。
感知机模型的基本形式是:
$$
f(x) = \text{sign}(w \cdot x + b)
$$
其中:
- $x$ 是输入向量。
- $w$ 是权重向量。
- $b$ 是偏置。
- $w \cdot x$ 是 $w$ 和 $x$ 的点积。
- $\text{sign}$ 是符号函数,如果它的参数为正则返回+1,如果参数为负则返回-1。
感知机的学习策略是通过迭代的方式,不断调整权重 $w$ 和偏置 $b$,以减少预测值与真实类标签之间的差异。
感知机难以处理XOR问题
XOR问题是什么
XOR问题可以看做是单位正方形的四个角,响应的输入模式为(0,0),(0,1),(1,1),(1,0)第一个和第三个模式属于类0,即输入模式(0,0)和(1,1)是单位正方形的两个相对的角,但它们产生相同的结果是0。另一方面,输入模式(0,1)和(1,0)是单位正方形另一对相对的角,但是它们属于类1。
感知机的一个重要限制是它只能分类线性可分的数据集。也就是说,如果数据集中存在两个类,它们可以通过一个直线、平面或超平面完全分开,那么感知机可以找到这个分类边界。但如果数据是线性不可分的,那么感知机将不能找到一个完美的分类边界。
感知机的代码实现
感知机比较简单,这里给出一个numpy的实现。注意,训练数据和测试数据一定要是线性可分的
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
def generate_data(num_samples):
# 定义直线 ax + by + c = 0
a, b = np.random.uniform(-10, 10, 2) # 随机选择a和b
c = np.random.uniform(-10, 10) # 随机选择c
# 生成随机点
X = np.random.uniform(-10, 10, (num_samples, 2))
# 根据点到直线的距离给予标签
# d = (a*x + b*y + c) / sqrt(a^2 + b^2)
distances = (a * X[:, 0] + b * X[:, 1] + c) / (np.sqrt(a**2 + b**2))
y = np.sign(distances)
return X, y
# 生成训练数据和测试数据
X, y = generate_data(200)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
w, b = np.zeros_like(X_train[0]), 0
epoch = 100
lr = 0.1
flag = True
while flag:
flag = False
for i in range(len(X_train)):
pred = w @ X_train[i].T + b
if y_train[i] * pred <= 0:
w_grad = lr * y_train[i] * X_train[i]
b_grad = lr * y_train[i]
w += w_grad.T
b += b_grad
flag = True
def plot_line_by_slope_intercept(slope, intercept, xmin=-10, xmax=10, color='g', label=None):
x_vals = np.linspace(xmin, xmax, 400)
y_vals = slope * x_vals + intercept
plt.plot(x_vals, y_vals, color=color, label=label)
plt.figure(figsize=(6, 6))
plt.scatter(X_test[y_test == 1][:, 0], X_test[y_test == 1][:, 1], c='r', label='positive', alpha=0.8)
plt.scatter(X_test[y_test == -1][:, 0], X_test[y_test == -1][:, 1], c='b', label='negative', alpha=0.8)
plot_line_by_slope_intercept(-w[0]/w[1], -b/w[1], color='g', label='Hyperplane')
# 使用 quiver 函数绘制法向量
plt.quiver(0, 0, w[0], w[1], angles='xy', scale_units='xy', scale=1, color='black', label='Normal Vector')
plt.legend()
ax = plt.gca()
ax.set_aspect('equal')
plt.show()
多层感知机
多层感知机 (MLP, Multi-Layer Perceptron) 是一种前馈式的人工神经网络,包含至少三层节点:输入层、至少一个隐藏层、和一个输出层。每个节点(除输入层外)都是一个神经元,或称为“感知机”,使用一个非线性激活函数。MLP 是一种全连接网络,也就是说,每一层的所有节点都与下一层的所有节点相连。
MLP 的数学表达可以由以下几个部分构成:
-
线性加权和:给定输入 $x$,权重 $W$ 和偏置 $b$,线性加权和表示为:
$z = Wx + b$
-
非线性激活函数:为了引入非线性性质,我们应用一个非线性激活函数 $f$ 到上述的加权和。常见的激活函数包括 sigmoid、ReLU (Rectified Linear Unit)、tanh 等。
$a = f(z)$
其中,$a$ 是激活后的输出。
为了表达一个具有一个隐藏层的 MLP:
-
第一层(输入到隐藏):
$z^{[1]} = W^{[1]}x + b^{[1]}$
$a^{[1]} = f(z^{[1]})$
-
第二层(隐藏到输出):
$z^{[2]} = W^{[2]}a^{[1]} + b^{[2]}$
$a^{[2]} = f(z^{[2]})$
其中:
- $x$ 是输入向量。
- $W^{[1]}$ 和 $b^{[1]}$ 是第一层的权重和偏置。
- $W^{[2]}$ 和 $b^{[2]}$ 是第二层的权重和偏置。
- $f$ 是激活函数。
多层感知机一定要一个非线性的激活函数,不然多个线性函数叠加在一起还是一个线性函数
代码实现
多层感知机的numpy实现涉及一些梯度计算,因此这里给出pytorch的实现:
import tqdm
import torch
from torch import nn
import numpy as np
from torch.utils.data import Dataset, DataLoader
from sklearn.metrics import accuracy_score
# 数据初始化
train_shape = (10000, 64)
X_train = torch.randn(train_shape[0], train_shape[1], dtype=torch.float)
y_train = torch.tensor(np.random.choice([-1, 1], (train_shape[0], 1)), dtype=torch.float)
test_shape = (1000, 64)
X_test = torch.randn(test_shape[0], test_shape[1], dtype=torch.float)
y_test = torch.tensor(np.random.choice([-1, 1], (test_shape[0], 1)), dtype=torch.float)
class MyDataset(Dataset):
def __init__(self, data, targets):
self.data = data
self.targets = targets
def __len__(self):
return len(self.data)
def __getitem__(self, index):
x = self.data[index]
y = self.targets[index]
return x, y
class MLP(nn.Module):
def __init__(self, in_size, out_size):
super().__init__()
self.hidden1 = nn.Linear(in_size, in_size//2)
self.hidden2 = nn.Linear(in_size//2, in_size//4)
self.relu = nn.ReLU()
self.output = nn.Linear(in_size//4, out_size)
# self.relu2 = nn.ReLU()
def forward(self, X):
X = self.hidden1(X)
X = self.relu(X)
X = self.hidden2(X)
X = self.relu(X)
X = self.output(X)
return X
mlp_net = MLP(64, 1)
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(mlp_net.parameters(), lr=0.01)
# 创建数据集实例
train_dataset = MyDataset(X_train, y_train)
# 使用DataLoader进行分批
batch_size = 64
dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
epoch = 100
for i in tqdm.tqdm(range(epoch)):
for batch_idx, (data_batch, target_batch) in enumerate(dataloader):
# 前向传递
y = mlp_net(data_batch)
target_batch = (target_batch > 0).to(torch.float)
loss = criterion(y, target_batch)
# 后向传递
optimizer.zero_grad() # 清除之前的梯度
loss.backward() # 计算loss的梯度
optimizer.step() # 反向传递
# print(f"epoch {i}: \n\t{mlp_net.state_dict()}")
test_dataset = MyDataset(X_test, y_test)
dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
mlp_net.eval()
y_true = []
y_pred = []
with torch.no_grad():
for batch_idx, (data_batch, target_batch) in enumerate(dataloader):
# 前向传递
y = mlp_net(data_batch)
y = [-1 if yi == 0 else 1 for yi in y]
y_pred.extend(y)
y_true.extend(target_batch.tolist())
ac = accuracy_score(y_true, y_pred)
print(f"{ac=}")
扫码阅读此文章
点击按钮复制分享信息
点击订阅