跳到主要内容

3-感知机

提示

感知机是最早的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 是线性输出(logits或rawscoreslogits 或 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的设计,多层的情况下,往往底部大点,提取特征,往上慢慢变小,扩张浓缩的特征,这里我觉得可以参考UNet的Encoder−DecoderUNet的Encoder-Decoder思想。

同时可以在尾层接入softmaxsoftmax来多分类,常用激活函数relu和tanhrelu和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()