分享

The Neural Network Zoo

 _为往圣继绝学_ 2016-09-24

本文翻译自http://www./neural-network-zoo/

其中部分代码源于Keras开源社区
本文不得用于任何形式的商业用途,如果需要转载请与作者:SCP-173 联系,如果发现未经允许复制转载,将保留追求其法律责任的权利。
Keras中文文档地址



新的神经网络架构随时随地都在出现,要时刻保持最新还有点难度。要把所有这些缩略语指代的网络(DCIGN,IiLSTM,DCGAN,知道吗?)都弄清,一开始估计还无从下手。

因此,我决定弄一个“作弊表”。这些图里面话的大多数都是神经网络,可也有一些是完全不同的物种。尽管所有这些架构都是新奇独特的,但当我开始把它们画下来的时候,每种架构的底层关系逐渐清晰。

一个问题是要把它们画成节点图:实际上这并没有展示出它们是如何被使用的。比如说,VAE 看起来跟 AE 差不都,但这两种网络的训练过程其实大不一样。训练好的网络的使用方法就更不同了,因为 VAE 是生成器,在新样本中插入噪音的,而 AE 则仅仅是将它们得到的输入映射到它们“记忆”中最近的训练样本!需要说明的是,这个图谱并没有清晰呈现不同节点内在工作原理(那个留做后话)。

要编一份完全的名单尤其困难,因为新的架构随时都在出现。即使是发表过的架构,有意识地去把它们都找全也有很大麻烦,要不就是有时候会落下一些。因此,虽说这幅图会为你提供一些见解,但可千万别认为这幅图里的内容就是全部了。

图中所描绘的每种架构,我都配上了非常非常简短的说明,希望有用。


1.前向传播网络(FF 或 MLP)


非常直接,它们从前往后传输信息(分别是输入和输出)。神经网络通常都有很多层,包括输入层、隐藏层、输出层。单独一层不会有连接,一般相邻的两层是全部相连的(每一层的每个神经元都与另一层的每个神经元相连)。最简单,从某种意义上说也是最实用的网络结构,有两个输入单元,一个输出单元,可以用来为逻辑关口建模。FFNN 通常用反向传播算法训练,因为网络会将“进来的”和“我们希望出来的”两个数据集配对。这也被称为监督学习,相对的是无监督学习,在无监督学习的情况下,我们只负责输入,由网络自己负责输出。由反向传播算法得出的误差通常是在输入和输出之间差别的变化(比如 MSE 或线性差)。由于网络有足够多的隐藏层,从理论上说对输入和输出建模总是可能的。实际上,它们的使用范围非常有限,但正向传播网络与其他网络结合在一起会形成十分强大的网络。

FF or MLP

  1. # MLP model
  2. from keras.models import Model
  3. from keras.layers import Input, Dense
  4. def mlp(nb_input, hidden_layers):
  5. # nb_input is the shape of input;
  6. # hidden_layers is a list like [200, 200, 30]
  7. # The structrue is described as <input-->[200]-->[200]-->[30]-->output>
  8. nb_hidden = len(hidden_layers)
  9. input = Input(shape=(nb_input,))
  10. mod = input
  11. for i in range(nb_hidden):
  12. mod = Dense(hidden_layers[i])(mod)
  13. model = Model(input=input, output=mod)
  14. return model

2.径向基函数网络(RBF)


是以径向基函数作为激活函数的 FFNN。RBF 就是这样简单。但是,这并不说它们没有用,只是用其他函数作为激活函数的 FFNN 一般没有自己单独的名字。要有自己的名字,得遇上好时机才行。

3.Hopfied 网络(HN)


所有的神经元都与另外的神经元相连;每个节点功能都一样。在训练前,每个节点都是输入;在训练时,每个节点都隐藏;在训练后,每个节点都是输出。训练 HN 的方法是将每个神经元的值设定为理想的模式,然后计算权重。这之后权重不会发生改变。一旦接收了训练,网络总会变成之前被训练成的模式,因为整个网络只有在这些状态下才能达到稳定。需要注意的是,HN 不会总是与理想的状态保持一致。网络稳定的部分原因在于总的“能量”或“温度”在训练过程中逐渐缩小。每个神经元都有一个被激活的阈值,随温度发生变化,一旦超过输入的总合,就会导致神经元变成两个状态中的一个(通常是 -1 或 1,有时候是 0 或 1)。更新网络可以同步进行,也可以依次轮流进行,后者更为常见。当轮流更新网络时,一个公平的随机序列会被生成,每个单元会按照规定的次序进行更新。因此,当每个单元都经过更新而且不再发生变化时,你就能判断出网络是稳定的(不再收敛)。这些网络也被称为联存储器,因为它们会收敛到与输入最相似的状态;当人类看到半张桌子的时候,我们会想象出桌子的另一半,如果输入一半噪音、一半桌子,HN 将收敛成一张桌子。

4. 马尔科夫链(MC或离散时间马尔科夫链,DTMC)


是 BM 和 HN 的前身。可以这样理解 DTMC:从我现在这个节点出发,达到相邻节点的几率有多大?它们是没有记忆的,也即你的每一个状态都完全取决于之前的状态。虽然 DTMC 不是一个真正的神经网络,他们却有与神经网络相似的性质,也构成了 BM 和 HN 的理论基础。

5. 玻尔兹曼机(BM)


和 HN 十分相似,但有些神经元被标记为输入神经元,其他的神经元继续保持“隐藏”。输入神经元在网络整体更新后会成为输入神经元。一开始权重是随机的,通过反向传播算法,或者通过最近出现的对比散度(用马尔科夫链决定两个获得信息之间的梯度)。相较于 HN,BM 的神经元有时候会呈现二元激活模式,但另一些时候则是随机的。BM 的训练和运行过程与 HN 十分相似:将输入神经元设定为固定值,然后任网络自己变化。反复在输入神经元和隐藏神经元之间来回走动,最终网络会在温度恰当时达到平衡。

