当前位置: 首页 > article >正文

【机器学习-无监督学习】自编码器

在这里插入图片描述

【作者主页】Francek Chen
【专栏介绍】 ⌈ ⌈ Python机器学习 ⌋ ⌋ 机器学习是一门人工智能的分支学科,通过算法和模型让计算机从数据中学习,进行模型训练和优化,做出预测、分类和决策支持。Python成为机器学习的首选语言,依赖于强大的开源库如Scikit-learn、TensorFlow和PyTorch。本专栏介绍机器学习的相关算法以及基于Python的算法实现。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/Python_machine_learning。

文章目录

    • 一、自编码器的结构
    • 二、动手实现自编码器
    • 三、拓展:自编码器变体


  在前面的文章中,我们介绍了各种类型的无监督学习算法,如聚类算法、降维算法等。归根结底,无监督学习的目的是从复杂数据中提取出可以代表数据的特征,例如数据的分布、数据的主要成分等等,再用这些信息帮助后续的其他任务。随着机器学习向深度学习发展和神经网络的广泛应用,用神经网络提取数据特征的方法也越来越重要。本文介绍的自编码器(autoencoder,AE)就是其中最基础的一种无监督特征提取方法,也是一种深度学习模型。

  自编码器的原理并不复杂,它将一个输入数据样本压缩成一个低维特征向量表示,然后试图基于该低维特征向量恢复出原数据样本。可以想象,如果该低维特征向量能充分保留原数据样本的信息,那么就可能基于该低维特征向量较好地恢复出原数据。比如在图1中,我们希望把梵高的 The Starry Night 存储在一台机器中,但是这幅画的细节非常丰富,如果用非常高的精度存储,要占用很大的空间。因此,我们可以通过某种算法,把这幅画编码成较少的数据;需要读取时,再通过对应的算法解码出来。这样,虽然解码出的画丢失了一些细节,但是存储的开销也大大降低了。计算机中常见的图像格式JPEG就是一种有损的图像编码方法。自编码器也一样,当我们提取特征时,必然也会保留主要特征、丢弃次要特征,因此最后解码的结果通常不会和输入完全相同。

在这里插入图片描述

图1 编码与解码

  设数据集 D = { x 1 , ⋯   , x N } \mathcal D=\{\boldsymbol x_1,\cdots,\boldsymbol x_N\} D={x1,,xN},其中每个样本 x i ∈ R d \boldsymbol x_i\in\mathbb R^d xiRd。在降维与主成分分析一文中,我们通过矩阵分解提取出了使样本方差最大的 k k k个主成分。然而,当样本数量或者样本维度 d d d较大时,PCA的计算复杂度非常高。并且如果样本的有效特征并非样本当前维度的线性组合,而是要经过非线性变换才能得到,那么PCA算法就无能为力了。为了解决这一问题,我们可以用神经网络中的非线性激活函数来引入非线性成分。利用神经网络强大的函数拟合能力,我们就可以近似任意的非线性变换,从而得到质量较高的样本特征。由于将高维样本 x i \boldsymbol x_i xi映射得到的低维特征向量 z i \boldsymbol z_i zi可以看作是样本的编码,提取样本特征的模块称作编码器(encoder)。而基于低维特征向量 z i \boldsymbol z_i zi恢复出接近原始样本 x ~ i \tilde{\boldsymbol x}_i x~i的模块称作解码器(decoder)。

  编码器和解码器的设计方式有很多。如果我们对数据分布有足够的先验知识,当然可以直接通过这些知识来对数据做编码和解码。例如,如果所有的样本都是独热向量,我们就可以用 1 , ⋯   , d 1,\cdots,d 1,,d 的正整数来编码样本,表示值为1的维度的下标,这样解码也很直接。但是,多数时候样本的分布非常复杂,我们很难用简单的分析手段就得出其分布情况。因此,我们可以用神经网络来直接学习编码器和解码器,并用反向传播等方式自动更新其参数,这就是自编码器。下面,我们来具体讲解自编码器的结构和训练方式。

