从0开始机器学习--6.2神经网络模型(深度学习、cnn卷积、rnn循环、lstm、解码编码器、自编码器,含mnist处理代码)

写在前面

在上一篇中,我们已经了解了pytorch框架下的神经网络模型的基本概念,本篇我们将聚焦于pytorch框架下的cnn(含mnist识别手写数字集)、rnn和lstm、解码编码器和自编码器。

1.python基础;
2.ai模型概念+基础;
3.数据预处理;
4.机器学习模型--1.聚类;2.降维;3.回归(预测);4.分类;
5.正则化技术;
6.神经网络模型--1.概念+基础;2.几种常见的神经网络模型
7.对回归、分类模型的评价方式;
8.简单强化学习概念;
9.几种常见的启发式算法及应用场景;
10.机器学习延申应用-数据分析相关内容--1.A/B Test;2.辛普森悖论;3.蒙特卡洛模拟;
以及其他的与人工智能相关的学习经历,如数据挖掘、计算机视觉-OCR光学字符识别等。


本文目录

写在前面

卷积神经网络(Convolutional Neural Network, CNN)

结构与工作原理

特点与优势

应用场景

卷积(Convolution)

1. 卷积操作的基本原理

2. 卷积的类型

3. 卷积的参数

4. 卷积的优势

5. 卷积的作用

示例

卷积运算 v.s 相关运算

1. 相似之处

2. 区别

数学定义

实际应用

在卷积神经网络中的应用

池化(Pooling)

工作原理

常见类型

池化过程

数学表示

池化的作用

示例

卷积和池化的结合

代码,识别mnist手写数字集

循环神经网络(Recurrent Neural Network, RNN)

结构与工作原理

特点与不足

应用场景

 代码

长短期记忆网络(Long Short-Term Memory, LSTM)

代码

编码器-解码器(Encoder-Decoder) 

结构与工作原理

1.1 编码器阶段

1.2 解码器阶段

工作流程

特点与优势

应用场景

改进:Attention机制

基本原理

工作步骤

代码

自编码器(Autoencoder)

结构与工作原理

自编码器模型结构

自编码器的工作流程

种类

特点

应用场景

局限性

代码

面经:解码编码器模型和自编码器模型的相同与不同

总结


卷积神经网络(Convolutional Neural Network, CNN)

结构与工作原理

CNN的结构通常由以下几部分组成:

  1. 输入层:接受原始数据,如图像的像素矩阵。

  2. 卷积层(Convolution Layer):核心操作是卷积(见下)。卷积核(filter)会在输入数据上滑动,计算内积,提取局部区域的特征。每个卷积核可以学习到不同的特征(如边缘、纹理等)。

    • 权重共享:卷积层通过卷积核在整个输入上共享同一组权重,大大减少了需要训练的参数数量。
    • 特征图(Feature Map):卷积操作产生特征图,表示数据的局部特征。
  3. 激活函数(Activation Function):通常使用非线性函数,如ReLU(Rectified Linear Unit),将线性输出转换为非线性输出,增加网络的表达能力。(在《6.1》中有过具体介绍)

  4. 池化层(Pooling Layer):通常是最大池化或平均池化(见下),用于减少特征图的尺寸,保留主要信息并减少计算复杂度和过拟合风险。

    • 最大池化:取窗口内的最大值。
    • 平均池化:取窗口内的平均值。
  5. 全连接层(Fully Connected Layer):将上层输出的特征图展平,连接到输出神经元上。这个过程类似于传统神经网络的结构,结合前面提取到的特征进行分类或回归。

  6. 输出层:根据具体任务,通常采用Softmax层(用于分类任务)或线性层(用于回归任务)生成最终的预测结果。

特点与优势

  • 参数共享:通过卷积核权重共享,大幅减少参数数量,适合高维数据(如图像)的处理。
  • 局部感受野:卷积操作局部感受区域的小窗口使CNN能够捕捉局部特征,并通过多层卷积逐步学习更高层次的全局特征。
  • 空间不变性:卷积和池化操作允许CNN对输入的某些平移、缩放、旋转等变化具有不变性。

应用场景

CNN广泛应用于图像相关的任务,如:

  • 图像分类(如ImageNet)
  • 目标检测(如YOLO, Faster R-CNN)
  • 语义分割
  • 风格迁移图像生成

卷积(Convolution)

1. 卷积操作的基本原理

卷积(Convolution)是卷积神经网络(CNN)的核心操作,它通过卷积核(filter)与输入数据(如图像)进行局部运算来提取特征。卷积的核心目的是减少高维数据的维度,同时保留重要的局部特征

  • 卷积核(Filter/Kernel):通常是一个小的矩阵,比如 3×3 或 5×5,包含可训练的权重
  • 输入:输入通常是二维的图像(也可以是高维数据,如三维图像RGB通道)。
  • 卷积操作:卷积核像滑动窗口一样在输入数据上移动,计算卷积核矩阵与输入区域的点积,然后将结果存储为特征图上的一个值。

公式: 假设输入矩阵为 I,卷积核为 K,那么卷积操作可以表示为:

2. 卷积的类型

  • 一维卷积(1D Convolution):用于处理一维数据,例如时间序列数据
  • 二维卷积(2D Convolution):用于处理二维数据,例如灰度图像或彩色图像
  • 三维卷积(3D Convolution):用于处理三维数据,例如视频或三维医学影像

3. 卷积的参数

  • 步幅(Stride):卷积核在输入数据上滑动的步长。步幅越大,输出特征图的尺寸越小。
    • 步幅为1时,卷积核每次移动1个像素。
    • 步幅为2时,卷积核每次移动2个像素,减少特征图的分辨率。
  • 填充(Padding):当卷积核在输入矩阵边缘时,可能会丢失边界信息。为了解决这个问题,通常会在输入矩阵的边缘填充0值(零填充),从而保持输出尺寸。
    • 无填充(valid padding):不会在边界填充,卷积后的输出尺寸变小。
    • 零填充(same padding):在输入矩阵的边界加上0,使输出与输入保持相同尺寸。

4. 卷积的优势

  • 参数共享:卷积核在整个输入上滑动,意味着它在不同位置上共享同一组参数。这使得卷积神经网络相比全连接神经网络参数更少,计算更高效
  • 局部连接:卷积操作只考虑输入的局部区域,而不是像全连接网络那样使用所有输入神经元。这种局部连接结构非常适合处理具有空间特性的图像数据

5. 卷积的作用

  • 特征提取:卷积核可以捕捉图像的局部特征,如边缘、角点、纹理等。通过多层卷积,模型可以逐渐从简单的局部特征提取到更复杂的高层次特征,如物体的形状、结构。

示例

假设输入图像为 6×6,卷积核为 3×3,步幅为1且没有填充,卷积操作步骤如下:

  • 卷积核从左上角开始滑动,与输入图像的局部区域进行点乘相加,生成一个特征图的元素。
  • 然后卷积核移动一个像素,重复上述步骤,直到遍历完整个图像,输出的特征图尺寸为 4×4。

卷积运算 v.s 相关运算

1. 相似之处
  • 点乘与求和:两者都涉及将输入数据与一个小窗口(卷积核或相关核)进行逐点的点乘,然后将结果相加,生成一个单一的数值作为输出。
  • 滑动窗口:无论是卷积还是相关操作,都需要在输入数据上滑动窗口,对不同的局部区域执行相同的计算。

