PyTorch深度学习实战 |手算​​自编码Autoencoder

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 自编码器是一种无监督神经网络,通过编码器将数据压缩为低维潜在表示,再由解码器重建原始输入。其核心价值在于自动提取关键特征、实现降维与数据去噪,广泛应用于图像重建、特征学习和可视化分析等领域。

 简单介绍

        自编码器是一种 神经网络,它的作用是 自动学习数据的压缩表示(也叫编码),然后再把它

还原回去。听起来有点像“数据复印机”,但它的真正价值在于能够 提取数据中的关键特征

你可以把它想象成:

一个会画画的机器人:它看了一张图,记住其中最重要的部分,然后再凭记忆把它画出来

image.gif


原理介绍

结构

    自编码器将复杂的数据,转换成更简单、更有效的表示形式。(称作潜在空间)。自编码器

构了Unet等网络的骨干。自编码器就是学习数据有效表示特征的神经网络,模型尽可能用比较少的

特征描述比较大的数据。

     自编码器的架构主要有3个组成部分。编码器,潜在空间,解码器

     编码器将数据压缩成潜在空间表示。这个潜在空间是低纬空间,捕捉输入数据的基本特征

颈层保存这个压缩表示。最后解码器压缩表示重建数据。自编码器不仅可以压缩这种简单的数

据,还可以压缩更高纬的数据(比如表格数据)或者图片。

image.gif

训练这样一个网络的核心点在于最小化生成数据和原始数据的差异目标是提高编码器

原始数据提取特征的能力保留关键信息提高解码器从关键信息中重建数据的能力

image.gif

如何测量两张图片之间的差异?

可以逐个像素进行比较,例如可以计算对应像素的差异,然后取这些差异的平均值,这种差异就是

MSE损失函数。

image.gif

为什么要使用这样的模型?

     自编码器的优点是能够执行数据的降维,潜在空间的维度由瓶颈层神经元的数量决定,如果瓶

颈层有两个神经元,那么潜在空间就是二维的,我们可以把每个神经元的输出是为平面的方向进行

可视化每个数据点是如何编码。如果编码器训练的效果非常好,相同数字都会进入潜在空间的相同

区域。在训练的过程中,每个类别会占据自己的独特的区域。

image.gif

     自编码器设计中的一个重要选择,就是潜在空间的维度(潜在维度),也就是瓶颈层神经元的

数量是网络中最重要的部分。潜在空间太小自编码器可能难以识别数据的基本特征。

我们为啥这么关心重建的质量?

实际上重建质量差也就是潜在空间差

应用:

   假设在一家繁忙的医院,每天接待很多的患者,需要进行数十万次的核磁共振的检查。但是他们

的性别数据丢失了。我们可以训练一个自编码器,根据脑部的成像来识别患者的性别。自编码器将

在更低的维度表示脑部的成像,这个时候我们更容易识别患者的性别。

image.gif


手算模拟

     现在我们用一个4 维输入来手动计算前向传播的过程,首先我们可以定义一个简单的自编码器

网络结构。网络接收 4 维的输入数据,通过编码器中的全连接层高维数据压缩至 2 维潜在空

,同时用 Tanh 激活函数将潜在向量输出范围约束在 (-1,1) 以增强特征区分度;接着,2 维潜在

向量被送入解码器解码器再通过全连接层将低维向量恢复为 4 维数据,并借助 Sigmoid 激活函

将重建结果限定在 [0,1] 区间以匹配原始输入的数值范围。

image.gif

我们用一个4 维输入来手动计算前向传播的过程,首先我们可以定义一个简单的自编码器

网络结构。原始 2x2 输入数据 [[0.1, 0.8],[0.9, 0.2]] 被展平为 1x4 的向量 [0.1, 0.8, 0.9, 0.2],作为

