【机器学习】深度学习概论(一)

经典的机器学习算法与深度学习对比

【机器学习】深度学习概论(一)_第1张图片

一、机器学习面临的挑战

    【机器学习】深度学习概论(一)_第2张图片

1.1 机器学习算法用于各种应用问题时所利用的典型特征

【机器学习】深度学习概论(一)_第3张图片

1.2 采用人工特征的机器学习算法处理流程

【机器学习】深度学习概论(一)_第4张图片

1.3 人工设计特征面临的问题

【机器学习】深度学习概论(一)_第5张图片

二、 深度学习技术

2.1 采用受限玻尔兹曼机和逐层训练的方法训练深层网络

【机器学习】深度学习概论(一)_第6张图片

2.2 自动编码器

【机器学习】深度学习概论(一)_第7张图片

2.3 训练有多个隐含层的自动编码器存在困难

【机器学习】深度学习概论(一)_第8张图片

2.4 AlexNet 网络

【机器学习】深度学习概论(一)_第9张图片

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

【机器学习】深度学习概论(一)_第10张图片

2.6 解决深层神经网络梯度消失和退化以及局部最优解等问题

【机器学习】深度学习概论(一)_第11张图片

三、进展和典型应用

深度学习技术在机器视觉领域、语音识别、自然语言处理、数据挖掘、推荐系统、计算机图形学等方向的应用

【机器学习】深度学习概论(一)_第12张图片

3.1 计算机视觉

【机器学习】深度学习概论(一)_第13张图片

【机器学习】深度学习概论(一)_第14张图片

【机器学习】深度学习概论(一)_第15张图片

3.2 语音识别

【机器学习】深度学习概论(一)_第16张图片

3.3 自然语言处理

【机器学习】深度学习概论(一)_第17张图片

3.4 计算机图形学

【机器学习】深度学习概论(一)_第18张图片

3.5 推荐系统

【机器学习】深度学习概论(一)_第19张图片

3.6 深度强化学习

【机器学习】深度学习概论(一)_第20张图片

【机器学习】深度学习概论(一)_第21张图片

四、自动编码器(Autoencoder)

【机器学习】深度学习概论(一)_第22张图片

4.1 自动编码器简介

【机器学习】深度学习概论(一)_第23张图片

【机器学习】深度学习概论(一)_第24张图片

示例代码:用TensorFlow实现的一个简单的自动编码器模型,用于对MNIST数据集进行降维和可视化。

# 导入所需的库
import numpy as np # 用于科学计算
import matplotlib.pyplot as plt # 用于绘图
from tensorflow.keras.datasets import mnist # 用于加载MNIST数据集
from tensorflow.keras.models import Model # 用于构建模型
from tensorflow.keras.layers import Input, Dense # 用于定义层


# 加载MNIST数据集,它包含了6万张训练图像和1万张测试图像,每张图像是28*28的灰度图
#x_train是一个形状为(60000, 28, 28)的数组,表示有6万张训练图像,每张图像有28*28个像素值。y_train是一个形状为(60000,)的数组,表示有6万个训练标签,每个标签是一个0到9的整数。x_test和y_test的含义类似,只是它们的数量是1万而已。
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 将图像数据归一化到[0,1]区间,这样可以加快模型的收敛速度
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# 将图像数据展平成一维向量,方便输入到全连接层
#-1表示自动计算该维度的大小,2828表示将每张图像展平成一个784维的向量
x_train = x_train.reshape(-1, 28*28)
x_test = x_test.reshape(-1, 28*28)


# 定义编码器的输入层和输出层,输入层的形状是(28*28,),输出层的形状是(2,),表示将784维的数据降维到2维,输出层使用了ReLU激活函数
input_img = Input(shape=(28*28,))
encoded = Dense(2, activation='relu')(input_img)
# 定义解码器的输入层和输出层,输入层的形状是(2,),输出层的形状是(28*28,),表示将2维的数据还原到784维,输出层使用了Sigmoid激活函数,使得输出值在[0,1]区间
decoded = Dense(28*28, activation='sigmoid')(encoded)


# 构建自动编码器模型,它由编码器和解码器组成,输入是图像数据,输出是重构后的图像数据
autoencoder = Model(input_img, decoded)
# 构建编码器模型,它只包含编码器部分,输入是图像数据,输出是编码后的数据
encoder = Model(input_img, encoded)
# 构建解码器模型,它只包含解码器部分,输入是编码后的数据,输出是重构后的图像数据
decoder_input = Input(shape=(2,))#解码器的输入层,它的形状是(2,),表示输入的数据是2维的向量,这是编码器的输出层的形状
decoder_layer = autoencoder.layers[-1] #获取了自动编码器模型的最后一层,它是一个全连接层,它的形状是(28*28,),表示输出的数据是784维的向量,这是原始图像数据的形状
decoder = Model(decoder_input, decoder_layer(decoder_input)) #构建了解码器模型,它的输入是解码器的输入层,它的输出是自动编码器的最后一层对输入层的计算结果。


# 编译自动编码器模型,使用Adam优化器和二元交叉熵损失函数
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
# 训练自动编码器模型,使用训练数据作为输入和输出,设置10个周期,每个批次256个样本,打乱数据顺序,使用测试数据作为验证数据
autoencoder.fit(x_train, x_train, epochs=10, batch_size=256, shuffle=True, validation_data=(x_test, x_test))


# 用编码器模型对测试数据进行编码,得到编码后的数据,它的形状是(10000, 2),表示有1万个样本,每个样本有2个特征
encoded_imgs = encoder.predict(x_test)
# 用解码器模型对编码后的数据进行解码,得到重构后的图像数据,它的形状是(10000, 28*28),表示有1万个样本,每个样本有784个像素值
decoded_imgs = decoder.predict(encoded_imgs)


# 绘制原始图像和重构图像,比较它们的相似度
n = 10 # 显示的图像数量
plt.figure(figsize=(20, 4))# 大小为20英寸宽,4英寸高
for i in range(n):
    # 显示原始图像,它是28*28的灰度图
    ax = plt.subplot(2, n, i + 1)#第一行第i+1列
    plt.imshow(x_test[i].reshape(28, 28))
    plt.gray()#设置图像的颜色为灰度
    ax.get_xaxis().set_visible(False)#隐藏子图的x轴
    ax.get_yaxis().set_visible(False)


    # 显示重构图像,它是28*28的灰度图
    ax = plt.subplot(2, n, i + 1 + n)# 第二行第i+1列
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show()