6. 受限玻尔兹曼机(RBM)


与 BM 十分相似(意外吧),因此也与 HN 十分相似。BM 与 RBM 最大的不同在于 RBM 因为受限所以实用性更大。RBM 的输入神经元不与其他输入神经元直接相连,隐藏神经元之间也不存在直接的连接。RBM 可以像 FFNN 一样训练:不采用将信息往前传输然后反向传播的方法,你将信息往前传,然后将输出作为输入(回到第一层)。这之后就可以用双向传播算法训练网络了。

RBM

  1. # RBM model
  2. from __future__ import division
  3. import numpy as np
  4. from keras import initializations, regularizers, constraints
  5. from keras import backend as K
  6. from keras.layers.core import Layer, Dense
  7. from .backend import random_binomial
  8. import theano
  9. class RBM(Layer):
  10. """
  11. Restricted Boltzmann Machine (RBM).
  12. """
  13. # keras.core.Layer part (modified from keras.core.Dense)
  14. # ------------------------------------------------------
  15. def __init__(self, input_dim, hidden_dim, init='glorot_uniform', weights=None, name=None,
  16. W_regularizer=None, bx_regularizer=None, bh_regularizer=None, #activity_regularizer=None,
  17. W_constraint=None, bx_constraint=None, bh_constraint=None):
  18. super(RBM, self).__init__()
  19. self.init = initializations.get(init)
  20. self.input_dim = input_dim
  21. self.hidden_dim = hidden_dim
  22. self.input = K.placeholder(ndim = 2)
  23. self.W = self.init((self.input_dim, self.hidden_dim))
  24. self.bx = K.zeros((self.input_dim))
  25. self.bh = K.zeros((self.hidden_dim))
  26. self.params = [self.W, self.bx, self.bh]
  27. self.regularizers = []
  28. self.W_regularizer = regularizers.get(W_regularizer)
  29. if self.W_regularizer:
  30. self.W_regularizer.set_param(self.W)
  31. self.regularizers.append(self.W_regularizer)
  32. self.bx_regularizer = regularizers.get(bx_regularizer)
  33. if self.bx_regularizer:
  34. self.bx_regularizer.set_param(self.bx)
  35. self.regularizers.append(self.bx_regularizer)
  36. self.bh_regularizer = regularizers.get(bh_regularizer)
  37. if self.bh_regularizer:
  38. self.bh_regularizer.set_param(self.bh)
  39. self.regularizers.append(self.bh_regularizer)
  40. self.W_constraint = constraints.get(W_constraint)
  41. self.bx_constraint = constraints.get(bx_constraint)
  42. self.bh_constraint = constraints.get(bh_constraint)
  43. self.constraints = [self.W_constraint, self.bx_constraint, self.bh_constraint]
  44. if weights is not None:
  45. self.set_weights(weights)
  46. if name is not None:
  47. self.set_name(name)
  48. def set_name(self, name):
  49. self.W.name = '%s_W' % name
  50. self.bx.name = '%s_bx' % name
  51. self.bh.name = '%s_bh' % name
  52. @property
  53. def nb_input(self):
  54. return 1
  55. @property
  56. def nb_output(self):
  57. return 0 # RBM has no output, use get_h_given_x_layer(), get_x_given_h_layer() instead
  58. def get_input(self, train=False):
  59. return self.input
  60. def get_output(self, train=False):
  61. return None # RBM has no output, use get_h_given_x_layer(), get_x_given_h_layer() instead
  62. def get_config(self):
  63. return {"name": self.__class__.__name__,
  64. "input_dim": self.input_dim,
  65. "hidden_dim": self.hidden_dim,
  66. "init": self.init.__name__,
  67. "W_regularizer": self.W_regularizer.get_config() if self.W_regularizer else None,
  68. "bx_regularizer": self.bx_regularizer.get_config() if self.bx_regularizer else None,
  69. "bh_regularizer": self.bh_regularizer.get_config() if self.bh_regularizer else None,
  70. #"activity_regularizer": self.activity_regularizer.get_config() if self.activity_regularizer else None,
  71. "W_constraint": self.W_constraint.get_config() if self.W_constraint else None,
  72. "bx_constraint": self.bx_constraint.get_config() if self.bx_constraint else None,
  73. "bh_constraint": self.bh_constraint.get_config() if self.bh_constraint else None}
  74. # persistence, copied from keras.models.Sequential
  75. def save_weights(self, filepath, overwrite=False):
  76. # Save weights to HDF5
  77. import h5py
  78. import os.path
  79. # if file exists and should not be overwritten
  80. if not overwrite and os.path.isfile(filepath):
  81. import sys
  82. get_input = input
  83. if sys.version_info[:2] <= (2, 7):
  84. get_input = raw_input
  85. overwrite = get_input('[WARNING] %s already exists - overwrite? [y/n]' % (filepath))
  86. while overwrite not in ['y', 'n']:
  87. overwrite = get_input('Enter "y" (overwrite) or "n" (cancel).')
  88. if overwrite == 'n':
  89. return
  90. print('[TIP] Next time specify overwrite=True in save_weights!')
  91. f = h5py.File(filepath, 'w')
  92. weights = self.get_weights()
  93. f.attrs['nb_params'] = len(weights)
  94. for n, param in enumerate(weights):
  95. param_name = 'param_{}'.format(n)
  96. param_dset = f.create_dataset(param_name, param.shape, dtype=param.dtype)
  97. param_dset[:] = param
  98. f.flush()
  99. f.close()
  100. # -------------
  101. # RBM internals
  102. # -------------
  103. def free_energy(self, x):
  104. """
  105. Compute free energy for Bernoulli RBM, given visible units.
  106. The marginal probability p(x) = sum_h 1/Z exp(-E(x, h)) can be re-arranged to the form
  107. p(x) = 1/Z exp(-F(x)), where the free energy F(x) = -sum_j=1^H log(1 + exp(x^T W[:,j] + bh_j)) - bx^T x,
  108. in case of the Bernoulli RBM energy function.
  109. """
  110. wx_b = K.dot(x, self.W) + self.bh
  111. hidden_term = K.sum(K.log(1 + K.exp(wx_b)), axis=1)
  112. vbias_term = K.dot(x, self.bx)
  113. return -hidden_term - vbias_term
  114. def sample_h_given_x(self, x):
  115. """
  116. Draw sample from p(h|x).
  117. For Bernoulli RBM the conditional probability distribution can be derived to be
  118. p(h_j=1|x) = sigmoid(x^T W[:,j] + bh_j).
  119. """
  120. h_pre = K.dot(x, self.W) + self.bh # pre-sigmoid (used in cross-entropy error calculation for better numerical stability)
  121. h_sigm = K.sigmoid(h_pre) # mean of Bernoulli distribution ('p', prob. of variable taking value 1), sometimes called mean-field value
  122. h_samp = random_binomial(shape=h_sigm.shape, n=1, p=h_sigm)
  123. # random sample
  124. # \hat{h} = 1, if p(h=1|x) > uniform(0, 1)
  125. # 0, otherwise
  126. # pre and sigm are returned to compute cross-entropy
  127. return h_samp, h_pre, h_sigm
  128. def sample_x_given_h(self, h):
  129. """
  130. Draw sample from p(x|h).
  131. For Bernoulli RBM the conditional probability distribution can be derived to be
  132. p(x_i=1|h) = sigmoid(W[i,:] h + bx_i).
  133. """
  134. x_pre = K.dot(h, self.W.T) + self.bx # pre-sigmoid (used in cross-entropy error calculation for better numerical stability)
  135. x_sigm = K.sigmoid(x_pre) # mean of Bernoulli distribution ('p', prob. of variable taking value 1), sometimes called mean-field value
  136. x_samp = random_binomial(shape=x_sigm.shape, n=1, p=x_sigm)
  137. # random sample
  138. # \hat{x} = 1, if p(x=1|h) > uniform(0, 1)
  139. # 0, otherwise
  140. # pre and sigm are returned to compute cross-entropy
  141. return x_samp, x_pre, x_sigm
  142. def gibbs_xhx(self, x0):
  143. """
  144. Perform one step of Gibbs sampling, starting from visible sample.
  145. h1 ~ p(h|x0)
  146. x1 ~ p(x|h1)
  147. """
  148. h1, h1_pre, h1_sigm = self.sample_h_given_x(x0)
  149. x1, x1_pre, x1_sigm = self.sample_x_given_h(h1)
  150. # pre and sigm are returned to compute cross-entropy
  151. return x1, x1_pre, x1_sigm
  152. def mcmc_chain(self, x, nb_gibbs_steps):
  153. """
  154. Perform Markov Chain Monte Carlo, run k steps of Gibbs sampling,
  155. starting from visible data, return point estimate at end of chain.
  156. x0 (data) -> h1 -> x1 -> ... -> xk (reconstruction, negative sample)
  157. """
  158. xi = x
  159. for i in xrange(nb_gibbs_steps):
  160. xi, xi_pre, xi_sigm = self.gibbs_xhx(xi)
  161. x_rec, x_rec_pre, x_rec_sigm = xi, xi_pre, xi_sigm
  162. x_rec = theano.gradient.disconnected_grad(x_rec) # avoid back-propagating gradient through the Gibbs sampling
  163. # this is similar to T.grad(.., consider_constant=[chain_end])
  164. # however, as grad() is called in keras.optimizers.Optimizer,
  165. # we do it here instead to avoid having to change Keras' code
  166. return x_rec, x_rec_pre, x_rec_sigm
  167. def contrastive_divergence_loss(self, nb_gibbs_steps=1):
  168. """
  169. Compute contrastive divergence loss with k steps of Gibbs sampling (CD-k).
  170. Result is a Theano expression with the form loss = f(x).
  171. """
  172. def loss(x):
  173. x_rec, _, _ = self.mcmc_chain(x, nb_gibbs_steps)
  174. cd = K.mean(self.free_energy(x)) - K.mean(self.free_energy(x_rec))
  175. return cd
  176. return loss
  177. def reconstruction_loss(self, nb_gibbs_steps=1):
  178. """
  179. Compute binary cross-entropy between the binary input data and the reconstruction generated by the model.
  180. Result is a Theano expression with the form loss = f(x).
  181. Useful as a rough indication of training progress (see Hinton2010).
  182. Summed over feature dimensions, mean over samples.
  183. """
  184. def loss(x):
  185. _, pre, _ = self.mcmc_chain(x, nb_gibbs_steps)
  186. # NOTE:
  187. # when computing log(sigmoid(x)) and log(1 - sigmoid(x)) of cross-entropy,
  188. # if x is very big negative, sigmoid(x) will be 0 and log(0) will be nan or -inf
  189. # if x is very big positive, sigmoid(x) will be 1 and log(1-0) will be nan or -inf
  190. # Theano automatically rewrites this kind of expression using log(sigmoid(x)) = -softplus(-x), which
  191. # is more stable numerically
  192. # however, as the sigmoid() function used in the reconstruction is inside a scan() operation, Theano
  193. # doesn't 'see' it and is not able to perform the change; as a work-around we use pre-sigmoid value
  194. # generated inside the scan() and apply the sigmoid here
  195. #
  196. # NOTE:
  197. # not sure how important this is; in most cases seems to work fine using just T.nnet.binary_crossentropy()
  198. # for instance; keras.objectives.binary_crossentropy() simply clips the value entering the log(); and
  199. # this is only used for monitoring, not calculating gradient
  200. cross_entropy_loss = -T.mean(T.sum(x*T.log(T.nnet.sigmoid(pre)) + (1 - x)*T.log(1 - T.nnet.sigmoid(pre)), axis=1))
  201. return cross_entropy_loss
  202. return loss
  203. def free_energy_gap(self, x_train, x_test):
  204. """
  205. Computes the free energy gap between train and test set, F(x_test) - F(x_train).
  206. In order to avoid overfitting, we cannot directly monitor if the probability of held out data is
  207. starting to decrease, due to the partition function.
  208. We can however compute the ratio p(x_train)/p(x_test), because here the partition functions cancel out.
  209. This ratio should be close to 1, if it is > 1, the model may be overfitting.
  210. The ratio can be compute as,
  211. r = p(x_train)/p(x_test) = exp(-F(x_train) + F(x_test)).
  212. Alternatively, we compute the free energy gap,
  213. gap = F(x_test) - F(x_train),
  214. where F(x) indicates the mean free energy of test data and a representative subset of
  215. training data respectively.
  216. The gap should around 0 normally, but when it starts to grow, the model may be overfitting.
  217. However, even when the gap is growing, the probability of the training data may be growing even faster,
  218. so the probability of the test data may still be improving.
  219. See: Hinton, "A Practical Guide to Training Restricted Boltzmann Machines", UTML TR 2010-003, 2010, section 6.
  220. """
  221. return T.mean(self.free_energy(x_train)) - T.mean(self.free_energy(x_test))
  222. def get_h_given_x_layer(self, as_initial_layer=False):
  223. """
  224. Generates a new Dense Layer that computes mean of Bernoulli distribution p(h|x), ie. p(h=1|x).
  225. """
  226. if as_initial_layer:
  227. layer = Dense(input_dim=self.input_dim, output_dim=self.hidden_dim, activation='sigmoid', weights=[self.W.get_value(), self.bh.get_value()])
  228. else:
  229. layer = Dense(output_dim=self.hidden_dim, activation='sigmoid', weights=[self.W.get_value(), self.bh.get_value()])
  230. return layer
  231. def get_x_given_h_layer(self, as_initial_layer=False):
  232. """
  233. Generates a new Dense Layer that computes mean of Bernoulli distribution p(x|h), ie. p(x=1|h).
  234. """
  235. if as_initial_layer:
  236. layer = Dense(input_dim=self.hidden_dim, output_dim=self.input_dim, activation='sigmoid', weights=[self.W.get_value().T, self.bx.get_value()])
  237. else:
  238. layer = Dense(output_dim=self.input_dim, activation='sigmoid', weights=[self.W.get_value().T, self.bx.get_value()])
  239. return layer