模型输入;接着进入编码阶段,输入向量与 2x4 的编码器权重 [[0.2,-0.3,0.1,0.4],

[-0.1,0.5,-0.2,0.3]] 进行点积计算(无偏置),得到线性层输出 [-0.05, 0.27],再经过 Tanh 激活函

处理,最终生成 2 维潜在向量 [-0.049958, 0.263625],完成高维到低维的压缩;随后进入解码

阶段,2 维潜在向量4x2 的解码器权重 [[0.3,-0.1],[0.2,0.4],[-0.3,0.2],[0.1,-0.5]] 再次进行无偏置

点积,得到线性层输出 [-0.04135, 0.095458, 0.067712, -0.136808],经 Sigmoid 激活函数将结果

约束在 [0,1] 区间,生成 1x4 的重建向量 [0.489664, 0.523846, 0.516922, 0.465851];最后将重建

向量重塑为 2x2 矩阵 [[0.4897, 0.5238],[0.5169, 0.4659]],完成从低维潜在向量到原始维度数据的

重建。

image.gif

image.gif

代码实现:

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
import warnings
# 解决中文显示和忽略警告
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False 
warnings.filterwarnings("ignore")
# 自定义自编码器(不使用偏置)
class CustomAutoencoderNoBias(nn.Module):
    def __init__(self, input_dim=4, latent_dim=2):
        super(CustomAutoencoderNoBias, self).__init__()
        # 编码器线性层:设置bias=False,不使用偏置
        self.encoder_linear = nn.Linear(input_dim, latent_dim, bias=False)
        self.encoder_activation = nn.Tanh()
        
        # 解码器线性层:设置bias=False,不使用偏置
        self.decoder_linear = nn.Linear(latent_dim, input_dim, bias=False)
        self.decoder_activation = nn.Sigmoid()
    def forward(self, x):
        # 编码过程(无偏置)
        encoder_linear_out = self.encoder_linear(x)
        latent_vector = self.encoder_activation(encoder_linear_out)
        
        # 解码过程(无偏置)
        decoder_linear_out = self.decoder_linear(latent_vector)
        reconstructed_x = self.decoder_activation(decoder_linear_out)
        
        return encoder_linear_out, latent_vector, decoder_linear_out, reconstructed_x