一、自编码器的结构

  设编码器表示的映射为 ϕ \phi ϕ,将样本 x \boldsymbol x x变换为特征向量 z = ϕ ( x ) \boldsymbol z=\phi(\boldsymbol x) z=ϕ(x)。以最简单的单层感知机为例,其变换 ϕ \phi ϕ由一次线性变换和一次非线性的激活函数复合而成: ϕ ( x ) = σ ( W ϕ x + b ϕ ) \phi(\boldsymbol x)=\sigma(\boldsymbol W_\phi\boldsymbol x+\boldsymbol b_\phi) ϕ(x)=σ(Wϕx+bϕ) 其中 W ϕ \boldsymbol W_\phi Wϕ b ϕ \boldsymbol b_\phi bϕ是网络参数, σ \sigma σ是激活函数。我们知道,在监督学习中神经网络参数的更新需要有监督信号、即样本的标签,用神经网络的预测和真实的样本标签计算出损失,再用损失的梯度回传更新参数。然而在无监督学习中,我们无法获得监督信号,并且由于我们缺乏对数据分布的认知,很难评判训练得到的特征的质量、得到训练损失,也就无法更新网络参数。

  这时,我们可以来考虑编码器的任务目标。编码器需要将高维的样本变换为低维的特征,并且这些特征应当保留原始样本尽可能多的信息。从高维到低维的变换中必定伴随着不可逆的信息损失,如果特征质量较差,保留的信息较少,那么我们无论如何都不可能从特征恢复出原始样本。反过来说,我们可以引入第二个网络 ψ \psi ψ,将特征 z \boldsymbol z z再变回接近原始样本 x \boldsymbol x x的输出 x ~ \tilde{\boldsymbol x} x~ x ~ = ψ ( z ) = σ ( W ψ z + b ψ ) \tilde{\boldsymbol x}=\psi(\boldsymbol z)=\sigma(\boldsymbol W_\psi\boldsymbol z+\boldsymbol b_\psi) x~=ψ(z)=σ(Wψz+bψ) 其中 W ψ \boldsymbol W_\psi Wψ b ψ \boldsymbol b_\psi bψ是网络参数, σ \sigma σ是激活函数。如果该网络可以尽可能将特征恢复成原始样本,就说明我们得到的特征质量较高。因此,我们就可以将恢复出的样本与原始样本之间的差别作为特征的评价指标。假设损失函数是MSE,那么总的损失可以写为 L ( ϕ , ψ ) = 1 2 ∑ i = 1 N ∥ x i − x ~ i ∥ 2 = 1 2 ∑ i = 1 N ∥ x i − ψ ( ϕ ( x i ) ) ∥ 2 \mathcal L(\phi,\psi)=\frac{1}{2}\sum_{i=1}^N\left\Vert\boldsymbol x_i-\tilde{\boldsymbol x}_i\right\Vert^2=\frac{1}{2}\sum_{i=1}^N\left\Vert\boldsymbol x_i-\psi(\phi(\boldsymbol x_i))\right\Vert^2 L(ϕ,ψ)=21i=1Nxix~i2=21i=1Nxiψ(ϕ(xi))2 该损失又称为重建损失(reconstruction loss)。由于 ψ \psi ψ将编码映射回原空间,与编码器 ϕ \phi ϕ的作用相反,我们将其称为解码器。从上式中可以看出,无论编码器与解码器的形式如何,我们都可以用重建损失的梯度来更新网络参数,与监督学习的方式很相似。像这样在无监督学习任务中,从数据集中自行构造出监督信号进行学习的方法就称为自监督学习(self-supervised learning)。需要注意,自监督学习中用到的监督信号也来自于样本自身,并非引入了额外的信息,因此它仍然属于无监督学习的范畴。

  将上面的编码器和解码器组合起来,就得到了自编码器,其结构如图2所示。通常来说,自编码器的结构不会特别复杂,简单的MLP就足够满足任务的要求。考虑到编码与解码过程的对称性,设编码器的隐藏层大小依次为 h 1 , ⋯   , h m h_1,\cdots,h_m h1,,hm,也就是说权重矩阵的维度为 d × h 1 , h 1 × h 2 , ⋯   , h m × k d\times h_1,h_1\times h_2,\cdots,h_m\times k d×h1,h1×h2,,hm×k,我们一般会将解码器的隐层大小依次设置为 h m , ⋯   , h 1 h_m,\cdots,h_1 hm,,h1,与编码器相反。但是由于非线性激活函数的存在,编码与解码过程并不完全对称,其权重应当不同,且解码器的权重与编码器的权重甚至大概率没有关联。