# 绘制编码后的数据的散点图,用不同的颜色表示不同的类别,观察它们的分布情况
plt.figure(figsize=(8, 8))
#绘制散点图,横坐标是编码后的数据的第一维,纵坐标是编码后的数据的第二维,颜色是测试数据的标签,颜色映射是彩虹色
plt.scatter(encoded_imgs[:, 0], encoded_imgs[:, 1], c=y_test, cmap='rainbow')
plt.colorbar()#添加一个颜色条,显示不同的颜色对应的数字类别
plt.xlabel('Dimension 1')#设置横坐标的标签
plt.ylabel('Dimension 2')
plt.show()

输出结果:

【机器学习】深度学习概论(一)_第25张图片

测试集中前10个原始图像和重建图像

【机器学习】深度学习概论(一)_第26张图片

4.2 去噪编码器(Denoising Autoencoder)

【机器学习】深度学习概论(一)_第27张图片

示例代码:

#去噪自编码器是一种神经网络,它可以从带有噪声的图像中恢复出原始的清晰图像。
# 用带噪声的图像作为输入,用原始的图像作为输出,让网络学习如何去除噪声
# 导入keras的相关模块
import os
from keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D
from keras.models import Model
from keras.callbacks import TensorBoard
from keras.datasets import mnist
# 导入numpy和matplotlib的相关模块
import numpy as np
import matplotlib.pyplot as plt


# 加载mnist数据集,只使用图像数据,不使用标签数据
(x_train, _), (x_test, _) = mnist.load_data()
# 将图像数据转换为浮点型,并归一化到[0,1]区间
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
# 将图像数据调整为四维张量,第一维是样本数,后三维是图像的高、宽、通道数
# 一个四维的numpy数组,存储了训练集的图像数据,每个图像的形状为(28, 28, 1)
x_train = np.reshape(x_train, (len(x_train), 28, 28, 1))
x_test = np.reshape(x_test, (len(x_test), 28, 28, 1))


# 定义噪声因子,用于在图像数据中添加随机噪声
noise_factor = 0.5
# 在训练集和测试集中添加正态分布的随机噪声
# np.random.normal(loc=0.0, scale=1.0, size=x_train.shape)是一个numpy函数,用于生成一个与x_train形状相同的随机数组,每个元素都服从均值为0,标准差为1的正态分布
x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape)
x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape)
# 将噪声数据裁剪到[0,1]区间 添加噪声后,图像的像素值可能会超出 0 到 1 的范围,这会影响网络的性能。
# 所以,我们需要用 np.clip 函数来将像素值限制在 0 到 1 之间,保证输入的合法性。
x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)


# 定义要显示的图像数量
n = 10
# 创建一个大小为(20, 2)的图形窗口
plt.figure(figsize=(20, 2))
# 循环显示测试集中的噪声图像
for i in range(n):
    # 创建一个子图,位置为第一行第i+1列
    ax = plt.subplot(1, n, i + 1)
    # 显示第i个噪声图像,将其调整为28*28的灰度图
    plt.imshow(x_test_noisy[i].reshape(28, 28))
    plt.gray()
    # 隐藏子图的坐标轴
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# 显示图形窗口
plt.show()


# 定义输入图像的形状,为(28, 28, 1)
input_img = Input(shape=(28, 28, 1))


# 定义编码器部分,使用卷积层和最大池化层实现特征提取和降维
x = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img)    # (28, 28, 32)
x = MaxPooling2D((2, 2), padding='same')(x)                             # (14, 14, 32)
x = Conv2D(32, (3, 3), activation='relu', padding='same')(x)            # (14, 14, 32)
encoded = MaxPooling2D((2, 2), padding='same')(x)                       # (7, 7, 32)


# 定义解码器部分,使用卷积层和上采样层实现特征还原和升维
x = Conv2D(32, (3, 3), activation='relu', padding='same')(encoded)      # (7, 7, 32)
x = UpSampling2D((2, 2))(x)                                             # (14, 14, 32)
x = Conv2D(32, (3, 3), activation='relu', padding='same')(x)            # (14, 14, 32)
x = UpSampling2D((2, 2))(x)                                             # (28, 28, 32)
# 使用卷积层输出重建的图像,激活函数为sigmoid,保证输出值在[0,1]区间
decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)


# 定义自动编码器模型,输入为噪声图像,输出为重建图像
autoencoder = Model(input_img, decoded)
# 编译自动编码器模型,优化器为adadelta,损失函数为二元交叉熵
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')


logdir = os.path.join(os.getcwd(), 'my_logs')
# 训练自动编码器模型,输入为噪声图像,输出为原始图像
autoencoder.fit(x_train_noisy, x_train,
                epochs=100, # 迭代次数为100
                batch_size=128, # 批次大小为128
                shuffle=True, # 每次迭代前打乱数据
                validation_data=(x_test_noisy, x_test), # 使用测试集作为验证集
                callbacks=[TensorBoard(log_dir=logdir, histogram_freq=0, write_graph=False)]) # 使用TensorBoard回调函数记录训练过程


# 使用自动编码器模型对测试集中的噪声图像进行预测,得到重建图像
decoded_imgs = autoencoder.predict(x_test_noisy)


# 定义要显示的图像数量
n = 10
# 创建一个大小为(20, 4)的图形窗口
plt.figure(figsize=(20, 4))
# 循环显示测试集中的噪声图像和重建图像
for i in range(n):
    # 显示噪声图像,位置为第一行第i+1列
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test_noisy[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)


    # 显示重建图像,位置为第二行第i+1列
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# 显示图形窗口
plt.show()

e8316971905300ab87d15e194fed9e70.png

测试集中前10个噪声图像

【机器学习】深度学习概论(一)_第28张图片

测试集中前10噪声图像和重建图像

4.3 稀疏自动编码器(Sparse Autoencoder)

【机器学习】深度学习概论(一)_第29张图片

示例代码(pytorch):

# 导入 torch 模块,用于构建和训练神经网络
import torch
# 导入 torch.nn 模块,用于定义神经网络的层和损失函数
import torch.nn as nn
# 导入 torch.nn.functional 模块,用于实现一些常用的激活函数和其他函数
import torch.nn.functional
# 导入 torch.optim 模块,用于实现优化算法
import torch.optim as optim
# 导入 torch.utils.data.dataloader 模块,用于加载和处理数据
import torch.utils.data.dataloader as dataloader


# 导入 torchvision 模块,用于处理图像数据
import torchvision
# 导入 torchvision.datasets 模块,用于获取一些常用的数据集
import torchvision.datasets as datasets
# 导入 torchvision.transforms 模块,用于对图像数据进行一些变换
import torchvision.transforms as transforms