这使得在实现层面,它们看起来非常相似,尤其在图像处理或神经网络的应用中,很多时候卷积和相关操作产生的结果也非常接近。

2. 区别
数学定义
  • 卷积:卷积在数学上是一个更为严格的定义,它包含一个翻转(flip)的过程。具体来说,卷积核在与输入数据进行卷积时,会先对卷积核进行水平和垂直方向的翻转,然后再执行点乘和求和操作。对于一个卷积核 K,卷积操作可以表示为:

  • 相关:相关运算(有时也称为“交叉相关”)与卷积的不同之处在于不翻转卷积核。相关操作直接将核与输入数据进行逐点相乘,然后求和,不需要对核进行翻转。其数学表示为: 

实际应用
  • 卷积:卷积在信号处理、数学物理等领域有着深厚的应用背景,特别是当处理线性时不变系统(LTI)时,卷积用来描述系统对输入信号的响应。在这些应用中,卷积操作的翻转特性是至关重要的。
  • 相关:相关更常用于模式匹配特征提取,因为它不涉及卷积核的翻转。在图像处理和神经网络中,很多时候我们需要保留卷积核的原有方向,因此卷积操作常被实现为相关操作
在卷积神经网络中的应用

尽管在理论上卷积操作要求对卷积核进行翻转,但在实践中,卷积神经网络(CNN)中的“卷积”操作通常实现的是相关操作。这主要是因为,翻转卷积核不会改变神经网络的学习能力和效果,而省略这一步骤可以简化计算。

因此,在实际使用CNN时,卷积核并不会翻转,它在模型训练过程中通过梯度下降算法学习最合适的参数。这种实现方式与交叉相关的数学定义更接近。

池化(Pooling)

池化操作是一种降采样技术,用于降低特征图的维度和计算量,同时保留关键的特征信息。池化通过缩小特征图的尺寸,减少模型的参数数量,防止过拟合(《4.4分类》中具体介绍过拟合)

工作原理

池化层对输入的特征图进行降采样,通常以固定大小的窗口(如 2×2)滑动,并将窗口内的值进行汇总,生成一个新的较小的特征图。池化层的操作无权重,它只对输入数据进行简单的数学运算。

常见类型

1. 最大池化(Max Pooling):选择池化窗口中的最大值,保留局部区域的最显著特征。

  • 优点:最大池化能够保留局部的主要特征,忽略不重要的细节。

2. 平均池化(Average Pooling):计算池化窗口内的平均值,降低特征图的整体尺寸。

  • 优点:平均池化能够平滑特征图,降低局部特征的敏感性。

池化过程

  1. 输入:池化层的输入通常是卷积层输出的特征图。
  2. 池化窗口:定义一个固定大小的窗口(如 2×2 或 3×3 ),该窗口将在特征图上滑动。
  3. 步幅(Stride):滑动步幅控制池化窗口在输入特征图上移动的步长。步幅较大会大幅缩小特征图的尺寸。
  4. 输出:每个池化窗口内的值经过最大或平均池化后,生成一个新的特征值。池化操作会将整个特征图降采样生成一个更小的特征图。

数学表示

对于最大池化,假设池化窗口是 2×2,窗口在输入特征图上的每个位置执行以下操作:

池化的作用

  • 降采样:减少特征图的空间维度,降低计算复杂度和参数数量。
  • 位置不变性:池化操作对特征的位置具有一定的鲁棒性,即即使特征位置发生微小变化,池化后的结果仍然能够保持不变
  • 防止过拟合:通过减少特征图的尺寸和复杂度,池化层有助于降低过拟合风险。

示例

假设输入特征图尺寸为 4×4,采用 2×2 的最大池化窗口,步幅为2,则池化后的输出特征图尺寸为 2×2。每个 2×2 区域内的最大值会被选中作为输出。

卷积和池化的结合

  • 卷积层用于提取输入数据的局部特征,并逐层构建更复杂、更抽象的表示。
  • 池化层则通过降采样简化特征图,降低数据维度,同时保持重要的特征。

在实际的CNN架构中,卷积和池化操作通常交替进行,卷积层提取特征,池化层降低维度,直到最后通过全连接层进行分类或回归任务

代码,识别mnist手写数字集

常用搭建逻辑:image输入、卷积层convolution、池化层max pooling、卷积层、池化层、全连接层*2fully connected、分类器classifier。

这段代码展示了搭建一个CNN网络、训练、预测的过程,其中,可视化的部分用到了实时作图和T-SNE(在《4.2降维》中有具体介绍)

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt
EPOCH=1 #cnn的计算时间比较长
BATCH_SIZE=50
LR=0.001
DOWNLOAD_MNIST=False #若后续已经下载过了有了就改成False
train_data=torchvision.datasets.MNIST(root=r"./mnist",
                                      train=True,
                                      transform=torchvision.transforms.ToTensor(),
                                      download=DOWNLOAD_MNIST)#下载mnist dataset,保存至root,transform至tensor的形式(把每一个RGB像素点从(0-255)压缩到(0-1)区间)
#第一张图片的例子
print(train_data.data.size())
print(train_data.targets.size())
plt.imshow(train_data.data[0].numpy(), cmap='gray')
plt.title('%i' % train_data.targets[0])
plt.show()

train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)

test_data = torchvision.datasets.MNIST(root=r"./mnist", train=False)
test_x = torch.unsqueeze(test_data.data, dim=1).type(torch.float32)[:2000] / 255. #手动压缩  [:2000]:在第一个维度上切片,只包含前2000个元素。这对于减少数据集的大小以便更快的测试或训练非常有用。
#unsqueeze(test_data.data, dim=1):在第1维度添加一个额外的维度。这通常是为了匹配神经网络期望的输入形状(例如,将形状为 [N, H, W] 的张量转换为 [N, 1, H, W],以适应灰度图像)。
test_y = test_data.targets[:2000]
class CNN(nn.Module):
    def __init__(self):
        super(CNN,self).__init__()  #Sequential:一个便捷的容器,用于按顺序组合一系列神经网络层。
        self.conv1=nn.Sequential(   #图片一开始是(1,28,28)1:channel,28*28:图片长*宽
            nn.Conv2d(              #卷积层 过滤器 在图片上一次次移动 收集信息,过滤器有长宽高度
                in_channels=1,   #图片的输入 rgb有3层,灰度图片只有1层
                out_channels=16, #filter的个数,即对同一个区域提取16个特征
                kernel_size=5,   #filter的宽和长都是5个像素点,一次扫描5*5
                stride=1,        #filter扫描像素的步长,每次只前进一个像素点
                padding=2,       #if stride=1, padding=(kernal_size-1)/2,在图片的最边缘加一圈0(没实际作用的像素点)
                ),    #->(16,28,28)因为使用了对应的stride和padding的途径,长宽不变
            nn.ReLU(),#->(16,28,28)
            nn.MaxPool2d(kernel_size=2), #可以想象为另一个filter,取当前区域的最大值作为最重要的特征
        )             #->(16,14,14)28->14是因为MaxPool2d(kernel_size=2),相当于在一个2*2的区域只选择一个点即长宽各缩小一半
        self.conv2=nn.Sequential(    #(16,14,14)
            nn.Conv2d(
                in_channels=16,
                out_channels=32,
                kernel_size=5, 
                stride=1,
                padding=2,
                ),     #->(32,14,14)
            nn.ReLU(), #->(32,14,14)
            nn.MaxPool2d(kernel_size=2), #->(32,7,7)
        )
        self.out=nn.Linear(32* 7* 7, 10) #全链接层,只能接受两维,32*7*7:来自展平后的(32,7,7)图片; 10:0-9共10个数字,即10个分类
        ####@@全连接层可以有好几层,只要保证最终输出的cell数量为正确的符合实际的数量(这里为10类)即可
        # self.fc1 = nn.Linear(32 * 7 * 7, 120)
        # self.fc2 = nn.Linear(120, 84)
        # self.fc3 = nn.Linear(84, 10)
    def forward(self,x):
        x=self.conv1(x)
        x=self.conv2(x)  #(batch,32,7,7)
        x=x.view(x.size(0),-1) #展平操作  将四维(batch,32,7,7)展平为二维(batch,32*7*7),以便可以输入到全连接层中
        # view 方法:x.view(new_shape),其中 new_shape 是张量的新形状。“()”即为新的形状,这里很明显为两维,一维是batchsize,二维是"-1"指代的维度。
        # x.size(0) 获取张量 x 的第一个维度的大小,即 batch_size。
        # -1 告诉 PyTorch 自动计算这一维度的大小,以确保总元素数量不变,即第二维度的结果为32*7*7。
        output=self.out(x)
        ####@@除了最终的输出层,其他全链接层都需要激活函数以引入非线性
        # x = F.relu(self.fc1(x))
        # x = F.relu(self.fc2(x))
        # x = self.fc3(x)
        return output