在这里插入图片描述

图2 自动编码器的结构示意

  下面,我们在手写数字数据集MNIST上实现自编码器,用自编码器提取图像的特征,并观察用解码器还原后的效果。

二、动手实现自编码器

  在K近邻算法一文中,我们已经介绍过MNIST数据集的内容。该数据集包含一些手写数字的黑白图像,其中白色的部分是数字,黑色的部分是背景,所有图像的大小都是28像素×28像素,且只有黑白两种颜色。由于图像大小较大,占用存储空间,并且通常还有许多空间上的关联信息。如果我们要完成基于图像上的任务,既可以利用卷积神经网络来提取其空间特征,也可以先从图像中提取出一些一维的特征,再用更简单的网络结构进行训练,降低训练的复杂度。因此,我们希望用自编码器完成这一任务。

  首先,我们导入必要的库和数据集。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import torch
import torch.nn as nn

# 导入数据
mnist_train = pd.read_csv("mnist_train.csv")
mnist_test = pd.read_csv("mnist_test.csv")
# 提取出图像信息,并将内容从0~255的整数转换为0.0~1.0的浮点数
# 图像大小为28*28,数组中每一行代表一张图像
x_train = mnist_train.iloc[:, 1:].to_numpy().reshape(-1, 28 * 28) / 255
x_test = mnist_test.iloc[:, 1:].to_numpy().reshape(-1, 28 * 28) / 255
print(f'训练集大小:{len(x_train)}')
print(f'测试集大小:{len(x_test)}')

在这里插入图片描述

  我们先来展示部分数据集中的图像,来对数据集有更清晰的认识。考虑到后面还要比较重建图像和原始图像,我们把展示图像的方法写成函数。

def display(data, m, n):
    # data:图像的像素数据,每行代表一张图像
    # m,n:按m行n列的方式展示前m * n张图像
    img = np.zeros((28 * m, 28 * n))
    for i in range(m):
        for j in range(n):
            # 填充第i行j列图像的数据
            img[i * 28: (i + 1) * 28, j * 28: (j + 1) * 28] = data[i * m + j].reshape(28, 28)
    plt.figure(figsize=(m * 1.5, n * 1.5))
    plt.imshow(img, cmap='gray')
    plt.show()

display(x_test, 3, 5)

在这里插入图片描述

  接下来,我们来用PyTorch库实现自编码器的网络结构。这里,我们用两层隐层的MLP作为编码器和解码器,且全部使用逻辑斯谛激活函数。由于两者结构本质上相同,我们只实现一个MLP类,再分别实例化为编码器和解码器。原始图像拉平成一维后尺寸是28像素×28像素=784像素,之后的隐层大小我们选择256和128,最后输出的特征向量大小为100。这些参数的选择都只是默认的,可以自行调整隐层和特征向量的大小,观察编码效果的变化。