# 导入 common.datas 模块,用于获取 MNIST 数据集的加载器
from common.datas import get_mnist_loader


# 导入 os 模块,用于操作系统相关的功能
import os
# 导入 time 模块,用于获取时间相关的信息
import time
# 导入 matplotlib.pyplot 模块,用于绘制图形
import matplotlib.pyplot as plt
# 导入 PIL.Image 模块,用于处理图像
from PIL import Image


# 定义一些超参数
batch_size = 100 # 批次大小,即每次训练的数据量
num_epochs = 50 # 训练的轮数,即所有数据训练的次数
in_dim = 784 # 输入维度,即图像的像素数,28*28=784
hidden_size = 30 # 隐藏层维度,即隐藏层神经元的个数
expect_tho = 0.05 # 期望的平均激活值,用于稀疏性约束




def KL_devergence(p, q):
    """
    计算两个分布的 KL 散度
    :param p: 期望的分布
    :param q: 实际的分布
    :return: KL 散度
    """
    # 编码器激活函数是relu,输出没有限制在0~1
    # 对 q 这个张量进行 softmax 函数的运算,使得 q 的每个元素缩放到 (0, 1) 区间且和为 1
    q = torch.nn.functional.softmax(q, dim=0) # 对 q 进行 softmax 归一化,使其和为 1
    q = torch.sum(q, dim=0)/batch_size  # 对 q 的第一维求和,即将第 j 个神经元在 batch_size 个输入下的所有输出取平均
    s1 = torch.sum(p*torch.log(p/q)) # 计算 p 和 q 的交叉熵
    s2 = torch.sum((1-p)*torch.log((1-p)/(1-q))) # 计算 1-p 和 1-q 的交叉熵
    return s1+s2 # 返回 KL 散度


# 定义一个自编码器类,继承自 nn.Module
class AutoEncoder(nn.Module): 
    def __init__(self, in_dim=784, hidden_size=30, out_dim=784): # 定义类的初始化方法,接受输入维度、隐藏层维度和输出维度作为参数
        super(AutoEncoder, self).__init__() # 调用父类的初始化方法
        self.encoder = nn.Sequential( # 定义编码器,即将输入数据压缩为隐藏层表示的部分
            nn.Linear(in_features=in_dim, out_features=hidden_size), # 定义一个全连接层,将输入维度映射为隐藏层维度
            nn.ReLU() # 定义一个 ReLU 激活函数,增加非线性
        )
        self.decoder = nn.Sequential( # 定义解码器,即将隐藏层表示恢复为输出数据的部分
            nn.Linear(in_features=hidden_size, out_features=out_dim), # 定义一个全连接层,将隐藏层维度映射为输出维度
            nn.Sigmoid() # 定义一个 Sigmoid 激活函数,将输出限制在 0 到 1 之间,因为图像的像素值在 0 到 1 之间
        )


    def forward(self, x): # 定义类的前向传播方法,接受输入数据 x 作为参数
        encoder_out = self.encoder(x) # 调用编码器,得到隐藏层表示
        decoder_out = self.decoder(encoder_out) # 调用解码器,得到输出数据
        return encoder_out, decoder_out # 返回隐藏层表示和输出数据




train_loader, test_loader = get_mnist_loader(batch_size=batch_size, shuffle=True) # 调用 get_mnist_loader 函数,获取 MNIST 数据集的训练集和测试集的加载器
autoEncoder = AutoEncoder(in_dim=in_dim, hidden_size=hidden_size, out_dim=in_dim) # 创建一个自编码器对象,传入输入维度、隐藏层维度和输出维度作为参数
if torch.cuda.is_available(): # 判断是否有 GPU 可用
    autoEncoder.cuda()  # 将模型放到 GPU 上,因此后续传入的数据必须也在 GPU 上


Loss = nn.BCELoss() # 定义损失函数,使用二元交叉熵损失,用于衡量输出数据和输入数据的差异
Optimizer = optim.Adam(autoEncoder.parameters(), lr=0.001) # 定义优化器,使用 Adam 算法,传入自编码器的参数和学习率作为参数


# 定义期望平均激活值和 KL 散度的权重
#用于计算隐藏层神经元的平均激活值和期望的平均激活值之间的 KL 散度,从而增加稀疏性的约束。
# 期望的平均激活值是一个很小的值,比如 0.05,表示我们希望隐藏层神经元的激活值的平均值接近于这个值,
# 这样可以使得隐藏层神经元只有少数的激活,而大多数的抑制,从而提取输入数据的重要特征
tho_tensor = torch.FloatTensor([expect_tho for _ in range(hidden_size)]) # 创建一个张量,存储期望的平均激活值,大小为隐藏层维度
if torch.cuda.is_available(): # 判断是否有 GPU 可用
    tho_tensor = tho_tensor.cuda() # 将张量放到 GPU 上
_beta = 3 # 定义 KL 散度的权重,用于控制稀疏性的程度


# def kl_1(p, q):
#     p = torch.nn.functional.softmax(p, dim=-1)
#     _kl = torch.sum(p*(torch.log_softmax(p,dim=-1)) - torch.nn.functional.log_softmax(q, dim=-1),1)
#     return torch.mean(_kl)


for epoch in range(num_epochs): # 对所有数据进行 num_epochs 轮训练
    time_epoch_start = time.time() # 记录每轮训练的开始时间
    #enumerate 是一个内置函数,它可以将一个可迭代的对象转换为一个枚举对象,即在每个元素前面加上一个计数值,从 0 开始。这样可以方便地获取每个元素的索引和值。
    # MNIST 数据集 train_data 的数据维度应该是 (batch_size, 1, 28, 28),其中 batch_size 是您设置的每次训练的数据量,1 是图像的通道数,28 是图像的高度和宽度
    for batch_index, (train_data, train_label) in enumerate(train_loader): # 对训练集的每个批次进行迭代,获取批次索引、数据和标签
        if torch.cuda.is_available(): # 判断是否有 GPU 可用
            train_data = train_data.cuda() # 将数据放到 GPU 上
            train_label = train_label.cuda() # 将标签放到 GPU 上
        input_data = train_data.view(train_data.size(0), -1) # 将 train_data 的每个图像数据转换为一个一维的向量,大小为 784 
        # train_data.size(0) 是一个整数,表示 train_data 的第一维的大小,即批次大小,即每次训练的数据量
        # view 是一个 torch 模块提供的函数,用于改变张量的形状,即维度和大小
        # input_data 的形状应该是 (batch_size, 784),其中 batch_size 是 train_data 的第一维的大小,784 是 train_data 的其他三维的乘积,即 1×28×28
        encoder_out, decoder_out = autoEncoder(input_data) # 调用自编码器的前向传播方法,得到隐藏层表示和输出数据
        loss = Loss(decoder_out, input_data) # 计算损失函数,比较输出数据和输入数据的差异


        # 计算并增加 KL 散度到损失
        _kl = KL_devergence(tho_tensor, encoder_out) # 调用 KL_devergence 函数,计算期望的分布和实际的分布的 KL 散度
        loss += _beta * _kl # 将 KL 散度乘以权重后加到损失上,增加稀疏性的约束


        Optimizer.zero_grad() # 清空优化器的梯度
        loss.backward() # 调用损失的反向传播方法,计算梯度
        Optimizer.step() # 调用优化器的更新

