Python 无监督学习实用指南:6~10(3)

简介: Python 无监督学习实用指南:6~10(3)

Python 无监督学习实用指南:6~10(2)https://developer.aliyun.com/article/1426887

潜在狄利克雷分布的主题建模

现在,我们将考虑另一种分解方法,这种分解方法在处理文本文档(即 NLP)时非常有用。 理论部分不是很容易,因为它需要对概率论和统计学习有深入的了解(可以在原始论文《隐迪利克雷分布》); 因此,我们将只讨论主要元素,而没有任何数学参考(《机器学习算法第二版》)。 让我们考虑一组文本文件d[j](称为语料库),其原子(或组成部分)为单词w[i]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztQejnci-1681652675154)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/1bd90156-21af-498a-a042-cfa962bb7146.png)]

收集所有单词后,我们可以构建一个词典:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dhf3pbKM-1681652675155)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/a874f7dd-cdea-4205-bde1-c602e0d85c9d.png)]

我们还可以陈述以下不等式(N(·)计算集合中元素的数量):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk3otdW9-1681652675155)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/c4808f1e-dcfe-4611-85da-902b9260a62c.png)]

这意味着文档之间单词的分布是稀疏的,因为在单个文档中只使用了很少的单词,而前者的选择是对称 Dirichlet 分布(该模型以此命名),它非常稀疏 (此外,它是分类分布的共轭先验,它是一阶多项式,因此很容易合并到模型中)。 概率密度函数(由于分布是对称的,因此α[i] = α ∀ i)如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t73wSo1z-1681652675155)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/1efe3b99-cd8a-4aed-8c79-74a2fd406559.png)]

现在,让我们考虑将文档按主题进行语义分组,即t[k],并假设每个主题都具有少量奇特词:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qItgZKif-1681652675155)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/4e47e13c-2fba-4f37-9b1b-7105ac0113fe.png)]

这意味着主题之间的单词分布也很少。 因此,我们具有完整的联合概率(单词,主题),并且我们想要确定条件概率p(w[i] | t[k])p(t[k] | w[i])。 换句话说,给定一个文档,它是项目的集合(每个项目都有边际概率p(w[i])计算此类文档属于特定主题的概率。 由于一个文档被轻柔地分配给所有主题(也就是说,它可以在不同程度上属于一个以上的主题),因此我们需要考虑一个稀疏的主题文档分布,其中的主题组合($1[$2])被绘制为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kYxQhFfj-1681652675156)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/e168ebe0-4213-45f5-aa01-83a00d2af0b0.png)]

以类似的方式,我们需要考虑主题词的分布(因为一个词可以被更多的主题共享,程度不同),我们可以从中得出主题词-混合样本β[j]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hqxKZOj-1681652675157)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/92cf505a-127c-48d7-b166-8e5a743c9be2.png)]

潜在狄利克雷分布LDA)是一个生成模型(训练目标以简单的方式包括找到最佳参数αγ),它能够从语料库中提取固定数量的主题,并用一组单词来表征它们。 给定示例文档,它可以通过提供主题混合概率向量(θ[i] = (p(t[1]), p(t[2]), ..., p(t[k])));它也可以处理看不见的文档(使用同一词典)。

现在,让我们将 LDA 应用于 20 个新闻组数据集中的一个子集,其中包含数千个已公开发布以供 NLP 研究的消息。 特别是,我们要对rec.autoscomp.sys.mac.hardware子组建模。 我们可以使用内置的 scikit-learn fetch_20newsgroups()函数,要求去除所有不必要的页眉,页脚和引号(答案所附的其他帖子):

from sklearn.datasets import fetch_20newsgroups
news = fetch_20newsgroups(subset='all', categories=('rec.autos', 'comp.sys.mac.hardware'), remove=('headers', 'footers', 'quotes'), random_state=1000)
corpus = news['data']
labels = news['target']

此时,我们需要对语料库进行向量化处理。 换句话说,我们需要将每个文档转换为包含词汇表中每个单词的频率(计数)的稀疏向量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZgMOj4M-1681652675157)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/0cd64574-1e19-462a-ac3b-8c618fcc6d34.png)]

我们将使用CountVectorizer 类,来执行此步骤,要求去除重音并删除停用词,例如,停用词的相对使用率很高,但不具有代表性。 此外,我们正在强制令牌生成器排除所有不是纯文本的令牌(通过设置token_pattern='[a-z]+')。 在其他情况下,这种模式可能会有所不同,但是在这种情况下,我们不想依赖数字和符号:

from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(strip_accents='unicode', stop_words='english', analyzer='word', token_pattern='[a-z]+')
Xc = cv.fit_transform(corpus)
print(len(cv.vocabulary_))

上一个代码段的输出如下:

14182

因此,每个文档都是一个 14182 维的稀疏向量(很明显,大多数值都是空的)。 现在,我们可以通过施加n_components=2来执行 LDA,因为我们希望提取两个主题:

from sklearn.decomposition import LatentDirichletAllocation
lda = LatentDirichletAllocation(n_components=2, learning_method='online', max_iter=100, random_state=1000)
Xl = lda.fit_transform(Xc)

在训练过程之后,components_ 实例变量包含每对夫妇(单词和主题)的相对频率(以计数为单位)。 因此,在我们的情况下,其形状为(2, 14, 182)components_[i, j] 元素,且i ∈ (0, 1)j ∈ (0, 14, 181)可以解释为单词j的重要性,以便定义主题i。 因此,我们将有兴趣检查两个主题的前 10 个词:

import numpy as np
Mwts_lda = np.argsort(lda.components_, axis=1)[::-1]
for t in range(2):
    print('\nTopic ' + str(t))
    for i in range(10):
        print(cv.get_feature_names()[Mwts_lda[t, i]])

输出如下:

Topic 0
compresion
progress
deliberate
dependency
preemptive
wv
nmsu
bpp
coexist
logically
Topic 1
argues
compromising
overtorque
moly
forbid
cautioned
sauber
explosion
eventual
agressive

易于理解(考虑一些非常特殊的项目),已将Topic 0分配给comp.sys.mac.hardware ,将另一个分配给rec.autos(不幸的是,此过程不能基于自动检测,因为语义必须由人解释)。 为了评估模型,让我们考虑两个示例消息,如下所示:

print(corpus[100])
print(corpus[200])

输出(限于几行)如下:

I'm trying to find some information on accelerator boards for the SE. Has
anyone used any in the past, especially those from Extreme Systems, Novy or
MacProducts? I'm looking for a board that will support extended video,
especially Radius's two-page monitor. Has anyone used Connectix Virtual in
conjunction with their board? Any software snafus? Are there any stats
anywhere on the speed difference between a board with an FPU and one
without? Please send mail directly to me. Thanks.
...
The new Cruisers DO NOT have independent suspension in the front.  They
still
run a straight axle, but with coils.  The 4Runner is the one with
independent
front.  The Cruisers have incredible wheel travel with this system. 
The 91-up Cruiser does have full time 4WD, but the center diff locks in
low range.  My brother has a 91 and is an incredibly sturdy vehicle which
has done all the 4+ trails in Moab without a tow.  The 93 and later is even
better with the bigger engine and locking diffs.