7. 自编码器(AE)


跟 FFNN 有些类似,它只是 FFNN 的一种不同的用法,称不上是从本质上与 FFNN 不同的另一种网络。AE 的外观看起来像沙漏,输入和输出比隐藏层大。AE 也沿中间层两边对称。最小的层总是在中间,这里也是信息压缩得最密集的地方。从开始到中间被称为编码部分,中间到最后被称为解码部分,中间(意外吧)被称为代码。你可以使用反向传播算法训练 AE。AE 两边是对称的,因此编码权重和解码权重也是相等的。

  1. # AE model
  2. import keras.backend as K
  3. from keras.layers import Input, Dense, Lambda, Dropout
  4. from keras.layers.noise import GaussianNoise
  5. from keras.models import Model
  6. from keras import regularizers
  7. import numpy as np
  8. def noise_output_shape(input_shape):
  9. return tuple(input_shape)
  10. def gaussian_noise(x, mean=0.0, std=0.1, random_state=1234):
  11. return x + K.random_normal(K.shape(x), mean=mean, std=std, seed=random_state)
  12. def AutoEncoder(input_dim, encoding_dim, add_noise=None, dropout_proba=None, l1=1e-4):
  13. model_input = Input(shape=(input_dim,))
  14. if add_noise is not None:
  15. x = Lambda(add_noise, output_shape=noise_output_shape)(model_input)
  16. else:
  17. x = model_input
  18. if l1 is not None:
  19. encoded = Dense(encoding_dim, activation='relu',
  20. activity_regularizer=regularizers.activity_l1(l1))(x)
  21. else:
  22. encoded = Dense(encoding_dim, activation='relu')(x)
  23. if dropout_proba:
  24. encoded = Dropout(dropout_proba)(encoded)
  25. decoded = Dense(input_dim, activation='sigmoid')(encoded)
  26. AE = Model(input=model_input, output=decoded)
  27. AE.compile(optimizer='adadelta',
  28. loss='binary_crossentropy',
  29. metrics=['accuracy'])
  30. return AE