最后几行输出:

Epoch: 50, Loss: 3.8174, Time: 6.95
Epoch: 50, Loss: 3.8174, Time: 6.97
Epoch: 50, Loss: 3.8171, Time: 6.98
Epoch: 50, Loss: 3.8215, Time: 6.99
Epoch: 50, Loss: 3.8229, Time: 7.00

4.4 收缩自动编码器(Contractive Autoencoder )

【机器学习】深度学习概论(一)_第30张图片

示例代码(pytorch)

# 导入所需的库
import os
import argparse
import torch
import torch.utils.data
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
from torchvision import datasets, transforms
# import pdb
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec


# 设置CUDA设备的顺序和可见性
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"   # see issue #152
os.environ["CUDA_VISIBLE_DEVICES"]="1"


# 打印导入成功的信息
print("Imported all libraries successfully!")


# 创建一个解析器对象,用于处理命令行参数
parser = argparse.ArgumentParser(description='PyTorch MNIST Example for CAE')
# 添加各种参数,包括批量大小,训练轮数,是否使用CUDA,随机种子,日志间隔等
parser.add_argument('--batch-size', type=int, default=128, metavar='N',
                    help='input batch size for training (default: 64)')
parser.add_argument('--epochs', type=int, default=19, metavar='N',
                    help='number of epochs to train (default: 2)')
parser.add_argument('--no-cuda', action='store_true', default=False,
                    help='enables CUDA training')
parser.add_argument('--seed', type=int, default=1, metavar='S',
                    help='random seed (default: 1)')
parser.add_argument('--log-interval', type=int, default=10, metavar='N',
                    help='how many batches to wait before logging training status')
# 解析参数并赋值给args对象
args = parser.parse_args()
# 判断是否使用CUDA
args.cuda = not args.no_cuda and torch.cuda.is_available()


# 设置随机种子
torch.manual_seed(args.seed)
# 如果使用CUDA,设置CUDA的随机种子
if args.cuda:
    torch.cuda.manual_seed(args.seed)


# 设置数据加载器的参数,如果使用CUDA,设置num_workers和pin_memory
kwargs = {'num_workers': 5, 'pin_memory': True} if args.cuda else {}


# 创建训练数据加载器,使用MNIST数据集,将图片转换为张量
train_loader = torch.utils.data.DataLoader(
  datasets.MNIST('data', train=True, download=True,
    transform=transforms.ToTensor()),
  batch_size=args.batch_size, shuffle=True, **kwargs)


# 创建测试数据加载器,使用MNIST数据集,将图片转换为张量
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('data', train=False, transform=transforms.ToTensor()),
    batch_size=args.batch_size, shuffle=True, **kwargs)


# 设置收缩损失的系数
lam = 1e-4


# 定义一个收缩自编码器(CAE)的类,继承自nn.Module
class CAE(nn.Module):
  def __init__(self):
    super(CAE, self).__init__()


    self.fc1 = nn.Linear(784, 400, bias = False) # 编码器
    self.fc2 = nn.Linear(400, 784, bias = False) # 解码器


    self.relu = nn.ReLU()
    self.sigmoid = nn.Sigmoid()




  def encoder(self, x):
    h1 = self.relu(self.fc1(x.view(-1, 784))) # 将输入图片展平为784维向量,然后通过全连接层和激活函数得到400维的隐层向量
    return h1


  def decoder(self,z):
    h2 = self.sigmoid(self.fc2(z)) # 将隐层向量通过全连接层和激活函数得到784维的重构向量
    return h2


  def forward(self, x):
            h1 = self.encoder(x) # 编码过程
            h2 = self.decoder(h1) # 解码过程
            return h1, h2 # 返回隐层向量和重构向量


        # 将重构的图片按网格排列并保存,用于检查质量和进度
  def samples_write(self, x, epoch):
    _, samples = self.forward(x) # 得到重构向量
    #pdb.set_trace()
    samples = samples.data.cpu().numpy()[:16] # 将重构向量转换为numpy数组,并取前16个
    fig = plt.figure(figsize=(4, 4)) # 创建一个4x4的画布
    gs = gridspec.GridSpec(4, 4) # 创建一个4x4的网格
    gs.update(wspace=0.05, hspace=0.05) # 设置网格间距
    for i, sample in enumerate(samples): # 遍历每个重构向量
      ax = plt.subplot(gs[i]) # 在对应的子图上绘制
      plt.axis('off') # 关闭坐标轴
      ax.set_xticklabels([]) # 设置x轴刻度为空
      ax.set_yticklabels([]) # 设置y轴刻度为空
      ax.set_aspect('equal') # 设置等比例缩放
      plt.imshow(sample.reshape(28, 28), cmap='Greys_r') # 将重构向量还原为28x28的图片,并以灰度显示
    if not os.path.exists('out/'): # 如果输出文件夹不存在,创建一个
      os.makedirs('out/')
    plt.savefig('out/{}.png'.format(str(epoch).zfill(3)), bbox_inches='tight') # 保存图片,文件名为训练轮数,用0补齐
    #self.c += 1
    plt.close(fig) # 关闭画布




# 定义均方误差损失函数,不取平均
mse_loss = nn.BCELoss(size_average = False)