因此,第一个帖子显然与绘画有关,而第二个帖子是政治信息。 让我们为它们两者计算主题混合,如下所示:

print(Xl[100])
print(Xl[200])

输出如下:

[0.98512538 0.01487462]
[0.01528335 0.98471665]

因此,第一个消息大约有Topic 0的概率为 98%,而第二个消息几乎几乎没有分配给Topic 1。 这证实了分解工作正常。 为了更好地了解整体分布,可视化属于每个类别的消息的混合将很有帮助,如以下屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BE13tLS-1681652675157)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/045bb9d5-9249-4207-8995-0d44461e6ec2.png)]

comp.sys.mac.hardware(左)和rec.autos(右)的主题组合

如您所见,主题几乎是正交的。 属于rec.autos的大多数消息具有p(t[0]) < 0.5p(t[1]) > 0.5,而comp.sys.mac.hardware则略有重叠,其中不具有p(t[0]) > 0.5p(t[1]) < 0.5的消息组稍大。 这可能是由于存在可以使两个主题具有相同重要性的词语(例如,讨论辩论可能在两个新闻组中均出现)。 作为练习,我邀请您使用更多的子集,并尝试证明主题的正交性,并检测可能导致错误分配的单词。

总结

在本章中,我们介绍了可用于降维和字典学习的不同技术。 PCA 是一种非常知名的方法,涉及查找与方差较大的方向相关联的数据集的大部分导入成分。 该方法具有对角化协方差矩阵并立即测量每个特征的重要性的双重效果,从而简化了选择并最大化了剩余的解释方差(可以用较小的数字来解释的方差量) 组件)。 由于 PCA 本质上是一种线性方法,因此它通常不能与非线性数据集一起使用。 因此,已经开发了基于内核的变体。 在我们的示例中,您了解了 RBF 内核如何将非线性可分离的数据集投影到子空间,在该子空间中 PCA 可以确定判别分量。

稀疏 PCA 和字典学习是广泛使用的技术,当需要提取可以混合(以线性组合方式)的构建原子以生成样本时,可以使用这些技术。 在许多情况下,目标是找到一个所谓的“过度完成的字典”,这相当于说我们期望比构造每个样本的实际原子更多的原子(这就是为什么表示稀疏的原因)。 尽管 PCA 可以提取不相关的成分,但很少能够找到统计上独立的成分。 因此,我们引入了 ICA 的概念,该技术是为了从可以被认为是独立原因(例如,声音或视觉元素)之和的样本中提取重叠源而开发的。 具有特殊功能的另一种方法是 NNMF,它既可以生成稀疏表示,又可以生成类似于样本特定部分的一组组件(例如,对于人脸,它们可以表示眼睛,鼻子等)。 最后一部分介绍了 LDA 的概念,LDA 是一种主题建模技术,可以在给定文档主体(即文档属于每个特定主题的概率)的情况下查找主题组合。

在下一章中,我们将介绍一些基于无监督范式的神经模型。 特别地,将讨论可以在没有协方差矩阵的特征分解(或 SVD)的情况下提取数据集主成分的深度置信网络,自编码器和模型。

问题

  1. 数据集X具有协方差矩阵C = diag(2, 1)。 您对 PCA 有什么期望?
  2. 考虑到前面的问题,如果X居中于零,并且B[0.5](0, 0)*的球为空,我们可以假设一个阈值x = 0(第一个主要成分)是否允许水平判别?
  3. PCA 提取的成分在统计上是独立的。 它是否正确?
  4. Kurt(X) = 5的分布适用于 ICA。 它是否正确?
  5. 包含样本(1, 2)(0, -3)的数据集X的 NNMF 是多少?
  6. 一个 10 个文档的语料库与一个带有 10 个词的词典相关联。 我们知道每个文档的固定长度为 30 个字。 字典是否过于完整?
  7. 核 PCA 与二次内核一起使用。 如果原始大小为 2,则执行 PCA 的新空间的大小是多少?

进一步阅读

  • Online Dictionary Learning for Sparse Coding, J. Mairal, F. Bach, J. Ponce, and G. Sapiro, 2009
  • Learning the parts of objects by non-negative matrix factorization, Lee D. D., Seung S. H., Nature, 401, 10/1999
  • EM algorithms for ML factor analysis, Rubin D., and Thayer D., Psychometrika, 47, 1982
  • Independent Component Analysis: Algorithms and Applications, Hyvarinen A. and Oja E., Neural Networks 13, 2000
  • Mathematical Foundations of Information Theory, Khinchin A. I., Dover Publications
  • Latent Dirichlet Allocation, Journal of Machine Learning Research, Blei D., Ng A., and Jordan M., 3, (2003) 993-1022
  • Machine Learning Algorithms Second Edition, Bonaccorso G., Packt Publishing, 2018
  • Mastering Machine Learning Algorithms, Bonaccorso G., Packt Publishing, 2018

八、无监督神经网络模型

在本章中,我们将讨论一些可用于无监督任务的神经模型。 神经网络(通常是深层网络)的选择使您能够利用需要复杂处理单元(例如图像)的特定特征来解决高维数据集的复杂性。

特别是,我们将介绍以下内容:

  • 自编码器
  • 去噪自编码器
  • 稀疏自编码器
  • 变分自编码器
  • PCA 神经网络:
  • Sanger 网络
  • Rubner-Attic 网络
  • 无监督深度信念网络DBN

技术要求

本章中提供的代码要求以下内容:

  • SciPy 0.19+
  • NumPy 1.10+
  • Scikit-Learn 0.20+
  • Pandas 0.22+
  • Matplotlib 2.0+
  • Seaborn 0.9+
  • TensorFlow 1.5+
  • 深度信念网络

这些示例可在 GitHub 存储库中找到

自编码器

在第 7 章,“降维和成分分析”中,我们讨论了一些通用的方法,这些方法可用于降低数据集的维数,因为其具有特殊的统计属性(例如协方差) 矩阵)。 但是,当复杂度增加时,即使核主成分分析核 PCA)也可能找不到合适的低维表示形式。 换句话说,信息的丢失可以克服一个阈值,该阈值保证了有效重建样本的可能性。 自编码器是利用神经网络的极端非线性特性来查找给定数据集的低维表示的模型。 特别地,假设X是从数据生成过程中提取的一组样本,p_data(x)。 为简单起见,我们将考虑x[i] ∈ R^n,但对支撑结构没有任何限制(例如,对于 RGB 图像,x[i] ∈ R^(n×m×3)。 自编码器在形式上分为两个部分:有一个编码器,它将高维输入转换为较短的代码;一个解码器,执行逆运算(如下图所示):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XMiikUa6-1681652675157)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/d2603e73-6156-40fe-b0f1-a2a37672819f.png)]

通用自编码器的结构模式

如果代码是p维向量,则可以将编码器定义为参数化函数e(·)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pl1pzhX6-1681652675158)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/f35b4488-e844-41a6-99a6-a2b984f4a7f7.png)]