8. 稀疏自编码器(SAE)


在某种程度上与 AE 相反。我们没有让网络在更少的“空间”或节点上表征一堆信息,而是将信息编码在更多的空间中。因此,网络不是在中间收敛,而是在中间膨胀。这种类型的网络可以被用来从一个数据集中提取很多小的特征。如果你使用训练 AE 的方法训练 SAE,最终你将会无一例外得到一个没有用的、跟输入一模一样的网络。因此,在反馈输入时要增加一个稀疏驱动器(sparsity driver),这样只有一定的误差才会被输送回去得到训练,其他的误差都“无关”,重新归零。在某种意义上,这就像脉冲神经网络一样,不是所有的神经元都在同一时间发射。

9. 变分自编码器(VAE)


和 AE 拥有同样的架构,但“被教授”的东西却不同:输入样本的近似概率分布。这有点回到本源的感觉,因为它们和 BM 及 RBM 的联系更紧密一点。但它们确实依赖于贝叶斯数学来处理概率推理和独立(probabilistic inference and independence),以及依靠重新参数化(re-parametrisation)来实现这种不同的表征。这种推理和独立部件理解起来很直观,但它们或多或少依赖于复杂的数学。其基础可以归结为:将影响考虑在内。如果某种事物在一个位置发生,而其它地方则发生其它事物,那么它们不一定是相关的。如果它们不相关,那么误差传播应该考虑一下这一点。这是一种有用的方法,因为神经网络是大型的图(graph,从某种角度来看),所以在深入到更深的层时如果排除掉一些节点对其它节点的影响,就会带来帮助。