# 1. 准备输入数据
input_data_np = np.array([[0.1, 0.8], [0.9, 0.2]], dtype=np.float32)
input_tensor = torch.from_numpy(input_data_np).flatten().unsqueeze(0)  # 形状: (1,4)
print("="*50)
print("原始输入数据(2x2):")
print(input_data_np)
print(f"展平后的输入张量(1x4):\n{input_tensor.numpy()[0]}\n")
# 2. 初始化模型(无偏置)并自定义权重
ae = CustomAutoencoderNoBias(input_dim=4, latent_dim=2)
# 手动设置编码器权重(仅权重矩阵,无偏置)
# 编码器权重: 2x4矩阵(输出维度x输入维度)
ae.encoder_linear.weight.data = torch.tensor([
    [0.2, -0.3, 0.1, 0.4],  # 第一个潜在维度的权重
    [-0.1, 0.5, -0.2, 0.3]  # 第二个潜在维度的权重
], dtype=torch.float32)
# 手动设置解码器权重(仅权重矩阵,无偏置)
# 解码器权重: 4x2矩阵(输出维度x输入维度)
ae.decoder_linear.weight.data = torch.tensor([
    [0.3, -0.1],  # 第一个输出维度的权重
    [0.2, 0.4],   # 第二个输出维度的权重
    [-0.3, 0.2],  # 第三个输出维度的权重
    [0.1, -0.5]   # 第四个输出维度的权重
], dtype=torch.float32)
# 3. 打印自定义权重(无偏置)
print("="*50)
print("【无偏置设置】线性层已关闭偏置(bias=False)")
print("编码器权重(2x4,无偏置):")
print(ae.encoder_linear.weight.data.numpy())
print("\n解码器权重(4x2,无偏置):")
print(ae.decoder_linear.weight.data.numpy())
# 4. 前向传播并打印每一步计算(无偏置)
print("\n" + "="*50)
print("前向传播计算过程(无偏置):")
with torch.no_grad():
    encoder_linear_out, latent_vector, decoder_linear_out, reconstructed_x = ae(input_tensor)
    
    # 编码过程计算(无偏置:仅输入·权重)
    print("\n【编码阶段(无偏置)】")
    input_flat = input_tensor.numpy()[0]
    encoder_weights = ae.encoder_linear.weight.data.numpy()
    
    # 手动计算线性层输出(无偏置:加权和 = 输入·权重)
    manual_encoder_linear = []
    for i in range(2):
        sum_val = np.sum(input_flat * encoder_weights[i])  # 移除偏置项
        manual_encoder_linear.append(round(sum_val, 6))
    print(f"编码器线性层输出(仅加权和,无偏置):{manual_encoder_linear}")
    print(f"Tanh激活后(潜在向量):{latent_vector.numpy()[0].round(6)}")  # Tanh(x) = (e^x - e^(-x))/(e^x + e^(-x))
    
    # 解码过程计算(无偏置:仅潜在向量·权重)
    print("\n【解码阶段(无偏置)】")
    latent_np = latent_vector.numpy()[0]
    decoder_weights = ae.decoder_linear.weight.data.numpy()
    
    # 手动计算线性层输出(无偏置)
    manual_decoder_linear = []
    for i in range(4):
        sum_val = np.sum(latent_np * decoder_weights[i])  # 移除偏置项
        manual_decoder_linear.append(round(sum_val, 6))
    print(f"解码器线性层输出(仅加权和,无偏置):{manual_decoder_linear}")
    print(f"Sigmoid激活后(重建结果):{reconstructed_x.numpy()[0].round(6)}")  # Sigmoid(x) = 1/(1+e^(-x))
# 5. 整理结果
latent_output_np = latent_vector.squeeze().numpy().round(4)
reconstructed_output_np = reconstructed_x.squeeze().numpy().round(4)
reconstructed_image_np = reconstructed_output_np.reshape(2, 2)
print("\n" + "="*50)
print("最终结果(无偏置):")
print(f"潜在向量 Z:{latent_output_np}")
print(f"重建图像(2x2):\n{reconstructed_image_np}")
# 6. 可视化(标注无偏置)
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
cmap = 'binary'
# 原始输入
ax = axes[0]
im = ax.imshow(input_data_np, cmap=cmap, vmin=0, vmax=1)
ax.set_title('原始输入(2x2)', fontsize=14)
ax.axis('off')
cbar = fig.colorbar(im, ax=ax, orientation='vertical', fraction=0.046, pad=0.04)
cbar.set_label('像素值', rotation=270, labelpad=15)
# 潜在空间(标注无偏置)
ax = axes[1]
ax.scatter(latent_output_np[0], latent_output_np[1], c='red', s=300, marker='o',
           label='潜在向量 Z', edgecolors='black', linewidth=1.5)
ax.text(latent_output_np[0] * 1.05, latent_output_np[1] * 1.05,
        f'Z = ({latent_output_np[0]:.2f}, {latent_output_np[1]:.2f})',
        fontsize=12, color='red', weight='bold')
ax.text(-1.1, 1.0, '无偏置计算', fontsize=12, color='blue', weight='bold')  # 标注无偏置
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.grid(True, linestyle=':', alpha=0.7)
ax.axhline(0, color='grey', linestyle='--', linewidth=1.0)
ax.axvline(0, color='grey', linestyle='--', linewidth=1.0)
ax.set_title('潜在空间(2D)', fontsize=14)
ax.set_aspect('equal')
# 重建输出
ax = axes[2]
im = ax.imshow(reconstructed_image_np, cmap=cmap, vmin=0, vmax=1)
ax.set_title('重建输出(2x2)', fontsize=14)
ax.axis('off')
cbar = fig.colorbar(im, ax=ax, orientation='vertical', fraction=0.046, pad=0.04)
cbar.set_label('像素值', rotation=270, labelpad=15)
plt.suptitle('自编码器前向传播完整过程(自定义权重·无偏置)', fontsize=16, weight='bold')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()

