Skip to main content

3-感知机

tip

感知机是最早的AI模型,是一个解决二分类的模型

1.单层感知机

1.1.数学表达

单层感知机的基础表达可以理解成一个线性回归函数,套上一个激活函数。

输入 xx ,权重ww,偏移bb,激活函数σ\sigma,则表达为:

output=σ(wx+b)output = \sigma(wx+b)

这里进行计算的都是向量,所以区别于回归和softmaxsoftmax,这里输出的是一个类。

而回归输出的是数,softmaxsoftmax输出的是概率。从数学表达上来看,我理解成一个二分类的线性函数,适于做二分类的任务,在坐标轴上找到一条合适的直线,在一定误差的范围内,较好的完成分类。

image-20260602175044625

image-20260602175103057

1.2.区别

与线性回归,全连接,softmaxsoftmax的区别:

他们的骨干完全相同,都是z=Wx+bz=Wx+b

其中:

  • xx 是输入向量。
  • WW 是权重矩阵(或系数向量)。
  • bb 是偏置。
  • zz 是线性输出(logitsrawscoreslogits 或 raw scores)。

所以,它们都共享“全连接”的线性变换结构。 现代神经网络的全连接层(nn.Linear)就是这个线性核心的通用实现。

差异如下:

image-20260602175457214

我个人直接理解就是感知机=激活(全连接)感知机=激活(全连接),这里需要意识到,不能简单以为加上非激活函数就可以解决异或问题。这是因为单层感知机本质上就决定了是单调线性。

1.3.异或问题

感知机直观来看就一个线性函数,所以无法做到非线性拟合,所以解决不了异或问题image-20260602175726043

2.多层感知机

2.1.目的

由于单层感知机,无法解决异或问题,深度学习在一段时间内,都没什么发展,后来就出现了多层感知机,得益于其非线性的拟合,能够解决异或问题。

2.2.数学表达

[ \mathbf{h}_1 = \sigma(\mathbf{W}_1 \mathbf{x} + \mathbf{b}_1) ] [ \mathbf{h}_2 = \sigma(\mathbf{W}_2 \mathbf{h}_1 + \mathbf{b}_2) ] [ \mathbf{h}_3 = \sigma(\mathbf{W}_3 \mathbf{h}_2 + \mathbf{b}_3) ] [ \mathbf{o} = \mathbf{W}_4 \mathbf{h}_3 + \mathbf{b}_4 ]

其中,隐藏层数,和隐藏层大小,是超参数。

image-20260603134849594

  • 隐藏层套激活函数
  • 激活函数是非线性拟合的关键
  • 先大后小,先缩后扩

最后一层往往直接输出,不需要过激活函数。

其中MLPMLP的设计,多层的情况下,往往底部大点,提取特征,往上慢慢变小,扩张浓缩的特征,这里我觉得可以参考UNetEncoderDecoderUNet的Encoder-Decoder思想。

同时可以在尾层接入softmaxsoftmax来多分类,常用激活函数relutanhrelu和tanh

2.3.代码

MLP

class MLP(nn.Module):
def __init__(self,input_size,hidden_size,output_size):
super(MLP,self).__init__()
self.fc1 = nn.Linear(input_size,hidden_size)
self.fc2 = nn.Linear(hidden_size,output_size)
self.relu = nn.ReLU()
def forward(self,x):
x = x.view(-1,28*28)
out = self.relu(self.fc1(x))
out = self.fc2(out)
return out

导包和定义

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import time
import os

BATCH_SIZE = 64
LEARNING_RATE = 0.001
EPOCHS = 10
HIDDEN_SIZE = 256 # 隐藏层神经元数量
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
PATIENCE = 3 # 早停耐心值

# 全局变量用于存储训练历史
train_losses = []
train_accuracies = []
test_accuracies = []

def create_data_loaders():
transform = transforms.Compose([
transforms.ToTensor(),
])
train_dataset = datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform
)
train_loader = DataLoader(
dataset=train_dataset,
batch_size=64,
shuffle=True
)
test_dataset = datasets.MNIST(
root='./data',
train=False,
download=True,
transform=transform
)
test_loader = DataLoader(
dataset=test_dataset,
batch_size=1000,
shuffle=True
)
return train_loader,test_loader

训练函数

def train(epoch,model,train_loader,criterion,optimizer):
model.train()
total_loss = 0
correct = 0
total = 0
start_time = time.time()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(DEVICE), target.to(DEVICE)
optimizer.zero_grad()

output = model(data)

loss = criterion(output,target)

loss.backward()
optimizer.step()

total = total + loss.item()

_, predicted = torch.max(output.data, 1)

total = total + target.size(0)

correct += (predicted == target).sum().item()