VAE

  1. from __future__ import division
  2. from keras.layers import Input, Dense, Activation, Merge
  3. from keras.models import Model, Sequential
  4. import keras.backend as K
  5. from probability_distributions import GaussianDistribution, BernoulliDistribution, CategoricalDistribution
  6. from custom_batchnormalization import CustomBatchNormalization
  7. class VAE(object):
  8. def __init__(self, in_dim=50, cat_dim=10, hid_dim=300, z_dim=50, alpha=0):
  9. self.in_dim = in_dim
  10. self.cat_dim = cat_dim
  11. self.hid_dim = hid_dim
  12. self.z_dim = z_dim
  13. self.alpha = alpha
  14. self.x_l = Input((self.in_dim, ))
  15. self.x_u = Input((self.in_dim, ))
  16. self.y_l = Input((self.cat_dim, ))
  17. y_u0 = Input((self.cat_dim, ))
  18. y_u1 = Input((self.cat_dim, ))
  19. y_u2 = Input((self.cat_dim, ))
  20. y_u3 = Input((self.cat_dim, ))
  21. y_u4 = Input((self.cat_dim, ))
  22. y_u5 = Input((self.cat_dim, ))
  23. y_u6 = Input((self.cat_dim, ))
  24. y_u7 = Input((self.cat_dim, ))
  25. y_u8 = Input((self.cat_dim, ))
  26. y_u9 = Input((self.cat_dim, ))
  27. self.y_u = [y_u0, y_u1, y_u2, y_u3, y_u4, y_u5, y_u6, y_u7, y_u8, y_u9]
  28. self.z = Input((self.z_dim, ))
  29. ###############
  30. # q(z | x, y) #
  31. ###############
  32. x_branch = Sequential()
  33. x_branch.add(Dense(self.hid_dim, input_dim=self.in_dim))
  34. x_branch.add(CustomBatchNormalization())
  35. x_branch.add(Activation('softplus'))
  36. y_branch = Sequential()
  37. y_branch.add(Dense(self.hid_dim, input_dim=self.cat_dim))
  38. y_branch.add(CustomBatchNormalization())
  39. y_branch.add(Activation('softplus'))
  40. merged = Sequential([Merge([x_branch, y_branch], mode='concat')])
  41. merged.add(Dense(self.hid_dim))
  42. merged.add(CustomBatchNormalization())
  43. merged.add(Activation('softplus'))
  44. mean = Sequential([merged])
  45. mean.add(Dense(self.hid_dim))
  46. mean.add(CustomBatchNormalization())
  47. mean.add(Activation('softplus'))
  48. mean.add(Dense(self.z_dim))
  49. var = Sequential([merged])
  50. var.add(Dense(self.hid_dim))
  51. var.add(CustomBatchNormalization())
  52. var.add(Activation('softplus'))
  53. var.add(Dense(self.z_dim, activation='softplus'))
  54. self.q_z_xy = GaussianDistribution(self.z, givens=[self.x_l, self.y_l], mean_model=mean, var_model=var)
  55. ###############
  56. # p(x | y, z) #
  57. ###############
  58. y_branch = Sequential()
  59. y_branch.add(Dense(self.hid_dim, input_dim=self.cat_dim))
  60. y_branch.add(CustomBatchNormalization())
  61. y_branch.add(Activation('softplus'))
  62. z_branch = Sequential()
  63. z_branch.add(Dense(self.hid_dim, input_dim=self.z_dim))
  64. z_branch.add(CustomBatchNormalization())
  65. z_branch.add(Activation('softplus'))
  66. merged = Sequential([Merge([y_branch, z_branch], mode='concat')])
  67. merged.add(Dense(self.hid_dim))
  68. merged.add(CustomBatchNormalization())
  69. merged.add(Activation('softplus'))
  70. mean = Sequential([merged])
  71. mean.add(Dense(self.hid_dim))
  72. mean.add(CustomBatchNormalization())
  73. mean.add(Activation('softplus'))
  74. mean.add(Dense(self.in_dim))
  75. var = Sequential([merged])
  76. var.add(Dense(self.hid_dim))
  77. var.add(CustomBatchNormalization())
  78. var.add(Activation('softplus'))
  79. var.add(Dense(self.in_dim, activation='softplus'))
  80. self.p_x_yz = GaussianDistribution(self.x_l, givens=[self.y_l, self.z], mean_model=mean, var_model=var)
  81. ########
  82. # p(y) #
  83. ########
  84. self.p_y = CategoricalDistribution(self.y_l)
  85. ############
  86. # q(y | x) #
  87. ############
  88. inference = Sequential()
  89. inference.add(Dense(self.hid_dim, input_dim=self.in_dim))
  90. inference.add(CustomBatchNormalization())
  91. inference.add(Activation('softplus'))
  92. inference.add(Dense(self.hid_dim))
  93. inference.add(CustomBatchNormalization())
  94. inference.add(Activation('softplus'))
  95. inference.add(Dense(self.cat_dim, activation='softmax'))
  96. self.q_y_x = CategoricalDistribution(self.y_l, givens=[self.x_l], model=inference)
  97. ##########################
  98. # sample and reconstruct #
  99. ##########################
  100. self.sampling_z = self.q_z_xy.sampling(givens=[self.x_l, self.y_l])
  101. self.reconstruct_x_l = self.p_x_yz.sampling(givens=[self.y_l, self.sampling_z])
  102. def _KL(self, mean, var):
  103. return -1/2*K.mean(K.sum(1+K.log(K.clip(var, K._epsilon, 1/K._epsilon))-mean**2-var, axis=1))
  104. def label_cost(self, y_true, y_false):
  105. ###########
  106. # Labeled #
  107. ###########
  108. self.mean, self.var = self.q_z_xy.get_params(givens=[self.x_l, self.y_l])
  109. KL = self._KL(self.mean, self.var)
  110. logliklihood = -self.p_x_yz.logliklihood(self.x_l, givens=[self.y_l, self.sampling_z])-self.p_y.logliklihood(self.y_l)
  111. L = KL+logliklihood
  112. L = L+self.alpha*self.q_y_x.logliklihood(self.y_l, givens=[self.x_l])
  113. return L
  114. def cost(self, y_true, y_false):
  115. ###########
  116. # Labeled #
  117. ###########
  118. self.mean, self.var = self.q_z_xy.get_params(givens=[self.x_l, self.y_l])
  119. KL = self._KL(self.mean, self.var)
  120. logliklihood = -self.p_x_yz.logliklihood(self.x_l, givens=[self.y_l, self.sampling_z])-self.p_y.logliklihood(self.y_l)
  121. L = KL+logliklihood
  122. L = L+self.alpha*self.q_y_x.logliklihood(self.y_l, givens=[self.x_l])
  123. #############
  124. # UnLabeled #
  125. #############
  126. U = 0
  127. # marginalization
  128. for y in self.y_u:
  129. mean, var = self.q_z_xy.get_params(givens=[self.x_u, y])
  130. sampling_z = self.q_z_xy.sampling(givens=[self.x_u, y])
  131. U += self.q_y_x.prob(y, givens=[self.x_u])*(-self.p_x_yz.logliklihood(self.x_u, givens=[y, sampling_z])
  132. -self.p_y.logliklihood(y)
  133. +self._KL(mean, var)
  134. +self.q_y_x.logliklihood(y, givens=[self.x_u])
  135. )
  136. return U+L
  137. def label_training_model(self):
  138. model = Model(input=[self.x_l, self.y_l], output=self.reconstruct_x_l)
  139. return model
  140. def training_model(self):
  141. model = Model(input=[self.x_l, self.y_l, self.x_u]+self.y_u, output=self.reconstruct_x_l)
  142. return model
  143. def encoder(self):
  144. model = Model(input=[self.x_l, self.y_l], output=self.mean)
  145. return model
  146. def decoder(self):
  147. decode = self.p_x_yz.sampling(givens=[self.y_l, self.z])
  148. model = Model(input=[self.y_l, self.z], output=decode)
  149. return model
  150. def classifier(self):
  151. inference = self.q_y_x.get_params(givens=[self.x_l])
  152. model = Model(input=self.x_l, output=inference)
  153. return model