cnn=CNN()
print(cnn)
optimizer=torch.optim.Adam(cnn.parameters(),lr=LR)
loss_func=torch.nn.CrossEntropyLoss() #回归问题用均方差(MSE),多分类问题用交叉熵(这种损失函数计算的是softmax,即每一类别的概率(概率和为1))

# following function (plot_with_labels) is for visualization, can be ignored if not interested
from matplotlib import cm
try: from sklearn.manifold import TSNE; HAS_SK = True
except: HAS_SK = False; print('Please install sklearn for layer visualization')
def plot_with_labels(lowDWeights, labels):
    plt.cla()
    X, Y = lowDWeights[:, 0], lowDWeights[:, 1]
    for x, y, s in zip(X, Y, labels):
        c = cm.rainbow(int(255 * s / 9)); plt.text(x, y, s, backgroundcolor=c, fontsize=9)
    plt.xlim(X.min(), X.max()); plt.ylim(Y.min(), Y.max()); plt.title('Visualize last layer'); plt.show(); plt.pause(0.01)

plt.ion()

for epoch in range(EPOCH):
    for step,(x,y) in enumerate(train_loader):
        out=cnn(x)
        loss=loss_func(out,y)
        optimizer.zero_grad() #优化步骤1:将所有参数的梯度降为0
        loss.backward()       #优化步骤2:当前次的反向传递
        optimizer.step()      #优化步骤3:以lr优化梯度
        if step % 50 == 0:
            test_output= cnn(test_x)
            pred_y = torch.max(test_output, 1)[1].data.numpy()
            accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0)) #astype(int):将布尔数组转换为整数数组; test_y.size(0) 返回测试数据的总样本数。
            print('Epoch: ', epoch, '| step: ', step, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy)
            if HAS_SK:
                # Visualization of trained flatten layer (T-SNE)
                tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
                plot_only = 500
                low_dim_embs = tsne.fit_transform(test_output.data.numpy()[:plot_only, :])
                labels = test_y.numpy()[:plot_only]
                plot_with_labels(low_dim_embs, labels)
# print 10 predictions from test data
test_output = cnn(test_x[:10])
pred_y = torch.max(test_output, 1)[1].data.numpy() #通过使用 [1],我们选择最大值的索引,这些索引对应于预测的类别标签。
print(pred_y, 'prediction number')
print(test_y[:10].numpy(), 'real number')

输出: 

