-
2019-04-15 20:35:46
How to Generate Images using Autoencoders
你知道什么会很酷吗?如果我们不需要所有那些标记数据来训练我们的模型。我的意思是标记和分类数据需要太多的工作。不幸的是,从支持向量机到卷积神经网络的大多数现有模型如果没有它们就无法进行训练。
除了一小组算法,他们可以。好奇?这就是所谓的无监督学习。无监督学习通过自己的无标签数据推断出一个函数。最着名的无监督算法是K-Means,它已被广泛用于将数据聚类成组和PCA,这是降维的解决方案。 K-Means和PCA可能是有史以来最好的两种机器学习算法。让他们变得更好的是他们的简单。我的意思是,如果你掌握它们,你就会像:“我为什么不早点想到它?”
我们想到的下一个问题是:“是否存在无监督的神经网络? “。你可能知道帖子标题的答案。自动编码。
为了更好地理解自动编码器,我将在解释的同时提供一些代码。请注意,我们将使用Pytorch来构建和训练我们的模型。
import torch from torch import nn, optim from torch.autograd import Variable from torch.nn import functional as F
自动编码器是简单的神经网络,它们的输出是它们的输入。 就那么简单。 他们的目标是学习如何重建输入数据。 但它有什么用呢? 诀窍是他们的结构。 网络的第一部分就是我们所说的编码器。 它接收输入并将其编码在较低维度的潜在空间中。 第二部分(解码器)采用该向量并对其进行解码以生成原始输入。
中间的潜在向量是我们想要的,因为它是输入的压缩表示。应用程序非常丰富,例如:-
压缩
-
降维
此外,很明显,我们可以应用它们来重现相同但有点不同甚至更好的数据。例如:
-
数据去噪:用嘈杂的图像输入它们并训练它们输出相同的图像但没有噪声
-
训练数据增加
-
异常检测:在一个类上训练它们,以便每个异常都会产生很大的重建错误。
然而,自动编码器面临与大多数神经网络相同的几个问题。他们往往过度适应,他们遭受消失的梯度问题。有解决方案吗?变分自动编码器是一个非常好的和优雅的努力。它本质上增加了随机性但不完全相同。
让我们进一步解释一下。变分自动编码器经过训练,可以学习模拟输入数据的概率分布,而不是映射输入和输出的函数。然后,它从该分布中采样点并将它们馈送到解码器以生成新的输入数据样本。但是等一下。当我听说概率分布时,只有一件事物浮现在脑海中:贝叶斯。是的,贝叶斯规则再次成为主要原则。顺便说一句,我并不是夸大其词,但贝叶斯公式是有史以来最好的方程式。而且我不是在开玩笑。无处不在。如果您不知道是什么,请查阅。放弃那篇文章,了解贝叶斯是什么。我会原谅你的。回到变分自动编码器。我认为以下图像清晰起来:
你有它。 随机神经网络。 在我们构建一个生成新图像的示例之前,讨论更多细节是合适的。VAE的一个关键方面是损失功能。 最常见的是,它由两部分组成。 重建损失测量重建数据与原始数据的不同程度(例如二进制交叉熵)。 KL-分歧试图使过程正规化并使重建数据尽可能多样化。
def loss_function(recon_x, x, mu, logvar) -> Variable: BCE = F.binary_cross_entropy(recon_x, x.view(-1, 784)) KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp()) KLD /= BATCH_SIZE * 784 return BCE + KLD
另一个重要方面是如何训练模型。 难以发生,因为变量是注释确定性的,但随机和梯度下降通常不会那样工作。 为解决这个问题,我们使用重新参数化。 潜在向量(z)将等于我们分布的学习平均值(μ)加上学习的标准偏差(σ)乘以epsilon(ε),其中ε遵循正态分布。 我们重新参数化样本,使随机性与参数无关。
def reparameterize(self, mu: Variable, logvar: Variable) -> Variable: #mu : mean matrix #logvar : variance matrix if self.training: std = logvar.mul(0.5).exp_() # type: Variable eps = Variable(std.data.new(std.size()).normal_()) return eps.mul(std).add_(mu) else: return mu
在我们的示例中,我们将尝试使用变分自动编码器生成新图像。 我们将使用MNIST数据集,重建的图像将是手写的数字。 正如我已经告诉过你的那样,除了熟悉之外,我使用Pytorch作为框架,没有特别的原因。 首先,我们应该定义我们的图层。
def __init__(self): super(VAE, self).__init__() # ENCODER self.fc1 = nn.Linear(784, 400) self.relu = nn.ReLU() self.fc21 = nn.Linear(400, 20) # mu layer self.fc22 = nn.Linear(400, 20) # logvariance layer # DECODER self.fc3 = nn.Linear(20, 400) self.fc4 = nn.Linear(400, 784) self.sigmoid = nn.Sigmoid()
如您所见,我们将使用一个非常简单的网络,只有Dense(在pytorch的情况下为Linear)层。 下一步是构建运行编码器和解码器的功能。
def encode(self, x: Variable) -> (Variable, Variable): h1 = self.relu(self.fc1(x)) return self.fc21(h1), self.fc22(h1) def decode(self, z: Variable) -> Variable: h3 = self.relu(self.fc3(z)) return self.sigmoid(self.fc4(h3)) def forward(self, x: Variable) -> (Variable, Variable, Variable): mu, logvar = self.encode(x.view(-1, 784)) z = self.reparameterize(mu, logvar) return self.decode(z), mu, logvar
它只是几行python代码。 没什么大不了。 最后,我们将训练我们的模型并查看生成的图像。
快速提醒:与tensorflow相比,Pytorch有一个动态图表,这意味着代码在运行中运行。 没有必要创建图形然后编译执行它,Tensorflow最近以其急切的执行模式引入了上述功能。
optimizer = optim.Adam(model.parameters(), lr=1e-3) def train(epoch): model.train() train_loss = 0 for batch_idx, (data, _) in enumerate(train_loader): data = Variable(data) optimizer.zero_grad() recon_batch, mu, logvar = model(data) loss = loss_function(recon_batch, data, mu, logvar) loss.backward() train_loss += loss.data[0] optimizer.step() def test(epoch): model.eval() test_loss = 0 for i, (data, _) in enumerate(test_loader): data = Variable(data, volatile=True) recon_batch, mu, logvar = model(data) test_loss += loss_function(recon_batch, data, mu, logvar).data[0] for epoch in range(1, EPOCHS + 1): train(epoch) test(epoch)
培训完成后,我们执行测试功能以检查模型的工作情况。 事实上,它做得非常好,构造的图像与原始图像完全相同,我相信没有人能够在不知道整个故事的情况下将它们区分开来。
下图显示了第一行中的原始照片和第二行中的原始照片。
相当不错,不是吗?在我们结束这篇文章之前,我想再介绍一个主题。 如我们所见,变分自动编码器能够生成新图像。 这是生成模型的经典行为。 生成模型正在生成新数据。 另一方面,判别模型是对类或类别中的现有数据进行分类或区分。
用一些数学术语来解释:生成模型学习联合概率分布p(x,y),而判别模型学习条件概率分布p(y | x)。
在我看来,生成模型更有趣,因为它们为从数据增加到模拟未来可能状态的众多可能性打开了大门。 但更多关于下一篇文章的内容。 关于一种称为生成性对抗性网络的相对较新的生成模型的帖子。
在那之前,继续学习AI。
更多相关内容 -
-
机器学习实战15-自动编码器
2019-04-17 12:44:53自动编码器 自动编码器是能够在无监督的情况下学习输入数据的有效表示(编码)的人工神经网络(训练集是未标记)。这些编码通常具有比输入数据低得多的维度,使得自编码器对降维有用。更重要的是,自动编码器可以...大话循环神经网络(RNN):https://my.oschina.net/u/876354/blog/1621839
自动编码器
自动编码器是能够在无监督的情况下学习输入数据的有效表示(编码)的人工神经网络(训练集是未标记)。这些编码通常具有比输入数据低得多的维度,使得自编码器对降维有用。更重要的是,自动编码器可以作为强大的特征检测器,它们可以用于无监督的深度神经网络预训练。最后,他们能够随机生成与训练数据非常相似的新数据;这被称为生成模型。例如,您可以在脸部图片上训练自编码器,然后可以生成新脸部。
自编码器只需学习将输入复制到其输出即可工作。 这听起来像是一件小事,但我们会看到以各种方式约束网络可能会让它变得相当困难。例如,限制内部表示的大小,或者可以向输入添加噪声并训练网络以恢复原始输入。这些约束防止自编码器将输入直接复制到输出,这迫使它学习表示数据的有效方法。编码是自编码器在某些限制条件下尝试学习恒等函数的副产品。
我们假设其输出与输入是相同的,然后训练调整其参数,得到每一层中的权重。我们就得到了输入I的几种不同表示(每一层代表一种表示),这些表示就是特征。自动编码器就是一种尽可能复现输入信号的神经网络。为了实现这种复现,自动编码器就必须捕捉可以代表输入数据的最重要的因素,就像PCA那样,找到可以代表原信息的主要成分。
具体过程简单的说明如下:
1)给定无标签数据,用非监督学习学习特征:
在我们之前的神经网络中,如第一个图,我们输入的样本是有标签的,即(input, target),这样我们根据当前输出和target (label)之间的差去改变前面各层的参数,直到收敛。但现在我们只有无标签数据,也就是右边的图。那么这个误差怎么得到呢?
如上图,我们将input输入一个encoder编码器,就会得到一个code,这个code也就是输入的一个表示,那么我们怎么知道这个code表示的就是input呢?我们加一个decoder解码器,这时候decoder就会输出一个信息,那么如果输出的这个信息和一开始的输入信号input是很像的(理想情况下就是一样的),那很明显,我们就有理由相信这个code是靠谱的。所以,我们就通过调整encoder和decoder的参数,使得重构误差最小,这时候我们就得到了输入input信号的第一个表示了,也就是编码code了。因为是无标签数据,所以误差的来源就是直接重构后与原输入相比得到。
自动编码器由两部分组成:一个编码器函数h = f(x) 和一个生成重构的解码器r = g(h)。传统上,自动编码器被用于降维或特征学习。是一种无监督学习算法,它使用了反向传播算法,并让目标值等于输入值,比如y(i)=x(i) 。
换句话说,它尝试逼近一个恒等函数,从而使得输出y(1)接近于输入x(1) 。恒等函数虽然看上去不太有学习的意义,但是当我们为自编码神经网络加入某些限制,比如限定隐藏神经元的数量,我们就可以从输入数据中发现一些有趣的结构。举例来说,假设某个自编码神经网络的输入x 是一张 10×10图像(共100个像素)的像素灰度值,于是 n=100 ,其隐藏层L2中有50个隐藏神经元。注意,输出也是100维。由于只有50个隐藏神经元,我们迫使自编码神经网络去学习输入数据的压缩表示,也就是说,它必须从50维的隐藏神经元激活度向量a(2)中重构出100维的像素灰度值输入x 。
自动编码器通常和多层感知器MLP具有相同的架构,不同之处在于自动编码器的输出层的神经元数量必须等于输入层的数量。在这个例子中,只有一个隐藏层,由两个神经元(编码器)组成,一个输出层,由三个神经元(解码器)组成。输出通常被称为重建,因为自动编码器尝试重建输入,并且成本函数包含重建损失,当重建与输入不同时,该损失会惩罚模型。
如上图,因为内部表示的维度低于输入数据(它是2D而不是3D),因此自动编码器不完整。不完整的自动编码器不能简单地复制输入到编码,但是它必须找到一种输出其输入副本的方法。它被强制学习输入数据中最重要的功能(并删除不重要的功能)。让我们看看如何实现一个用来降低维度的非常简单的不完整自动编码器。
1、使用不完整的线性自动编码器实现PCA
如果自动编码器仅使用线性激活,并且成本函数是均方误差(MSE),则表示它可以用来实现主要组件分析。以下代码构建了一个简单的线性自动编码器,用于执行PCA,将3D数据集投影为2D。
import numpy.random as rnd rnd.seed(4) m=200 w1,w2=0.1,0.3 noise=0.1 angles=rnd.rand(m)*3*np.pi/2-0.5 data=np.empty((m,3)) data[:,0]=np.cos(angles) + np.sin(angles)/2 + noise * rnd.randn(m) / 2 data[:, 1] = np.sin(angles) * 0.7 + noise * rnd.randn(m) / 2 data[:, 2] = data[:, 0] * w1 + data[:, 1] * w2 + noise * rnd.randn(m) from sklearn.preprocessing import StandardScaler#标准化 scaler=StandardScaler() X_train=scaler.fit_transform(data[:100])# 前100行用于训练 X_test=scaler.transform(data[100:]) import tensorflow as tf reset_graph() n_inputs=3 n_hidden=2 n_outputs=n_inputs learning_rate=0.01 X=tf.placeholder(tf.float32,shape=[None,n_inputs]) hidden=tf.layers.dense(X,n_hidden) outputs=tf.layers.dense(hidden,n_outputs) reconstruction_loss=tf.reduce_mean(tf.square(outputs-X)) optimizer=tf.train.AdamOptimizer(learning_rate) train_op=optimizer.minimize(reconstruction_loss) init=tf.global_variables_initializer() n_iterations=1000 codings=hidden with tf.Session() as sess: init.run() for iteration in range(n_iterations): train_op.run(feed_dict={X:X_train}) codings_val=codings.eval(feed_dict={X:X_test})
与MLP相比,这段代码并没有多大的不同。需要注意的有两点:
- ·输出的数量等于输入的数量。
- ·为了执行简单的PCA,我们设置activation_fn=None(即所有的神经元是线性的),并且成本函数是MSE。
2、栈式自动编码器
自动编码器可以有多个隐藏层。在这种情况下,它被称为栈式自动编码器(或者深度自动编码器)。增加更多的层帮助自动编码器学习更加复杂的编码。然而,不要让自动编码器变得太强大。想象一下,编码器强大到只是学习将每个输入映射到任意单个数字上。将完美地重建训练数据,但是在这个过程中没有任何有效的数据表示(并且不可能推广到新的实例)。
栈式自动编码器的结构通常对称于中央隐藏层(编码层)。例如一个MNIST的自动编码器可能有784个输入,随后是一个有300个神经元的隐藏层,然后是一个有150个神经元的中央隐藏层,然后是一个有300个神经元的隐藏层,最后是一个有784个神经元的输出层。这个栈式自动编码器如图所示:
可以像实现一个常规的深度MLP一样实现一个栈式自动编码器。特别是,可以使用我们在第11章训练深度网络的技术实现。例如,以下代码使用He初始化,ELU激活函数,以及正则化构建了一个MNIST栈式自动编码器。除了没有标签外(没有y),代码基本相似。源码到文末GitHub链接查找。
权重绑定
当自动编码器和我们刚刚构建的那样严格对称时,一种常见的术是将解码层的权重和编码层的权重联系起来。这种方式将模型的权重减半,提高训练速度,限制了过度配置的风险。
weights1 = tf.Variable(weights1_init, dtype=tf.float32, name="weights1") weights2 = tf.Variable(weights2_init, dtype=tf.float32, name="weights2") weights3 = tf.transpose(weights2, name="weights3") # tied weights weights4 = tf.transpose(weights1, name="weights4") # tied weights
需要注意:
·第一,weights3和weights4不是变量,它们分别是weights2和weights1的转置(它们被“绑定”在一起)。
·第二,因为它们不是变量,所以没有必要进行正则化:只正则化weights1和weights2。
·第三,偏置项从来不会被绑定,也不会被正则化3、去噪自动编码器
另一种强制自动编码器学习有用特征的方法是在输入中增加噪音,训练它以恢复原始的无噪音输入。这种方法阻止了自动编码器简单地复制其输入到输出,最终必须找到数据中的模式。噪音可以是添加到输入中的纯高斯噪音,或者是随机打断输入的噪音,如dropout。下图显示了上面的两种方法:
在TensorFlow实现去噪自动编码器不是很难。从高斯噪音开始。除了为输入增加噪音和根据原始输入计算重建损坏之外,它和训练常规自动编码器很相似。
X = tf.placeholder(tf.float32, shape=[None, n_inputs]) X_noisy = X + tf.random_normal(tf.shape(X)) [...] hidden1 = activation(tf.matmul(X_noisy, weights1) + biases1) [...] reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE [...]
4、稀疏自动编码器
另一种导致良好特征提取的约束是稀疏性:通过在成本函数中增加适当的条件,推动自动编码器减小编码层中活动神经元的数量。例如,使编码层只有平均5%的显著激活神经元。这迫使自动编码器使用少量激活神经元的组合来表示输入。结果编码层的每个神经元都最终代表一个有用特征(如果你每个月只能说几个单词,那么你可能会尝试让它们变得有意义)。
一旦我们对每个神经元进行平均激活,我们希望通过向损失函数添加稀疏损失来惩罚太活跃的神经元。 例如,如果我们测量一个神经元的平均激活值为 0.3,但目标稀疏度为 0.1,那么它必须受到惩罚,才能激活更少。 一种方法可以简单地将平方误差
(0.3-0.1)^2
添加到损失函数中,但实际上更好的方法是使用 K-L 散度,其具有比均方误差更强的梯度。一旦计算了编码层每个神经元的稀疏度损失,我们只需要累加这些损失,然后将其结果加到成本函数中。为了控制稀疏度损伤和重建损失的相对重要性,我们可以通过一个稀疏度权重超参数来增加稀疏度损失。
如果这个权重过高,模型将非常接近目标稀疏度,但是可能不能正确地重建输入,使得模型无用。相反,如果它的值过低,模型将忽略大多数稀疏性目标,并且学习不到什么有用特征。
5、变分自编码器(VAE)
最受欢迎的自编码器类型之一:变分自编码器。它们与我们迄今为止讨论的所有自编码器完全不同,特别是:
- 它们是概率自编码器,意味着即使在训练之后,它们的输出部分也是偶然确定的(相对于仅在训练过程中使用随机性的自编码器的去噪)。
- 最重要的是,它们是生成自编码器,这意味着它们可以生成看起来像从训练集中采样的新实例。
这两个属性使它们与 RBM 非常相似,但它们更容易训练,并且取样过程更快。
我们来看看它们是如何工作的。图左展示了一个变分自动编码器。一个编码器后是一个解码器(在这个例子中,它们都有两个隐藏层)。
但是有一点不同:不是直接为给定的输入生成编码 ,而是编码器产生平均编码
μ
和标准差σ
。然后从平均值μ
和标准差σ
的高斯分布随机采样实际编码。之后,解码器正常解码采样的编码。在此之后,自动编码器只需要正常地解码采样编码。图右半部分展示了一个通过此编码器的训练实例。首先,编码器产生了μ和σ,然后编码是随机采样(注意,它不是精确位于μ中),最后这些编码被解码,并最终输出训练实例。
从图中可以看出,尽管输入可能具有非常复杂的分布,但变分自动编码器往往会产出一个像是从简单的高斯分布中采样的编码:在训练期间,损失函数推动编码在编码空间内(也称潜在空间)逐步迁移到看起来像一个高斯点集成的大致(超)球面区域。一个重要的结果是,变分自动编码器在训练之后,可以很容易地生成一个新实例:只需从高斯分布中抽取一个随机编码,对它进行解码就可以了!
我们来看看成本函数。它由两部分组成。首先是常规的重建损耗,推动自动编码器重现其输入(可以使用交叉熵)。第二部分是潜在损耗,使用编码的目标分布(高斯分布)和实际分布之间的KL散度,使得自动编码器的编码看起来像是从简单的高斯分布中进行采样。数学计算比之前更加复杂,尤其是因为高斯噪音限制了传输到编码层的信息量(从而推动自动编码器学习更加有意
义的特征)。幸运的是,潜在损坏可以简化为如下代码:eps = 1e-10 # smoothing term to avoid computing log(0) which is NaN latent_loss = 0.5 * tf.reduce_sum( tf.square(hidden3_sigma) + tf.square(hidden3_mean) - 1 - tf.log(eps + tf.square(hidden3_sigma)))
一个常见的变体是训练编码器输出
γ= log(σ^2)
而不是σ
。 只要我们需要σ
,我们就可以计算σ= exp(2/γ)
。这使得编码器更容易捕获不同比例的σ
,从而帮助提高收敛的速度。潜在损失最终将会比较简单 。latent_loss = 0.5 * tf.reduce_sum( tf.exp(hidden3_gamma) + tf.square(hidden3_mean) - 1 - hidden3_gamma)
网络为每一个样本Xk都配上了一个专属的正态分布,才方便后面的生成器做还原。这样有多少个X就有多少个正态分布了。我们知道正态分布有两组参数:均值μ和方差σ2,那我怎么找出专属于Xk的正态分布p(Z|Xk)的均值和方差呢?好像并没有什么直接的思路。那好吧,那我就用神经网络来拟合出来吧!这就是神经网络时代的哲学:难算的我们都用神经网络来拟合。
于是我们构建两个神经网络
,
来算它们了。我们选择拟合
而不是直接拟合σ2,是因为σ2总是非负的,需要加激活函数处理,而拟合
不需要加激活函数,因为它可正可负。到这里,我能知道专属于Xk的均值和方差了,也就知道它的正态分布长什么样了,然后从这个专属分布中采样一个Zk出来,然后经过一个生成器得到
,现在我们可以放心地最小化距离D(X^k,Xk),因为Zk是从专属Xk的分布中采样出来的,这个生成器应该要把开始的Xk还原回来。VAE的示意图:
VAE的本质
在VAE中,它的Encoder有两个,一个用来计算均值,一个用来计算方差,这已经让人意外了:Encoder不是用来Encode的,是用来算均值和方差的,这真是大新闻了,还有均值和方差不都是统计量吗,怎么是用神经网络来算的?
事实上,VAE从让普通人望而生畏的变分和贝叶斯理论出发,最后落地到一个具体的模型中,虽然走了比较长的一段路,但最终的模型其实是很接地气的:它本质上就是在我们常规的自编码器的基础上,对encoder的结果(在VAE中对应着计算均值的网络)加上了“高斯噪声”,使得结果decoder能够对噪声有鲁棒性;而那个额外的KL loss(目的是让均值为0,方差为1),事实上就是相当于对encoder的一个正则项,希望encoder出来的东西均有零均值。
那另外一个encoder(对应着计算方差的网络)的作用呢?它是用来动态调节噪声的强度的。直觉上来想,当decoder还没有训练好时(重构误差远大于KL loss),就会适当降低噪声(KL loss增加),使得拟合起来容易一些(重构误差开始下降);反之,如果decoder训练得还不错时(重构误差小于KL loss),这时候噪声就会增加(KL loss减少),使得拟合更加困难了(重构误差又开始增加),这时候decoder就要想办法提高它的生成能力了。
说白了,重构的过程是希望没噪声的,而KL loss则希望有高斯噪声的,两者是对立的。所以,VAE跟GAN一样,内部其实是包含了一个对抗的过程,只不过它们两者是混合起来,共同进化的。从这个角度看,VAE的思想似乎还高明一些,因为在GAN中,造假者在进化时,鉴别者是安然不动的,反之亦然。当然,这只是一个侧面,不能说明VAE就比GAN好。GAN真正高明的地方是:它连度量都直接训练出来了,而且这个度量往往比我们人工想的要好。
from:https://spaces.ac.cn/archives/5253
生成数字
让我们用这个变分自动编码器生成一些看起来像手写数字的图片。我们需要做的是训练模型,然后从高斯分布中随机采样编码并对其进行解码。
1、训练模型
from functools import partial n_inputs = 28 * 28 n_hidden1 = 500 n_hidden2 = 500 n_hidden3 = 20 # codings n_hidden4 = n_hidden2 n_hidden5 = n_hidden1 n_outputs = n_inputs learning_rate = 0.001 initializer = tf.contrib.layers.variance_scaling_initializer() my_dense_layer = partial( tf.layers.dense, activation=tf.nn.elu, kernel_initializer=initializer) X = tf.placeholder(tf.float32, [None, n_inputs]) hidden1 = my_dense_layer(X, n_hidden1) hidden2 = my_dense_layer(hidden1, n_hidden2) hidden3_mean = my_dense_layer(hidden2, n_hidden3, activation=None) hidden3_sigma = my_dense_layer(hidden2, n_hidden3, activation=None) noise = tf.random_normal(tf.shape(hidden3_sigma), dtype=tf.float32) hidden3 = hidden3_mean + hidden3_sigma * noise hidden4 = my_dense_layer(hidden3, n_hidden4) hidden5 = my_dense_layer(hidden4, n_hidden5) logits = my_dense_layer(hidden5, n_outputs, activation=None) outputs = tf.sigmoid(logits) xentropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits) reconstruction_loss = tf.reduce_sum(xentropy) eps = 1e-10 # smoothing term to avoid computing log(0) which is NaN latent_loss = 0.5 * tf.reduce_sum( tf.square(hidden3_sigma) + tf.square(hidden3_mean) - 1 - tf.log(eps + tf.square(hidden3_sigma))) loss = reconstruction_loss + latent_loss optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer() saver = tf.train.Saver() n_epochs = 2 # 用50 batch_size = 150 with tf.Session() as sess: init.run() for epoch in range(n_epochs): n_batches = mnist.train.num_examples // batch_size for iteration in range(n_batches): print("\r{}%".format(100 * iteration // n_batches), end="") sys.stdout.flush() X_batch, y_batch = mnist.train.next_batch(batch_size) sess.run(training_op, feed_dict={X: X_batch}) loss_val, reconstruction_loss_val, latent_loss_val = sess.run([loss, reconstruction_loss, latent_loss], feed_dict={X: X_batch}) print("\r{}".format(epoch), "Train total loss:", loss_val, "\tReconstruction loss:", reconstruction_loss_val, "\tLatent loss:", latent_loss_val) saver.save(sess, "./my_model_variational.ckpt")
2、加载模型:
import numpy as np n_digits = 60 with tf.Session() as sess: saver.restore(sess,"./my_model_variational.ckpt") #加载模型 codings_rnd = np.random.normal(size=[n_digits, n_hidden3]) outputs_val = outputs.eval(feed_dict={hidden3: codings_rnd}) n_rows = 6 n_cols = 10 plot_multiple_images(outputs_val.reshape(-1, 28, 28), n_rows, n_cols) save_fig("generated_digits_plot") plt.show()
-
Dex格式消亡史——最新Dex保护技术:流式编码.pdf
2021-08-11 21:49:30在该演讲中,曹阳为我们介绍了一种开创性的全新Dex保护方案,通过对字节码重新编码,自定义变长指令,而非以往的映射表形式。这种升级之后的DexVmp方案,使基于映射表完美还原代码成为历史。全新方式吸引了众多安全... -
[深度学习]降噪自动编码器
2017-10-27 15:18:05自动编码器基于这样一个事实:原始input(设为x)经过加权(W、b)、映射(Sigmoid)之后得到y,再对y反向加权映射回来成为z。 通过反复迭代训练两组(W、b),使得误差函数最小,即 尽可能保证z近似于x,即完美...转载于http://www.cnblogs.com/neopenx/p/4370350.html
起源:PCA、特征提取....
随着一些奇怪的高维数据出现,比如图像、语音,传统的统计学-机器学习方法遇到了前所未有的挑战。
数据维度过高,数据单调,噪声分布广,传统方法的“数值游戏”很难奏效。数据挖掘?已然挖不出有用的东西。
为了解决高维度的问题,出现的线性学习的PCA降维方法,PCA的数学理论确实无懈可击,但是却只对线性数据效果比较好。
于是,寻求简单的、自动的、智能的特征提取方法仍然是机器学习的研究重点。比如LeCun在1998年CNN总结性论文中就概括了今后机器学习模型的基本架构。
当然CNN另辟蹊径,利用卷积、降采样两大手段从信号数据的特点上很好的提取出了特征。对于一般非信号数据,该怎么办呢??
Part I 自动编码器(AutoEncoder)
自动编码器基于这样一个事实:原始input(设为x)经过加权(W、b)、映射(Sigmoid)之后得到y,再对y反向加权映射回来成为z。
通过反复迭代训练两组(W、b),使得误差函数最小,即尽可能保证z近似于x,即完美重构了x。
那么可以说正向第一组权(W、b)是成功的,很好的学习了input中的关键特征,不然也不会重构得如此完美。结构图如下:
从生物的大脑角度考虑,可以这么理解,学习和重构就好像编码和解码一样。
这个过程很有趣,首先,它没有使用数据标签来计算误差update参数,所以是无监督学习。
其次,利用类似神经网络的双隐层的方式,简单粗暴地提取了样本的特征。
这个双隐层是有争议的,最初的编码器确实使用了两组(W,b),但是Vincent在2010年的论文中做了研究,发现只要单组W就可以了。
即W'=WT, W和W’称为Tied Weights。实验证明,W'真的只是在打酱油,完全没有必要去做训练。
逆向重构矩阵让人想起了逆矩阵,若W-1=WT的话,W就是个正交矩阵了,即W是可以训成近似正交阵的。
由于W'就是个酱油,训练完之后就没它事了。正向传播用W即可,相当于为input预先编个码,再导入到下一layer去。所以叫自动编码器,而不叫自动编码解码器。
Part II 降噪自动编码器(Denoising Autoencoder)
Vincent在2008年的论文中提出了AutoEncoder的改良版——dA。推荐首先去看这篇paper。
论文的标题叫 "Extracting and Composing Robust Features",译成中文就是"提取、编码出具有鲁棒性的特征"
怎么才能使特征很鲁棒呢?就是以一定概率分布(通常使用二项分布)去擦除原始input矩阵,即每个值都随机置0, 这样看起来部分数据的部分特征是丢失了。
以这丢失的数据x'去计算y,计算z,并将z与原始x做误差迭代,这样,网络就学习了这个破损(原文叫Corruputed)的数据。
这个破损的数据是很有用的,原因有二:
其之一,通过与非破损数据训练的对比,破损数据训练出来的Weight噪声比较小。降噪因此得名。
原因不难理解,因为擦除的时候不小心把输入噪声给×掉了。
其之二,破损数据一定程度上减轻了训练数据与测试数据的代沟。由于数据的部分被×掉了,因而这破损数据
一定程度上比较接近测试数据。(训练、测试肯定有同有异,当然我们要求同舍异)。
这样训练出来的Weight的鲁棒性就提高了。图示如下:
关键是,这样胡乱擦除原始input真的很科学?真的没问题? Vincent又从大脑认知角度给了解释:
paper中这么说到:人类具有认知被阻挡的破损图像能力,此源于我们高等的联想记忆感受机能。
我们能以多种形式去记忆(比如图像、声音,甚至如上图的词根记忆法),所以即便是数据破损丢失,我们也能回想起来。
另外,就是从特征提取的流形学习(Manifold Learning)角度看:
破损的数据相当于一个简化的PCA,把特征做一个简单的降维预提取。
Part III 自动编码器的奇怪用法
自动编码器相当于创建了一个隐层,一个简单想法就是加在深度网络的开头,作为原始信号的初级filter,起到降维、提取特征的效果。
关于自动编码器取代PCA的基本用法,参考 http://www.360doc.com/content/15/0324/08/20625606_457576675.shtml
当然Bengio在2007年论文中仿照DBN较之于RBM做法:作为深度网络中各个layer的参数初始化值,而不是用随机小值。
即变成了Stacked AutoEncoder。
当然,这种做法就有一个问题,AutoEncoder可以看作是PCA的非线性补丁加强版,PCA的取得的效果是建立在降维基础上的。
仔细想想CNN这种结构,随着layer的推进,每层的神经元个数在递增,如果用了AutoEncoder去预训练,岂不是增维了?真的没问题?
paper中给出的实验结果认为AutoEncoder的增维效果还不赖,原因可能是非线性网络能力很强,尽管神经元个数增多,但是每个神经元的效果在衰减。
同时,随机梯度算法给了后续监督学习一个良好的开端。整体上,增维是利大于弊的。
Part IV 代码与实现
具体参考 http://deeplearning.net/tutorial/dA.html
有几个注意点说下:
①cost函数可以使用交错熵(Cross Entroy)设计,对于定义域在[0,1]这类的数据,交错熵可用来设计cost函数。
其中Logistic回归的似然函数求导结果可看作是交错熵的特例。参考 http://en.wikipedia.org/wiki/Cross_entropy
也可以使用最小二乘法设计。
②RandomStreams函数存在多个,因为要与非Tensor量相乘,必须用shared版本。
所以是 from theano.tensor.shared_randomstreamsimportRandomStreams
而不是 from theano.tensor import RandomStreams
-
Spring Boot - 自动配置实现原理
2021-05-16 11:54:26文章目录Pre@SpringBootApplication 注解@ComponentScan 注解@SpringBootConfiguration 注解@...Spring Boot 中的配置体系是一套强大而复杂的体系,其中最基础、最核心的要数自动配置(AutoConfiguration)机制了文章目录
Pre
Spring Boot 中的配置体系是一套强大而复杂的体系,其中最基础、最核心的要数自动配置(AutoConfiguration)机制了。
今天我们将围绕这个话题详细展开讨论,看看 Spring Boot 如何实现自动配置。那我们就先从 @SpringBootApplication 注解开始讲起。
@SpringBootApplication 注解
@SpringBootApplication 注解位于** spring-boot-autoconfigure** 工程的 org.springframework.boot.autoconfigure 包中,定义如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
相较一般的注解,@SpringBootApplication 注解显得有点复杂。我们可以
- 通过 exclude 和 excludeName 属性来配置不需要实现自动装配的类或类名,
- 也可以通过 scanBasePackages 和 scanBasePackageClasses 属性来配置需要进行扫描的包路径和类路径。
注意到 @SpringBootApplication 注解实际上是一个组合注解,它由三个注解组合而成,分别是 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan。
@ComponentScan 注解
@ComponentScan 注解不是 Spring Boot 引入的新注解,而是属于 Spring 容器管理的内容。@ComponentScan 注解就是扫描基于 @Component 等注解所标注的类所在包下的所有需要注入的类,并把相关 Bean 定义批量加载到容器中。显然,Spring Boot 应用程序中同样需要这个功能。
@SpringBootConfiguration 注解
@SpringBootConfiguration 注解比较简单,事实上它是一个空注解,只是使用了 Spring 中的 @Configuration 注解。@Configuration 注解比较常见,提供了 JavaConfig 配置类实现。
@EnableAutoConfiguration 注解
@EnableAutoConfiguration 注解是我们需要重点剖析的对象,下面进行重点展开。该注解的定义如下代码所示:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
这里我们关注两个新注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)。
@AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
从命名上讲,在这个注解中我们对该注解所在包下的类进行自动配置,而在实现方式上用到了 Spring 中的 @Import 注解。在使用 Spring Boot 时,@Import 也是一个非常常见的注解,可以用来动态创建 Bean。为了便于理解后续内容,这里有必要对 @Import 注解的运行机制做一些展开,该注解定义如下:
@Import
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { Class<?>[] value(); }
在 @Import 注解的属性中可以设置需要引入的类名,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)。根据该类的不同类型,Spring 容器针对 @Import 注解有以下四种处理方式:
- 如果该类实现了 ImportSelector 接口,Spring 容器就会实例化该类,并且调用其 selectImports 方法;
- 如果该类实现了 DeferredImportSelector 接口,则 Spring 容器也会实例化该类并调用其 selectImports方法。DeferredImportSelector 继承了 ImportSelector,区别在于 DeferredImportSelector 实例的 selectImports 方法调用时机晚于 ImportSelector 的实例,要等到 @Configuration 注解中相关的业务全部都处理完了才会调用;
- 如果该类实现了 ImportBeanDefinitionRegistrar 接口,Spring 容器就会实例化该类,并且调用其 registerBeanDefinitions 方法;
- 如果该类没有实现上述三种接口中的任何一个,Spring 容器就会直接实例化该类。
有了对 @Import 注解的基本理解,我们再来看 AutoConfigurationPackages.Registrar 类,定义如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
可以看到这个 Registrar 类实现了前面第三种情况中提到的 ImportBeanDefinitionRegistrar 接口并重写了 registerBeanDefinitions 方法,该方法中调用 AutoConfigurationPackages 自身的 register 方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition .getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); } }
这个方法的逻辑是先判断整个 Bean 有没有被注册,如果已经注册则获取 Bean 的定义,通过 Bean 获取构造函数的参数并添加参数值;如果没有,则创建一个新的 Bean 的定义,设置 Bean 的类型为 AutoConfigurationPackages 类型并进行 Bean 的注册。
@Import(AutoConfigurationImportSelector.class)
然后我们再来看 @EnableAutoConfiguration 注解中的 @Import(AutoConfigurationImportSelector.class) 部分,首先我们明确 AutoConfigurationImportSelector 类实现了 @Import 注解第二种情况中的 DeferredImportSelector 接口,所以会执行如下所示的 selectImports 方法:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); //获取 configurations 集合 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
看一下
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
这段代码的核心是通过 getCandidateConfigurations 方法获取 configurations 集合并进行过滤。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
Assert 校验,该校验是一个非空校验,会提示 “在 META-INF/spring.factories 中没有找到自动配置类” 这个异常信息。
看到这里,不得不提到 JDK 中的 SPI 机制,因为无论从 SpringFactoriesLoader 这个类的命名上,还是 META-INF/spring.factories 这个文件目录,两者之间都存在很大的相通性。
从类名上看,**AutoConfigurationImportSelector 类是一种选择器,负责从各种配置项中找到需要导入的具体配置类。**如下图所示
显然,AutoConfigurationImportSelector 所依赖的最关键组件就是 SpringFactoriesLoader,下面我们对其进行具体展开。
SPI 机制和 SpringFactoriesLoader
要想理解 SpringFactoriesLoader 类,我们首先需要了解 JDK 中 SPI(Service Provider Interface,服务提供者接口)机制。
JDK 中的 SPI 机制
JDK 提供了用于服务查找的一个工具类 java.util.ServiceLoader 来实现 SPI 机制。当服务提供者提供了服务接口的一种实现之后,我们可以在 jar 包的 META-INF/services/ 目录下创建一个以服务接口命名的文件,该文件里配置着一组 Key-Value,用于指定服务接口与实现该服务接口具体实现类的映射关系。而当外部程序装配这个 jar 包时,就能通过该 jar 包 META-INF/services/ 目录中的配置文件找到具体的实现类名,并装载实例化,从而完成模块的注入。
SPI 提供了一种约定,基于该约定就能很好地找到服务接口的实现类,而不需要在代码里硬编码指定。
JDK 中 SPI 机制开发流程如下图所示:
SpringFactoriesLoader
SpringFactoriesLoader 类似这种 SPI 机制,只不过以服务接口命名的文件是放在 META-INF/spring.factories 文件夹下,对应的 Key 为 EnableAutoConfiguration。SpringFactoriesLoader 会查找所有 META-INF/spring.factories 文件夹中的配置文件,并把 Key 为 EnableAutoConfiguration 所对应的配置项通过反射实例化为配置类并加载到容器中。
这一点我们可以在 SpringFactoriesLoader 的 loadSpringFactories 方法中进行印证:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
查看看 getSpringFactoriesLoaderFactoryClass()
protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
可知 Key 为 EnableAutoConfiguration
紧接着看
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
继续 loadSpringFactories
以下就是 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件片段,可以看到 EnableAutoConfiguration 项中包含了各式各样的配置项,这些配置项在 Spring Boot 启动过程中都能够通过 SpringFactoriesLoader 加载到运行时环境,从而实现自动化配置:
以上就是 Spring Boot 中基于 @SpringBootApplication 注解实现自动配置的基本过程和原理。当然,@SpringBootApplication 注解也可以基于外部配置文件加载配置信息。基于约定优于配置思想,Spring Boot 在加载外部配置文件的过程中大量使用了默认配置。
@ConditionalOn 系列条件注解
Spring Boot 默认提供了 100 多个 AutoConfiguration 类,显然我们不可能会全部引入。所以在自动装配时,系统会去类路径下寻找是否有对应的配置类。如果有对应的配置类,则按条件进行判断,决定是否需要装配。这里就引出了在阅读 Spring Boot 代码时经常会碰到的另一批注解,即 @ConditionalOn 系列条件注解。
我们先通过一个简单的示例来了解 @ConditionalOn 系列条件注解的使用方式,例如以下代码就是这类注解的一种典型应用,该代码位于 Spring Cloud Config 的客户端代码工程 spring-cloud-config-client 中:
@Bean @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true) public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) { ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator( properties); return locator; }
可以看到,这里运用了两个 @ConditionalOn 注解,一个是 @ConditionalOnMissingBean,一个是 @ConditionalOnProperty。
再比如在 Spring Cloud Config 的服务器端代码工程 spring-cloud-config-server 中,存在如下 ConfigServerAutoConfiguration 自动配置类:
@Configuration @ConditionalOnBean(ConfigServerConfiguration.Marker.class) @EnableConfigurationProperties(ConfigServerProperties.class) @Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class }) public class ConfigServerAutoConfiguration { }
这里我们运用了 @ConditionalOnBean 注解。实际上,Spring Boot 中提供了一系列的条件注解,常见的包括:
-
@ConditionalOnProperty:只有当所提供的属性属于 true 时才会实例化 Bean
-
@ConditionalOnBean:只有在当前上下文中存在某个对象时才会实例化 Bean
-
@ConditionalOnClass:只有当某个 Class 位于类路径上时才会实例化 Bean
-
@ConditionalOnExpression:只有当表达式为 true 的时候才会实例化 Bean
-
@ConditionalOnMissingBean:只有在当前上下文中不存在某个对象时才会实例化 Bean
-
@ConditionalOnMissingClass:只有当某个 Class 在类路径上不存在的时候才会实例化 Bean
-
@ConditionalOnNotWebApplication:只有当不是 Web 应用时才会实例化 Bean
当然 Spring Boot 还提供了一些不大常用的 @ConditionalOnXXX 注解,这些注解都定义在 org.springframework.boot.autoconfigure.condition 包中。
显然上述 ConfigServicePropertySourceLocator 类中只有在 “spring.cloud.config.enabled” 属性为 true(通过 matchIfMissing 配置项表示默认即为 true)以及类路径上不存在 ConfigServicePropertySourceLocator 时才会进行实例化。
而 ConfigServerAutoConfiguration 只有在类路径上存在 ConfigServerConfiguration.Marker 类时才会进行实例化,这是一种常用的自动配置控制技巧。
@ConditionalOn 系列条件注解的实现原理
@ConditionalOn 系列条件注解非常多,我们无意对所有这些组件进行展开。事实上这些注解的实现原理也大致相同,我们只需要深入了解其中一个就能做到触类旁通。这里我们挑选 @ConditionalOnClass 注解进行展开,该注解定义如下:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
可以看到, @ConditionalOnClass 注解本身带有两个属性,一个 Class 类型的 value,一个 String 类型的 name,所以我们可以采用这两种方式中的任意一种来使用该注解。同时 ConditionalOnClass 注解本身还带了一个 @Conditional(OnClassCondition.class) 注解。所以, ConditionalOnClass 注解的判断条件其实就包含在 OnClassCondition 这个类中。
OnClassCondition 是 SpringBootCondition 的子类,而 SpringBootCondition 又实现了Condition 接口。
Condition 接口只有一个 matches 方法,如下所示:
@FunctionalInterface public interface Condition { /** * Determine if the condition matches. * @param context the condition context * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class} * or {@link org.springframework.core.type.MethodMetadata method} being checked * @return {@code true} if the condition matches and the component can be registered, * or {@code false} to veto the annotated component's registration */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
SpringBootCondition 中的 matches 方法实现如下:
这里的 getClassOrMethodName 方法获取被添加了@ConditionalOnClass 注解的类或者方法的名称,而 getMatchOutcome 方法用于获取匹配的输出。我们看到 getMatchOutcome 方法实际上是一个抽象方法,需要交由 SpringBootCondition 的各个子类完成实现,这里的子类就是 OnClassCondition 类。
在理解 OnClassCondition 时,我们需要明白在 Spring Boot 中,@ConditionalOnClass 或者 @ConditionalOnMissingClass 注解对应的条件类都是 OnClassCondition,所以在 OnClassCondition 的 getMatchOutcome 中会同时处理两种情况。这里我们挑选处理 @ConditionalOnClass 注解的代码,核心逻辑如下所示:
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader); if (!missing.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) .didNotFind("required class", "required classes").items(Style.QUOTE, missing)); } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes") .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader)); } List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader); if (!present.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class) .found("unwanted class", "unwanted classes").items(Style.QUOTE, present)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class) .didNotFind("unwanted class", "unwanted classes") .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader)); } return ConditionOutcome.match(matchMessage); }
这里有两个方法值得注意,一个是 getCandidates 方法,一个是 getMatches 方法。首先通过 getCandidates 方法获取了 ConditionalOnClass 的 name 属性和 value 属性。然后通过 getMatches 方法将这些属性值进行比对,得到这些属性所指定的但在类加载器中不存在的类。如果发现类加载器中应该存在但事实上又不存在的类,则返回一个匹配失败的 Condition;反之,如果类加载器中存在对应类的话,则把匹配信息进行记录并返回一个 ConditionOutcome。
小结
自动配置是 Spring Boot 最核心和最基本的功能,而 @SpringBootApplication 注解又是 Spring Boot 应用程序的入口。本课时从 @SpringBootApplication 注解入手,分析了自动配置机制的实现过程。涉及的知识点比较多,包含 JDK 中的 SPI 机制,以及 @ConditionalOn 系列条件注解。
-
Python中的映射类型详解
2020-07-18 13:50:11泛映射类型 collections.abc模块中有Mapping和MutableMapping这两个抽象基类,它们的作用事为dict和其他类似的类型定义形式接口 非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对dict或者是collections.... -
特征工程之One-Hot编码、label-encoding、自定义编码
2022-01-26 17:43:34目录 One-Hot编码 代码实现 ...到目前为止,表示分类变量最常用的方法就是使用 one-hot 编码(one-hot-encoding)或N 取一编码(one-out-of-N encoding), 也叫虚拟变量(dummy variable)。 虚拟变 -
去噪自动编码机
2016-12-04 20:26:39去噪自动编码机(Denoising Autoencoder) 自动编码机的主要作用是数据降维,提取特征。 原始输入xx 经过加权 (W,b)(W,b),映射函数(如:非线性函数Sigmoid)之后得到yy,在对yy反向加权映射回来成为zz。通过反复... -
ADASISv3简述,自动驾驶怎么进行地图数据传输?
2021-01-14 23:27:18ADASIS(Advanced Driver Assistance Systems Interface Specification)直译过来就是 ADAS 接口规格,它要负责的东西其实很简单,就是为自动驾驶车辆提供前方道路交通相关的数据,这些数据被抽象成一个标准化的概念... -
-
-
字符编码那些事--彻底理解掌握编码知识
2020-05-04 16:42:33每一个程序员都不可避免的遇到字符编码的问题,很多人在字符编码方面同样遇到不少问题,而且一直对各种编码懵懵懂懂、不清不楚。这篇文章就是针对字符编码中的一些问题进行了详细的阐述,能从根本上理解字符编码。 -
C#内存映射文件学习总结
2017-05-05 22:43:54C#内存映射文件学习 http://www.cnblogs.com/flyant/p/4443187.html 内存映射文件是由一个文件到进程地址空间的映射。 C#提供了允许应用程序把文件映射到一个进程的函(MemoryMappedFile.CreateOrOpen)... -
深度学习模型---稀疏编码 Sparse Coding
2017-10-26 09:59:35根据前面的的描述,稀疏编码是有一个明显的局限性的,这就是即使已经学习得到一组基向量,如果为了对新的数据样本进行“编码”,我们必须再次执行优化过程来得到所需的系数a。这意味着在测试中,实现稀疏编码 需要... -
一文弄懂字符串编码
2019-07-29 19:18:04但对于变长编码(例如UTF-8和UTF-16)来说,该映射比较复杂,把一些码点映射到一个码元,把另外一些码点映射到由多个码元组成的序列。 编码空间(code space) 编码空间指的是一个编码集中码点的范围, 例如 Unicode ... -
图像处理总复习6、图像编码
2021-08-07 08:32:22图像数据压缩的可能性 数据冗余 图像压缩的目的 图像数据压缩技术的重要指标 图像数据压缩的应用领域 图像编码中的保真度准则 信息论基础 图像压缩模型 1.为什么要进行图像压缩? 数字图像通常要求很大的比特数,这... -
Unicode字符编码标准
2018-08-16 16:46:051. 编码知识 1.1 文本和字符 在计算机程序中或者数据文件里,文本(text)是作为数字序列存储的。序列中的数字是具有不同大小、取值和解释的整数。如何解释这些整数是由字符集(character set)、编码(encoding... -
万万字详解自动驾驶、车路协同中的高精度地图
2022-04-01 14:00:52自动驾驶现在是否需要高精地图?特斯拉的纯视觉更强吗? - 知乎 (zhihu.com) 本站文章引用或转载写明来源,感谢原作者的辛苦写作,如果有异议或侵权,及时联系我处理,谢谢! 更多车路协同,车路协同建设解决方案... -
图解通信原理与案例分析-8:以太网通信案例及其物理层工作原理深入剖析--物理层编码
2020-08-26 11:53:11前言: 以太网是一种计算机局域网通信技术,主要由介质访问层(MAC L2) 协议、物理层(PHY L1)协议、电子信号连接组成。MAC层主要有交换芯片实现,物理层由PHY芯片实现,...信源信息发送-》离散数据-》信源编码-》应. -
机器学习模型可解释性的详尽介绍
2019-11-20 17:30:00机器之心平台来源:腾讯技术工程模型可解释性方面的研究,在近两年的科研会议上成为关注热点,因为大家不仅仅满足于模型的效果,更对模型效果的原因产生更多的思考,这样的思考有助于... -
系统学习深度学习(二) --自编码器,DA算法,SDA,稀疏自编码器
2017-01-10 13:21:569.1、AutoEncoder自动编码器 Deep Learning最简单的一种方法是利用人工神经网络的特点,人工神经网络(ANN)本身就是具有层次结构的系统,如果给定一个神经网络,我们假设其输出与输入是相同的,然后训练调整其... -
Transformer 详解(上) — 编码器【附pytorch代码实现】
2021-04-12 20:46:51Transformer 详解(上)编码器Transformer结构文本嵌入层位置编码注意力机制编码器之多头注意力机制层编码器之前馈全连接层规范化层和残差连接代码实现Transfomer参考文献 Transformer结构 论文: Attention is All ... -
JAVAWEB开发之mybatis详解(二)——高级映射、查询缓存、mybatis与Spring整合以及懒加载的配置和逆向工程
2017-06-13 09:46:27mybatis是一个持久层框架,是Apache下的开源项目,前身是ibatis,是一个不完全的ORM框架,mybatis提供输入和输出的映射,需要程序员自己手动写SQL语句,mybatis重点对SQL语句进行灵活操作。适用场合:需求变化频繁,... -
grails之对象关系映射GORM
2017-04-28 15:52:56GORM是Grails的对象关系映射(ORM)的实现,实际上它使用的是Hibernate3(非常流行和灵活的开源ORM解决方案),但因为有Groovy的动态特性支持,因此GORM既支持动态类型也支持静态类型,再加上Grails的规约,现 -
JAVAWEB开发之Servlet Filter(过滤器)详解包括post和get编码过滤器、URL访问权限控制、自动登录。...
2017-02-26 00:03:35将FirstFilter的标签对放置在SecondFilter的映射标签对的前面 重启服务器 访问/demo (3)演示Filter类中init方法中FilterConfig的使用 创建Demo2Filter package cn.itcast.web.filter; import ... -
常见音频编码格式解析
2017-12-15 11:22:06常见音频编码格式解析常见音频编码格式解析 MP3编码格式 1MP3概述 2MPEG音频压缩基础 3MPEG Layer3编解码的基本原理 4整个MP3文件结构 41ID3V2 42音频数据帧 43ID3v1 AAC编码格式 1AAC概述 2AAC扩展名 3AAC规格 4AAC... -
麦克风阵列技术 二 (自动增益控制 自动噪声抑制 回声消除 语音活动检测)
2019-08-28 15:18:58麦克风阵列技术麦克风阵列技术详解自动增益控制自动增益处理分类AGC环自动噪声抑制原理及实现噪声噪声抑制的一般流程回声消除回声传统回声消除声学回声消除原理及实现语音活动检测音频端点检测原理及实现 ... -
理解S12(X)架构中的地址映射方案
2017-09-28 18:49:08在一个S12或S12X架构中,很有必要分清楚两种类型的内存地址:banked和non-banked。这篇文档描述了应该怎么样正确的访问某个内存地址,同时还较详细地描述了CodeWarrior的链接器是怎么把你的代码分配在这两种地址中的... -
H264编码简介
2019-05-11 08:38:38H264编码简介 H.264,同时也是MPEG-4第十部分,是由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。H.264 不仅... -
跟着小程学微服务-Mock自动化系统的原理及实现
2017-06-02 13:18:03在之前的文章 http://blog.csdn.net/u013970991/article/details/54862772 中已经介绍了“自动化Mock系统0.9版本”,今天我将和大家一起探讨我们的“自动化Mock系统1.0版本”。 二、测试人员面临的测试问题 我公司...