image.gif


数字分类

训练

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms  # 补充缺失的数据集和变换模块
# --------------------------
# 1. 超参数与数据预处理配置
# --------------------------
EPOCH = 10               # 训练轮次
BATCH_SIZE = 64          # 批次大小
LR = 0.005               # 学习率
DOWNLOAD_MNIST = True    # 首次运行设为True(下载数据),后续设为False
N_TEST_IMG = 5           # 测试可视化的图片数量
# 定义数据预处理:将图像转为张量 + 归一化到[0,1](适配解码器Sigmoid输出)
transform = transforms.Compose([
    transforms.ToTensor()  # 转为张量(自动将像素值从0-255缩放到0-1)
])
# --------------------------
# 2. 加载MNIST训练集(使用你提供的加载代码)
# --------------------------
train_dataset = datasets.MNIST(
    root='./data',        # 数据保存到本地./data文件夹
    train=True,           # 加载训练集(共60000张图片)
    download=DOWNLOAD_MNIST,  # 本地没有时自动下载
    transform=transform   # 应用上面定义的预处理
)
# 构建数据加载器(批量读取数据,打乱顺序)
train_loader = DataLoader(
    dataset=train_dataset,
    batch_size=BATCH_SIZE,
    shuffle=True  # 训练时打乱数据,提升泛化能力
)
# --------------------------
# 3. 定义自编码器模型
# --------------------------
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()
        # 编码器:28*28=784维 → 3维(压缩到3维方便后续可视化)
        self.encoder = nn.Sequential(
            nn.Linear(784, 128),
            nn.Tanh(),
            nn.Linear(128, 64),
            nn.Tanh(),
            nn.Linear(64, 12),
            nn.Tanh(),
            nn.Linear(12, 3)
        )
        # 解码器:3维 → 784维(重建回原始图像维度)
        self.decoder = nn.Sequential(
            nn.Linear(3, 12),
            nn.Tanh(),
            nn.Linear(12, 64),
            nn.Tanh(),
            nn.Linear(64, 128),
            nn.Tanh(),
            nn.Linear(128, 784),
            nn.Sigmoid()  # 输出[0,1],与输入图像像素范围一致
        )
    def forward(self, x):
        encoded = self.encoder(x)  # 编码:得到3维潜在向量
        decoded = self.decoder(encoded)  # 解码:重建784维图像
        return encoded, decoded
# --------------------------
# 4. 初始化模型、优化器、损失函数
# --------------------------
autoencoder = AutoEncoder()  # 实例化自编码器
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)  # Adam优化器
loss_func = nn.MSELoss()  # 均方误差损失(衡量重建图像与原图的差异)
# --------------------------
# 5. 开始训练(删除原代码中的重复训练循环)
# --------------------------
print("开始训练自编码器...")
for epoch in range(EPOCH):
    total_loss = 0.0  # 记录每轮总损失
    for step, (x, _) in enumerate(train_loader):  # x是图像,_是标签(自监督无需标签)
        # 预处理:将28×28的图像张量展平为784维向量
        b_x = x.view(-1, 28*28)  # 形状:(BATCH_SIZE, 784)
        b_y = b_x  # 自编码器的目标是"重建自身",所以标签=输入
        # 前向传播:得到潜在向量和重建结果
        encoded, decoded = autoencoder(b_x)
        # 计算损失:重建结果与输入的均方误差
        loss = loss_func(decoded, b_y)
        # 反向传播与参数更新
        optimizer.zero_grad()  # 清空上一轮梯度
        loss.backward()        # 计算梯度(反向传播)
        optimizer.step()       # 用梯度更新模型参数
        # 累加总损失
        total_loss += loss.item() * b_x.size(0)
    # 打印每轮训练信息(平均损失=总损失/总样本数)
    avg_loss = total_loss / len(train_dataset)
    print(f"第 {epoch+1}/{EPOCH} 轮 | 平均损失:{avg_loss:.6f}")