torch.Size([60000, 28, 28])
torch.Size([60000])
CNN(
  (conv1): Sequential(
    (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (conv2): Sequential(
    (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))        
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (out): Linear(in_features=1568, out_features=10, bias=True)
)
Epoch:  0 | step:  0 | train loss: 2.3062 | test accuracy: 0.09
Epoch:  0 | step:  50 | train loss: 0.4463 | test accuracy: 0.82
Epoch:  0 | step:  100 | train loss: 0.3839 | test accuracy: 0.89
Epoch:  0 | step:  150 | train loss: 0.2534 | test accuracy: 0.92
Epoch:  0 | step:  200 | train loss: 0.1585 | test accuracy: 0.93
Epoch:  0 | step:  250 | train loss: 0.0392 | test accuracy: 0.94
Epoch:  0 | step:  300 | train loss: 0.2464 | test accuracy: 0.94
Epoch:  0 | step:  350 | train loss: 0.0382 | test accuracy: 0.95
Epoch:  0 | step:  400 | train loss: 0.1939 | test accuracy: 0.96
Epoch:  0 | step:  450 | train loss: 0.0425 | test accuracy: 0.97
Epoch:  0 | step:  500 | train loss: 0.0396 | test accuracy: 0.95
Epoch:  0 | step:  550 | train loss: 0.0545 | test accuracy: 0.96
Epoch:  0 | step:  600 | train loss: 0.0786 | test accuracy: 0.96
Epoch:  0 | step:  650 | train loss: 0.1965 | test accuracy: 0.97
Epoch:  0 | step:  700 | train loss: 0.1050 | test accuracy: 0.97
Epoch:  0 | step:  750 | train loss: 0.0403 | test accuracy: 0.97
Epoch:  0 | step:  800 | train loss: 0.2255 | test accuracy: 0.97
Epoch:  0 | step:  850 | train loss: 0.0605 | test accuracy: 0.97
Epoch:  0 | step:  900 | train loss: 0.0871 | test accuracy: 0.98
Epoch:  0 | step:  950 | train loss: 0.0558 | test accuracy: 0.98
Epoch:  0 | step:  1000 | train loss: 0.0296 | test accuracy: 0.97
Epoch:  0 | step:  1050 | train loss: 0.0344 | test accuracy: 0.97

[7 2 1 0 4 1 4 9 5 9] prediction number
[7 2 1 0 4 1 4 9 5 9] real number

 

可以看到随着训练不断进行,准确率不断提高后趋于稳定 ,从输出层的可视化分布中也能看出不同的数字被逐渐划分到了不同的区域,也不乏有类似与“4”和“9”这样机器难以辨认的数字,体现在图中的空间距离(《4.1聚类》中有具体介绍)始终很近,或许数据增强技术(《3.数据预处理》中有具体介绍)能带来改善,但这也是未来一段时间内的前沿研究方向。

循环神经网络(Recurrent Neural Network, RNN)

结构与工作原理

RNN的关键在于序列信息的处理,它与传统的前馈神经网络不同,能够通过时间步之间的反馈(在《6.1基础概念》中有具体介绍)机制存储并使用前序信息。

  1. 输入层:输入的序列数据可以是文本、时间序列、音频等形式的数据。

  2. 隐藏层(Hidden Layer):每个时间步都有一个隐藏状态 h_t,它由当前输入和前一时间步的隐藏状态共同决定:

    这种递归结构允许RNN保存前面的信息并用于未来的计算,实现了对序列数据的建模

  3. 输出层:RNN可以在每个时间步输出结果(适用于逐步预测),或在最后一个时间步输出一个整体结果(适用于整个序列的分类或预测)。

特点与不足

  • 时间依赖性:RNN通过隐藏状态的递归结构捕捉序列中前后信息的依赖关系,适合处理时间序列、自然语言等数据。
  • 梯度消失与梯度爆炸:RNN在长时间序列上容易发生梯度消失或爆炸(《6.1基础概念》中有具体介绍),导致模型无法有效捕捉长程依赖。

应用场景

RNN主要应用于以下序列数据任务:

  • 语言模型(如文本生成、机器翻译)
  • 时间序列预测
  • 语音识别
  • 音乐生成

 代码

这段代码展示rnn通过学习正弦波的模式,来预测同一时间步上的余弦波值。这可以看作是一个时间序列回归问题。

import torch
from torch import nn
import numpy as np
import matplotlib.pyplot as plt
# torch.manual_seed(1)    # reproducible
# Hyper Parameters
TIME_STEP = 10      # rnn time step
INPUT_SIZE = 1      # rnn input size
LR = 0.02           # learning rate
# show data
steps = np.linspace(0, np.pi*2, 100, dtype=np.float32)  # float32 for converting torch FloatTensor
x_np = np.sin(steps)
y_np = np.cos(steps)
plt.plot(steps, y_np, 'r-', label='target (cos)')
plt.plot(steps, x_np, 'b-', label='input (sin)')
plt.legend(loc='best')
plt.show()
class RNN(nn.Module):
    def __init__(self):
        super(RNN, self).__init__()
        self.rnn = nn.RNN( #与上一节不同的继承模型
            input_size=INPUT_SIZE, #input是1因为用一个x预测一个x'
            hidden_size=32,        # rnn hidden unit
            num_layers=1,          # number of rnn layer
            batch_first=True,      # input & output will has batch size as 1s dimension. e.g. (batch, time_step, input_size)
        )
        self.out = nn.Linear(32, 1)#所以输出也是1个特征,32是因为这个全连接层连接到的hiddensize为32

    def forward(self, x, h_state):
        # x (batch, time_step, input_size)
        # h_state (n_layers, batch, hidden_size)
        # r_out (batch, time_step, hidden_size)
        r_out, h_state = self.rnn(x, h_state) #hidden_state(相当于记忆,在预测当前input的时候考虑之前预测的方式,可理解为相当马尔可夫) 因为上一个分类rnn模型用的全是0,所以不需要传递记忆

        outs = []    # save all predictions
        for time_step in range(r_out.size(1)):  # calculate output for each time step
            outs.append(self.out(r_out[:, time_step, :]))
        return torch.stack(outs, dim=1), h_state  #因为out在上一步是list的形式,所以要把它变为tensor的形式,并把h_state的值返回,传递给下一次预测回归

        # instead, for simplicity, you can replace above codes by follows
        # r_out = r_out.view(-1, 32)
        # outs = self.out(r_out)
        # outs = outs.view(-1, TIME_STEP, 1)
        # return outs, h_state
        
        # or even simpler, since nn.Linear can accept inputs of any dimension 
        # and returns outputs with same dimension except for the last
        # outs = self.out(r_out)
        # return outs
rnn = RNN()
print(rnn)

optimizer = torch.optim.Adam(rnn.parameters(), lr=LR)   # optimize all cnn parameters
loss_func = nn.MSELoss()

h_state = None      # for initial hidden state, 自动生成一个全0的hiddenstate
plt.figure(1, figsize=(12, 5))
plt.ion()           # continuously plot

for step in range(100):
    start, end = step * np.pi, (step+1)*np.pi   # time range
    # use sin predicts cos
    steps = np.linspace(start, end, TIME_STEP, dtype=np.float32, endpoint=False)  # float32 for converting torch FloatTensor
    x_np = np.sin(steps)
    y_np = np.cos(steps)
    x = torch.from_numpy(x_np[np.newaxis, :, np.newaxis])# shape(batch(多加一个维度), time_step, input_size(原先为1,也多加一个维度))
    y = torch.from_numpy(y_np[np.newaxis, :, np.newaxis])
    prediction, h_state = rnn(x, h_state)   # rnn output
    # !! next step is important !!
    h_state = h_state.data        # repack the hidden state, break the connection from last iteration(比如初始化的None)
    loss = loss_func(prediction, y)         # calculate loss
    optimizer.zero_grad()                   # clear gradients for this training step
    loss.backward()                         # backpropagation, compute gradients
    optimizer.step()                        # apply gradients
    # plotting
    plt.plot(steps, y_np.flatten(), 'r-')
    plt.plot(steps, prediction.data.numpy().flatten(), 'b-')
    plt.draw(); plt.pause(0.05)
plt.ioff()
plt.show()

输出:

RNN(
  (rnn): RNN(1, 32, batch_first=True)
  (out): Linear(in_features=32, out_features=1, bias=True)
)

这张图其实是动图,能详细的展示RNN的回归过程。

长短期记忆网络(Long Short-Term Memory, LSTM)

LSTMRNN的改进版本,旨在解决RNN的梯度消失问题。LSTM通过引入门机制来更好地处理长时间依赖。

  • 遗忘门(Forget Gate):决定遗忘哪些信息。
  • 输入门(Input Gate):决定在当前时间步添加哪些新信息。
  • 输出门(Output Gate):决定输出哪些信息。

LSTM能够保留重要的长期信息并丢弃无关的信息,因此在处理长序列任务(如语言模型、时间序列预测)时表现更优。

代码

与rnn同样的任务,便于理解。

  • 使用了 nn.LSTM 代替了 nn.RNN,并处理了 LSTM 的双隐状态(hidden state 和 cell state)。
  • h_state 的更新过程中使用 tuple([each.data for each in h_state]) 来分离LSTM的隐状态,以断开与前一批次的梯度连接。
import torch
from torch import nn
import numpy as np
import matplotlib.pyplot as plt

# Hyper Parameters
TIME_STEP = 10      # lstm time step
INPUT_SIZE = 1      # lstm input size
LR = 0.02           # learning rate

# show data
steps = np.linspace(0, np.pi*2, 100, dtype=np.float32)  # float32 for converting torch FloatTensor
x_np = np.sin(steps)
y_np = np.cos(steps)
plt.plot(steps, y_np, 'r-', label='target (cos)')
plt.plot(steps, x_np, 'b-', label='input (sin)')
plt.legend(loc='best')
plt.show()

# LSTM Model
class LSTM(nn.Module):
    def __init__(self):
        super(LSTM, self).__init__()
        self.lstm = nn.LSTM( 
            input_size=INPUT_SIZE, # input 是1,因为用一个 x 预测一个 y
            hidden_size=32,        # LSTM hidden unit
            num_layers=1,          # LSTM layer数量
            batch_first=True,      # input & output 以batch size作为第一维度,形如 (batch, time_step, input_size)
        )
        self.out = nn.Linear(32, 1)  # 全连接层,将 hidden_size 映射为输出的特征数量(1)

    def forward(self, x, h_state):
        # x (batch, time_step, input_size)
        # h_state: (n_layers, batch, hidden_size) for both hidden state and cell state in LSTM
        r_out, h_state = self.lstm(x, h_state)  # LSTM 有 hidden state 和 cell state 两部分
        outs = []  # 保存所有时间步的输出
        for time_step in range(r_out.size(1)):  # 逐时间步计算输出
            outs.append(self.out(r_out[:, time_step, :]))
        return torch.stack(outs, dim=1), h_state  # 将list转换为 tensor,并返回 h_state

# 实例化 LSTM 模型
lstm = LSTM()
print(lstm)

optimizer = torch.optim.Adam(lstm.parameters(), lr=LR)   # 优化器
loss_func = nn.MSELoss()  # 损失函数

h_state = None      # 初始化hidden state,LSTM有两个 hidden states
plt.figure(1, figsize=(12, 5))
plt.ion()  # 动态绘图

for step in range(100):
    start, end = step * np.pi, (step + 1) * np.pi   # 时间范围
    # 使用 sin 预测 cos
    steps = np.linspace(start, end, TIME_STEP, dtype=np.float32, endpoint=False)
    x_np = np.sin(steps)
    y_np = np.cos(steps)
    x = torch.from_numpy(x_np[np.newaxis, :, np.newaxis])  # shape: (batch, time_step, input_size)
    y = torch.from_numpy(y_np[np.newaxis, :, np.newaxis])

    prediction, h_state = lstm(x, h_state)   # LSTM输出
    # !! 关键步骤 !!
    h_state = tuple([each.data for each in h_state])  # 分离hidden state 和 cell state,使其与上一次迭代断开连接
    loss = loss_func(prediction, y)  # 计算损失
    optimizer.zero_grad()  # 清除梯度
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数

    # 绘图
    plt.plot(steps, y_np.flatten(), 'r-')
    plt.plot(steps, prediction.data.numpy().flatten(), 'b-')
    plt.draw(); plt.pause(0.05)

plt.ioff()
plt.show()

输出:

LSTM(
  (lstm): LSTM(1, 32, batch_first=True)
  (out): Linear(in_features=32, out_features=1, bias=True)
)

编码器-解码器(Encoder-Decoder) 

是一种常用于处理序列数据的神经网络架构。它主要应用在需要将一个序列映射到另一个序列的任务中,例如机器翻译、文本摘要、对话生成等。这个架构最初是为解决序列到序列(Seq2Seq)问题而提出的,特别适合输入和输出序列长度不相等的场景。

结构与工作原理

编码器(Encoder)解码器(Decoder)是这个架构的核心组件,通常使用循环神经网络(RNN)、长短时记忆网络(LSTM)或门控循环单元(GRU)作为基本单元。它们的结构可以简化为以下两个阶段:

1.1 编码器阶段

编码器负责将输入序列压缩为一个固定长度的向量,称为上下文向量(Context Vector)隐状态(Hidden State),这一过程涉及到捕获输入序列的时间和上下文信息。

  • 输入序列:一个长度为 T_x​ 的序列 X = [x_1, x_2, ..., x_{T_x}]
  • 编码器将每个输入通过RNN(如LSTM或GRU),逐步处理输入数据,并将其隐状态传递到下一个时间步
  • 最后一个时间步的隐状态 h_T​ 是输入序列的压缩表示,也就是上下文向量。

1.2 解码器阶段

解码器负责将编码器输出的上下文向量转化为目标序列。解码器在每个时间步会生成一个输出,并将其输入到下一个时间步中。

  • 解码器初始化时,接收编码器传递过来的上下文向量和可能的初始输入(通常是一个“开始”标志)。
  • 解码器逐步生成输出序列,每个时间步的输出会依赖于上一个时间步的输出和解码器的隐状态

解码器通过最大化生成的序列与目标序列之间的概率来进行学习

工作流程

  1. 编码器处理输入序列并生成上下文向量。
  2. 解码器使用上下文向量生成输出序列,每一步都会根据上一步的输出调整自身状态。

特点与优势

特点

  • 灵活性:可以处理输入和输出长度不相等的任务。传统RNN可能会受限于输入和输出序列长度相同的要求,而编码器-解码器结构通过将输入序列压缩为一个固定维度的上下文向量,可以适应不同长度的输出序列。
  • 信息压缩:编码器将整个输入序列的信息压缩为一个固定大小的上下文向量,这个向量携带了输入序列的所有关键信息。
  • 解码器的依赖性:解码器在每个时间步的预测依赖于前面时间步的预测和隐状态,这使得生成的序列具有一定的连贯性。

优势

  • 处理可变长度的序列:由于编码器和解码器的独立性,这种结构适用于输入和输出长度不同的序列,解决了传统模型处理可变长度序列的困难。
  • 广泛的适应性:编码器-解码器可以灵活应用于各种类型的序列任务,包括机器翻译、对话生成、文本摘要等。
  • 上下文理解能力:通过编码器,模型能够将输入序列的上下文信息提取出来,使得解码器在生成输出时能够依赖完整的上下文。

应用场景

机器翻译

编码器-解码器模型最初是为了机器翻译任务提出的。在机器翻译中,输入序列是源语言的句子,输出序列是目标语言的翻译。编码器将源语言句子编码成一个上下文向量,解码器将该向量转换为目标语言的翻译。

文本摘要

在文本摘要任务中,输入是原文,输出是该文本的简短总结。编码器负责将原文的内容编码成上下文向量,解码器则基于该向量生成简短的总结。

图像描述生成

编码器-解码器模型也被应用于图像描述生成。编码器可以是一个卷积神经网络(CNN),用于从图像中提取特征,解码器则是RNN(如LSTM或GRU),负责根据图像特征生成文本描述。

对话系统

在对话生成任务中,输入序列是用户的提问或话语,解码器负责生成合适的回复。编码器捕获上下文信息,解码器基于该信息生成连贯的对话。

语音识别

在语音识别中,输入序列是语音特征,解码器负责将这些特征转换为文字。编码器可以从语音信号中提取关键特征,解码器生成对应的文本序列。

改进:Attention机制

经典的编码器-解码器模型虽然在许多任务中表现出色,但由于上下文向量是固定长度的,当输入序列过长时,可能无法捕获足够的细节信息。Attention机制应运而生,作为对编码器-解码器架构的改进,Attention允许解码器在生成每个输出时可以动态地关注输入序列的不同部分,而不是仅依赖一个固定的上下文向量。这极大提高了长序列任务的表现,如长文翻译、长对话生成等。

这不是这篇博客的主要内容,但是attention机制对人工智能的发展极为重要,transformer架构便建立在attention机制上,具体可以品读这篇论文:《Attention is all you need》,b站上李沐老师的解读视频很精彩。

基本原理

它的核心思想是:让模型在每个时间步关注输入序列的不同部分,而不是只依赖一个固定的上下文向量(如编码器-解码器模型中的上下文向量)。在生成输出的每一步时,Attention机制会为输入序列的每个元素分配一个权重,表示该输入元素对当前输出的重要性。然后,模型根据这些权重从输入序列中提取信息,生成输出。

工作步骤

  1. 计算注意力权重:根据当前输出的状态与输入序列的每个元素计算相关性,分配权重。
  2. 加权求和:用这些权重对输入序列进行加权平均,得到一个新的上下文向量。
  3. 生成输出:上下文向量帮助模型生成当前输出。

代码

该模型接受一个输入序列,并试图输出一个相同的序列,模拟一个简单的“回声”功能。

import torch
import torch.nn as nn
import torch.optim as optim

# 定义超参数
INPUT_SIZE = 10     # 输入序列每个时间步的特征数
HIDDEN_SIZE = 16    # RNN隐藏层大小
OUTPUT_SIZE = 10    # 输出序列每个时间步的特征数
NUM_LAYERS = 1      # RNN层数
TIME_STEP = 5       # 序列的长度
LR = 0.01           # 学习率
EPOCHS = 100        # 训练轮数

# 编码器模型
class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        self.rnn = nn.RNN(INPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, batch_first=True)
    
    def forward(self, x):
        # x: (batch, time_step, input_size)
        out, hidden = self.rnn(x)  # hidden: (num_layers, batch, hidden_size)
        return out, hidden

# 解码器模型
class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.rnn = nn.RNN(HIDDEN_SIZE, HIDDEN_SIZE, NUM_LAYERS, batch_first=True)
        self.fc = nn.Linear(HIDDEN_SIZE, OUTPUT_SIZE)
    
    def forward(self, x, hidden):
        # x: (batch, time_step, hidden_size)
        out, hidden = self.rnn(x, hidden)  # 使用编码器的隐藏状态
        out = self.fc(out)  # 输出经过全连接层进行转换
        return out, hidden

# 定义Encoder-Decoder模型
class Seq2Seq(nn.Module):
    def __init__(self):
        super(Seq2Seq, self).__init__()
        self.encoder = Encoder()
        self.decoder = Decoder()

    def forward(self, x):
        encoder_output, hidden = self.encoder(x)
        decoder_output, _ = self.decoder(encoder_output, hidden)
        return decoder_output

# 初始化模型、损失函数和优化器
model = Seq2Seq()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=LR)

# 生成一些模拟数据
input_seq = torch.randn(1, TIME_STEP, INPUT_SIZE)  # 输入序列 (batch, time_step, input_size)
target_seq = torch.randn(1, TIME_STEP, OUTPUT_SIZE)  # 目标序列 (batch, time_step, output_size)

# 训练模型
for epoch in range(EPOCHS):
    output = model(input_seq)
    loss = criterion(output, target_seq)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{EPOCHS}], Loss: {loss.item():.4f}')