去噪自编码器(DAE)


是一种输入中不仅包含数据,也包含噪声(比如使图像更有颗粒感)的自动编码器。但我们以同样的方式计算误差,所以该网络的输出是与不带噪声的原始输入进行比较。这能让网络不会学习细节,而是学习更广泛的特征,因为学习更小的特征往往会被证明是「错误的」,因为更小的特征会不断随噪声变化。

深度信念网络(DBN)


基本上是 RBM 或 VAE 堆叠起来的架构。事实已经证明这些网络可以堆叠起来高效地训练,其中的每一个 AE 或 REM 只必须编码编码之前的网络即可。这种技术也被称为贪婪训练(greedy training),其中贪婪是指得到局部最优的解决方案,从而得到一个合理的但可能并非最优的答案。DBN 可通过对比发散(contrastive divergence)或反向传播进行训练,以及学习将数据表征为概率模型,就像普通的 RBM 或 VAE 一样。一旦通过无监督学习训练或收敛成了一个(更)稳定的状态,该模型就可被用于生成新数据。如果采用对比发散进行训练,它甚至可以对已有的数据进行分类,因为其神经元已经学会了寻找不同的特征。

12. 卷积神经网络(CNN)


和其它大多数网络非常不同。它们主要被用于图像处理,但也可应用于音频等其它类型的输入。CNN 的一种典型的用例是让网络对输入的图像进行分类,CNN 往往开始带有一个输入“scanner”,其目的是不一次性解析所有的训练数据。CNN 的真实世界实现往往会在末端连接一个 FFNN 以便进一步处理数据,这可以实现高度非线性的抽象。这样的网络被称为 DCNN,但这两者的名字和缩写往往可以混用。

CNN

  1. from keras.models import Sequential
  2. from keras.layers import Dense, Dropout, Activation, Flatten
  3. from keras.layers import Convolution2D, MaxPooling2D
  4. def CNN(nb_filters, kernel_size, input_shape, pool_size):
  5. model = Sequential()
  6. model.add(Convolution2D(nb_filters, kernel_size[0], kernel_size[1],
  7. border_mode='valid',
  8. input_shape=input_shape))
  9. model.add(Activation('relu'))
  10. model.add(Convolution2D(nb_filters, kernel_size[0], kernel_size[1]))
  11. model.add(Activation('relu'))
  12. model.add(MaxPooling2D(pool_size=pool_size))
  13. model.add(Flatten())
  14. model.add(Dense(128))
  15. model.add(Activation('relu'))
  16. model.add(Dense(nb_classes))
  17. model.add(Activation('softmax'))
  18. return model

13. 解卷积神经网络(DCNN)