if batch_idx % 100 == 0:
elapsed = time.time() - start_time
batches_done = batch_idx + 1
eta = elapsed / max(batches_done, 1) * (len(train_loader) - batches_done)
print(f'Epoch {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} '
f'({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.4f}\tETA: {eta:.1f}s')
end_time = time.time()

avg_loss = total_loss / len(train_loader)
accuracy = 100.* (correct/total)

train_losses.append(avg_loss)
train_accuracies.append(accuracy)

print(
f'\nEpoch {epoch} 训练完成!平均损失:{avg_loss:.4f}, 训练准确率:{accuracy:.2f}% (耗时:{end_time - start_time:.2f}s)')

测试函数

def test(model, test_loader, criterion):
model.eval()
test_loss = 0
correct = 0
total = 0

with torch.no_grad():
for data,target in test_loader:
data, target = data.to(DEVICE), target.to(DEVICE)
output = model(data)

test_loss += criterion(output,target).item()

_, predicted = torch.max(output.data, 1)

total += target.size(0)

correct += (predicted == target).sum().item()

avg_loss = test_loss / len(test_loader)
accuracy = 100. * correct / total

test_accuracies.append(accuracy)
print(f'\n测试集结果:平均损失:{avg_loss:.4f}, 测试准确率:{accuracy:.2f}% ({correct}/{total})')
return accuracy

epoch训练

def train_model(model, train_loader, test_loader, criterion, optimizer, scheduler):
print("\n开始训练...\n")
best_acc = 0.0
patience_counter = 0
prev_lr = LEARNING_RATE

for epoch in range(1, EPOCHS + 1):
train(epoch, model, train_loader, criterion, optimizer)
acc = test(model, test_loader, criterion)

scheduler.step(acc)
current_lr = scheduler.get_last_lr()[0]
if epoch > 1 and current_lr < prev_lr:
print(f'→ 学习率已调整:{prev_lr:.6f} -> {current_lr:.6f}')
prev_lr = current_lr

if acc > best_acc:
best_acc = acc
patience_counter = 0
# 保存最佳模型
torch.save(model.state_dict(), 'best_mlp_mnist.pth')
print(f'✓ 保存最佳模型 (准确率:{best_acc:.2f}%)')
else:
patience_counter += 1
print(f'⚠ 未提升,耐心计数:{patience_counter}/{PATIENCE}')

if patience_counter >= PATIENCE:
print(f'\n达到早停条件,停止训练')
break

print(f"\n训练结束!最高测试准确率:{best_acc:.2f}%")
return best_acc


启动

input_size = 28 * 28
num_classes = 10
model = MLP(input_size, HIDDEN_SIZE, num_classes).to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=2)
best_acc = train_model(model, train_data, test_data,
criterion, optimizer, scheduler)

测试检验

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")

# 加载训练好的模型
model = MLP(input_size, HIDDEN_SIZE, num_classes).to(DEVICE)
# model = MLP().to(device)
model.load_state_dict(torch.load('best_mlp_mnist.pth', map_location=device))
model.eval() # 设置为评估模式
print("模型加载成功!")

# 加载测试集
transform = transforms.Compose([transforms.ToTensor()])
test_dataset = datasets.MNIST(
root='./data',
train=False,
download=True,
transform=transform
)
print(f"测试集大小: {len(test_dataset)} 张图片")

可视化输出

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import random
from torchvision import datasets, transforms
# 随机选择6张图片进行测试
random.seed(42) # 设置随机种子以便复现结果
indices = random.sample(range(len(test_dataset)), 6)

# 创建图形
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
axes = axes.flatten()

with torch.no_grad():
for i, idx in enumerate(indices):
# 获取图片和标签
img, true_label = test_dataset[idx]

# 预测
img_input = img.unsqueeze(0).to(device) # 添加 batch 维度
output = model(img_input)
_, predicted = torch.max(output, 1)
predicted_label = predicted.item()

# 获取预测概率
probabilities = torch.softmax(output, dim=1)
confidence = probabilities[0][predicted_label].item() * 100

# 显示图片
axes[i].imshow(img.squeeze(), cmap='gray')

# 设置标题,显示真实标签和预测结果
color = 'green' if predicted_label == true_label else 'red'
axes[i].set_title(
f'真实: {true_label} | 预测: {predicted_label}\n置信度: {confidence:.1f}%',
fontsize=12,
color=color
)
axes[i].axis('off')

plt.suptitle('MNIST 手写数字识别测试结果', fontsize=16, fontweight='bold')
plt.tight_layout()
# plt.savefig('./results/test_predictions.png', dpi=150, bbox_inches='tight')
# print("\n预测结果已保存到: ./results/test_predictions.png")
plt.show()