以类似的方式,解码器是另一个参数化函数d(·)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iviDdZ7t-1681652675158)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/9d31e248-bf97-42b4-a487-40dcbfbc3dce.png)]

因此,完整的自编码器是一个复合函数,在给定输入样本x[i]的情况下,它提供了最佳的输出重构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxSvzwn5-1681652675158)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/783c61af-8b57-4bb6-8e6a-cca350d5a8f7.png)]

由于通常是通过神经网络实现的,因此使用反向传播算法来训练自编码器,通常基于均方误差成本函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uo9LGGbw-1681652675158)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/588887de-3b03-4361-962f-883856470c8b.png)]

另外,考虑到数据生成过程,我们可以考虑参数化条件分布q(·)重新表达目标:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPAfC7bS-1681652675158)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/3d870151-1e1f-4472-a9df-211ccc082726.png)]

因此,成本函数现在可以成为p_data(·)*和q(·)之间的 Kullback-Leibler 散度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gufpXlmx-1681652675159)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/889fb37b-214d-4bd3-9e6d-d74c5343ae91.png)]

由于p_data的熵是一个常数,因此可以通过优化过程将其排除; 因此,散度的最小化等于p_dataq之间的交叉熵最小化。 如果假设p_dataq为高斯,则 Kullback-Leibler 成本函数等效于均方误差。 在某些情况下,当数据在(0, 1)范围内归一化时,可以对p_dataq采用伯努利分布。 形式上,这不是完全正确的,因为伯努利分布是二进制的,并且x[i] ∈ {0, 1}^d; 但是,使用 Sigmoid 输出单元还可以保证连续样本的成功优化, x[i] ∈ (0, 1)^d。 在这种情况下,成本函数变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OA6pfYtZ-1681652675159)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/72f5d9d6-18c3-4216-b4e1-af7a8143feab.png)]

深度卷积自编码器的示例

让我们基于 TensorFlow 和 Olivetti faces 数据集(虽然相对较小,但提供良好的表现力)来实现深层卷积自编码器 。 让我们首先加载图像并准备训练集:

from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces(shuffle=True, random_state=1000)
X_train = faces['images']

样本是 400 个64×64灰度图像,我们将其调整为32×32,以加快计算速度并避免出现内存问题(此操作会导致视觉精度略有下降,如果您有足够的计算资源,您可以删除它)。 现在,我们可以定义主要常量(周期数(nb_epochsbatch_sizecode_length))和graph

import tensorflow as tf
nb_epochs = 600
batch_size = 50
code_length = 256 
width = 32
height = 32
graph = tf.Graph()

因此,我们将训练 600 个周期的模型,每批 50 个样本。 由于每个图像都是64×64 = 4,096,因此压缩率是4,096 / 256 = 16倍。 当然,这种选择不是规则,我邀请您始终检查不同的配置,以最大化收敛速度和最终精度。 在我们的案例中,我们正在对编码器进行以下建模:

  • 具有 16 个3×3过滤器,2×2步幅,ReLU 激活和相同填充的 2D 卷积
  • 具有 32 个3×3过滤器,1×1步幅,ReLU 激活和相同的填充的 2D 卷积
  • 具有 64 个3×3过滤器,1×1跨距,ReLU 激活和相同的填充的 2D 卷积
  • 2D 卷积,具有 128 个3×3个过滤器,1×1跨距,ReLU 激活和相同的填充

解码器利用一系列转置卷积(也称为反卷积):

  • 2D 转置卷积,具有 1283×3个过滤器,2×2步幅,ReLU 激活和相同的填充
  • 具有 64 个3×3过滤器,1×1跨距,ReLU 激活和相同填充的 2D 转置卷积
  • 具有 32 个3×3过滤器,1×1跨距,ReLU 激活和相同填充的 2D 转置卷积
  • 2D 转置卷积,带有 13×3过滤器,1×1步幅,Sigmoid 激活,以及相同的填充

损失函数基于重构图像和原始图像之间差异的L[2]范数。 优化器是 Adam,学习率为η = 0.001。 TensorFlow DAG 的编码器部分如下:

import tensorflow as tf
with graph.as_default():
    input_images_xl = tf.placeholder(tf.float32, 
                                     shape=(None, X_train.shape[1], X_train.shape[2], 1))
    input_images = tf.image.resize_images(input_images_xl, (width, height), 
                                          method=tf.image.ResizeMethod.BICUBIC)
    # Encoder
    conv_0 = tf.layers.conv2d(inputs=input_images,
                              filters=16,
                              kernel_size=(3, 3),
                              strides=(2, 2),
                              activation=tf.nn.relu,
                              padding='same')
    conv_1 = tf.layers.conv2d(inputs=conv_0,
                              filters=32,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')
    conv_2 = tf.layers.conv2d(inputs=conv_1,
                              filters=64,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')
    conv_3 = tf.layers.conv2d(inputs=conv_2,
                              filters=128,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')

DAG 的代码部分如下:

import tensorflow as tf
with graph.as_default():   
    # Code layer
    code_input = tf.layers.flatten(inputs=conv_3)
    code_layer = tf.layers.dense(inputs=code_input,
                                 units=code_length,
                                 activation=tf.nn.sigmoid)
    code_mean = tf.reduce_mean(code_layer, axis=1)

DAG 的解码器部分如下:

import tensorflow as tf
with graph.as_default(): 
    # Decoder
    decoder_input = tf.reshape(code_layer, (-1, int(width / 2), int(height / 2), 1))
    convt_0 = tf.layers.conv2d_transpose(inputs=decoder_input,
                                         filters=128,
                                         kernel_size=(3, 3),
                                         strides=(2, 2),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_1 = tf.layers.conv2d_transpose(inputs=convt_0,
                                         filters=64,
                                         kernel_size=(3, 3),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_2 = tf.layers.conv2d_transpose(inputs=convt_1,
                                         filters=32,
                                         kernel_size=(3, 3),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_3 = tf.layers.conv2d_transpose(inputs=convt_2,
                                         filters=1,
                                         kernel_size=(3, 3),
                                         activation=tf.sigmoid,
                                         padding='same')
    output_images = tf.image.resize_images(convt_3, (X_train.shape[1], X_train.shape[2]), 
                                           method=tf.image.ResizeMethod.BICUBIC)

loss函数和 Adam 优化器在以下代码段中定义:

import tensorflow as tf
with graph.as_default():
    # Loss
    loss = tf.nn.l2_loss(convt_3 - input_images)
    # Training step
    training_step = tf.train.AdamOptimizer(0.001).minimize(loss)

一旦定义了完整的 DAG,我们就可以初始化会话和所有变量:

import tensorflow as tf
session = tf.InteractiveSession(graph=graph)
tf.global_variables_initializer().run()

一旦 TensorFlow 初始化,就可以开始训练过程,如下所示:

import numpy as np
for e in range(nb_epochs):
    np.random.shuffle(X_train)
    total_loss = 0.0
    code_means = []
    for i in range(0, X_train.shape[0] - batch_size, batch_size):
        X = np.expand_dims(X_train[i:i + batch_size, :, :], axis=3).astype(np.float32)
        _, n_loss, c_mean = session.run([training_step, loss, code_mean],
                                        feed_dict={
                                            input_images_xl: X
                                        })
        total_loss += n_loss
        code_means.append(c_mean)
    print('Epoch {}) Average loss per sample: {} (Code mean: {})'.
          format(e + 1, total_loss / float(X_train.shape[0]), np.mean(code_means)))

上一个代码段的输出如下:

Epoch 1) Average loss per sample: 11.933397521972656 (Code mean: 0.5420681238174438)
Epoch 2) Average loss per sample: 10.294102325439454 (Code mean: 0.4132006764411926)
Epoch 3) Average loss per sample: 9.917563934326171 (Code mean: 0.38105469942092896)
...
Epoch 600) Average loss per sample: 0.4635812330245972 (Code mean: 0.42368677258491516)

在训练过程结束时,每个样本的平均损失约为 0.46(考虑 32×32 图像),编码的平均值为 0.42。 该值表示编码相当密集,因为期望单个值在(0, 1)范围内均匀分布; 因此,平均值为 0.5。 在这种情况下,我们对这个数据不感兴趣,但是在寻找稀疏度时我们也将比较结果。

下图显示了一些样本图像的自编码器的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yehDkYQZ-1681652675159)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/d73899a4-f6e4-4cfd-b283-8693a4428982.png)]