# 定义总损失函数,包括均方误差和收缩损失
def loss_function(W, x, recons_x, h, lam):
    mse = mse_loss(recons_x, x) # 计算重构向量和输入向量的均方误差
    # Since: W is shape of N_hidden x N. So, we do not need to transpose it as
    # opposed to #1
    dh = h * (1 - h) # 计算隐层向量的导数,得到N_batch x N_hidden的矩阵
    # Sum through the input dimension to improve efficiency, as suggested in #1
    w_sum = torch.sum(Variable(W)**2, dim=1) # 计算全连接层的权重矩阵的平方和,得到N_hidden维的向量
    # unsqueeze to avoid issues with torch.mv
    w_sum = w_sum.unsqueeze(1) # 将向量扩展为N_hidden x 1的矩阵
    contractive_loss = torch.sum(torch.mm(dh**2, w_sum), 0) # 计算收缩损失,即隐层向量导数的平方与权重平方和的乘积的和
    return mse + contractive_loss.mul_(lam) # 返回总损失,即均方误差加上收缩损失乘以系数




# 创建一个CAE模型的实例
model = CAE()
# 创建一个优化器,使用Adam算法,学习率为0.0001
optimizer = optim.Adam(model.parameters(), lr = 0.0001)


# 如果使用CUDA,将模型转移到GPU上
if args.cuda:
    model.cuda()


# 定义一个训练函数,接受训练轮数作为参数
def train(epoch):
    model.train() # 将模型设置为训练模式
    train_loss = 0 # 初始化训练损失为0


    # 遍历训练数据加载器,得到每个批次的数据和标签(标签在这里不需要)
    for idx, (data, _) in enumerate(train_loader):
        data = Variable(data) # 将数据转换为变量
        if args.cuda:
            data = data.cuda() # 如果使用CUDA,将数据转移到GPU上


        optimizer.zero_grad() # 清空优化器的梯度缓存


        hidden_representation, recons_x = model(data) # 将数据输入模型,得到隐层向量和重构向量


        # 获取权重矩阵
        # model.state_dict().keys()
        # 根据手动查看的键名修改
        # (将来我会尝试自动化这个过程)
        W = model.state_dict()['fc1.weight'] # 获取编码器的权重矩阵
        loss = loss_function(W, data.view(-1, 784), recons_x,
                             hidden_representation, lam) # 计算总损失函数


        loss.backward() # 反向传播,计算梯度
        train_loss += loss.data[0] # 累加训练损失
        optimizer.step() # 更新参数


        # 如果达到日志间隔,打印训练信息
        if idx % args.log_interval == 0:
            print('Train epoch: {} [{}/{}({:.0f}%)]\t Loss: {:.6f}'.format(
                  epoch, idx*len(data), len(train_loader.dataset),
                  100*idx/len(train_loader),
                  loss.data[0]/len(data)))




    # 打印每轮训练的平均损失
    print('====> Epoch: {} Average loss: {:.4f}'.format(
         epoch, train_loss / len(train_loader.dataset)))
    # 调用模型的方法,将重构的图片保存
    model.samples_write(data,epoch)


# 遍历训练轮数,调用训练函数
for epoch in range(args.epochs):
    train(epoch)

out目录输出:

【机器学习】深度学习概论(一)_第31张图片

4.5 卷积自动编码器(Convolutional Autoencoder)

卷积自编码器(Convolutional Autoencoder)是一种利用卷积神经网络(Convolutional Neural Network, CNN)来实现自编码器功能的深度学习模型,它可以对输入的图像数据进行有效的编码和解码,从而实现图像的降维、去噪、重构等任务。卷积自编码器的结构由两部分组成:卷积编码器(Convolutional Encoder)和卷积解码器(Convolutional Decoder)。卷积编码器使用多个卷积层和池化层(Pooling Layer)来逐渐减小图像的尺寸,提取图像的高级特征,并输出一个压缩的隐层表示。卷积解码器使用多个卷积层和上采样层(Upsampling Layer)来逐渐增大图像的尺寸,恢复图像的细节,并输出一个重构的图像。

示例代码(keras):

# 导入所需的模块和库
from keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D # 导入 Keras 中的层模块
from keras.models import Model # 导入 Keras 中的模型模块
from keras.callbacks import TensorBoard # 导入 Keras 中的回调模块,用于可视化训练过程
from keras.datasets import mnist # 导入 Keras 中的数据集模块,用于加载 MNIST 数据集
from keras import backend as K # 导入 Keras 中的后端模块,用于处理张量运算
import numpy as np # 导入 NumPy 库,用于处理数组运算
import matplotlib.pyplot as plt # 导入 Matplotlib 库,用于绘制图像


# 定义输入图像的形状,即 (28, 28, 1),表示高度为 28,宽度为 28,通道数为 1 的灰度图像
input_img = Input(shape=(28, 28, 1))


# 定义编码器部分,即将输入图像压缩为一个低维的向量
x = Conv2D(16, (3, 3), activation='relu', padding='same')(input_img)    # 使用 16 个 3×3 的卷积核对输入图像进行卷积操作,激活函数为 ReLU,填充方式为 same,保持输出图像的大小不变,即 (28, 28, 16)
x = MaxPooling2D((2, 2), padding='same')(x)                             # 使用 2×2 的池化核对卷积后的图像进行最大池化操作,填充方式为 same,将输出图像的大小减半,即 (14, 14, 16)
x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)             # 使用 8 个 3×3 的卷积核对池化后的图像进行卷积操作,激活函数为 ReLU,填充方式为 same,保持输出图像的大小不变,即 (14, 14, 8)
x = MaxPooling2D((2, 2), padding='same')(x)                             # 使用 2×2 的池化核对卷积后的图像进行最大池化操作,填充方式为 same,将输出图像的大小减半,即 (7, 7, 8)
x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)             # 使用 8 个 3×3 的卷积核对池化后的图像进行卷积操作,激活函数为 ReLU,填充方式为 same,保持输出图像的大小不变,即 (7, 7, 8)
encoded = MaxPooling2D((2, 2), padding='same')(x)                       # 使用 2×2 的池化核对卷积后的图像进行最大池化操作,填充方式为 same,将输出图像的大小减半,即 (4, 4, 8),这就是编码后的向量,共有 128 个元素


# 在这一点上,表示是 (4, 4, 8),即 128 维