# 测试模型
with torch.no_grad():
    test_output = model(input_seq)
    print("Test output:", test_output)

输出:

Epoch [10/100], Loss: 0.1605
Epoch [20/100], Loss: 0.0201
Epoch [30/100], Loss: 0.0127
Epoch [40/100], Loss: 0.0038
Epoch [50/100], Loss: 0.0010
Epoch [60/100], Loss: 0.0006
Epoch [70/100], Loss: 0.0002
Epoch [80/100], Loss: 0.0001
Epoch [90/100], Loss: 0.0000
Epoch [100/100], Loss: 0.0000
Test output: tensor([[[-0.8829, -1.0463,  0.8059, -0.1008, -1.1938, -0.0027, -0.0088,
          -0.4321, -0.3705,  0.0737],
         [ 0.3589,  0.8391, -0.4151,  0.7814,  0.1077, -1.8705,  1.4006,
           0.9975, -0.0879,  0.5289],
         [ 0.2580, -0.7881,  1.6762, -0.4722,  0.1414,  0.2241,  0.7449,
           1.6271, -0.5808, -1.7505],
         [ 0.2750,  0.0037, -1.1034, -0.4922,  0.3541, -0.3718, -1.2127,
           0.6665, -1.3511, -0.3671],
         [-0.3195, -1.8411, -1.6648,  1.6444,  1.4295, -1.5667, -0.0936,
          -1.3089,  1.0419,  0.7502]]])