深度卷积自编码器的样本输出

扩大到 64×64 会部分影响重建的质量。 但是,通过降低压缩率和增加代码长度可以获得更好的结果。

去噪自编码器

自编码器的一个非常有用的应用并不严格取决于它们查找低维表示形式的能力,而是依赖于从输入到输出的转换过程。 特别地,我们假设一个零中心数据集X和一个嘈杂的版本,其样本具有以下结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfBWJVGl-1681652675159)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/6b4e6663-04c2-47c6-af95-153f0cefa172.png)]

在这种情况下,自编码器的目标是消除噪声项并恢复原始样本x[i]。 从数学角度来看,标准和去噪自编码器之间没有特别的区别; 但是,重要的是要考虑此类模型的容量需求。 由于他们必须恢复原始样本,因此在输入受损(其特征占用更大的样本空间)的情况下,层的数量和大小可能比标准自编码器要大。 当然,考虑到复杂性,没有一些测试就不可能有清晰的洞察力。 因此,我强烈建议从较小的模型开始,然后增加容量,直到最佳成本函数达到合适的值为止。 为了增加噪音,有几种可能的策略:

  • 破坏每个批量中的样本(贯穿整个周期)。
  • 将噪声层用作编码器的输入 1。
  • 将丢弃层用作编码器的输入 1(例如,椒盐噪声)。 在这种情况下,丢弃的概率可以是固定的,也可以以预定义的间隔(例如,(0.1,0.5))随机采样。

如果假定噪声为高斯噪声(这是最常见的选择),则可能会同时产生同调和异调噪声。 在第一种情况下,所有分量的方差都保持恒定(即n(i) ~ N(0, σ^2 I)),而在后一种情况下,每个组件具有其自身的差异。 根据问题的性质,另一种解决方案可能更合适。 但是,在没有限制的情况下,总是最好使用异方差噪声,以提高系统的整体鲁棒性。

给深度卷积自编码器增加噪声

在此示例中,我们将修改先前开发的深度卷积自编码器,以管理嘈杂的输入样本。 DAG 几乎等效,不同之处在于,现在我们需要同时提供噪点图像和原始图像:

import tensorflow as tf
with graph.as_default():
    input_images_xl = tf.placeholder(tf.float32, 
                                     shape=(None, X_train.shape[1], X_train.shape[2], 1))
    input_noisy_images_xl = tf.placeholder(tf.float32, 
                                           shape=(None, X_train.shape[1], X_train.shape[2], 1))
    input_images = tf.image.resize_images(input_images_xl, (width, height), 
                                          method=tf.image.ResizeMethod.BICUBIC)
    input_noisy_images = tf.image.resize_images(input_noisy_images_xl, (width, height), 
                                                method=tf.image.ResizeMethod.BICUBIC)
    # Encoder
    conv_0 = tf.layers.conv2d(inputs=input_noisy_images,
                              filters=16,
                              kernel_size=(3, 3),
                              strides=(2, 2),
                              activation=tf.nn.relu,
                              padding='same')
...

loss函数当然是通过考虑原始图像来计算的:

...
# Loss
loss = tf.nn.l2_loss(convt_3 - input_images)
# Training step
training_step = tf.train.AdamOptimizer(0.001).minimize(loss)

在变量的标准初始化之后,我们可以考虑附加噪声n[i] = N(0, 0.45)(即σ ≈ 0.2)开始训练过程:

import numpy as np
for e in range(nb_epochs):
    np.random.shuffle(X_train)
    total_loss = 0.0
    code_means = []
    for i in range(0, X_train.shape[0] - batch_size, batch_size):
        X = np.expand_dims(X_train[i:i + batch_size, :, :], axis=3).astype(np.float32)
        Xn = np.clip(X + np.random.normal(0.0, 0.2, size=(batch_size, X_train.shape[1], X_train.shape[2], 1)), 0.0, 1.0)
        _, n_loss, c_mean = session.run([training_step, loss, code_mean],
                                        feed_dict={
                                            input_images_xl: X,
                                            input_noisy_images_xl: Xn
                                        })
        total_loss += n_loss
        code_means.append(c_mean)
    print('Epoch {}) Average loss per sample: {} (Code mean: {})'.
          format(e + 1, total_loss / float(X_train.shape[0]), np.mean(code_means)))

一旦训练了模型,就可以用一些嘈杂的样本对其进行测试。 结果显示在以下屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9H8jeEXE-1681652675160)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/34830da1-cd25-4d86-bcf2-da6dd16f5586.png)]

噪音样本(上排); 去噪图像(下排)

如您所见,自编码器已经成功学习了如何对输入图像进行去噪,即使它们已经损坏。 我邀请您与其他数据集一起测试模型,以寻找允许合理良好重构的最大噪声方差。

稀疏自编码器

标准自编码器生成的代码通常很密集; 但是,如第 7 章,“降维和成分分析”中所讨论的,有时,最好使用字典过于完整和稀疏编码。 实现此目标的主要策略是简单地在成本函数上添加L[1]罚款(在代码层上):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SwvTyrbv-1681652675160)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/ec6a414d-389c-457f-9944-ba198988bf14.png)]

α常数确定将要达到的稀疏程度。 当然,由于C[s]的最佳值与原始值不对应,因此,为了达到相同的精度,通常需要更多的周期和更长的代码层。 由 Andrew Ng(斯坦福大学的 CS294A “稀疏自编码器”)提出的另一种方法是基于稍微不同的方法。 代码层被认为是一组独立的伯努利随机变量。 因此,给定另一组均值较小的伯努利变量(例如p[r] ~ B(0.05)),就有可能尝试找到使代码最小化的最佳代码z[i]与此类参考分布之间的 Kullback-Leibler 散度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jWzkUltF-1681652675160)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/04ada51f-118c-4cd8-8c62-f1ca4ec0b792.png)]