# 定义解码器部分,即将编码后的向量还原为原始的输入图像
x = Conv2D(8, (3, 3), activation='relu', padding='same')(encoded)       # 使用 8 个 3×3 的卷积核对编码后的向量进行卷积操作,激活函数为 ReLU,填充方式为 same,保持输出图像的大小不变,即 (4, 4, 8)
x = UpSampling2D((2, 2))(x)                                             # 使用 2×2 的上采样核对卷积后的图像进行上采样操作,将输出图像的大小增加一倍,即 (8, 8, 8)
x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)            # 使用 16 个 3×3 的卷积核对上采样后的图像进行卷积操作,激活函数为 ReLU,填充方式为 same,保持输出图像的大小不变,即 (8, 8, 8)
x = UpSampling2D((2, 2))(x)                                             # 使用 2×2 的上采样核对卷积后的图像进行上采样操作,将输出图像的大小增加一倍,即 (16, 16, 8)
x = Conv2D(16, (3, 3), activation='relu')(x)                            # 使用 16 个 3×3 的卷积核对上采样后的图像进行卷积操作,激活函数为 ReLU,填充方式为 valid,将输出图像的大小减少 2,即 (14, 14, 8)
x = UpSampling2D((2, 2))(x)                                             # 使用 2×2 的上采样核对卷积后的图像进行上采样操作,将输出图像的大小增加一倍,即 (28, 28, 8)
decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)    # 使用 1 个 3×3 的卷积核对上采样后的图像进行卷积操作,激活函数为 sigmoid,填充方式为 same,保持输出图像的大小不变,即 (28, 28, 1),这就是解码后的图像,与输入图像的形状相同


# 定义自编码器模型,即将输入图像和解码后的图像连接起来
autoencoder = Model(input_img, decoded)


# 编译自编码器模型,使用 adadelta 优化器和loss='binary_crossentropy' 二元交叉熵损失函数
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')


# 定义编码器模型,即将输入图像和编码后的向量连接起来
encoder = Model(input_img, encoded)


# 从 MNIST 数据集中加载训练数据和测试数据,只需要图像数据,不需要标签数据
(x_train, _), (x_test, _) = mnist.load_data()
# 将图像数据转换为浮点类型,并归一化到 [0, 1] 区间
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
# 将图像数据调整为 (28, 28, 1) 的形状,以适应输入图像的形状
x_train = np.reshape(x_train, (len(x_train), 28, 28, 1))
x_test = np.reshape(x_test, (len(x_test), 28, 28, 1))


# 训练自编码器模型,使用训练数据作为输入和输出,设置迭代次数为 50,批次大小为 128,打乱数据顺序,使用测试数据作为验证数据,使用 TensorBoard 回调函数来可视化训练过程,将日志文件保存在 conv_autoencoder 目录下
autoencoder.fit(x_train, x_train,
                epochs=50,
                batch_size=128,
                shuffle=True,
                validation_data=(x_test, x_test),
                callbacks=[TensorBoard(log_dir='./conv_autoencoder')])


# 使用编码器模型对测试数据进行编码,得到编码后的向量
encoded_imgs = encoder.predict(x_test)
# 使用自编码器模型对测试数据进行解码,得到解码后的图像
decoded_imgs = autoencoder.predict(x_test)


# 设置要显示的图像的个数,这里为 10 个
n = 10
# 创建一个新的图形窗口,设置大小为 (20, 4)
plt.figure(figsize=(20, 4))
# 循环遍历每个图像
for i in range(n):
    # 显示原始图像
    # 创建一个子图,位置为第 i + 1 个,共有 2 行 n 列
    ax = plt.subplot(2, n, i + 1)
    # 将测试数据中的第 i 个图像从 (28, 28, 1) 的形状还原为 (28, 28) 的形状,并显示出来
    plt.imshow(x_test[i].reshape(28, 28))
    # 设置为灰度模式
    plt.gray()
    # 隐藏 x 轴和 y 轴的刻度
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)


    # 显示重建图像
    # 创建一个子图,位置为第 i + 1 + n 个,共有 2 行 n 列
    ax = plt.subplot(2, n, i + 1 + n)
    # 将解码数据中的第 i 个图像从 (28, 28, 1) 的形状还原为 (28, 28) 的形状,并显示出来
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    # 设置为灰度模式
    plt.gray()
    # 隐藏 x 轴和 y 轴的刻度
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# 显示图形窗口
plt.show()


# 创建一个新的图形窗口,设置大小为 (20, 8)
plt.figure(figsize=(20, 8))
# 循环遍历每个图像
for i in range(n):
    # 显示编码向量
    # 创建一个子图,位置为第 i + 1 个,共有 1 行 n 列
    ax = plt.subplot(1, n, i + 1)
    # 将编码数据中的第 i 个向量从 (4, 4, 8) 的形状还原为 (4, 32) 的形状,并转置为 (32, 4) 的形状,然后显示出来
    plt.imshow(encoded_imgs[i].reshape(4, 4 * 8).T)
    # 设置为灰度模式
    plt.gray()
    # 隐藏 x 轴和 y 轴的刻度
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# 显示图形窗口
plt.show()

输出结果:

【机器学习】深度学习概论(一)_第32张图片

【机器学习】深度学习概论(一)_第33张图片

4.5 多层编码器-层叠自动编码器(Stacked_AutoEncoder-SAE)

【机器学习】深度学习概论(一)_第34张图片

【机器学习】深度学习概论(一)_第35张图片

【机器学习】深度学习概论(一)_第36张图片

示例代码-SAE 层叠自动编码器(pytorch)

models.py

# 导入所需的库,包括torch,torchvision,time和os
import torch
from torch import nn, optim, functional, utils
import torchvision
from torchvision import datasets, utils


import time, os




# 定义一个自编码器层的类,继承自nn.Module
class AutoEncoderLayer(nn.Module):
    """
    fully-connected linear layers for stacked autoencoders.
    This module can automatically be trained when training each layer is enabled
    Yes, this is much like the simplest auto-encoder
    """


    # 定义初始化方法,接受输入维度,输出维度和是否进行逐层预训练的参数
    def __init__(self, input_dim=None, output_dim=None, SelfTraining=False):
        super(AutoEncoderLayer, self).__init__() # 调用父类的初始化方法
        # if input_dim is None or output_dim is None:
        #     raise ValueError
        self.in_features = input_dim # 保存输入维度
        self.out_features = output_dim # 保存输出维度
        self.is_training_self = SelfTraining  # 指示是否进行逐层预训练,还是训练整个网络
        # 定义编码器,使用全连接层和Sigmoid激活函数
        self.encoder = nn.Sequential(
            nn.Linear(self.in_features, self.out_features, bias=True),
            nn.Sigmoid()  # 统一使用Sigmoid激活
        )
        # 定义解码器,使用全连接层和Sigmoid激活函数
        self.decoder = nn.Sequential(  # 此处decoder不使用encoder的转置, 并使用Sigmoid进行激活.
            nn.Linear(self.out_features, self.in_features, bias=True),
            nn.Sigmoid()
        )


    # 定义前向传播方法,接受输入x
    def forward(self, x):
        out = self.encoder(x) # 将x通过编码器得到隐层向量
        if self.is_training_self: # 如果是逐层预训练,返回解码器的输出
            return self.decoder(out)
        else: # 否则,返回隐层向量
            return out


    # 定义一个方法,锁定该层的梯度,即不更新参数
    def lock_grad(self):
        for param in self.parameters():
            param.requires_grad = False


    # 定义一个方法,解锁该层的梯度,即更新参数
    def acquire_grad(self):
        for param in self.parameters():
            param.requires_grad = True


    # 定义一个属性,返回输入维度
    @property
    def input_dim(self):
        return self.in_features


    # 定义一个属性,返回输出维度
    @property
    def output_dim(self):
        return self.out_features


    # 定义一个属性,返回是否进行逐层预训练的标志
    @property
    def is_training_layer(self):
        return self.is_training_self


    # 定义一个属性的设置方法,接受一个布尔值,设置是否进行逐层预训练的标志
    @is_training_layer.setter
    def is_training_layer(self, other: bool):
        self.is_training_self = other