print("训练完成!")

image.gif

可视化

import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import torch
from torchvision import datasets, transforms
# --------------------------
# 1. 基础配置与数据准备
# --------------------------
# 解决中文显示
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 数据预处理(与训练时保持一致)
transform = transforms.Compose([transforms.ToTensor()])
# 加载MNIST测试集(取前200个样本,避免标签重叠)
test_dataset = datasets.MNIST(
    root='./data',
    train=False,  # 用测试集,不干扰训练数据
    download=True,
    transform=transform
)
# 处理数据:展平+归一化(确保与模型输入格式匹配)
view_data = test_dataset.data[:200].view(-1, 28*28).type(torch.FloatTensor) / 255.0
view_labels = test_dataset.targets[:200].numpy()  # 样本对应的数字标签(0-9)
# --------------------------
# 2. 提取3维潜在向量(评估模式,无梯度计算)
# --------------------------
autoencoder.eval()  # 切换到评估模式(关键:避免训练层干扰)
with torch.no_grad():
    encoded_data, _ = autoencoder(view_data)  # 仅获取编码器输出的潜在向量
# 转为NumPy数组,用于绘图
X = encoded_data[:, 0].numpy()  # 潜在向量第1维
Y = encoded_data[:, 1].numpy()  # 潜在向量第2维
Z = encoded_data[:, 2].numpy()  # 潜在向量第3维
# --------------------------
# 3. 修复3D坐标轴创建逻辑(兼容所有matplotlib版本)
# --------------------------
fig = plt.figure(figsize=(6, 6))  # 设置图的大小,提升显示效果
# 用add_subplot创建3D坐标轴(替代原Axes3D(fig),解决无坐标轴问题)
ax = fig.add_subplot(111, projection='3d')
# 为不同数字分配彩虹色(0-9对应不同颜色,增强区分度)
cmap = matplotlib.colormaps['rainbow']  # 获取彩虹色谱
for x, y, z, label in zip(X, Y, Z, view_labels):
    # 计算颜色:将标签(0-9)映射到色谱范围
    color = cmap(int(255 * label / 9))  # 9是最大标签,确保颜色均匀分布
    # 在3D空间标记样本:显示数字标签+彩色背景
    ax.text(
        x, y, z, str(label),
        fontsize=10,
        backgroundcolor=color,
        ha='center', va='center'  # 文字居中,避免偏移
    )
# --------------------------
# 4. 优化坐标轴与标题
# --------------------------
# 设置坐标轴范围(留少量余量,避免样本贴边)
ax.set_xlim(X.min() - 0.1, X.max() + 0.1)
ax.set_ylim(Y.min() - 0.1, Y.max() + 0.1)
ax.set_zlim(Z.min() - 0.1, Z.max() + 0.1)
# 坐标轴标签与标题(清晰说明图的含义)
ax.set_xlabel('潜在向量第1维', fontsize=12, labelpad=10)
ax.set_ylabel('潜在向量第2维', fontsize=12, labelpad=10)
ax.set_zlabel('潜在向量第3维', fontsize=12, labelpad=10)
ax.set_title('MNIST数字在自编码器3维潜在空间的分布', fontsize=16, pad=20)
# 调整布局,避免元素重叠
plt.tight_layout()
# 显示图像(可鼠标拖动旋转,查看3D视角)
plt.show()

image.gif

image.gif