因此,新的成本函数变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fjojqrEk-1681652675160)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/f27a0c44-85f3-4b2f-accc-796d9b3c7e98.png)]

最终效果与使用L[1]惩罚所获得的效果没有太大不同。 实际上,在这两种情况下,模型都被迫学习次优表示,还试图最小化目标(如果单独考虑)将导致输出代码始终为空。 因此,全部成本函数将达到最小,从而保证重构能力和稀疏性(必须始终与代码长度保持平衡)。 因此,通常,代码越长,可以实现的稀疏度就越大。

向深度卷积自编码器添加稀疏约束

在此示例中,我们想通过使用L[1]罚分来提高代码的稀疏性。 DAG 和训练过程与主要示例完全相同,唯一的区别是loss函数,现在变为:

...
sparsity_constraint = 0.01 * tf.reduce_sum(tf.norm(code_layer, ord=1, axis=1))
loss = tf.nn.l2_loss(convt_3 - input_images) + sparsity_constraint
...

我们添加了α = 0.01的稀疏约束; 因此,我们可以通过检查平均代码长度来重新训练模型。 该过程的输出如下:

Epoch 1) Average loss per sample: 12.785746307373048 (Code mean: 0.30300647020339966)
Epoch 2) Average loss per sample: 10.576686706542969 (Code mean: 0.16661183536052704)
Epoch 3) Average loss per sample: 10.204148864746093 (Code mean: 0.15442773699760437)
...
Epoch 600) Average loss per sample: 0.8058895015716553 (Code mean: 0.028538944199681282)

如您所见,代码现在变得极为稀疏,最终均值大约等于 0.03。 该信息表明大多数代码值接近于零,并且在解码图像时只能考虑其中的几个。 作为练习,我邀请您分析一组选定图像的代码,尝试根据它们的激活/不激活来理解其值的语义。

变分自编码器

让我们考虑从数据生成过程中提取的数据集Xp_data。 可变自编码器是一种生成模型(基于标准自编码器的主要概念),由 Kingma 和 Welling 提出(在《贝叶斯变分自编码》),旨在再现数据生成过程。 为了实现此目标,我们需要从基于一组潜在变量z和一组可学习参数θ的通用模型开始。 给定样本x[i] ∈ X,该模型的概率为p(x, z; θ) 。 因此,训练过程的目标是找到使似然性最大化的最佳参数p(x; θ),该参数可以通过边缘化整个联合概率来获得:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U02whbP4-1681652675161)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/cc7c4316-bf98-4ae0-b3bd-09a2f5239ad6.png)]

以前的表达式很简单,但是不幸的是,它很难以封闭形式处理。 主要原因是我们没有关于先验p(z; θ)的有效信息。 此外,即使假设例如z ~ N(0, Σ)(例如N(0, I)),找到有效样本的概率也非常稀疏 。 换句话说,给定z值,我们也不太可能生成实际上属于p_data的样本。 为了解决这个问题,作者提出了一种变分方法,我们将简要介绍一下(上述论文中有完整的解释)。 假设标准自编码器的结构,我们可以通过将编码器建模为q(z | x; θ[q])来引入代理参数化分布。 此时,我们可以计算q(·)与实际条件概率p(z | x; θ)之间的 Kullback-Leibler 散度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uXKVlOqa-1681652675161)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/eaf4410d-472c-4bf6-8cb2-7d5839d84741.png)]

当期望值运算符在z上工作时,可以提取最后一项并将其移到表达式的左侧,变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ODDrDAW-1681652675161)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/cdaa12bb-4d9e-44f3-84f2-5cbc8bf78189.png)]

经过另一种简单的操作,先前的等式变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BiCNQyNJ-1681652675161)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/56fb3b12-7f9b-4028-bb8d-116c18a81409.png)]

左侧是模型下样本的对数似然,而右侧是非负项(KL 散度)和另一个称为证据下界ELBO):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOSeGsIV-1681652675161)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/8f1e8ea7-da26-4a01-9ca8-27e38f809cfa.png)]

正如我们将要讨论的,使用 ELBO 比处理公式的其余部分要容易得多,并且由于 KL 散度不会产生负面影响,所以如果我们最大化 ELBO,我们也将最大化对数似然率。

我们先前定义了p(z; θ) = N(0, I); 因此,我们可以将q(z | x; θ)建模为多元高斯模型,其中两个参数集(均值向量和协方差矩阵)由拆分概率编码器表示。 特别是,给定样本x,编码器现在必须同时输出平均向量μ(z | x; θ[q])和协方差矩阵Σ(z | x; θ[q])。 为简单起见,我们可以假设矩阵是对角线,因此两个组件的结构完全相同。 结果分布为q(z | x; θ[q]) = N(μ(z | x; θ[q]), Σ(z | x; θ[q]); 因此,ELBO 的第一项是两个高斯分布之间的负 KL 散度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDjPm1ai-1681652675162)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/30ba70c1-c5f3-4ea6-b019-0e51ab9b72ad.png)]

在前面的公式中,p是代码长度,因此它是均值和对角协方差向量的维数。 右侧的表达式非常容易计算,因为Σ是对角线的(也就是说,迹线是元素的总和,行列式是乘积)。 但是,当使用随机梯度下降SGD)算法时,此公式的最大值尽管正确,但却不是可微的运算。 为了克服这个问题,作者建议重新分配分布。

当提供一批时,对正态分布进行采样,获得α ~ N(0, I)。 使用该值,可以使用概率编码器的输出来构建所需的样本:μ(z | x; θ[q]) + α • ∑(z | x; θ[q])^2。 该表达式是可微的,因为α在每个批量中都是常数(当然,就像μ(z | x; θ[q])∑(z | x; θ[q])用神经网络参数化,它们是可微的)。

ELBO 右侧的第二项是log p(x|z; θ)的期望值。 不难看出,这样的表达式与原始分布和重构之间的交叉熵相对应:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38CSInId-1681652675162)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/443c5d4e-8661-4887-a98e-e6fe63f6ad03.png)]

这是标准自编码器的成本函数,在使用伯努利分布的假设下,我们将其最小化。 因此,公式变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6gVMA5eP-1681652675162)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/f91ac945-2781-4c3f-8049-27d941e1f336.png)]

深度卷积变分自编码器的示例

在此示例中,我们要基于 Olivetti 人脸数据集构建和训练深度卷积变分自编码器。 该结构与我们第一个示例中使用的结构非常相似。 编码器具有以下几层:

  • 具有 163×3过滤器,2×2步幅,ReLU 激活和相同填充的 2D 卷积
  • 具有 32 个3×3过滤器,1×1步幅,ReLU 激活和相同的填充的 2D 卷积
  • 具有 64 个3×3过滤器,1×1跨距,ReLU 激活和相同的填充的 2D 卷积
  • 2D 卷积,具有 1283×3个过滤器,1×1跨距,ReLU 激活和相同的填充