# 定义一个栈式自编码器的类,继承自nn.Module
class StackedAutoEncoder(nn.Module):
    """
    Construct the whole network with layers_list
    > 栈式自编码器的架构一般是关于中间隐层对称的
    """


    # 定义初始化方法,接受一个自编码器层的列表作为参数
    def __init__(self, layers_list=None):
        super(StackedAutoEncoder, self).__init__() # 调用父类的初始化方法
        self.layers_list = layers_list # 保存自编码器层的列表
        self.initialize() # 调用初始化方法,将所有层的逐层预训练标志设为False
        # 将列表中的四个自编码器层分别命名为encoder_1, encoder_2, encoder_3, encoder_4
        self.encoder_1 = self.layers_list[0]
        self.encoder_2 = self.layers_list[1]
        self.encoder_3 = self.layers_list[2]
        self.encoder_4 = self.layers_list[3]


    # 定义一个初始化方法,将所有层的逐层预训练标志设为False
    def initialize(self):
        for layer in self.layers_list:
            # assert isinstance(layer, AutoEncoderLayer)
            layer.is_training_layer = False
            # for param in layer.parameters():
            #     param.requires_grad = True


    # 定义前向传播方法,接受输入x
    def forward(self, x):
        out = x # 将x赋值给out
        # 遍历自编码器层的列表,将out依次通过每一层
        # for layer in self.layers_list:
        #     out = layer(out)
        # 也可以直接使用命名的四个自编码器层
        out = self.encoder_1(out)
        out = self.encoder_2(out)
        out = self.encoder_3(out)
        out = self.encoder_4(out)
        return out # 返回最终的输出

run.py

# 导入系统库
import sys
# 将上一级目录添加到系统路径中,以便导入其他模块
sys.path.append('../')


# 从common.datas模块中导入get_mnist_loader函数,用于获取MNIST数据集的加载器
#from common.datas import get_mnist_loader
# 从models模块中导入AutoEncoderLayer和StackedAutoEncoder类,分别用于定义自编码器层和栈式自编码器模型
from models import AutoEncoderLayer, StackedAutoEncoder
# 导入torch库,用于构建和训练神经网络
import torch
# 从torch.nn模块中导入BCELoss类,用于计算二元交叉熵损失函数
from torch.nn import BCELoss
# 从torch模块中导入optim子模块,用于优化神经网络的参数
from torch import optim
# 导入torchvision库,用于处理图像数据
import torchvision
# 从torchvision.datasets模块中导入MNIST类,用于获取MNIST数据集
from torchvision.datasets import MNIST




# 定义一些超参数,包括逐层预训练的轮数,整体训练的轮数,批量大小,是否打乱数据等
num_tranin_layer_epochs = 20
num_tranin_whole_epochs = 50
batch_size = 100
shuffle = True




# 定义一个函数,用于获取MNIST数据集的加载器,接受批量大小和是否打乱数据的参数
def get_mnist_loader(batch_size=100, shuffle=True):
    """


    :return: train_loader, test_loader
    """
    # 创建一个训练数据集的对象,指定数据集的根目录,是否为训练集,是否进行图像转换(转换为张量),是否下载数据集
    train_dataset = MNIST(root='../data',
                          train=True,
                          transform=torchvision.transforms.ToTensor(),
                          download=True)
    # 创建一个测试数据集的对象,指定数据集的根目录,是否为训练集,是否进行图像转换(转换为张量),是否下载数据集
    test_dataset = MNIST(root='../data',
                         train=False,
                         transform=torchvision.transforms.ToTensor(),
                         download=True)


    # 创建一个训练数据加载器的对象,指定数据集,批量大小,是否打乱数据
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                               batch_size=batch_size,
                                               shuffle=shuffle)
    # 创建一个测试数据加载器的对象,指定数据集,批量大小,是否打乱数据
    test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=batch_size,
                                              shuffle=shuffle)
    # 返回训练数据加载器和测试数据加载器
    return train_loader, test_loader


# 定义一个函数,用于训练自编码器层,接受自编码器层的列表,要训练的层的索引,训练轮数,是否验证等参数
def train_layers(layers_list=None, layer=None, epoch=None, validate=True):
    # 如果GPU可用,将所有层转移到GPU上
    if torch.cuda.is_available():
        for model in layers_list:
            model.cuda()
    # 调用get_mnist_loader函数,获取训练数据和测试数据的加载器
    train_loader, test_loader = get_mnist_loader(batch_size=batch_size, shuffle=True)
    # 创建一个优化器,使用SGD算法,学习率为0.001,优化要训练的层的参数
    optimizer = optim.SGD(layers_list[layer].parameters(), lr=0.001)
    # 创建一个损失函数,使用二元交叉熵损失函数
    criterion = BCELoss()


    # 训练
    # 遍历训练轮数
    for epoch_index in range(epoch):
        # 初始化总损失为0
        sum_loss = 0.


        # 如果要训练的层不是第0层,将前面的层的梯度锁定,并将逐层预训练的标志设为False
        if layer != 0:
            for index in range(layer):
                layers_list[index].lock_grad()
                layers_list[index].is_training_layer = False 


        # 遍历训练数据加载器,得到每个批次的数据和标签(标签在这里不需要)
        for batch_index, (train_data, _) in enumerate(train_loader):
            # 如果GPU可用,将数据转移到GPU上
            if torch.cuda.is_available():
                train_data = train_data.cuda()
            # 将数据展平为一维向量
            out = train_data.view(train_data.size(0), -1)


            # 如果要训练的层不是第0层,将数据依次通过前面的层,得到该层的输入
            if layer != 0:
                for l in range(layer):
                    out = layers_list[l](out)


            # 训练第layer层,将输入通过该层,得到输出
            pred =  layers_list[layer](out)


            # 清空优化器的梯度缓存
            optimizer.zero_grad()
            # 计算输出和输入的二元交叉熵损失
            loss = criterion(pred, out)
            # 累加总损失
            sum_loss += loss
            # 反向传播,计算梯度
            loss.backward()
            # 更新参数
            optimizer.step()
            # 如果达到日志间隔,打印训练信息
            if (batch_index + 1) % 10 == 0:
                print("Train Layer: {}, Epoch: {}/{}, Iter: {}/{}, Loss: {:.4f}".format(
                    layer, (epoch_index + 1), epoch, (batch_index + 1), len(train_loader), loss
                ))


        # 如果需要验证,执行验证过程
        if validate:
            pass