是反向的卷积神经网络。比如给网络输入一个词「cat」,然后训练它生成一张类似猫的图像(通过将其与真实的猫图片进行比较)。和普通的 CNN 一样,DNN 也能和 FFNN 结合使用,但我们就不给这种网络缩写了。我们也许可以将其称之为深度解卷积神经网络,但你也可以认为当你在 DNN 的前端和后端都接上 FFNN 时,你得到的架构应该有一个新名字。请注意在大多数应用中,人们实际上并不会为该网络送入类似文本的输入,而更多的是一个二元的分类输入向量。比如设 <0, 1> 是猫,<1, 0> 是狗,<1, 1> 是猫和狗。CNN 中常见的池化层往往会被相似的逆向运算替代,主要使用偏差假设(biased assumptions)做插值和外推(interpolation and extrapolation )(如果一个池化层使用的是最大池化,你可以通过其逆向过程产生特定度更低的新数据)。

DCNN

  1. # apply a 3x3 transposed convolution with stride 1x1 and 3 output filters on a 12x12 image:
  2. model = Sequential()
  3. model.add(Deconvolution2D(3, 3, 3, output_shape=(None, 3, 14, 14), border_mode='valid', input_shape=(3, 12, 12)))
  4. # output_shape will be (None, 3, 14, 14)
  5. # apply a 3x3 transposed convolution with stride 2x2 and 3 output filters on a 12x12 image:
  6. model = Sequential()
  7. model.add(Deconvolution2D(3, 3, 3, output_shape=(None, 3, 25, 25), subsample=(2, 2), border_mode='valid', input_shape=(3, 12, 12)))
  8. model.summary()
  9. # output_shape will be (None, 3, 25, 25)

14. 深度卷积反向图形网络(DCIGN)

从某种程度上说,这个神经网络的名字有一点欺骗性,它们实际上是VAE,但是在编码和解码中分别有CNN 和 DNN。这些网络尝试在编码的过程中对“特征”作为概率建模,这样一来,它只需要分别“看”猫和狗的独照,就能学会生成一张既有猫又有狗的合照。类似的,你也可以让它把猫狗合照中的狗去掉,如果你很讨厌那只狗的话。Demo显示,这些模型可以学习为图像中复杂的转换进行建模,比如源文件中3D物体光线的改变。这种网络倾向于使用反向传播进行训练。

15. 生成对抗网络(GAN)

GAN由不同的网络组成,这些网络是成对的:每两个网络配对工作。任何一对网站都可以组成 GAN(虽然通常由FFs 和 CNN 配对),一个网络的任务是生成内容,另一个负责对内容进行评价。进行鉴别的网络从训练数据或者生成式的网络中获得内容。鉴别网络能正确地预测数据源,随后将会被当成误差生成网络中的一部分。这形成了一种对抗:鉴别器在对生成数据和真实数据进行区分时做得越来越好,生成器也在学习如何不被鉴别器预测到。这种网络能取得良好的效果,部分原因是,即便非常复杂的嘈杂模型最终也都是可预测的,但是它只生成与特征类似的内容,所以很难学会鉴别。GAN 很难训练,因为你不仅需要训练两个网络(况且其中的每一个都有自己的问题),而且二者的动态也需要被平衡。

GAN

  1. # Build Generative model ...
  2. nch = 200
  3. g_input = Input(shape=[100])
  4. H = Dense(nch*14*14, init='glorot_normal')(g_input)
  5. H = BatchNormalization(mode=2)(H)
  6. H = Activation('relu')(H)
  7. H = Reshape( [nch, 14, 14] )(H)
  8. H = UpSampling2D(size=(2, 2))(H)
  9. H = Convolution2D(nch/2, 3, 3, border_mode='same', init='glorot_uniform')(H)
  10. H = BatchNormalization(mode=2)(H)
  11. H = Activation('relu')(H)
  12. H = Convolution2D(nch/4, 3, 3, border_mode='same', init='glorot_uniform')(H)
  13. H = BatchNormalization(mode=2)(H)
  14. H = Activation('relu')(H)
  15. H = Convolution2D(1, 1, 1, border_mode='same', init='glorot_uniform')(H)
  16. g_V = Activation('sigmoid')(H)
  17. generator = Model(g_input,g_V)
  18. generator.compile(loss='binary_crossentropy', optimizer=opt)
  19. generator.summary()
  20. # Build Discriminative model ...
  21. d_input = Input(shape=shp)
  22. H = Convolution2D(256, 5, 5, subsample=(2, 2), border_mode = 'same', activation='relu')(d_input)
  23. H = LeakyReLU(0.2)(H)
  24. H = Dropout(dropout_rate)(H)
  25. H = Convolution2D(512, 5, 5, subsample=(2, 2), border_mode = 'same', activation='relu')(H)
  26. H = LeakyReLU(0.2)(H)
  27. H = Dropout(dropout_rate)(H)
  28. H = Flatten()(H)
  29. H = Dense(256)(H)
  30. H = LeakyReLU(0.2)(H)
  31. H = Dropout(dropout_rate)(H)
  32. d_V = Dense(2,activation='softmax')(H)
  33. discriminator = Model(d_input,d_V)
  34. discriminator.compile(loss='categorical_crossentropy', optimizer=dopt)
  35. discriminator.summary()

16. 循环神经网络(RNN)

RNN 是一种包含时间纠缠的 FFNN: 他们不是无主的stateless;他们在通道间是有联系的,通过时间进行连接。神经元不仅从上一层神经网络获得信息,而且可以从自身、从上一个通道中获得信息。也就是说,输入和训练网络的顺序会变得很重要。RNN 有一个很大的问题是梯度消失或爆炸,这取决于所使用的激活函数,在这一过程中,信息会不断地迅速消失,正如极深的FFNN 网络在的信息丢失一样。直观地说,这并不是一个大问题,因为他们只是权重而不是神经元状态,但是,经过多次加权后,权重已经成为了旧信息的存储地,如果权重达到0或者100万,那么此前的状态就没有什么信息意义了。RNN 可以在许多领域得到应用,因为绝大多数形式的数据并不真的拥有可以用序列表示的时间线(比如,声音或者视频)。总的来说,循环网络对于完整的信息来说是一个很好的选择。