# 多层感知机
class MLP(nn.Module):

    def __init__(self, layer_sizes):
        super().__init__()
        self.layers = nn.ModuleList() # ModuleList用列表存储PyTorch模块
        num_in = layer_sizes[0]
        for num_out in layer_sizes[1:]:
            # 创建全连接层
            self.layers.append(nn.Linear(num_in, num_out))
            # 创建逻辑斯谛激活函数层
            self.layers.append(nn.Sigmoid())
            num_in = num_out

    def forward(self, x):
        # 前向传播
        for l in self.layers:
            x = l(x)
        return x

layer_sizes = [784, 256, 128, 100]
encoder = MLP(layer_sizes)
decoder = MLP(layer_sizes[::-1]) # 解码器的各层大小与编码器相反

  我们按照上面讲解的方式,先用编码器计算出每个样本的编码 z = ϕ ( x ) \boldsymbol z=\phi(\boldsymbol x) z=ϕ(x),再用解码器计算恢复出的样本 x ~ = ψ ( z ) \tilde{\boldsymbol x}=\psi(\boldsymbol z) x~=ψ(z),计算 x \boldsymbol x x x ~ \tilde{\boldsymbol x} x~之间的重建损失,通过重建损失来训练编码器和解码器的参数。训练过程我们利用PyTorch进行自动化,并采用Adam优化器。下面,我们设置训练所需的超参数。在训练过程中,为了更清晰地展示编码质量的变化,我们每隔一定轮数就将重建的图像绘制出来,展示其随训练过程的变化。

# 训练超参数
learning_rate = 0.01 # 学习率
max_epoch = 10 # 训练轮数
batch_size = 256 # 批量大小
display_step = 2 # 展示间隔
np.random.seed(0)
torch.manual_seed(0)

# 采用Adam优化器,编码器和解码器的参数共同优化
optimizer = torch.optim.Adam(list(encoder.parameters()) + list(decoder.parameters()), lr=learning_rate)
# 开始训练
for i in range(max_epoch):
    # 打乱训练样本
    idx = np.arange(len(x_train))
    idx = np.random.permutation(idx)
    x_train = x_train[idx]
    st = 0
    ave_loss = [] # 记录每一轮的平均损失
    while st < len(x_train):
        # 遍历数据集
        ed = min(st + batch_size, len(x_train))
        X = torch.from_numpy(x_train[st: ed]).to(torch.float32)
        Z = encoder(X)
        X_rec = decoder(Z)
        loss = 0.5 * nn.functional.mse_loss(X, X_rec) # 重建损失
        ave_loss.append(loss.item())
        optimizer.zero_grad() 
        loss.backward() # 梯度反向传播
        optimizer.step()
        st = ed
    ave_loss = np.average(ave_loss)

    if i % display_step == 0 or i == max_epoch - 1:
        print(f'训练轮数:{i},平均损失:{ave_loss:.4f}')
        # 选取测试集中的部分图像重建并展示
        with torch.inference_mode():
            X_test = torch.from_numpy(x_test[:3 * 5]).to(torch.float32)
            X_test_rec = decoder(encoder(X_test))
            X_test_rec = X_test_rec.cpu().numpy()
        display(X_test_rec, 3, 5)

在这里插入图片描述

  最后,我们把得到的模型在测试集上选取部分图像进行重建,并与原图比较,观察模型的效果。可以看出,重建的图像与原始图像非常相近,肉眼很容易辨认出重建图像中的数字,但也能观察出部分缺失的细节。然而,原始图像的大小是784像素,而经由编码器得到的编码长度只有100,大大减小了数据的复杂度。即使算上解码器的模型参数,因为解码器对所有图像的编码都是通用的,无非是加上一个常数。但是需要存储的图像越多,由编码节约的空间就越大,完全可以覆盖模型参数需要的空间了。

print('原始图像')
display(x_test, 3, 5)

print('重建图像')
X_test = torch.from_numpy(x_test[:3 * 5]).to(torch.float32)
X_test_rec = decoder(encoder(X_test))
X_test_rec = X_test_rec.detach().cpu().numpy()
display(X_test_rec, 3, 5)

在这里插入图片描述