自编码器(Autoencoder)

是一种用于无监督学习(在《2.ai模型基础概念》中具体介绍过)的神经网络模型,目的是让输出尽可能地重建输入,通常用于特征提取、数据压缩、去噪等任务。自编码器由两个部分组成:编码器(Encoder)解码器(Decoder)

结构与工作原理

自编码器模型结构

  1. 编码器(Encoder)

    • 编码器部分负责将高维的输入数据映射到一个低维的隐含空间(也叫潜在空间潜在表示)。
    • 这部分的工作类似于降维(详可见《4.2降维》,目的是提取输入数据的关键特征,去除冗余信息。
    • 通常使用全连接层(也可以用卷积层等)逐渐缩小输入数据的维度
  2. 解码器(Decoder)

    • 解码器的作用是从编码器生成的低维表示中重建原始输入数据。
    • 解码器通常是编码器的对称结构,即它将数据从低维逐渐还原到原始的高维空间。

自编码器的工作流程

  1. 输入数据经过编码器的多层网络,压缩到一个隐含表示(通常是低维的)。
  2. 隐含表示经过解码器的多层网络,尝试重建出与输入数据尽可能接近的输出数据。
  3. 优化目标是通过最小化输入数据与重建数据之间的差异(如使用均方误差损失函数),使模型学会如何有效地表示数据。

种类

  • 标准自编码器:最基本的自编码器,编码器将输入压缩成低维表示,解码器将其还原成高维数据。
  • 去噪自编码器(Denoising Autoencoder, DAE):在训练过程中加入噪声,模型学习从噪声数据中重建干净的数据,常用于去噪任务。
  • 稀疏自编码器(Sparse Autoencoder):通过引入稀疏性正则化(详可见《5.正则化技术》),强制隐含表示中的某些单元大部分时间不激活,能够发现数据的稀疏特征。
  • 变分自编码器(Variational Autoencoder, VAE):它是生成模型的一种,通过在隐含层引入概率分布,生成新的数据样本,常用于图像生成等任务。

特点

  1. 无监督学习:自编码器不需要标注数据,只需要输入数据本身作为训练样本,非常适合特征学习或降维。
  2. 特征提取和数据压缩:通过编码器的压缩作用,自编码器可以从高维数据中提取出低维、有意义的特征,类似于主成分分析(PCA),但能够捕获非线性关系。
  3. 自适应重建能力:自编码器能够学习到数据的内在结构,并在解码时根据这些结构重建数据。

应用场景

  • 数据降维:自编码器可以作为一种非线性降维工具,将高维数据压缩成低维表示(比PCA更灵活),例如图像的特征提取。
  • 去噪:去噪自编码器可以用于去除数据中的噪声,常用于图像和语音的去噪任务。
  • 异常检测:由于自编码器在正常数据上表现良好,训练后的自编码器在异常数据上无法有效重建,从而可以用来检测异常点。
  • 生成模型:变分自编码器(VAE)等可以生成新的数据样本,常用于图像生成。

局限性

  • 对训练数据依赖较强:自编码器只能学习它所见过的训练数据类型的特征,难以很好地处理与训练数据差异较大的样本。
  • 重建能力有限:对于复杂任务或高维度数据,自编码器可能需要非常复杂的网络结构才能有效重建输入。
  • 不适用于序列数据:传统自编码器更适用于固定长度的数据,对于变长数据(如自然语言或时间序列),需要引入RNN、LSTM等机制。

代码

这段代码实现了对MNIST数据集图像的降维和重建。具体来说,这个自编码器模型的编码器将28x28的图像逐步压缩为3维的潜在空间表示(方便可视化),解码器则尝试从3维空间重建出原始图像。

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import numpy as np
# torch.manual_seed(1)    # reproducible
EPOCH = 10
BATCH_SIZE = 64
LR = 0.005        
DOWNLOAD_MNIST = False
N_TEST_IMG = 5
train_data=torchvision.datasets.MNIST(root=r"./mnist",
                                      train=True,
                                      transform=torchvision.transforms.ToTensor(),
                                      download=DOWNLOAD_MNIST)
# plot one example
print(train_data.data.size())     # (60000, 28, 28)
print(train_data.targets.size())   # (60000)
plt.imshow(train_data.data[2].numpy(), cmap='gray')
plt.title('%i' % train_data.targets[2])
plt.show()
# Data Loader for easy mini-batch return in training, the image batch shape will be (50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(28*28, 128), #28行28列,放入128的隐藏层
            nn.Tanh(), #激活
            nn.Linear(128, 64), #把128压缩为64
            nn.Tanh(),
            nn.Linear(64, 12),  #怕损失太多关键信息,所以可以多几层,多用激活函数,增加非线性
            nn.Tanh(),
            nn.Linear(12, 3),   # compress to 3 features which can be visualized in plt
        )
        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, 28*28),
            nn.Sigmoid(),       # 因为train_data的范围在(0, 1),compress to a range (0, 1)才能当做合适的output
        )
    def forward(self, x):
        encoded = self.encoder(x)
        decoded = self.decoder(encoded)
        return encoded, decoded