17. 长/短时记忆网络(LSTM)

LSTM 通过引入关口(gate)和一个精确定义的记忆单元,尝试解决梯度消失或者爆炸的问题。这一概念大部分是从电路学获得的启发,而不是从生物学。每一个神经元都有一个存储单元和三个关口:输入、输出和忽略(forget)。这些关口的功能是通过运行或者禁止流动来保证信息的安全。输入关口决定有多少上一层的信息可以存储到单元中。输出层承担了另一端的工作,决定下一层可以了解到多少这一层的信息。忽略关口初看是一个很奇怪的设计,但是,有时候忽略也是很重要的:如果网络正在学习一本书,并开始新的一章,那么忘掉前几章的一些内容也是很有必要的。LSTM已经被证明可以学习复杂的序列,包括像莎士比亚一样写作,或者创作音乐。需要注意的是,这些关口中的每一个都对前一个神经元中的存储单元赋有权重,所以他们一般会需要更多想资源来运行。

18 .关口循环单元(GRU)

是 LSTM 的一种轻量级变体。它们有一个关口,连线方式也稍微不同:没有输入、输出、遗忘关口,它们有一个更新关口(update gate)。该更新关口既决定来自上个状态的信息保留多少,也决定允许进入多少来自上个层的信息。重置的关口函数很像 LSTM 中遗忘关口函数,但位置稍有不同。GRU 的关口函数总是发出全部状态,它们没有一个输出关口。在大多案例中,它们的职能与 LSTM 很相似。最大的不同就是 GRU 更快、更容易运行(但表达力也更弱)。在实践中,可能彼此之间要做出平衡,当你需要具有更大表达力的大型网络时,你可能要考虑性能收益。在一些案例中,r如果额外的表达力不再需要,GRU 就要比 LSTM 好。

19. 神经图灵机(NTM)

可被理解为 LSTM 的抽象化,并试图将神经网络去黑箱化( un-black-box,让我们洞见里面到底发生了什么。)NTM 中并非直接编码记忆单元到神经元中,里面的记忆是分离的。这种网络试图想将常规数字存储的功效与永久性和神经网络的效率与表达力结合起来。这种网络的思路是有一个可内容寻址的记忆库,神经网络可以直接从中读取并编写。NTM 中的「Turing」来自于图灵完备(Turing complete):基于它所读取的内容读取、编写和改变状态的能力,意味着它能表达一个通用图灵机可表达的一切事情。

20. 双向循环神经网络(BiRNN)、双向长短期记忆网络(BiLSTM)和双向关口控循环单元(BiGRU)


在词表中并未展现,因为它们看起来和各自单向的结构一样。不同的是这些网络不仅连接过去,也连接未来。举个例子,通过一个接一个的输入 fish 这个词训练单向 LSTM 预测 fish,在这里面循环连接随时间记住最后的值。而一个 BiLSTM 在后向通路(backward pass)的序列中就被输入下一个词,给它通向未来的信息。这训练该网络填补空白而非预报信息,也就是在图像中它并非扩展图像的边界,而是可以填补一张图片中的缺失。

21. 深度残差网络(DRN)

是非常深度的 FFNN 网络,有着额外的连接将输入从一层传到后面几层(通常是 2 到 5 层)。DRN 并非是要发现将一些输入(比如一个 5 层网络)映射到输出的解决方案,而是学习将一些输入映射到一些输出 + 输入上。大体上,它在解决方案中增加了一个恒等函数,携带旧的输入作为后面层的新输入。有结果显示,在超过 150 层后,这些网络非常擅长学习模式,这要比常规的 2 到 5 层多得多。然而,有结果证明这些网络本质上只是没有基于具体时间建造的 RNN ,它们总是与没有 关口的 LSTM 相对比。

22. 回声状态网络(ESN)

是另一种不同类型的网络。它不同于其他网络的原因在于它在不同神经元之间有随机连接(即,不是在层之间整齐连接。),而且它们训练方式也不同。在这种网络中,我们先给予输入,向前推送并对神经元更新一段时间,然后随时间观察输出,而不是像其他网络那样输入信息然后反向传播误差。ESN 的输入和输出层有一些轻微的卷积,因为输入层被用于准备网络,输出层作为随时间展开的激活模式的观测器。在训练过程中,只有观测器和隐藏单元之间连接会被改变。

23. 支持向量机(SVM)

能发现分类问题的最佳解决方案。传统上只能够分类线性可分的数据,比如说发现哪个图像是加菲猫,哪张图片是史努比,不可能有其他输出。在训练过程中,SVM 可被视为在一张图上(2D)标绘所有数据(加菲猫和史努比),并搞清楚如何在这些数据点间画条线。这条线将分割数据,以使得加菲猫在一边,史努比在一边。调整这条线到最佳的方式是边缘位于数据点之间,这条线最大化到两端。分类新数据可通过在这张图上标绘一个点来完成,然后就简单看到这个点位于线的哪边。使用核(kernel)方法,它们可被教授进行 n 维数据的分类。这要在 3D 图上标绘数据点,从而让其可分类史努比、加菲猫、Simon’s cat,甚至分类更多的卡通形象。


技术支持

你可以在Keras Google group里提问以获得帮助,如果你生活在中国大陆的话,梯子请自备

你也可以在Github issues里提问。在提问之前请确保你阅读过我们的指导

同时,我们也欢迎同学们加我们的QQ群119427073进行讨论(潜水和灌水会被T,入群说明公司/学校-职位/年级)


声明与联系方式

本文不得用于任何形式的商业用途,如果需要转载请与作者联系,如果发现未经允许复制转载,将保留追求其法律责任的权利。
作者:SCP-173
E-mail :scp173.cool@gmail.com
如果您需要及时得到指导帮助,可以加微信:SCP-173-cool,酌情打赏即可

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多