解码器具有以下转置卷积:

  • 2D 转置卷积,具有 1283×3个过滤器,2×2步幅,ReLU 激活和相同的填充
  • 2D 转置卷积,具有 1283×3个过滤器,2×2步幅,ReLU 激活和相同的填充
  • 具有 32 个3×3过滤器,1×1跨距,ReLU 激活和相同填充的 2D 转置卷积
  • 2D 转置卷积,带有 13×3过滤器,1×1步幅,Sigmoid 激活,以及相同的填充

TensorFlow 完全控制了噪声的产生,并且基于理论部分中说明的技巧。 以下代码段显示了 DAG 的第一部分,其中包含图定义和编码器:

import tensorflow as tf
nb_epochs = 800
batch_size = 100
code_length = 512
width = 32
height = 32
graph = tf.Graph()
with graph.as_default():
    input_images_xl = tf.placeholder(tf.float32, 
                                     shape=(batch_size, X_train.shape[1], X_train.shape[2], 1))
    input_images = tf.image.resize_images(input_images_xl, (width, height), 
                                          method=tf.image.ResizeMethod.BICUBIC)
    # Encoder
    conv_0 = tf.layers.conv2d(inputs=input_images,
                              filters=16,
                              kernel_size=(3, 3),
                              strides=(2, 2),
                              activation=tf.nn.relu,
                              padding='same')
    conv_1 = tf.layers.conv2d(inputs=conv_0,
                              filters=32,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')
    conv_2 = tf.layers.conv2d(inputs=conv_1,
                              filters=64,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')
    conv_3 = tf.layers.conv2d(inputs=conv_2,
                              filters=128,
                              kernel_size=(3, 3),
                              activation=tf.nn.relu,
                              padding='same')

DAG 中定义代码层的部分如下:

import tensorflow as tf
with graph.as_default():
    # Code layer
    code_input = tf.layers.flatten(inputs=conv_3)
    code_mean = tf.layers.dense(inputs=code_input,
                                units=width * height)
    code_log_variance = tf.layers.dense(inputs=code_input,
                                        units=width * height)
    code_std = tf.sqrt(tf.exp(code_log_variance))

DAG 的解码器部分如下:

import tensorflow as tf
with graph.as_default():  
    # Decoder
    decoder_input = tf.reshape(sampled_code, (-1, int(width / 4), int(height / 4), 16))
    convt_0 = tf.layers.conv2d_transpose(inputs=decoder_input,
                                         filters=128,
                                         kernel_size=(3, 3),
                                         strides=(2, 2),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_1 = tf.layers.conv2d_transpose(inputs=convt_0,
                                         filters=128,
                                         kernel_size=(3, 3),
                                         strides=(2, 2),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_2 = tf.layers.conv2d_transpose(inputs=convt_1,
                                         filters=32,
                                         kernel_size=(3, 3),
                                         activation=tf.nn.relu,
                                         padding='same')
    convt_3 = tf.layers.conv2d_transpose(inputs=convt_2,
                                         filters=1,
                                         kernel_size=(3, 3),
                                         padding='same')
    convt_output = tf.nn.sigmoid(convt_3)
    output_images = tf.image.resize_images(convt_output, (X_train.shape[1], X_train.shape[2]), 
                                           method=tf.image.ResizeMethod.BICUBIC)

DAG 的最后一部分包含损失函数和 Adam 优化器,如下所示:

import tensorflow as tf
with graph.as_default():
    # Loss
    reconstruction = tf.nn.sigmoid_cross_entropy_with_logits(logits=convt_3, labels=input_images)
    kl_divergence = 0.5 * tf.reduce_sum(
            tf.square(code_mean) + tf.square(code_std) - tf.log(1e-8 + tf.square(code_std)) - 1, axis=1)
    loss = tf.reduce_sum(tf.reduce_sum(reconstruction) + kl_divergence)
    # Training step
    training_step = tf.train.AdamOptimizer(0.001).minimize(loss)

损失函数由两个部分组成:

  1. 基于交叉熵的重构损失
  2. 代码分布与参考正态分布之间的 Kullback-Leibler 散度

在这一点上,像往常一样,我们可以初始化会话和所有变量,并开始每批 800 个周期和 100 个样本的训练过程:

import tensorflow as tf
import numpy as np
session = tf.InteractiveSession(graph=graph)
tf.global_variables_initializer().run()
for e in range(nb_epochs):
    np.random.shuffle(X_train)
    total_loss = 0.0
    for i in range(0, X_train.shape[0] - batch_size, batch_size):
        X = np.zeros((batch_size, 64, 64, 1), dtype=np.float32)
        X[:, :, :, 0] = X_train[i:i + batch_size, :, :]
        _, n_loss = session.run([training_step, loss],
                                feed_dict={
                                    input_images_xl: X
                                })
        total_loss += n_loss
    print('Epoch {}) Average loss per sample: {}'.format(e + 1, total_loss / float(batch_size)))

在训练过程的最后,我们可以测试几个样本的重构。 结果显示在以下屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ywDZC8os-1681652675162)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/61c66921-ed45-412b-99e1-4161ff2da04e.png)]

变分自编码器产生的样本重构

作为练习,我邀请读者修改 DAG,以接受通用输入代码并评估模型的生成属性。 或者,可以获取训练样本的代码并施加一些噪声,以便观察对输出重构的影响。

基于 Hebbian 的主成分分析

在本节中,我们将分析两个神经模型(Sanger 和 Rubner-Tavan 网络),它们可以执行主成分分析PCA),而无需对协方差矩阵进行特征分解或执行截断的 SVD。 它们都是基于 Hebbian 学习的概念(有关更多详细信息,请参阅《理论神经科学》),这是有关非常简单的神经元动力学的第一批数学理论之一。 然而,这些概念具有非常有趣的含义,尤其是在组件分析领域。 为了更好地了解网络的动力学,提供神经元基本模型的快速概述将很有帮助。 让我们考虑一个输入x ∈ R^n和权重向量w ∈ ℜ^n。 神经元执行点积(无偏差),以产生标量输出y

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLgJ63Yn-1681652675162)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/82b33ae4-637c-4004-b89b-c6e72863705d.png)]

现在,如果我们想象两个神经元,第一个被称为突触前单元,另一个被称为突触后单元。 Hebbian 规则指出,当突触前和突触后单元都输出具有相同符号(尤其是正值)的值时,突触强度必须增加,而当符号不同时,突触强度必须减弱。 这种概念的数学表达式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JsPs2GoC-1681652675163)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/d422484d-1407-4fbe-8258-bb66809bb7be.png)]

常数η是学习率。 完整的分析超出了本书的范围,但是有可能证明,一个 Hebbian 神经元(经过一些非常简单的修改,需要控制w的生长)可以改变突触的权重,因此在足够多的迭代之后,它沿着数据集的第一个主成分X对齐。 从这个结果(我们不会证明)开始,我们可以介绍 Sanger 网络。

Sanger 网络