# 定义一个函数,用于训练整个栈式自编码器模型,接受模型,训练轮数,是否验证等参数
def train_whole(model=None, epoch=50, validate=True):
    # 打印开始训练的信息
    print(">> start training whole model")
    # 如果GPU可用,将模型转移到GPU上
    if torch.cuda.is_available():
        model.cuda()


    # 将模型的所有参数的梯度解锁,即更新参数
    for param in model.parameters():
        param.require_grad = True


    # 调用get_mnist_loader函数,获取训练数据和测试数据的加载器
    train_loader, test_loader = get_mnist_loader(batch_size=batch_size, shuffle=shuffle)
    # 创建一个优化器,使用SGD算法,学习率为0.001,优化模型的所有参数
    optimizer = optim.SGD(model.parameters(), lr=0.001)
    # 创建一个损失函数,使用均方误差损失函数
    # criterion = BCELoss()
    criterion = torch.nn.MSELoss()


    # 从测试数据加载器中获取一批测试数据,并保存为图片
    test_data, _ = next(iter(test_loader))
    torchvision.utils.save_image(test_data, './test_images/real_test_images.png')


    # 训练
    # 遍历训练轮数
    for epoch_index in range(epoch):
        # 初始化总损失为0
        sum_loss = 0.
        # 遍历训练数据加载器,得到每个批次的数据和标签(标签在这里不需要)
        for batch_index, (train_data, _) in enumerate(train_loader):
            # 如果GPU可用,将数据转移到GPU上
            if torch.cuda.is_available():
                train_data = train_data.cuda()
            # 将数据展平为一维向量
            x = train_data.view(train_data.size(0), -1)


            # 将数据输入模型,得到输出
            out = model(x)


            # 清空优化器的梯度缓存
            optimizer.zero_grad()
            # 计算输出和输入的均方误差损失
            loss = criterion(out, x)
            # 累加总损失
            sum_loss += loss
            # 反向传播,计算梯度
            loss.backward()
            # 更新参数
            optimizer.step()


            # 如果达到日志间隔,打印训练信息
            if (batch_index + 1) % 10 == 0:
                print("Train Whole, Epoch: {}/{}, Iter: {}/{}, Loss: {:.4f}".format(
                    (epoch_index + 1), epoch, (batch_index + 1), len(train_loader), loss
                ))
            # 如果是最后一个批次,将输出重构为图片,并保存
            if batch_index == len(train_loader) - 1:
                torchvision.utils.save_image(out.view(100, 1, 28, 28), "./test_images/out_{}_{}.png".format(epoch_index, batch_index))


        # 每个轮数验证一次
        if validate:
            # 如果GPU可用,将测试数据转移到GPU上
            if torch.cuda.is_available():
                test_data = test_data.cuda()
            # 将测试数据展平为一维向量
            x = test_data.view(test_data.size(0), -1)
            # 将测试数据输入模型,得到输出
            out = model(x)
            # 计算输出和输入的均方误差损失
            loss = criterion(out, x)
            # 打印验证信息
            print("Test Epoch: {}/{}, Iter: {}/{}, test Loss: {}".format(
                epoch_index + 1, epoch, (epoch_index + 1), len(test_loader), loss
            ))




# 判断是否为主模块,如果是,则执行以下代码
if __name__ == '__main__':
    # 导入os库,用于操作系统相关的功能
    import os
    # 如果不存在test_images文件夹,就创建一个
    if not os.path.exists('test_images'):
        os.mkdir('test_images')
    # 如果不存在models文件夹,就创建一个
    if not os.path.exists('models'):
        os.mkdir('models')


    # 定义自编码器层数为5
    nun_layers = 5
    # 创建四个自编码器层的对象,分别为encoder_1, encoder_2, decoder_3, decoder_4,设置输入维度,输出维度和逐层预训练的标志
    encoder_1 = AutoEncoderLayer(784, 256, SelfTraining=True)
    encoder_2 = AutoEncoderLayer(256, 64, SelfTraining=True)
    decoder_3 = AutoEncoderLayer(64, 256, SelfTraining=True)
    decoder_4 = AutoEncoderLayer(256, 784, SelfTraining=True)
    # 将四个自编码器层的对象放入一个列表中,命名为layers_list
    layers_list = [encoder_1, encoder_2, decoder_3, decoder_4]


    # 按照顺序对每一层进行预训练
    # 遍历层数,从0到3
    for level in range(nun_layers - 1):
        # 调用train_layers函数,传入自编码器层的列表,要训练的层的索引,训练轮数,是否验证等参数,进行逐层预训练
        train_layers(layers_list=layers_list, layer=level, epoch=num_tranin_layer_epochs, validate=True)


    # 统一训练
    # 创建一个栈式自编码器的对象,传入自编码器层的列表,命名为SAE_model
    SAE_model = StackedAutoEncoder(layers_list=layers_list)
    # 调用train_whole函数,传入栈式自编码器的对象,训练轮数,是否验证等参数,进行整体训练
    train_whole(model=SAE_model, epoch=num_tranin_whole_epochs, validate=True)


    # 保存模型 refer: https://pytorch.org/docs/master/notes/serialization.html
    # 调用torch.save函数,传入栈式自编码器的对象和保存路径,将模型保存为sae_model.pt文件
    torch.save(SAE_model, './models/sae_model.pt')

输出结果:

【机器学习】深度学习概论(一)_第37张图片

test_images文件夹

【机器学习】深度学习概论(一)_第38张图片

real_test_images.png


你可能感兴趣的:(机器学习,深度学习,人工智能)