autoencoder = AutoEncoder()
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)
loss_func = nn.MSELoss()

# initialize figure
f, a = plt.subplots(2, N_TEST_IMG, figsize=(5, 2))
plt.ion()   # continuously plot
# original data (first row) for viewing
view_data = train_data.data[:N_TEST_IMG].view(-1, 28*28).type(torch.FloatTensor)/255.
for i in range(N_TEST_IMG):
    a[0][i].imshow(np.reshape(view_data.data.numpy()[i], (28, 28)), cmap='gray'); a[0][i].set_xticks(()); a[0][i].set_yticks(())

for epoch in range(EPOCH):
    for step, (x, b_label) in enumerate(train_loader):
        b_x = x.view(-1, 28*28)   # batch x,图片数据, shape (batch, 28*28)
        b_y = x.view(-1, 28*28)   # batch y, shape (batch, 28*28) 因为autoencoder是非监督,所以其实用不到batch_y,但pytorch结构规定了一定要有label,即y,即比较解码后与编码前的自身的loss
        encoded, decoded = autoencoder(b_x)

        loss = loss_func(decoded, b_y)      # mean square error对比解压缩的数据和y(与原图片的差别),decoded对应之前网络中out
        optimizer.zero_grad()               # clear gradients for this training step
        loss.backward()                     # backpropagation, compute gradients
        optimizer.step()                    # apply gradients

        if step % 100 == 0:
            print('Epoch: ', epoch, '|step: ', step, '| train loss: %.4f' % loss.data.numpy())

            # plotting decoded image (second row)
            _, decoded_data = autoencoder(view_data)
            for i in range(N_TEST_IMG):
                a[1][i].clear()
                a[1][i].imshow(np.reshape(decoded_data.data.numpy()[i], (28, 28)), cmap='gray')
                a[1][i].set_xticks(()); a[1][i].set_yticks(())
            plt.draw(); plt.pause(0.05)

plt.ioff()
plt.show()

# visualize in 3D plot
view_data = train_data.data[:200].view(-1, 28*28).type(torch.FloatTensor)/255.
encoded_data, _ = autoencoder(view_data)
fig = plt.figure(2); ax = Axes3D(fig)
X, Y, Z = encoded_data.data[:, 0].numpy(), encoded_data.data[:, 1].numpy(), encoded_data.data[:, 2].numpy()
values = train_data.targets[:200].numpy()
for x, y, z, s in zip(X, Y, Z, values):
    c = cm.rainbow(int(255*s/9)); ax.text(x, y, z, s, backgroundcolor=c)
ax.set_xlim(X.min(), X.max()); ax.set_ylim(Y.min(), Y.max()); ax.set_zlim(Z.min(), Z.max())
plt.show()
  • 加载MNIST数据集train_data包含了MNIST的训练集,输入为28x28的灰度图像,标签为对应的数字(0-9)。

  • 自编码器(AutoEncoder)结构

    • 编码器部分逐步将28x28=784维的数据压缩为3维:
      • 784 → 128 → 64 → 12 → 3
    • 解码器部分则逆向操作,从3维的数据恢复到原始的784维:
      • 3 → 12 → 64 → 128 → 784
  • 损失函数MSELoss用于计算重建图像和输入图像之间的均方误差。

  • 训练过程

    • 训练过程中,网络会不断调整参数,以最小化解码器重建图像与原始图像之间的误差。
    • 每训练100步,输出训练损失并绘制解码后的图像以便可视化。
  • 3D可视化

    • 在最后,代码将200张图片的编码结果可视化为3D点图。编码后的三维空间可以通过颜色区分不同的数字(0-9),展示不同类别的数据在压缩空间中的分布情况。

输出:

torch.Size([60000, 28, 28])
torch.Size([60000])
Epoch:  0 |step:  0 | train loss: 0.2333
Epoch:  0 |step:  100 | train loss: 0.0628
Epoch:  0 |step:  200 | train loss: 0.0611
Epoch:  0 |step:  300 | train loss: 0.0603
Epoch:  0 |step:  400 | train loss: 0.0530
Epoch:  0 |step:  500 | train loss: 0.0503
Epoch:  0 |step:  600 | train loss: 0.0496
Epoch:  0 |step:  700 | train loss: 0.0487
Epoch:  0 |step:  800 | train loss: 0.0443
Epoch:  0 |step:  900 | train loss: 0.0411
Epoch:  1 |step:  0 | train loss: 0.0426
Epoch:  1 |step:  100 | train loss: 0.0434
Epoch:  1 |step:  200 | train loss: 0.0436
Epoch:  1 |step:  300 | train loss: 0.0405
Epoch:  1 |step:  400 | train loss: 0.0423
Epoch:  1 |step:  500 | train loss: 0.0391
Epoch:  1 |step:  600 | train loss: 0.0403
Epoch:  1 |step:  700 | train loss: 0.0383
Epoch:  1 |step:  800 | train loss: 0.0335
Epoch:  1 |step:  900 | train loss: 0.0369
Epoch:  2 |step:  0 | train loss: 0.0350
Epoch:  2 |step:  100 | train loss: 0.0407
Epoch:  2 |step:  200 | train loss: 0.0374
Epoch:  2 |step:  300 | train loss: 0.0369
Epoch:  2 |step:  400 | train loss: 0.0387
Epoch:  2 |step:  500 | train loss: 0.0368
Epoch:  2 |step:  600 | train loss: 0.0412
Epoch:  2 |step:  700 | train loss: 0.0347
Epoch:  2 |step:  800 | train loss: 0.0358
Epoch:  2 |step:  900 | train loss: 0.0371
Epoch:  3 |step:  0 | train loss: 0.0385
Epoch:  3 |step:  100 | train loss: 0.0328
Epoch:  3 |step:  200 | train loss: 0.0355
Epoch:  3 |step:  300 | train loss: 0.0333
Epoch:  3 |step:  400 | train loss: 0.0355
Epoch:  3 |step:  500 | train loss: 0.0337
Epoch:  3 |step:  600 | train loss: 0.0313
Epoch:  3 |step:  700 | train loss: 0.0380
Epoch:  3 |step:  800 | train loss: 0.0362
Epoch:  3 |step:  900 | train loss: 0.0320
Epoch:  4 |step:  0 | train loss: 0.0359
Epoch:  4 |step:  100 | train loss: 0.0346
Epoch:  4 |step:  200 | train loss: 0.0383
Epoch:  4 |step:  300 | train loss: 0.0345
Epoch:  4 |step:  400 | train loss: 0.0359
Epoch:  4 |step:  500 | train loss: 0.0317
Epoch:  4 |step:  600 | train loss: 0.0391
Epoch:  4 |step:  700 | train loss: 0.0385
Epoch:  4 |step:  800 | train loss: 0.0379
Epoch:  4 |step:  900 | train loss: 0.0342
Epoch:  5 |step:  0 | train loss: 0.0386
Epoch:  5 |step:  100 | train loss: 0.0367
Epoch:  5 |step:  200 | train loss: 0.0358
Epoch:  5 |step:  300 | train loss: 0.0370
Epoch:  5 |step:  400 | train loss: 0.0352
Epoch:  5 |step:  500 | train loss: 0.0346
Epoch:  5 |step:  600 | train loss: 0.0332
Epoch:  5 |step:  700 | train loss: 0.0351
Epoch:  5 |step:  800 | train loss: 0.0351
Epoch:  5 |step:  900 | train loss: 0.0328
Epoch:  6 |step:  0 | train loss: 0.0355
Epoch:  6 |step:  100 | train loss: 0.0358
Epoch:  6 |step:  200 | train loss: 0.0349
Epoch:  6 |step:  300 | train loss: 0.0370
Epoch:  6 |step:  400 | train loss: 0.0335
Epoch:  6 |step:  500 | train loss: 0.0324
Epoch:  6 |step:  600 | train loss: 0.0374
Epoch:  6 |step:  700 | train loss: 0.0325
Epoch:  6 |step:  800 | train loss: 0.0340
Epoch:  6 |step:  900 | train loss: 0.0354
Epoch:  7 |step:  0 | train loss: 0.0337
Epoch:  7 |step:  100 | train loss: 0.0355
Epoch:  7 |step:  200 | train loss: 0.0358
Epoch:  7 |step:  300 | train loss: 0.0345
Epoch:  7 |step:  400 | train loss: 0.0378
Epoch:  7 |step:  500 | train loss: 0.0364
Epoch:  7 |step:  600 | train loss: 0.0309
Epoch:  7 |step:  700 | train loss: 0.0331
Epoch:  7 |step:  800 | train loss: 0.0384
Epoch:  7 |step:  900 | train loss: 0.0355
Epoch:  8 |step:  0 | train loss: 0.0336
Epoch:  8 |step:  100 | train loss: 0.0357
Epoch:  8 |step:  200 | train loss: 0.0360
Epoch:  8 |step:  300 | train loss: 0.0325
Epoch:  8 |step:  400 | train loss: 0.0343
Epoch:  8 |step:  500 | train loss: 0.0348
Epoch:  8 |step:  600 | train loss: 0.0366
Epoch:  8 |step:  700 | train loss: 0.0350
Epoch:  8 |step:  800 | train loss: 0.0347
Epoch:  8 |step:  900 | train loss: 0.0346
Epoch:  9 |step:  0 | train loss: 0.0322
Epoch:  9 |step:  100 | train loss: 0.0348
Epoch:  9 |step:  200 | train loss: 0.0341
Epoch:  9 |step:  300 | train loss: 0.0334
Epoch:  9 |step:  400 | train loss: 0.0340
Epoch:  9 |step:  500 | train loss: 0.0332
Epoch:  9 |step:  600 | train loss: 0.0340
Epoch:  9 |step:  700 | train loss: 0.0350
Epoch:  9 |step:  800 | train loss: 0.0345
Epoch:  9 |step:  900 | train loss: 0.0360

对于该数据集中前5个数字的可视化的输出结果展示了解码的过程,与cnn一样,同样可以观察到数字“4”与“9”是机器难以区分的。以及数字“3”“5”“8”。

面经:解码编码器模型和自编码器模型的相同与不同

相同点

1. 基本结构相似

  • 两者都有一个编码器(Encoder)和解码器(Decoder)结构。编码器负责将输入数据压缩为低维表示,解码器负责将其从低维表示重建回原始数据或生成其他输出。

2. 降维与特征提取

  • 编码器的主要功能是降维或提取输入数据的特征表示,无论是解码编码器模型还是自编码器,都涉及到从高维数据到低维潜在表示的映射过程。

3. 无监督学习(对于自编码器)

  • 自编码器是一种无监督学习模型,虽然解码编码器模型可以用于监督学习(如翻译任务),但也可以应用于无监督场景(如序列预测、生成任务等)。

4. 损失函数

  • 在解码器部分,两者都会计算输出与目标之间的误差。对于自编码器,损失函数通常是输入和重建输出之间的差异(如均方误差),而对于解码器模型,则可能是生成数据与目标输出之间的差异(如交叉熵损失)。

不同点

1. 目标和任务

  • 自编码器的任务是对输入数据进行压缩和重建,目标是学习输入数据的简化表示,并尝试从这种表示中重建原始输入。自编码器一般是用于数据降维特征提取去噪等任务。
  • 解码编码器模型的任务是将一种数据形式转换为另一种形式,通常用于序列到序列(Seq2Seq)任务。典型应用包括机器翻译(将一个语言的句子翻译为另一个语言的句子)、文本摘要图像描述生成等。

2. 输入和输出

  • 自编码器的输入和输出是相同的。例如,输入一张图片,目标是通过压缩后再解码重建出与输入相似的图片。
  • 解码编码器模型的输入和输出可以不同。例如,输入一个英语句子,输出一个法语句子。在这种情况下,输入和输出是不同的数据类型或格式。

3. 编码器和解码器的联系

  • 自编码器中,编码器和解码器是对称结构,即通过相同的低维潜在空间传递信息。
  • 解码编码器模型中,编码器和解码器可以是异构的,通常在序列到序列任务中,通过注意力机制上下文向量在编码器和解码器之间传递信息。

4. 应用场景

  • 自编码器主要用于无监督任务,如图像压缩、特征提取、数据降噪等。
  • 解码编码器模型主要用于有监督任务,如机器翻译、文本生成、对话系统、语音识别等。

总结

  • 相同点:两者都采用编码器-解码器结构,都有降维和特征提取功能,且涉及输入和输出之间的映射。
  • 不同点:自编码器的目的是从输入中重建输入本身,而解码编码器模型用于将输入映射到不同的输出形式,尤其是在序列到序列任务中,通常会使用注意力机制进行复杂的转换任务。

总结

CNN、RNN、LSTM、解码编码器模型和自编码器模型都是深度学习中的重要模型,各有不同的应用场景。CNN擅长处理图像数据,通过卷积和池化提取局部特征;RNN用于序列数据,能够捕捉时序信息;LSTM是RNN的改进版,解决了长依赖问题,适合处理长序列。解码编码器模型常用于序列到序列任务(如翻译),结合注意力机制效果更好。自编码器用于数据降维和重建,广泛应用于无监督学习。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值