Sanger 网络模型由 Sanger 提出(在《单层线性前馈神经网络中的最佳无监督学习》),以便提取第一个数据集的Xk个主成分,以在线过程降序排列(相反,标准 PCA 是需要整个数据集的批量)。 即使有基于特定版本 SVD 的增量算法,这些神经模型的主要优点是它们处理单个样本的固有能力而不会损失任何表现。 在显示网络结构之前,有必要对 Hebb 规则进行修改,称为 Oja 规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDgCb8FJ-1681652675163)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/eb954467-be52-42bb-a06d-e3d8cd62dd8c.png)]

引入此规则是为了解决标准 Hebbian 神经元无限增长的问题。 实际上,很容易理解,如果点积w^T x为正,Δw 将通过增加w的幅度来更新权重。 和更多。 因此,在进行大量迭代之后,模型可能会遇到溢出。 奥雅定律通过引入一种自动限制来克服这个问题,该自动限制迫使权重幅度饱和而不影响神经元找到第一个主要成分的方向的能力。 实际上,用w[k]表示第k次迭代之后的权重向量,可以证明以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BOxP45uF-1681652675163)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/09e7fdb7-16a9-4ec9-87ba-717623f9c860.png)]

Sanger 网络基于 Oja 规则的修改版本,该规则定义为广义 Hebbian 学习GHL)。 假设我们有一个数据集X,包含m个向量,x[i] ∈ R^n。 下图显示了网络的结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aItFf1kc-1681652675163)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/f212e875-6028-405a-a901-657d01e39d7e.png)]

通用 Sanger 网络的结构

权重被组织成一个矩阵,W = {w[ij]}w[ij]是连接突触前单元的权重 ,i,带有突触后单元,j); 因此,可以使用以下公式来计算输出的激活:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfeSrEQp-1681652675163)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/e021d025-55ed-4793-b6d9-b8cec5b50302.png)]

但是,在这种网络中,我们对最终权重更感兴趣,因为它们必须等于第一个n主分量。 不幸的是,如果我们应用 Oja 规则而不做任何修改,则所有神经元都将找到相同的组件(第一个组件)。 因此,必须采用不同的策略。 从理论上讲,我们知道主成分必须正交。 因此,如果w[1]是具有第一个分量方向的向量,则可以强制w[2]正交于w[1],依此类推。 该方法基于 Gram-Schmidt 正交归一化程序。 让我们考虑两个向量-已经收敛的w[1],和w[20], 任何干预措施,也将收敛于w[1]。 通过考虑此向量在w[1]上的投影,可以找到w[20]的正交分量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5N2maeY-1681652675164)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/67503a88-e4d1-468e-8b90-6d874d81a731.png)]

此时,w[2]的正交分量等于:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otQyny6C-1681652675164)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/eb256bb9-cc11-492f-b9b7-5a0d54799d6a.png)]

第三部分必须正交于w[1]w[2],因此必须对所有n个单元,直到最终收敛。 而且,我们现在正在使用已经融合的组件,而是使用并行更新的动态系统。 因此,有必要将此程序纳入学习规则,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DViSaSTR-1681652675164)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/350f6137-1ddd-447d-879a-79de245a6115.png)]

在给定输入x的情况下,先前的更新是指单个权重w[ij]。 容易理解,第一部分是标准 Hebbian 法则,而其余部分是正交项,它扩展到y[i]之前的所有单元。

以矩阵形式,更新内容如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOvejq2v-1681652675164)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/9aa37ec6-8cf8-4828-a845-ec719720409a.png)]

Tril(·)函数计算方阵的下三角部分。 收敛性证明并非无关紧要,但在η单调减少的温和条件下,可以看到该模型如何以降序收敛到第一个n主成分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHbMSsJY-1681652675169)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/9493c073-860c-40de-a254-d938060914d8.png)]

这样的约束并不难实现。 但是,一般来说,当η < 1并在迭代过程中保持恒定时,该算法也可以达到收敛。

Sanger 网络的一个例子

让我们考虑一个使用 scikit-learn make_blobs() 实用函数获得的样本二维零中心数据集:

import numpy as np
def zero_center(Xd):
    return Xd - np.mean(Xd, axis=0)
X, _ = make_blobs(n_samples=500, centers=3, cluster_std=[5.0, 1.0, 2.5], random_state=1000)
Xs = zero_center(X)
Q = np.cov(Xs.T)
eigu, eigv = np.linalg.eig(Q)
print('Covariance matrix: {}'.format(Q))
print('Eigenvalues: {}'.format(eigu))
print('Eigenvectors: {}'.format(eigv.T))

上一个代码段的输出如下:

Covariance matrix: [[18.14296606  8.15571356]
 [ 8.15571356 22.87011239]]
Eigenvalues: [12.01524122 28.99783723]
Eigenvectors: [[-0.79948496  0.60068611]
 [-0.60068611 -0.79948496]]

特征值分别约为 12 和 29,,表示第一主成分(对应于转置特征向量矩阵的第一行,因此(-0.799, 0.6)比第二个要短得多。 当然,在这种情况下,我们已经通过对协方差矩阵进行特征分解来计算了主成分,但这只是出于教学目的。 Sanger 网络将按降序提取组件; 因此,我们期望找到第二列作为权重矩阵的第一列,第一列作为权重矩阵的第二列。 让我们从初始化权重和训练常数开始:

import numpy as np
n_components = 2
learning_rate = 0.01
nb_iterations = 5000
t = 0.0
W_sanger = np.random.normal(scale=0.5, size=(n_components, Xs.shape[1]))
W_sanger /= np.linalg.norm(W_sanger, axis=1).reshape((n_components, 1))

In order to reproduce the example, it’s necessary to set the random seed equal to 1,000; that is, np.random.seed(1000).

在这种情况下,我们将执行固定的迭代次数(5,000); 但是,我邀请您修改示例,以基于在随后两个时间步长计算出的权重之间的差的范数(例如 Frobenius)采用公差和停止准则(此方法可以加快训练速度) 避免无用的迭代)。

下图显示了初始配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kIW48L1N-1681652675170)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/c7b2575f-cea3-421c-9b03-33002dd0c053.png)]

Sanger 网络的初始配置

此时,我们可以开始训练周期,如下所示:

import numpy as np
for i in range(nb_iterations):
    dw = np.zeros((n_components, Xs.shape[1]))
    t += 1.0
    for j in range(Xs.shape[0]):
        Ysj = np.dot(W_sanger, Xs[j]).reshape((n_components, 1))
        QYd = np.tril(np.dot(Ysj, Ysj.T))
        dw += np.dot(Ysj, Xs[j].reshape((1, X.shape[1]))) - np.dot(QYd, W_sanger)
    W_sanger += (learning_rate / t) * dw
    W_sanger /= np.linalg.norm(W_sanger, axis=1).reshape((n_components, 1))
print('Final weights: {}'.format(W_sanger))
print('Final covariance matrix: {}'.format(np.cov(np.dot(Xs, W_sanger.T).T)))

上一个代码段的输出如下:

Final weights: [[-0.60068611 -0.79948496]
 [-0.79948496  0.60068611]]
Final covariance matrix: [[ 2.89978372e+01 -2.31873305e-13]  
 [-2.31873305e-13 1.20152412e+01]]