三、拓展:自编码器变体

  本文介绍了无监督学习和深度学习中的重要模型之一——自编码器。它结构简单,不依赖监督信号,只需要数据本身,易于和其他模块结合,可以作为复杂任务的数据处理和特征提取步骤。例如我们要完成手写数字分类任务,就可以先用自编码器获得样本的特征,再用这些特征作为输入,训练其他有监督学习任务的机器学习模型。自编码器的这种自监督学习范式是现代深度学习中的一种非常重要的范式,也是机器学习里重要的思维方式之一。

  除了上面讲解的最简单的自编码器之外,它还有许多变式。栈式自编码器(stacked autoencoder)采用分层训练的方式,先训练只有一层的MLP编码器和解码器。第一层训练完成后,再固定其参数,添加第二层,用同样的方法训练第二层的参数,依次类推。这种方式减小了训练多层复杂编码器的难度,但也会增加训练时间。去噪自编码器(denoising autoencoder)通过对输入数据样本加噪,再通过自编码器恢复原始数据样本的方式,让模型能对带有噪音的数据样本做降噪和编码。将自编码器和贝叶斯推断结合可以得到变分自编码器(variational autoencoder,VAE),其中的编码器和解码器分别拟合特征 z \boldsymbol z z的后验概率分布 p ( z ∣ x ) p(\boldsymbol z|\boldsymbol x) p(zx)和样本的条件概率分布 p ( x ∣ z ) p(\boldsymbol x|\boldsymbol z) p(xz)。在VAE训练完毕后,可以通过在编码空间中采样不同的 z \boldsymbol z z,用解码器生成与真实样本相似的虚拟样本。因此,VAE常被视为生成式模型,用来拟合数据分布,生成同一分布的更多数据用于后续训练。在如今的计算机视觉和自然语言处理领域中,由于输入的图像或文本维度都相当大,编码器已经成为了模型中必不可少的部分,而编码器结构的设计也是算法十分重要的一个环节,有着大量而广泛的应用。

:以上文中的数据集及相关资源下载地址:
链接:https://pan.quark.cn/s/61130d78ed43
提取码:RSXn


http://www.kler.cn/news/368604.html

相关文章:

  • Zookeeper面试整理-分布式系统知识
  • 大粤金融智能交易系统的创新与应用
  • kan代码阅读
  • Java 基于 poi 和 itextpdf 实现 excel 转 pdf
  • 基于丑萌气质狗--C#的sqlserver学习
  • 飞书文档解除复制限制
  • First - Word Fall - Through ( FWFT ) Read Operation
  • 【制造业&PPE】施工安全防护装备检测系统源码&数据集全套:改进yolo11-RVB-EMA
  • ubuntu20上部署gitlab并开启ipv6访问
  • 鸿蒙生态开发以及技术栈介绍
  • 图像平滑处理
  • 关于Drawio调整圆角弧度
  • 突破AI极限!GPT写的英语作文在全国大学生大赛中得分99!
  • No.19 笔记 | WEB安全 - 任意文件操作详解 part 1
  • JAVA基础面试题准备
  • SQL进阶技巧:巧用异或运算解决经典换座位问题
  • C语言数据结构学习:单链表
  • 【Ubuntu】服务器系统重装SSHxrdpcuda
  • C语言 | Leetcode C语言题解之第507题完美数
  • 资源所有者管理共享交换机
  • 啤酒游戏—企业经营决策沙盘
  • 人工智能_神经网络103_感知机_感知机工作原理_感知机具备学习能力_在学习过程中自我调整权重_优化效果_多元线性回归_逻辑回归---人工智能工作笔记0228
  • 落实“双碳”行动,深兰科技推动分子能源技术在AI硬件产品领域的应用及产业化进程
  • 【开发日记】如何让指定用户执行sudo命令时无需输入密码
  • 例程学习(学习笔记)
  • 盲盒小程序/APP系统,市场发展下的新机遇