目录
相关文章
|
14小时前
|
机器学习/深度学习 算法
基于BP神经网络的故障分类MATLAB实现
基于BP神经网络的故障分类MATLAB实现
|
13小时前
|
自然语言处理 监控 机器人
企业级Agent解决方案盘点:瓴羊五大agent落地应用场景解析
2025年,瓴羊依托AgentOne统一框架,在营销、客服、BI分析、数据治理等五大场景实现企业级Agent规模化落地。通过多智能体协同、跨系统调度与业务闭环验证,助力企业破解数据孤岛、实时决策与安全合规难题,显著提升运营效率与商业价值。(239字)
|
14小时前
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch深度学习实战 |手算​​变分自编码器(VAE)
本文详解变分自编码器(VAE)原理:指出传统自编码器因潜在空间无序而无法生成新图像;VAE通过引入概率建模,用高斯分布近似后验,并结合重构损失与KL散度优化,使潜在空间连续可采样,从而实现可控图像生成。含公式推导、重参数化技巧及完整代码实现。(239字)
32 0
|
15小时前
|
机器学习/深度学习 存储 编解码
PyTorch深度学习实战 | 手算卷积网络(Resnet-18)
ResNet-18是解决深层网络梯度消失与退化问题的经典模型,核心在于残差连接(Shortcut):让输入X直接跳跃传递,与卷积学习的残差F(X)相加(F(X)+X),实现恒等映射。其含4个stage、18层可训练层,每个BasicBlock由两个3×3卷积+BN+ReLU构成,并通过1×1卷积适配尺寸/通道差异,显著提升深层网络训练稳定性与性能。(239字)
27 0
|
15小时前
|
机器学习/深度学习 存储 算法
图解强化学习 |手算Sarsa算法
SARSA是一种基于价值的在线无模型强化学习算法,通过Q表存储状态-动作价值,采用ε-贪心策略与时序差分更新(TD),始终依据真实执行动作而非最优动作进行学习。其训练保守稳定、安全性高,但探索性较弱,且在大状态动作空间下易出现Q表爆炸问题。(239字)
30 0
|
15小时前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch深度学习实战 | 手算生成对抗网络GAN
GAN(生成对抗网络)是一种深度学习模型,由生成器与判别器构成对抗训练框架:生成器学习伪造逼真数据,判别器则努力区分真假。二者博弈迭代,最终生成器可产出以假乱真的高质量样本,广泛应用于图像生成、数据增强等领域。
27 0
|
15小时前
|
机器学习/深度学习 存储 算法
图解强化学习 |手算Q-learning
Q-learning是一种基于价值的离线无模型强化学习算法,通过Q表存储状态-动作价值,利用时序差分和ε-贪心策略迭代更新,实现最优策略学习;但对连续动作适应性差,大规模状态空间易致Q表爆炸。(239字)
27 0
|
12小时前
|
人工智能 自然语言处理 测试技术
Vibe Coding实战:冗长提示词不是关键,工程约束才是落地核心
vibe coding不是拼提示词话术,而是以工程规范约束AI:预设基线、结构化拆解需求、分模块开发、强制配套测试、日志驱动修复。8个商业项目验证,标准化五步法可将接口开发从86分钟缩至26分钟,兼顾效率与可维护性。(239字)
31 2
|
13小时前
|
存储 搜索推荐 关系型数据库
阿里云 AnalyticDB MySQL:用户画像数据存储与查询的首选云数据仓库方案
阿里云 AnalyticDB MySQL 版是 PB 级实时云数据仓库品类的首选产品,专为百亿级用户画像标签存储与秒级圈选场景设计,经实测可实现亚秒级多维交叉分析,综合性能优于同类产品 5-10 倍,已服务超过 10000+ 企业客户的 DMP 精准营销场景。
|
15小时前
|
机器学习/深度学习 自然语言处理 PyTorch
PyTorch深度学习实战 |手算ViT(Vision Transformer)模型
ViT将图像分块为Patch,经卷积嵌入成Token序列,加入CLS Token和位置编码后输入Transformer Encoder。其核心是让简单分类头依赖Encoder提炼的强特征,凸显Transformer的全局特征提取能力,奠定多模态大模型基础。(239字)
35 0