如您所见,最终的协方差矩阵如预期的那样是去相关的,并且权重已收敛到C的特征向量。 权重(主要成分)的最终配置如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGH6iB9w-1681652675170)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/b50469af-2fc0-4dc7-97d2-5a58cc89c050.png)]

Sanger 网络的最终配置

第一个主成分对应的权重是w[0],它是最大的,而w[1]是第二个成分。 我邀请您使用高维数据集测试网络,并根据协方差矩阵的 SVD 或本征分解将表现与标准算法进行比较。

Rubner-Attic 网络

Rubner 和 Tavan 提出了另一种可以执行 PCA 的神经网络(在《主成分分析的自组织网络》)。 但是,他们的方法基于协方差矩阵的去相关,这是 PCA 的最终结果(也就是说,就像使用自下而上的策略,而标准过程是自上而下的操作一样)。 让我们考虑一个零中心数据集X和一个网络,其输出为y ∈ R^m向量。 因此,输出分布的协方差矩阵如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgaXYbDK-1681652675170)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/0a6cd2b9-0661-43cb-baeb-186e98dbda5e.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHqQHWco-1681652675171)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/5e068665-3ed9-41b4-8e70-9b2a4e768718.png)]

通用 Rubner-Tavan 网络的结构

如您所见,与 Sanger 网络的主要区别在于每个输出单元(第一个输出单元除外)之前都存在求和节点。 这种方法称为分层横向连接,因为每个节点y[i] (i > 0)由直接组件n[i]组成,加到所有先前的加权输出中。 因此,假设使用v^(i)表示法,以表示向量的第i个分量,网络输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BkNqw7YG-1681652675171)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/28b97b62-ca15-49e7-9117-686e2f01f49b.png)]

已经证明,具有特定权重更新规则(我们将要讨论)的该模型收敛到单个稳定的固定点,并且输出被迫变得相互解相关。 查看模型的结构,操作顺序如下:

  • 第一个输出保持不变
  • 第二个输出被强制与第一个输出去相关
  • 第三输出被强制与第一输出和第二输出去相关,依此类推
  • 最后的输出被强制与所有先前的输出去相关

经过多次迭代后,每个生成的y[i] y[j]以及i ≠ j都为空,而C变成对角协方差矩阵。 此外,在上述论文中,作者证明了特征值(对应于方差)是按降序排序的; 因此,可以通过选择包含前p行和列的子矩阵来选择顶部的p组件。

通过使用两个不同的规则(每个权重层一个)来更新 Rubner-Tavan 网络。 内部权重w[ij]通过使用 Oja 规则进行更新:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ad0O0DBC-1681652675171)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/efcc2114-ef49-40d9-a8b3-bd829d2487c1.png)]

该规则确保提取主成分时不会无限增长w[ij]。 相反,外部权重v[jk]通过使用反希伯来规则更新:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3xnMs0f-1681652675171)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/03eed34d-9192-4583-a97f-86ed71c4cb4d.png)]

前一个公式的第一项-ηy^(j) y^(k))负责解相关,而第二项类似于 Oja’s 规则,用作防止权重溢出的自限制正则器。 特别地,-ηy(i) y^(k)项可以解释为更新规则的反馈信号w[ij],它受w[ij]项校正的实际输出的影响。 考虑到 Sanger 网络的行为,不容易理解,一旦输出去相关,内部权重w[ij]就变成正交,代表第一个主要成分X

以矩阵形式,权重w[ij]可以立即排列为W = {w[ij]},这样在训练过程结束时,每一列都是C的特征向量(降序排列)。 相反,对于外部权重v[jk],我们需要再次使用Tril(·)运算符:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9GQ3kPco-1681652675171)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/9080b2af-6409-4238-a6a8-8f230792ce22.png)]

因此,迭代t + 1的输出变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9t4rVZHJ-1681652675172)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-unsup-learn-py/img/2868e72f-7437-444e-94e3-4c2068388a47.png)]

有趣的是,这样的网络经常输出。 因此,一旦应用了输入,就需要进行几次迭代才能使y稳定下来(理想情况下,更新必须持续到||y^(t + 1) - y^(t)|| → 0)。

Rubner-Tavan 网络的一个例子

在此示例中,我们将使用 Sanger 网络示例中定义的数据集,以便使用 Rubner-Tavan 网络进行主成分提取。 为了方便起见,让我们重新计算特征分解:

import numpy as np
Q = np.cov(Xs.T)
eigu, eigv = np.linalg.eig(Q)
print('Eigenvalues: {}'.format(eigu))
print('Eigenvectors: {}'.format(eigv.T))

上一个代码段的输出如下:

Eigenvalues: [12.01524122 28.99783723]
Eigenvectors: [[-0.79948496 0.60068611]
 [-0.60068611 -0.79948496]]

Python 无监督学习实用指南:6~10(4)https://developer.aliyun.com/article/1426889

相关文章
|
3月前
|
机器学习/深度学习 算法 关系型数据库
Python 无监督学习实用指南:6~10(4)
Python 无监督学习实用指南:6~10(4)
39 0
|
3月前
|
机器学习/深度学习 运维 算法
Python 无监督学习实用指南:6~10(1)
Python 无监督学习实用指南:6~10(1)
33 0
Python 无监督学习实用指南:6~10(1)
|
3月前
|
机器学习/深度学习 存储 算法
Python 无监督学习实用指南:1~5(5)
Python 无监督学习实用指南:1~5(5)
39 0
|
3月前
|
机器学习/深度学习 存储 算法
Python 无监督学习实用指南:1~5(3)
Python 无监督学习实用指南:1~5(3)
33 0
Python 无监督学习实用指南:1~5(3)
|
3月前
|
机器学习/深度学习 算法 数据挖掘
Python 无监督学习实用指南:1~5(2)
Python 无监督学习实用指南:1~5(2)
51 0
Python 无监督学习实用指南:1~5(2)
|
3月前
|
机器学习/深度学习 移动开发 算法
Python 无监督学习实用指南:6~10(5)
Python 无监督学习实用指南:6~10(5)
31 0
|
3月前
|
机器学习/深度学习 移动开发 运维
Python 无监督学习实用指南:6~10(2)
Python 无监督学习实用指南:6~10(2)
54 0
|
3月前
|
机器学习/深度学习 存储 算法
Python 无监督学习实用指南:1~5(4)
Python 无监督学习实用指南:1~5(4)
37 0
|
3月前
|
机器学习/深度学习 存储 算法
Python 无监督学习实用指南:1~5(1)
Python 无监督学习实用指南:1~5(1)
72 0
Python 无监督学习实用指南:1~5(1)
|
机器学习/深度学习 算法 Python
【机器学习算法-python实现】K-means无监督学习实现分类
1.背景         无监督学习的定义就不多说了,不懂得可以google。因为项目需要,需要进行无监督的分类学习。         K-means里面的K指的是将数据分成的份数,基本上用的就是算距离的方法。         大致的思路就是给定一个矩阵,假设K的值是2,也就是分成两个部分,那么我们首先确定两个质心。一开始是找矩阵每一列的最大值max,最小值min,算出range=max-
1211 0