Category Archives: 数据挖掘与机器学习

计算机视觉中的注意力机制

引言

在机器翻译(Machine Translation)或者自然语言处理(Natural Language Processing)领域,以前都是使用数理统计的方法来进行分析和处理。近些年来,随着 AlphaGo 的兴起,除了在游戏AI领域,深度学习在计算机视觉领域,机器翻译和自然语言处理领域也有着巨大的用武之地。在 2016 年,随着深度学习的进一步发展,seq2seq 的训练模式和翻译模式已经开始进入人们的视野。除此之外,在端到端的训练方法中,除了需要海量的业务数据之外,在网络结构中加入一些重要的模块也是非常必要的。在此情形下,基于循环神经网咯(Recurrent Neural Network)的注意力机制(Attention Mechanism)进入了人们的视野。除了之前提到的机器翻译和自然语言处理领域之外,计算机视觉中的注意力机制也是十分有趣的,本文将会简要介绍一下计算机视觉领域中的注意力方法。在此事先声明一下,笔者并不是从事这几个领域的,可能在撰写文章的过程中会有些理解不到位的地方,请各位读者指出其中的不足。

LSTM_1

注意力机制

顾名思义,注意力机制是本质上是为了模仿人类观察物品的方式。通常来说,人们在看一张图片的时候,除了从整体把握一幅图片之外,也会更加关注图片的某个局部信息,例如局部桌子的位置,商品的种类等等。在翻译领域,每次人们翻译一段话的时候,通常都是从句子入手,但是在阅读整个句子的时候,肯定就需要关注词语本身的信息,以及词语前后关系的信息和上下文的信息。在自然语言处理方向,如果要进行情感分类的话,在某个句子里面,肯定会涉及到表达情感的词语,包括但不限于“高兴”,“沮丧”,“开心”等关键词。而这些句子里面的其他词语,则是上下文的关系,并不是它们没有用,而是它们所起的作用没有那些表达情感的关键词大。

在以上描述下,注意力机制其实包含两个部分

  1. 注意力机制需要决定整段输入的哪个部分需要更加关注;
  2. 从关键的部分进行特征提取,得到重要的信息。

通常来说,在机器翻译或者自然语言处理领域,人们阅读和理解一句话或者一段话其实是有着一定的先后顺序的,并且按照语言学的语法规则来进行阅读理解。在图片分类领域,人们看一幅图也是按照先整体再局部,或者先局部再整体来看的。再看局部的时候,尤其是手写的手机号,门牌号等信息,都是有先后顺序的。为了模拟人脑的思维方式和理解模式,循环神经网络(RNN)在处理这种具有明显先后顺序的问题上有着独特的优势,因此,Attention 机制通常都会应用在循环神经网络上面。

虽然,按照上面的描述,机器翻译,自然语言处理,计算机视觉领域的注意力机制差不多,但是其实仔细推敲起来,这三者的注意力机制是有明显区别的。

  1. 在机器翻译领域,翻译人员需要把已有的一句话翻译成另外一种语言的一句话。例如把一句话从英文翻译到中文,把中文翻译到法语。在这种情况下,输入语言和输出语言的词语之间的先后顺序其实是相对固定的,是具有一定的语法规则的;
  2. 在视频分类或者情感识别领域,视频的先后顺序是由时间戳和相应的片段组成的,输入的就是一段视频里面的关键片段,也就是一系列具有先后顺序的图片的组合。NLP 中的情感识别问题也是一样的,语言本身就具有先后顺序的特点;
  3. 图像识别,物体检测领域与前面两个有本质的不同。因为物体检测其实是在一幅图里面挖掘出必要的物体结构或者位置信息,在这种情况下,它的输入就是一幅图片,并没有非常明显的先后顺序,而且从人脑的角度来看,由于个体的差异性,很难找到一个通用的观察图片的方法。由于每个人都有着自己观察的先后顺序,因此很难统一成一个整体。

在这种情况下,机器翻译和自然语言处理领域使用基于 RNN 的 Attention 机制就变得相对自然,而计算机视觉领域领域则需要必要的改造才能够使用 Attention 机制。

LSTM_3

基于 RNN 的注意力机制

通常来说,RNN 等深度神经网络可以进行端到端的训练和预测,在机器翻译领域和或者文本识别领域有着独特的优势。对于端到端的 RNN 来说,有一个更简洁的名字叫做 sequence to sequence,简写就是 seq2seq。顾名思义,输入层是一句话,输出层是另外一句话,中间层包括编码和解码两个步骤。

而基于 RNN 的注意力机制指的是,对于 seq2seq 的诸多问题,在输入层和输出层之间,也就是词语(Items)与词语之间,存在着某种隐含的联系。例如:“中国” -> “China”,“Excellent” -> “优秀的”。在这种情况下,每次进行机器翻译的时候,模型需要了解当前更加关注某个词语或者某几个词语,只有这样才能够在整句话中进行必要的提炼。在这些初步的思考下,基于 RNN 的 Attention 机制就是:

  1. 建立一个编码(Encoder)和解码(Decoder)的非线性模型,神经网络的参数足够多,能够存储足够的信息;
  2. 除了关注句子的整体信息之外,每次翻译下一个词语的时候,需要对不同的词语赋予不同的权重,在这种情况下,再解码的时候,就可以同时考虑到整体的信息和局部的信息。

LSTM_4

注意力机制的种类

从初步的调研情况来看,注意力机制有两种方法,一种是基于强化学习(Reinforcement Learning)来做的,另外一种是基于梯度下降(Gradient Decent)来做的。强化学习的机制是通过收益函数(Reward)来激励,让模型更加关注到某个局部的细节。梯度下降法是通过目标函数以及相应的优化函数来做的。无论是 NLP 还是 CV 领域,都可以考虑这些方法来添加注意力机制。

LSTM_5

计算机视觉领域的 Attention 部分论文整理

下面将会简单的介绍几篇近期阅读的计算机视觉领域的关于注意力机制的文章。

Look Closer to See Better:Recurrent Attention Convolutional Neural Network for Fine-grained Image Recognition

在图像识别领域,通常都会遇到给图片中的鸟类进行分类,包括种类的识别,属性的识别等内容。为了区分不同的鸟,除了从整体来对图片把握之外,更加关注的是一个局部的信息,也就是鸟的样子,包括头部,身体,脚,颜色等内容。至于周边信息,例如花花草草之类的,则显得没有那么重要,它们只能作为一些参照物。因为不同的鸟类会停留在树木上,草地上,关注树木和草地的信息对鸟类的识别并不能够起到至关重要的作用。所以,在图像识别领域引入注意力机制就是一个非常关键的技术,让深度学习模型更加关注某个局部的信息。

RA_CNN_1

在这篇文章里面,作者们提出了一个基于 CNN 的注意力机制,叫做 recurrent attention convolutional neural network(RA-CNN),该模型递归地分析局部信息,从局部的信息中提取必要的特征。同时,在 RA-CNN 中的子网络(sub-network)中存在分类结构,也就是说从不同区域的图片里面,都能够得到一个对鸟类种类划分的概率。除此之外,还引入了 attention 机制,让整个网络结构不仅关注整体信息,还关注局部信息,也就是所谓的 Attention Proposal Sub-Network(APN)。这个 APN 结构是从整个图片(full-image)出发,迭代式地生成子区域,并且对这些子区域进行必要的预测,并将子区域所得到的预测结果进行必要的整合,从而得到整张图片的分类预测概率。

RA_CNN_2

RA-CNN 的特点是进行一个端到端的优化,并不需要提前标注 box,区域等信息就能够进行鸟类的识别和图像种类的划分。在数据集上面,该论文不仅在鸟类数据集(CUB Birds)上面进行了实验,也在狗类识别(Stanford Dogs)和车辆识别(Stanford Cars)上进行了实验,并且都取得了不错的效果。

RA_CNN_4

从深度学习的网络结构来看,RA-CNN 的输入时是整幅图片(Full Image),输出的时候就是分类的概率。而提取图片特征的方法通常来说都是使用卷积神经网络(CNN)的结构,然后把 Attention 机制加入到整个网络结构中。从下图来看,一开始,整幅图片从上方输入,然后判断出一个分类概率;然后中间层输出一个坐标值和尺寸大小,其中坐标值表示的是子图的中心点,尺寸大小表示子图的尺寸。在这种基础上,下一幅子图就是从坐标值和尺寸大小得到的图片,第二个网络就是在这种基础上构建的;再迭代持续放大图片,从而不停地聚焦在图片中的某些关键位置。不同尺寸的图片都能够输出不同的分类概率,再将其分类概率进行必要的融合,最终的到对整幅图片的鸟类识别概率。

因此,在整篇论文中,有几个关键点需要注意:

  1. 分类概率的计算,也就是最终的 loss 函数的设计;
  2. 从上一幅图片到下一幅图片的坐标值和尺寸大小。

只要获得了这些指标,就可以把整个 RA-CNN 网络搭建起来。

大体来说,第一步就是给定了一幅输入图片 X, 需要提取它的特征,可以记录为 W_{c}*X,这里的 * 指的是卷积等各种各样的操作。所以得到的概率分布情况其实就是 p(X) = f(W_{c}*X)f 指的是从 CNN 的特征层到全连接层的函数,外层使用了 Softmax 激活函数来计算鸟类的概率。

第二步就是计算下一个 box 的坐标 (t_{x}, t_{y}) 和尺寸大小 t_{\ell},其中 t_{x}, t_{y} 分别指的是横纵坐标,正方形的边长其实是 2*t_{\ell}。用数学公式来记录这个流程就是 [t_{x}, t_{y}, t_{\ell}] = g(W_{c}*X)。在坐标值的基础上,我们可以得到以下四个值,分别表示 x, y 两个坐标轴的上下界:

t_{x(t\ell)} = t_{x} - t_{\ell}, t_{x(br)} = t_{x} + t_{\ell},

t_{y(t\ell)} = t_{y} - t_{\ell}, t_{y(br)} = t_{y} + t_{\ell}.

局部注意力和放大策略(Attention Localization and Amplification)指的是:从上面的方法中拿到坐标值和尺寸,然后把图像进行必要的放大。为了提炼局部的信息,其实就需要在整张图片 X 的基础上加上一个面具(Mask)。所谓面具,指的是在原始图片的基础上进行点乘 0 或者 1 的操作,把一些数据丢失掉,把一些数据留下。在图片领域,就是把周边的信息丢掉,把鸟的信息留下。但是,有的时候,如果直接进行 0 或者 1 的硬编码,会显得网络结构不够连续或者光滑,因此就有其他的替代函数。

在激活函数里面,逻辑回归函数(Logistic Regression)是很常见的。其实通过逻辑回归函数,我们可以构造出近似的阶梯函数或者面具函数。

sigmoid_1

对于逻辑回归函数 \sigma(x) = 1/(1+e^{-kx}) 而言,当 k 足够大的时候,\sigma(x) \approx 1x \geq 0\sigma(x) \approx 0x<0。此时的逻辑回归函数近似于一个阶梯函数。如果假设 x_{0}<x_{1},那么 \sigma(x-x_{0}) - \sigma(x-x_{1}) 就是光滑一点的阶梯函数,\sigma(x-x_{0}) - \sigma(x-x_{1}) \approx 0x < x_{0} \text{ or } x > x_{1}\sigma(x-x_{0}) - \sigma(x-x_{1}) \approx 1x_{0}\leq x\leq x_{1}

因此,基于以上的分析和假设,我们可以构造如下的函数:X^{attr} = X \odot M(t_{x}, t_{y}, t_{\ell}), 其中,X^{attr} 表示图片需要关注的区域,M(\cdot) 函数就是 M(t_{x}, t_{y}, t_{\ell}) = [\sigma(x-t_{x(t\ell)}) - \sigma(x-t_{x(br)})]\cdot[\sigma(y-t_{y(t\ell)}) - \sigma(y-t_{y(br)})], 这里的 \sigma 函数对应了一个足够大的 k 值。

当然,从一张完整的图片到小图片,在实际操作的时候,需要把小图片继续放大,在放大的过程中,可以考虑使用双线性插值算法来扩大。也就是说:

X_{(i,j)}^{amp} = \sum_{\alpha,\beta=0}^{1}|1-\alpha-\{i/\lambda\}|\cdot|1-\beta-\{j/\lambda\}|\cdot X_{(m,n)}^{att},

其中 m = [i/\lambda] + \alpha, n = [j/\lambda] + \beta\lambda 表示上采样因子,[\cdot], \{\cdot\} 分别表示一个实数的正数部分和小数部分。

在分类(Classification)和排序(Ranking)部分,RA-CNN 也有着自己的方法论。在损失函数(Loss Function)里面有两个重要的部分,第一个部分就是三幅图片的 LOSS 函数相加,也就是所谓的 classification loss,Y^{(s)} 表示预测类别的概率,Y 表示真实的类别。除此之外,另外一个部分就是排序的部分,L_{rank}(p_{t}^{(s)}, p_{t}^{(s+1)}) = \max\{0,p_{t}^{(s)}-p_{t+1}^{(s+1)}+margin\}, 其中 p^{(s)} 表示在第 s 个尺寸下所得到的类别 t 的预测概率,并且最大值函数强制了该深度学习模型在训练中可以保证 p_{t}^{(s+1)} > p_{t}^{(s)} + margin,也就是说,局部预测的概率值应该高于整体的概率值。

L(X) = \sum_{s=1}^{3}\{L_{cls}(Y^{(s)},Y^{*})\} + \sum_{s=1}^{2}\{L_{rank}(p_{t}^{(s)},p_{t}^{(s+1)})\}.

RA_CNN_3

在这种 Attention 机制下,可以使用训练好的 conv5_4 或者 VGG-19 来进行特征的提取。在图像领域,location 的位置是需要通过训练而得到的,因为每张图片的鸟的位置都有所不同。进一步通过数学计算可以得到,t_{\ell} 会随着网络而变得越来越小,也就是一个层次递进的关系,越来越关注到局部信息的提取。简单来看,

\frac{\partial L_{rank}}{\partial t_{x}} \propto D_{top} \odot \frac{\partial M(t_{x},t_{y},t_{\ell})}{\partial t_{x}},

这里的 \odot 表示元素的点乘,D_{top} 表示之前的网络所得到的导数。

x\rightarrow t_{x(t\ell)}\frac{\partial M}{\partial t_{x}}<0;

x \rightarrow t_{x(br)}\frac{\partial M}{\partial t_{x}}>0;

其余情况,\frac{\partial M}{\partial t_{x}}=0.

y\rightarrow t_{y(t\ell)}\frac{\partial M}{\partial t_{y}}<0;

y \rightarrow t_{y(br)}\frac{\partial M}{\partial t_{y}}>0;

其余情况,\frac{\partial M}{\partial t_{y}}=0.

x \rightarrow t_{x(t\ell)}\text{ or } x \rightarrow t_{x(br)}\text{ or } y \rightarrow t_{y(t\ell)}\text{ or } y \rightarrow t_{y(br)}, \frac{\partial M}{\partial t_{\ell}}>0;

其余情况,\frac{\partial M}{\partial t_{\ell}}<0.

因此,t_{\ell} 在迭代的过程中会越来越小,也就是说关注的区域会越来越集中。

RA-CNN 的实验效果如下:

RA_CNN_5.png

 

Multiple Granularity Descriptors for Fine-grained Categorization

这篇文中同样做了鸟类的分类工作,与 RA-CNN 不同之处在于它使用了层次的结构,因为鸟类的区分是按照一定的层次关系来进行的,粗糙来看,有科 -> 属 -> 种三个层次结构。

MC_CNN_1

因此,在设计网络结构的过程中,需要有并行的网络结构,分别对应科,属,种三个层次。从前往后的顺序是检测网络(Detection Network),区域发现(Region Discovery),描述网络(Description Network)。并行的结构是 Family-grained CNN + Family-grained Descriptor,Genus-grained CNN + Genus-grained Descriptor,Species-grained CNN + Species-grained Descriptor。而在区域发现的地方,作者使用了 energy 的思想,让神经网络分别聚焦在图片中的不同部分,最终的到鸟类的预测结果。

MC_CNN_2MC_CNN_3

Recurrent Models of Visual Attention

在计算机视觉中引入注意力机制,DeepMind 的这篇文章 recurrent models of visual attention 发表于 2014 年。在这篇文章中,作者使用了基于强化学习方法的注意力机制,并且使用收益函数来进行模型的训练。从网络结构来看,不仅从整体来观察图片,也从局部来提取必要的信息。

DeepMind_1

DeepMind_2DeepMind_3

整体来看,其网络结构是 RNN,上一个阶段得到的信息和坐标会被传递到下一个阶段。这个网络只在最后一步进行分类的概率判断,这是与 RA-CNN 不同之处。这是为了模拟人类看物品的方式,人类并非会一直把注意力放在整张图片上,而是按照某种潜在的顺序对图像进行扫描。Recurrent Models of Visual Attention 本质上是把图片按照某种时间序列的形式进行输入,一次处理原始图片的一部分信息,并且在处理信息的过程中,需要根据过去的信息和任务选择下一个合适的位置进行处理。这样就可以不需要进行事先的位置标记和物品定位了。

DeepMind_4

正如上图所示,enc 指的是对图片进行编码,r_{i}^{(1)} 表示解码的过程,x_{i} 表示图片的一个子区域。而 y_{s} 表示对图片的预测概率或者预测标签。

 

Multiple Object Recognition with Visual Attention

这篇文章同样是 DeepMind 的论文,与 Recurrent Models of Visual Attention 不同之处在于,它是一个两层的 RNN 结构,并且在最上层把原始图片进行输入。其中 enc 是编码网络,r^{(1)}_{i} 是解码网络,r_{i}^{(2)} 是注意力网络,输出概率在解码网络的最后一个单元输出。

deep_recurrent_attention_model_1

在门牌识别里面,该网络是按照从左到右的顺序来进行图片扫描的,这与人类识别物品的方式极其相似。除了门牌识别之外,该论文也对手写字体进行了识别,同样取得了不错的效果。

deep_recurrent_attention_model_3deep_recurrent_attention_model_2

实验效果如下:

deep_recurrent_attention_model_4.png

总结

本篇 Blog 初步介绍了计算机视觉中的 Attention 机制,除了这些方法之外,应该还有一些更巧妙的方法,希望各位读者多多指教。

参考文献

  1. Look Closer to See Better:Recurrent Attention Convolutional Neural Network for Fine-grained Image Recognition,CVPR,2017.
  2. Recurrent Models of Visual Attention,NIPS,2014
  3. GitHub 代码:Recurrent-Attention-CNN,https://github.com/Jianlong-Fu/Recurrent-Attention-CNN
  4. Multiple Granularity Descriptors for Fine-grained Categorization,ICCV,2015
  5. Multiple Object Recognition with Visual Attention,ICRL,2015
  6. Understanding LSTM Networks,Colah’s Blog,2015,http://colah.github.io/posts/2015-08-Understanding-LSTMs/
  7. Survey on the attention based RNN model and its applications in computer vision,2016

 

Advertisements

启发式优化算法

启发式优化算法的一些调研

启发式优化算法的背景

1. 首先,我们需要有一个目标函数 f(\bold{x}), 其中的 \bold{x} \in \mathbb{R}^{n}n 是一个正整数。然后,目标函数 f(\bold{x}) 不一定需要是显式的(所谓显式指的是能够精确写出 f(\bold{x}) 的表达式,例如逻辑回归的目标函数,神经网络的目标函数,XGBoost 的目标函数等),隐式的目标函数使用启发式优化算法也是可以研究的(即无法写出 f(\bold{x}) 表达式)。

2. 其次,需要计算目标函数 f(\bold{x}) 的最大值 \max f(\bold{x}) 或者最小值 \min f(\bold{x})

3. 再次,如果存在多个函数 f_{i}(\bold{x}), 1\leq i\leq m, 并且每一个 f_{i}(\bold{x}) 的值域都在 [0,1] 内。如果目标函数是

\min_{x}(\max_{i}f_{i}(x)-\min_{i}f_{i}(x)),

希望求该目标函数的最小值,那么意思就是使得每一个 1\leq i\leq m, f_{i}(\bold{x})\bold{x} 的定义域内都相差得不多,也就是这 m 条曲线几乎重合。

4. 拥有目标函数的目的是寻找最优的 \bold{x} 使得它满足 argmin_{\bold{x}}f(x) 或者 argmax_{\bold{x}}f(x)

5. 在这里的 \bold{x}\in \mathbb{R}^{n} 一般都是连续的特征,而不是那种类别特征。如果是类别的特征,并不是所有的启发式优化算法都适用,但是遗传算法之类的算法在这种情况下有着一定的用武之地。

启发式优化算法的实验方法

1. 论文中的方法:找到一些形状怪异的函数作为目标函数,需要寻找这些函数的全局最大值或者全局最小值。除了全局的最大最小值点之外,这些函数也有很多局部极值点。例如 Rastrigin 函数(Chapter 6,Example 6.1,Search and Optimization by Metaheuristics)

\min_{\bold{x}}f(\bold{x}) = 10\cdot n +\sum_{i=1}^{n}(x_{i}^{2}-10\cos(2\pi x_{i})),

其中,\bold{x}\in[-5.12,5.12]^{n}. 或者 Easom 函数(Chapter 2,Example 2.1,Search and Optimization by Metaheuristics)

\min_{\bold{x}}f(\bold{x}) = - \cos(x_{1})\cos(x_{2})\exp(-(x_{1}-\pi)^{2}-(x_{2}-\pi)^{2}),

其中,\bold{x} \in [-100,100]^{2}.

2. 最朴素的算法:随机搜索法(Random Search)。也就是把 \bold{x} 的定义域切分成 m^{n} 份,其中 \bold{x}\in\mathbb{R}^{n}. 计算每一个交点的函数取值,然后统计出其最大值或者最小值就可以了。实际中不适用,因为当 n\geq 100 的时候,这个是指数级别的计算复杂度。

3. 启发式优化算法:针对某一个目标函数,使用某种启发式优化算法,然后与随机搜索法做对比,或者与其余的启发式优化算法做对比。

4. 在线调优:启发式优化算法大部分都可以做成实时调优的算法,调优的速度除了算法本身的收敛速度之外,还有给定一个 \bold{x} 之后,获取到 f(\vec{x}) 的时间。

注:可以尝试使用启发式优化算法在线调优推荐系统中的 AUC 或者 CTR。不过 CTR 一般具有波动性,需要几个小时甚至一天的数据才能够统计出一个相对靠谱的取值。同时,如果用户量不够多的话,如果切流量的话,可能会造成一些流量的 CTR 偏高,另外一些流量的 CTR 偏低。并且流量的条数会有上限,一般来说不会超过 100 左右,甚至只有 10 个不同的流量。因此,在其余场景下使用这类方法的时候,一定要注意获取目标函数 value 的时间长度。时间长度越短,能够进行的尝试(探索次数)就会越多,收敛的希望就会越大;反之,时间长度越长,能够进行的尝试(探索次数)就会受到限制,收敛的时间长度就会变长。

启发式优化算法的一些常见算法

以下的内容暂时只涉及最基本的算法,有很多算法的变种暂时不做过多的描述。

粒子群算法(Particle Swarm Optimization)

PSO 算法是有一个粒子群,根据群体粒子和单个粒子的最优效果,来调整每一个粒子的下一步行动方向。假设粒子的个数是 N_{p},每一个粒子 \bold{x}_{i}\in \mathbb{R}^{n} 都是 n 维欧几里德空间里面的点,同时需要假设粒子的速度 \bold{v}_{i}\in\mathbb{R}^{n}。在每一轮迭代中,需要更新两个最值,分别是每一个粒子在历史上的最优值和所有粒子在历史上的最优值,分别记为 \bold{x}_{i}^{*}1\leq i \leq N_{p})和 \bold{x}^{g}。在第 t+1 次迭代的时候,

\bold{v}_{i}(t+1) = \bold{v}_{i}(t) + c r_{1}[\bold{x}_{i}^{*}(t) - \bold{x}_{i}(t)] + c r_{2}[\bold{x}^{g}(t) - \bold{x}_{i}(t)],

\bold{x}_{i}(t+1) = \bold{x}_{i}(t)+\bold{v}_{i}(t+1), \text{ } 1\leq i\leq N_{p}.

在这里,c>0,并且 r_{1}, r_{2}[0,1] 中间的随机数。

注:需要解决的问题是如何设置这 N_{p} 个粒子,也就是构造粒子群的问题。在每次只能够设置调整一次 \bold{x} 的时候,可以把时间窗口按照连续的 N_{p} 段来进行切分,在一个时间段内的这 N_{p} 个时间点可以当成 N_{p} 个粒子,然后进行下一轮的迭代即可。

模拟退火(Simulated Annealing)

其核心思想是构造了温度 T 这个维度,当温度 T 高的时候,分子运动速度快,能够探索更多的区域;温度 T 低的时候,分子运动速度慢,能够探索的区域有限。随着时间的迁移,温度 T 会从一个较大的值逐渐降低到 0。

模拟退火的大体思路是这样的:先设置一个较大的温度值 T,随机初始化 \bold{x}(0)。假设目标函数是 E(\bold{x}),需要寻找 argmin_{\bold{x}}E(\bold{x}),然后执行以下的程序:

Repeat:

a. Repeat:

i. 进行一个随机扰动 \bold{x} = \bold{x} + \Delta \bold{x}

ii. 计算 \Delta E(\bold{x}) = E(\bold{x}+\Delta\bold{x}) - E(\bold{x})

如果 \Delta E(\bold{x}) <0,也就是 E(\bold{x} + \Delta\bold{x})<E(\bold{x}),选择 \bold{x}+\Delta\bold{x}

否则,按照 P = e^{-\Delta E/T} 的概率来接受 \bold{x} +\Delta\bold{x}

b. 令 T = T-\Delta T

直到 T 足够小。

遗传算法的个人理解:

  1. 如何选择合适的 \Delta \bold{x},可以选择 Gaussian 正态分布,并且可以同时修改 \bold{x}n 个维度,也可以每次只修改一个维度或者部分维度的值;
  2. 一开始在温度 T 很大的时候,即使 E(\bold{x}+\Delta\bold{x})\geq E(\bold{x}) 的时候,都会以极大概率接受 E(\bold{x}+\Delta\bold{x}),因为此时 \lim_{T\rightarrow\infty}e^{-\Delta E/T}=1。然后一开始都在四处游荡;
  3. 当温度 T 逐渐降低的时候,当时 \bold{x} 不一定在一个合适的位置,因此,需要记录历史上探索过的满足 E(\bold{x}) 最小的值,从历史上的值中选择出一个合适的值继续迭代。因此,在温度下降的时候,如何选择一个合适的值继续迭代可能是一个关键问题。
  4. 从遗传算法和 PSO 的对比效果来看,因为 PSO 算法会随时记录当前粒子和所有粒子的最优状态,然后在整个种群中,总会有少数粒子处于当前的最优状态,所以 PSO 的效果从实验效果上来看貌似比不记录状态的遗传算法会好一些。

进化策略(Evolutionary Strategies)

ES 和 GA 有一些方法论上面的不同,这个在书上有论述,等撰写了 GA 在一起看这一块的内容。

进化策略(Evolutionary Strategies)的大体流程与遗传算法(Genetic Algorithm) 相似,不同的是 ES 的步骤是交叉/变异(Crossover/Mutation)在前,选择(Selection)在后。

对于经典的进化策略而言,交叉算子(Crossover Operator)可以按照如下定义:两个父代(parents)\bold{x}_{1}\bold{x}_{2},那么子代(children)的交叉可以定义为:

\bold{x}'=(\bold{x}_{1}+\bold{x}_{2})/2,

这里的相加就是向量之间的相加。

对于经典的进化策略而言,变异算子(Mutation Operator)可以按照如下定义:对于一个向量 \bold{x} = (x_{1},\cdots,x_{n}),使用 Gaussian 正态分布可以构造出一个后代如下:

x_{i}'=x_{i}+N(0,\sigma_{i}),\text{ } i=1,\cdots,n,

这里的 \sigma_{i}, \text{ } i=1,\cdots,n 是基于具体问题的,很难给出一个通用的值。

就经验而谈,ES 算法一般来说比 GA 更加 Robust。在交叉(Crossover)或者变异(Mutation)之后,ES 算法就需要进行选择,通常来说,ES 算法有两种方式,分别是 (\lambda+\mu)(\lambda,\mu) 两种策略。这里的 \mu 表示人口数量(population size),\lambda 表示从人口中产生的后代的数量。

(1)在 (\lambda+\mu) 策略中,从 \mu 人口中通过变异或者交叉得到 \lambda 个人,然后从这 (\lambda+\mu) 个人中选择出 \mu 个最优的人。(\lambda+\mu) 是精英策略,每一次迭代都比上一次要好。

(2)在 (\lambda, \mu) 策略中,\mu 个最优的人是从 \lambda(\lambda\geq\mu) 中选择出来的。

传统的梯度搜索法,例如 Newton-Raphson 方法,需要能够计算目标函数 f(x) 的导数,也就是说目标函数必须可导。但是在 Evolutionary Gradient Search 中,不需要 f(x) 是可导的,而是使用导数的近似值来计算。

如果是 (1,\lambda)-ES 的方案,它整体来说其实只有一个人口。从当前的点 \bold{x} 开始,通过 Gaussian Mutation 的方法可以生成 \lambda 个后代,记为 \bold{t}_{1},\cdots,\bold{t}_{\lambda}。计算它们的函数取值 f(\bold{t}_{1}),\cdots,f(\bold{t}_{\lambda})。那么估算的梯度就可以计算出来:

\bold{g}=\sum_{i=1}^{\lambda}(f(\bold{t}_{i})-f(\bold{x}))(\bold{t}_{i}-\bold{x}),

归一化之后得到 \bold{e}=\bold{g}/||\bold{g}||.

Evolutionary gradient search 生成了两个点,分别是:

\bold{x}_{1}=\bold{x}+(\sigma \psi)\bold{e}, \bold{x}_{2}=\bold{x}+(\sigma/\psi)\bold{e},

这里的 \psi>1. 新的个体定义为: \bold{x}'=\bold{x}+\sigma'\bold{e}. 如果 f(\bold{x}_{1})>f(\bold{x}_{2}), 那么 \sigma' = \sigma\psi; 如果 f(\bold{x}_{1})\leq f(\bold{x}_{2}), 那么 \sigma'=\sigma/\psi.

进化策略(ES)包括 gradient search 和 gradient evolution,并且 covariance matrix adaptation(CMA)ES 在一定的限制条件下加速了搜索的效率。

参考资料:

  1. Search and Optimization by Metaheuristics,Kelin DU, M.N.S. SWAMY,2016年

聚类算法(二)

聚类是一种无监督学习(无监督学习是指事先并不知道要寻找的内容,没有固定的目标变量)的算法,它将相似的一批对象划归到一个簇,簇里面的对象越相似,聚类的效果越好。聚类的目标是在保持簇数目不变的情况下提高簇的质量。给定一个 m 个对象的集合。把这 m 个对象划分到 K 个区域,每个对象只属于一个区域。所遵守的一般准则是:同一个簇的对象尽可能的相互接近或者相关,而不同簇的对象尽可能的区分开。之前已经介绍过 KMeans 算法与 bisecting KMeans 算法,在这里介绍一个流式的聚类算法。

流式聚类算法(One Pass Cluster Algorithm)

既然是流式的聚类算法,那么每个数据只能够进行一次聚类的操作。有一个简单的思路就是对于每一个未知的新样本,如果它与某个类足够相似,那么就把这个未知的新样本加入这个类;否则就自成为一个新的类。如下图所示:

SinglePass 聚类原理

基于以上思路,我们可以设计一个流式的聚类算法。假设 dataMat 是一个 m 行 n 列的一个矩阵,每一行代表一个向量,n 代表向量的维度。i.e. 在 n 维欧几里德空间里面有 m 个点需要进行聚类。

流式聚类的算法细节:

参数的设定:

K 表示在聚类的过程中允许形成的簇的最大个数;

D 表示距离的阀值,在这里两个点之间的距离可以使用 L^{1}, L^{2}, L^{\infty} 范数;

聚簇的质心定义为该类中所有点的平均值。i.e. 如果该类中的点是 A[0],A[1],\cdot\cdot\cdot,A[n-1],那么质心就是 \sum_{i=0}^{n-1}A[i]/n

第 j 个聚簇中元素个数用 num[j] 表示。

方法:

Step 1:对于 dataMat[0] 而言,自成一类。i.e. 质心就是它本身 C[0]=dataMat[0],该聚簇的元素个数就是 num[0]=1,当前所有簇的个数是 K^{'}=1

Step 2:  对于每一个 1\leq i\leq m-1,进行如下的循环操作:

假设当前有 K^{'} 个簇,第 j 个簇的质心是 C[j],第 j 个簇的元素个数是 num[j],其中 0\leq j\leq K^{'}-1

计算 d= \min_{0\leq j\leq K^{'}-1}Distance(dataMat[i],C[j]),其中的 Distance 可以是欧几里德空间的 L^{1},L^{2},L^{\infty} 范数,对应的下标是 j^{'}。i.e. j'=argmin_{0\leq j\leq K'-1}Distance(dataMat[i],C[j])

(i)如果当前 K'=K 或者 d\leq D,则把 dataMat[i] 加入到第 j^{'} 个聚簇。也就是说:

质心更新为 C[j'] \leftarrow (C[j']*num[j] + dataMat[i])/(num[j] + 1)

元素的个数更新为 num[j'] \leftarrow num[j'] + 1

(ii)否则,dataMat[i] 需要自成一类。i.e. K^{'} \leftarrow K^{'} + 1num[K'-1]=1C[K'-1]=dataMat[i]

注:

(1)数据的先后顺序对聚类的效果有一定的影响。

(2)该算法的时间复杂度与簇的个数是相关的,通常来说,如果形成的簇越多,对于一个新的样本与质心的比较次数就会越多。

(3)如果设置簇的最大个数是一个比较小的数字的时候,那么对于一个新的样本,它需要比较的次数就会大量减少。如果最小距离小于 D 或者目前已经达到最大簇的个数的话,那么把新的样本放入某个类。此时,可能会出现某个类由于这个新的样本导致质心偏移的情况。如果设置 K 很大,那么计算的时间复杂度就会增加。因此,在保证计算实时性的时候,需要考虑到 K 的设置问题。

效果展示:

使用一批二维数据集合,进行流式聚类算法可以得到下图:’+’ 表示的是质心的位置,其余就是数据点的位置。通过选择不同的距离阈值 D,就可以得到不同的聚类情况。其中 ‘+’ 表示该簇质心的位置,蓝色的点表示数据点。如下图所示:

流式聚类算法1流式聚类算法2

流式聚类算法3

三十分钟理解博弈论“纳什均衡” — Nash Equilibrium

欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld。
技术交流QQ群:433250724,欢迎对算法、技术感兴趣的同学加入。


纳什均衡(或者纳什平衡),Nash equilibrium ,又称为非合作博弈均衡,是博弈论的一个重要策略组合,以约翰·纳什命名。


约翰·纳什,生于1928年6月13日。著名经济学家、博弈论创始人、《美丽心灵》男主角原型。前麻省理工学院助教,后任普林斯顿大学数学系教授,主要研究博弈论、微分几何学和偏微分方程。由于他与另外两位数学家(经济学家,约翰·C·海萨尼和莱因哈德·泽尔腾)在非合作博弈的均衡分析理论方面做出了开创性的贡献,对博弈论和经济学产生了重大影响,而获得1994年诺贝尔经济学奖。

纳什的人生非常曲折,一度学术成果不被认可,甚至换上严重的精神分裂症,在爱的力量下在很多年后奇迹般地恢复,并最终获得诺内尔经济学奖。影片《美丽心灵》(A Beautiful Mind)是一部改编自同名传记而获得奥斯卡金像奖的电影,影片以约翰·纳什与他的妻子艾莉西亚(曾离婚,但2001年复婚)以及普林斯顿的朋友、同事的真实感人故事为题材,艺术地重现了这个爱心呵护天才的传奇故事。

这里写图片描述
年轻时的Nash,很帅噢


纳什均衡定义

经济学定义[3]
所谓纳什均衡,指的是参与人的这样一种策略组合,在该策略组合上,任何参与人单独改变策略都不会得到好处。换句话说,如果在一个策略组合上,当所有其他人都不改变策略时,没有人会改变自己的策略,则该策略组合就是一个纳什均衡。

数学定义
纳什均衡的定义:在博弈G=﹛S1,…,Sn:u1,…,un﹜中,如果由各个博弈方的各一个策略组成的某个策略组合(s1*,…,sn*)中,任一博弈方i的策略si*,都是对其余博弈方策略的组合(s1*,…s*i-1,s*i+1,…,sn*)的最佳对策,也即ui(s1*,…s*i-1,si*,s*i+1,…,sn*)≥ui(s1*,…s*i-1,sij*,s*i+1,…,sn*)对任意sij∈Si都成立,则称(s1*,…,sn*)为G的一个纳什均衡。

:经济学定义从字面上还是相对比较好理解的;这里稍微解释一下数学定义,博弈论也称Game Theory,一场博弈用G表示,Si表示博弈方i的策略,ui表示收益。因此,纳什均衡的意思是:任何一方采取的策略都是对其余所有方采取策略组合下的最佳对策;当所有其他人都不改变策略时,为了让自己的收益最大,任何一方都不会(或者无法)改变自己的策略,这个时候的策略组合就是一个纳什均衡。

纳什证明了在每个参与者都只有有限种策略选择、并允许混合策略的前提下,纳什均衡一定存在。以两家公司的价格大战为例,纳什均衡意味着两败俱伤的可能:在对方不改变价格的条件下,既不能提价,否则会进一步丧失市场;也不能降价,因为会出现赔本甩卖。于是两家公司可以改变原先的利益格局,通过谈判寻求新的利益评估分摊方案,也就是Nash均衡。类似的推理当然也可以用到选举,群体之间的利益冲突,潜在战争爆发前的僵局,议会中的法案争执等。


纳什均衡案例

以下介绍几个经典的纳什均衡案例[2][4],因为本文主要是以科普为主,所以案例不会涉及到复杂深奥的经济学问题(事实上,我也不懂,哈~)。

(1)囚徒困境

假设有两个小偷A和B联合犯事、私入民宅被警察抓住。警方将两人分别置于不同的两个房间内进行审讯,对每一个犯罪嫌疑人,警方给出的政策是:如果一个犯罪嫌疑人坦白了罪行,交出了赃物,于是证据确凿,两人都被判有罪。如果另一个犯罪嫌疑人也作了坦白,则两人各被判刑8年;如果另一个犯罪嫌人没有坦白而是抵赖,则以妨碍公务罪(因已有证据表明其有罪)再加刑2年,而坦白者有功被减刑8年,立即释放。如果两人都抵赖,则警方因证据不足不能判两人的偷窃罪,但可以私入民宅的罪名将两人各判入狱1年。

此时产生了两个嫌疑人之间的一场博弈:

这里写图片描述

表中的数字表示A,B各自的判刑结果。博弈论分析中一般都用这样的表来表示。

该案例,显然最好的策略是双方都抵赖,结果是大家都只被判1年。但是由于两人处于隔离的情况,首先应该是从心理学的角度来看,当事双方都会怀疑对方会出卖自己以求自保、其次才是亚当·斯密的理论,假设每个人都是“理性的经济人”,都会从利己的目的出发进行选择。这两个人都会有这样一个盘算过程:假如他坦白,如果我抵赖,得坐10年监狱,如果我坦白最多才8年;假如他要是抵赖,如果我也抵赖,我就会被判一年,如果我坦白就可以被释放,而他会坐10年牢。综合以上几种情况考虑,不管他坦白与否,对我而言都是坦白了划算。两个人都会动这样的脑筋,最终,两个人都选择了坦白,结果都被判8年刑期。

注:亚当·斯密的理论(“看不见的手”原理),在市场经济中,每一个人都从利己的目的出发,而最终全社会达到利他的效果。但是我们可以从“纳什均衡”中引出“看不见的手”原理的一个悖论:从利己目的出发,结果损人不利己,既不利己也不利他。

(2)智猪博弈

猪圈里有两头猪,一头大猪,一头小猪。猪圈的一边有个踏板,每踩一下踏板,在远离踏板的猪圈的另一边的投食口就会落下少量的食物。如果有一只猪去踩踏板,另一只猪就有机会抢先吃到另一边落下的食物。当小猪踩动踏板时,大猪会在小猪跑到食槽之前刚好吃光所有的食物;若是大猪踩动了踏板,则还有机会在小猪吃完落下的食物之前跑到食槽,争吃到另一半残羹。

那么,两只猪各会采取什么策略?答案是:小猪将选择“搭便车”策略,也就是舒舒服服地等在食槽边;而大猪则为一点残羹不知疲倦地奔忙于踏板和食槽之间。

原因何在?因为,小猪踩踏板将一无所获,不踩踏板反而能吃上食物。对小猪而言,无论大猪是否踩动踏板,不踩踏板总是好的选择。反观大猪,已明知小猪是不会去踩动踏板的,自己亲自去踩踏板总比不踩强吧,所以只好亲力亲为了。

(3)普通范式博弈

GOO公司和SAM公司是某手机产品生态的两大重量级参与者,双方在产业链的不同位置上各司其职且关系暧昧,有时也往往因商业利益和产品影响力的争夺而各怀异心。二者的收益也随着博弈的变化而不断更替。

这里写图片描述

上图表格模拟了两家公司的博弈现状,双方各有两个可选策略“合作”与“背叛”,格中的四组数据表示四个博弈结局的分数(收益),每组数据的第一个数字表示GOO公司的收益,后一个数字表示SAM公司的收益。

博弈是同时进行的,一方参与者必须站在对方的角度上来思考我方的策略选择,以追求收益最大化。这在博弈论里称作Putting yourselves into other people’s shoes。

现在我们以GOO公司为第一人称视角来思考应对SAM公司的博弈策略。假如SAM公司选择合作,那么我方也选择合作带来的收益是3,而我方选择背叛带来的收益是5,基于理性的收益最大化考虑,我方应该选择背叛,这叫严格优势策略;假如SAM公司选择背叛,那么我方选择合作带来的收益是-3,而选择背叛带来的收益为-1,为使损失降到最低,我方应该选择背叛。最后,GOO公司的分析结果是,无论SAM公司选择合作还是背叛策略,我方都必须选择背叛策略才能获得最大化的收益。

同理,当SAM公司也以严格优势策略来应对GOO公司的策略选择时,我们重复上述分析过程,就能得出结论:无论GOO公司选择合作还是背叛策略,SAM公司都必须选择背叛策略才能获得最大化收益。

最后我们发现,本次博弈的双方都采取了背叛策略,各自的收益都为-1,这是一个比较糟糕的结局,尽管对任何一方来说都不是最糟糕的那种。这种局面就是著名的“囚徒困境”。

但是,博弈的次数往往不止一次,就像COO与SAM公司双方的商业往来也许会有很多机会。当二者经历了多次背叛策略的博弈之后,发现公式上还有一个(3,3)收益的双赢局面,这比(-1,-1)的收益结果显然要好很多,因此二者在之后的博弈过程中必然会尝试互建信任,从而驱使双方都选择合作策略。

这里有一个理想化假设,那就是假设双方都知道博弈次数是无限的话,也就是说双方的商业往来是无止尽的,那么二者的策略都将持续选择合作,最终的博弈收益将定格在(3,3),这就是一个纳什均衡。既然博弈次数是无限的,那么任何一方都没有理由选择背叛策略去冒险追求5点短暂收益,而招致对方在下一轮博弈中的报复(这种报复在博弈论里称作“以牙还牙”策略)。

还有另一种假设情况是,假使双方都知道博弈次数是有限的,也许下一次博弈就是最后一次,那么为了避免对方在最后一轮博弈中选择背叛策略而使我方遭受-3的收益损失,于是双方都重新采取了背叛的策略选择,最后的博弈结果又回到了(-1,-1),这就形成了第二个纳什均衡。

由此可见,随着次数(博弈性质)的变化,纳什均衡点也并非唯一。

(4)饿狮博弈

假设有A、B、C、D、E、F六只狮子(强弱从左到右依次排序)和一只绵羊。假设狮子A吃掉绵羊后就会打盹午睡,这时比A稍弱的狮子B就会趁机吃掉狮子A,接着B也会午睡,然后狮子C就会吃掉狮子B,以此类推。那么问题来了,狮子A敢不敢吃绵羊?

为简化说明,我们先给出此题的解法。该题须采用逆向分析法,也就是从最弱的狮子F开始分析,依次前推。假设狮子E睡着了,狮子F敢不敢吃掉狮子E?答案是肯定的,因为在狮子F的后面已没有其它狮子,所以狮子F可以放心地吃掉午睡中的狮子E。

继续前推,既然狮子E睡着会被狮子F吃掉,那么狮子E必然不敢吃在他前面睡着的狮子D。

再往前推,既然狮子E不敢吃掉狮子D,那么D则可以放心去吃午睡中的狮子C。依次前推,得出C不吃,B吃,A不吃。所以答案是狮子A不敢吃掉绵羊。

推理结果如下图:
这里写图片描述

但是,如果我们在狮子F的后面增加了一只狮子G,总数变成7只,用逆向分析法按照上题步骤再推一次,很容易得出结论:狮子G吃,狮子F不吃,E吃,D不吃,C吃,B不吃,A吃。这次的答案变成了狮子A敢吃掉绵羊。

这里写图片描述

对比两次博弈我们发现,狮子A敢不敢吃绵羊取决于狮子总数的奇偶性,总数为奇数时,A敢吃掉绵羊;总数为偶数时,A则不敢吃。因此,总数为奇数和总数为偶数的狮群博弈结果形成了两个稳定的纳什均衡点。

(5)硬币正反

你正在图书馆枯坐,一位陌生美女主动过来和你搭讪,并要求和你一起玩个数学游戏。美女提议:“让我们各自亮出硬币的一面,或正或反。如果我们都是正面,那么我给你3元,如果我们都是反面,我给你1元,剩下的情况你给我2元就可以了。”那么该不该和这位姑娘玩这个游戏呢?

每一种游戏依具其规则的不同会存在两种纳什均衡,一种是纯策略纳什均衡,也就是说玩家都能够采取固定的策略(比如一直出正面或者一直出反面),使得每人都赚得最多或亏得最少;或者是混合策略纳什均衡,而在这个游戏中,便应该采用混合策略纳什均衡。

这里写图片描述

假设我们出正面的概率是x,反面的概率是1-x,美女出正面的概率是y,反面的概率是1-y。为了使利益最大化,应该在对手出正面或反面的时候我们的收益都相等,由此列出方程就是

3x + (-2)(1-x)=(-2) * x + 1*( 1-x )——解方程得x=3/8;同样,美女的收益,列方程-3y + 2( 1-y)= 2y+ (-1) * ( 1-y)——解得y也等于3/8。

于是,我们就可以算美女每次的期望收益是: (1-y)(2x-(1-x)) + y(-3x+2(1-x)) = 1/8元,也就是说,双方都采取最优策略的情况下,平均每次美女赢1/8元。

其实只要美女采取了(3/8,5/8)这个方案,不论你再采用什么方案,都是不能改变局面的。如果全部出正面,每次的期望收益是 (3+3+3-2-2-2-2-2)/8=-1/8元;如果全部出反面,每次的期望收益也是(-2-2-2+1+1+1+1+1)/8=-1/8元。而任何策略无非只是上面两种策略的线性组合,所以期望还是-1/8元。但是当你也采用最佳策略时,至少可以保证自己输得最少。否则,你肯定就会被美女采用的策略针对,从而赔掉更多。


纳什均衡分类

最后讲一讲纳什均衡的分类。纳什均衡可以分成两类:“纯战略纳什均衡”和“混合战略纳什均衡”。

要说明纯战略纳什均衡和混合战略纳什均衡,要先说明纯战略和混合战略。所谓纯战略是提供给玩家要如何进行赛局的一个完整的定义。特别地是,纯战略决定在任何一种情况下要做的移动。战略集合是由玩家能够施行的纯战略所组成的集合。而混合战略是对每个纯战略分配一个机率而形成的战略。混合战略允许玩家随机选择一个纯战略。混合战略博弈均衡中要用概率计算,因为每一种策略都是随机的,达到某一概率时,可以实现支付最优。因为机率是连续的,所以即使战略集合是有限的,也会有无限多个混合战略。

当然,严格来说,每个纯战略都是一个“退化”的混合战略,某一特定纯战略的机率为 1,其他的则为 0。
故“纯战略纳什均衡”,即参与之中的所有玩家都玩纯战略;而相应的“混合战略纳什均衡”,之中至少有一位玩家玩混合战略。并不是每个赛局都会有纯战略纳什均衡,例如“钱币问题”就只有混合战略纳什均衡,而没有纯战略纳什均衡。不过,还是有许多赛局有纯战略纳什均衡(如协调赛局,囚徒困境和猎鹿赛局)。甚至,有些赛局能同时有纯战略和混合战略均衡。

[转载]【重磅】无监督学习生成式对抗网络突破,OpenAI 5大项目落地

http://www.cnblogs.com/wangxiaocvpr/p/5966574.html

【重磅】无监督学习生成式对抗网络突破,OpenAI 5大项目落地

 

【新智元导读】“生成对抗网络是切片面包发明以来最令人激动的事情!”LeCun前不久在Quroa答问时毫不加掩饰对生成对抗网络的喜爱,他认为这是深度学习近期最值得期待、也最有可能取得突破的领域。生成对抗学习是无监督学习的一种,该理论由 Ian Goodfellow 提出,此人现在 OpenAI 工作。作为业内公认进行前沿基础理论研究的机构,OpenAI 不久前在博客中总结了他们的5大项目成果,结合丰富实例介绍了生成对抗网络,并对OpenAI 五大落地项目进行梳理,包括完善对抗生成网络(GAN)、完善变分推断(VAE)、提出GAN的扩展 InfoGAN,以及提出生成对抗模仿学习及代码。

 

 

 

OpenAI 旨在开发能够让计算机理解世界的算法和技术。

 

我们常会忽略自己对周遭世界的理解:你知道世界由三维环境构成,物体可以移动、碰撞、相互作用;人能行走、说话、思考;动物会吃草、飞翔、奔跑或者鸣叫;屏幕会显示经过编码的信息,内容涉及天气、篮球赛的结果或者 1970 年的事情。

 

这些海量信息就在那里,大都触手可及——其存在形式要么是现实世界中的原子,要么是数字世界里的比特。唯一的问题是如何设计模型和算法,分析和理解这些宝贵的数据。

 

生成模型是实现这一目标最值得期待的方法。训练生成模型,首先要大量收集某种数据(比如成千上万的图像、句子或声音),然后训练一个模型,让这个模型可以生成这样的数据。

 

其原理是费曼的名言:“做不出来就没有真正明白。”(What I cannot create, I do not understand.)

 

用于生成模型的神经网络,很多参数都远远小于用于训练的数据的量,因此模型能够发现并有效内化数据的本质,从而可以生成这些数据。

 

生成式模型有很多短期应用。但从长远角度看,生成模型有望自动学会数据集的类型、维度等特征。

 

生成图像

 

举个例子,假设有某个海量图像数据集,比如含有 120 万幅图像的 ImageNet 数据集。如果将每幅图的宽高设为 256,这个数据集就是 1200000*256*256*3(约 200 GB)的像素块。其中的一些样例如下:

 

 

这些图像是人类肉眼所见的样子,我们将它们称为“真实数据分布中的样本”。现在我们搭建生成模型,训练该模型生成类似上图的图像,在这里,这个生成模型就是一个输出为图像的大型神经网络,这些输出的图像称为“模型样本”。

 

 

DCGAN

 

Radford 等人提出的 DCGAN 网络(见下图)就是这样一个例子。DCGAN 网络以 100 个从一个高斯分布中采样的随机数作为输入(即代码,或者叫“隐含变量”,靠左红色部分),输出图像(在这里就是 64*64*3 的图像,靠右绿色部分)。当代码增量式变化时,生成的图像也随之变化——说明模型已经学会了如何描述世界,而不是仅仅是记住了一些样本。

 

网络(黄色部分)由标准的卷积神经网络(CNN)构成:

DCGAN 使用随机权重初始化,所以随机代码输入会生成一个完全随机的图像。但是,这个网络有好几百万的参数可以调整,而我们的目的是设置参数,让根据随机代码输入产生的样本与训练数据看上去相似。换句话说,我们想要模型分布与图像空间中真实数据的分布相匹配。

 

训练生成模型

 

假设我们使用最新初始化的网络生成 200 幅图,每次都从不同的随机代码开始。问题是:我们该如何调整网络的参数,让每次输出的新图像都更接近理想?需要注意的是,这里并非监督学习场景,我们也没有对 200 幅输出图像设定明确的预期;我们只是希望这些图像看起来跟真实的一样。

 

一个巧妙的处理方式是依照生成对抗网络(Generative Adversarial Network,GAN)方法。这里,我们引入另一个判别器网络(discriminator network,通常是一个标准的卷积神经网络),判断输入的图像是真实的还是生成的。我们可以将 200 幅生成的图像和 200 幅真实图像用作训练数据,将这个判别器训练成一个标准的分类器,其功能就是区分这两种不同的图像。

 

此外,我们还可以经由判别器和生成器反向传播(backpropagate),找出应该如何改变生成器的参数,使其生成的 200 幅样本对判别器而言混淆度更大。这两个网络就形成了一种对抗:判别器试着从伪造图像中区分出真实图像,而生成器则努力产生可以骗过判别器的图像。最后,生成器网络输出的结果就是在判别器看来无法区分的图像。

 

下图展示了两种从生成模型采样的过程。两种情况下,输入都是有噪声和混乱的,经过一段时间收敛,可以得到较为可信的图像统计:

 

VAE 学会产生图像(log time)

 

GAN 学会产生图像(linear time)

 

这令人兴奋——这些神经网络正在逐渐学会世界看起来是什么样子的!这些模型通常只有 10 亿参数,所以一个在 ImageNet 上训练的网络(粗略地)将 200GB 的像素数据压缩到 100MB 的权重。这让模型得以发现数据最主要的特征:例如,模型很可能学会位置邻近的像素更有可能拥有同样的颜色,或者世界是由水平或竖直的边构成。

 

最终,模型可能会发现很多更复杂的规律:例如,图像中有特定类型的背景、物体、纹理,它们会以某种可能的排列方式出现,或者在视频中随时间按某种方式变化等等。

 

 

更泛化的表现形式

 

数学上看,我们考虑数据集 x1,…,xn 是从真实数据分布 p(x) 中的一段。下图中,蓝色区域展示了一部分图像空间,这部分空间以高概率(超过某阈值)包含真实图像,而黑色点表示数据点(每个都是数据集中一副图像)。现在,我们的模型同样刻画了一个分布 p^θ(x) (绿色),将从一个单位 Gaussian 分布 (红色) 获得的点,通过一个(判别器)神经网络映射,得到了生成模型 (黄色)。

 

我们的网络是参数为 θ 的函数,调整这些参数就能改变生成出的图像分布。目标是找到参数 θ 可以产生一个较好匹配真实数据分布的分布。因此,你可以想象绿色分布从随机开始,然后训练过程迭代式改变参数 θ 拉长和压缩自己使得更匹配蓝色分布。

 

生成模型三种搭建方法

 

大多数生成模型有一个基础的设置,只是在细节上有所不同。下面是生成模型的三个常用方法:

 

  • Generative Adversarial Network(GAN)这个我们在上面讨论过了,将训练过程作为两个不同网络的对抗:一个生成器网络和一个判别器网络,判别器网络试图区分样来自于真实分布 p(x) 和模型分布 p^(x) 的样本。每当判别器发现两个分布之间有差异时,生成器网络便微整参数,使判别器不能从中找到差异。

     

  • Variational Autoencoders(VAE)让我们可以在概率图模型框架下形式化这个问题,我们会最大化数据的对数似然(log likelihood)的下界

     

  • PixelRNN 等自回归模型。训练一个建模了给定前面像素下每个独立像素条件分布的网络(从左到右,从上到下). 这类似于将图像的像素输入到一个 char-rnn,但是 RNN 水平和垂直遍历图像,而不是 1D 的字符序列

 

所有这些方法有各自的优缺点。例如,变分自编码器可以执行学习和在复杂的包含隐含变量的概率图模型上进行高效贝叶斯推断(如 DRAW 或者 Attend Infer Repeat 近期相对复杂的模型)。但是,生成的样本会有些模糊不清。GAN 目前生成了清楚的图像,但是因为不稳定的训练动态性很难优化。PixelRNN 有一个非常简单和稳定的训练过程(softmax loss),并且当前给出了最优的对数似然(产生出数据的可信程度)。然而,PixelRNN 在采样时相对低效,而且没有给图像以简单的低维代码。

 

OpenAI 5 大落地

 

我们对 OpenAI 做出的生成式模型非常兴奋,刚刚发布了四个对近期工作项目改进工作. 对每个贡献,我们同样发布了技术报告和源代码.

 

1. 完善对抗生成网络(GAN)

 

 

GAN 是非常值得期待的生成模型,不像其他方法,GAN 产生的图像干净、清晰,并且能够学会与纹理有关的代码。然而,GAN 被构建成两个网络之间的对抗,保持平衡十分重要(而且相当考验技巧):两个网络可能在解析度之间震荡,生成器容易崩溃。

 

Tim Salimans, Ian Goodfellow, Wojciech Zaremba 等人引入了一些新技巧,让 GAN 训练更加稳定。这些技巧让我们能够放大 GAN ,获得清晰的 128*128 ImageNet 样本:

 

[左]真实图像(ImageNet);[右]生成的图像

 

我们 CIFAR-10 的样本看起来也是非常清晰的——Amazon 为图像打标签的工人(Amazon Mechanical Turk workers)在区分这些图像和真实图像时,错误率为 21.3% (50% 的错误率代表随机猜测)。

 

[左]真实图像(CIFAR-10);[右]生成的图像

 

 

除了生成更好的图像,我们还引入了一种结合 GAN 和半监督学习的方法。这使我们在不需要大量带标签样本的前提下,在 MNIST、SVHN 和 CIFAR-10 获得当前最佳的结果。在 MNIST,我们对每个类仅有 10 个带标签的样本,使用了一个全连接的神经网络,达到了 99.14% 的准确率——这个结果接近已知最好的监督学习,而后者使用了 6 万个带标签的样本。由于为样本打标签非常麻烦,所以上述方法是很值得期待的。

 

生成对抗网络是两年多前才提出来的,我们期望在未来出现更多提升其训练稳定性的研究。

 

2. 完善变分推断(VAE)

 

在这项工作中,Durk Kingma 和 Tim Salimans 引入了一个灵活、可扩展的计算方法,提升变分推断的准确率。目前,大多数 VAE 训练采用暴力近似后验分布(approximate posterior),每个隐含变量都是独立的。最近的扩展工作虽然解决了这个问题,但由于引入的序列依赖,在计算上仍然称不上高效。

 

这项工作的主要贡献是“逆自递归流”(Inverse Autoregressive Flow,IAF),这种方法使 rich approximate posterior 能够并行计算,从而高度灵活,可以达到近乎随机的任意性。

 

我们在下面的图中展示了一些 32*32 的图像样本。前一幅是来自 DRAW 模型的早期样本(初级 VAE 样本看起来更差和模糊)。DRAW 模型一年前才发表的,由此也可以感受到训练生成模型的发展迅速。

 

[左]用 IAF 训练 VAE 生成的图像;[右]DRAW 模型生成的图像

 

3. InfoGAN

 

Peter Chen 等人提出了 InfoGAN ——GAN 的扩展。普通的 GAN 通过在模型里重新生成数据分布,让模型学会图像的disentangled 和 interpretable 表征。但是,其 layout 和 organization 是 underspecified。

 

InfoGAN 引入了额外的结构,得到了相当出色的结果。在三维人脸图像中,改变代码的一个连续维度,保持其他维度不变,很明显从每张图片给出的 5 行例子中,代码的 resulting dimension 是可解释的,模型在事先不知道摄像头角度、面部变化等特征存在的前提下,可能已经理解这些特征是存在的,并且十分重要:

 

(a)pose                    (b)Elevation

 

(c)Lighting             (d)Wide or Narrow

 

同时,值得一提的是,上述方法是非监督学习的方法。 因此,相比通过监督学习的方法实现了同样结果的思路,这种方法体现出了更高的水平。

 

3. 生成模型的深度强化学习(两项)

 

下面是两个强化学习场景下(另一个 OpenAI 聚焦的领域),生成式模型的完善:Curiosity-driven Exploration in Deep Reinforcement Learning via Bayesian Neural Networks。

 

在高维度连续空间中进行高效的探索是当前强化学习尚未解决的一个难题。没有有效的探索方法,智能体只能到处乱闯直到碰巧遇到奖励。若要对高维行动空间进行探索(比如机器人),这些算法是完全不够的。这篇论文中,Rein Houthooft 等人提出了 VIME,一个使用生成模型对不确定性进行探索的实用方法。

 

VIME 让智能体本身驱动;它主动地寻求意外的状态-行动。作者展示了 VIME 可以提高一系列策略搜索方法,并在更多稀疏奖励的实际任务(比如智能体需要在无指导的情形下学习原始行动的场景)取得了显著改进。

 

用 VIME 训练的策略

 

没有受训的策略

 

 

4. 生成对抗模仿学习

 

Jonathan Ho 等人提出了一个新的模仿学习(imitation learning)方法。Jonathan Ho 在斯坦福大学完成了这项工作的主要内容,他作为暑期实习生加入 OpenAI 后,结合 GAN 以及 RL 等相关工作的启发,最终完成了生成对抗模仿学习及代码。

 

标准的强化学习场景通常需要设计一个规定智能体预期行为的奖励函数。但实际情况是,有样做有时候会为了实现细节上的正确,而引入代价过高的试错过程。相较而言,模仿学习中,智能体从样本展示中学习,就免去了对奖励函数的依赖。

 

 

常用模仿方法包含两个阶段的流程:首先学习奖励函数,然后依照奖励函数执行深度强化学习。这样的过程非常缓慢,也由于这种间接性方法,很难保证结果策略的质量。这项工作展示了如何通过 GAN 直接从数据中抽取策略。这个方法在 OpenAI Gym 环境中可以根据专家表现进行策略学习。

 

 

 

 

展望未来

 

生成模型是快速发展的研究领域。在完善这些模型,扩展训练和数据集的同时,我们完全可以认为最终将会产生能够以假乱真的图像或视频样本。这可以用很多应用, 图像降噪(image denoising)、inpainting、高清分辨率(super-resolution)、结构化预测(structured prediction)、强化学习探索(exploration in reinforcement learning),以及神经网络预处理这些为数据打标签的很复杂、造价很高的领域,有很多潜力。

 

 

这项工作更深的启示是,在训练生成模型的过程中,我们最终会增进计算机对世界及其构成的理解。

一文学会用 Tensorflow 搭建神经网络

http://www.jianshu.com/p/e112012a4b2d

cs224d-Day 6: 快速入门 Tensorflow

本文是学习这个视频课程系列的笔记,课程链接是 youtube 上的,
讲的很好,浅显易懂,入门首选, 而且在github有代码,
想看视频的也可以去他的优酷里的频道找。

Tensorflow 官网


神经网络是一种数学模型,是存在于计算机的神经系统,由大量的神经元相连接并进行计算,在外界信息的基础上,改变内部的结构,常用来对输入和输出间复杂的关系进行建模。

神经网络由大量的节点和之间的联系构成,负责传递信息和加工信息,神经元也可以通过训练而被强化。

这个图就是一个神经网络系统,它由很多层构成。输入层就是负责接收信息,比如说一只猫的图片。输出层就是计算机对这个输入信息的认知,它是不是猫。隐藏层就是对输入信息的加工处理。

神经网络是如何被训练的,首先它需要很多数据。比如他要判断一张图片是不是猫。就要输入上千万张的带有标签的猫猫狗狗的图片,然后再训练上千万次。

神经网络训练的结果有对的也有错的,如果是错误的结果,将被当做非常宝贵的经验,那么是如何从经验中学习的呢?就是对比正确答案和错误答案之间的区别,然后把这个区别反向的传递回去,对每个相应的神经元进行一点点的改变。那么下一次在训练的时候就可以用已经改进一点点的神经元去得到稍微准确一点的结果。

神经网络是如何训练的呢?每个神经元都有属于它的激活函数,用这些函数给计算机一个刺激行为。

在第一次给计算机看猫的图片的时候,只有部分的神经元被激活,被激活的神经元所传递的信息是对输出结果最有价值的信息。如果输出的结果被判定为是狗,也就是说是错误的了,那么就会修改神经元,一些容易被激活的神经元会变得迟钝,另外一些神经元会变得敏感。这样一次次的训练下去,所有神经元的参数都在被改变,它们变得对真正重要的信息更为敏感。

Tensorflow 是谷歌开发的深度学习系统,用它可以很快速地入门神经网络。

它可以做分类,也可以做拟合问题,就是要把这个模式给模拟出来。

这是一个基本的神经网络的结构,有输入层,隐藏层,和输出层。
每一层点开都有它相应的内容,函数和功能。

那我们要做的就是要建立一个这样的结构,然后把数据喂进去。
把数据放进去后它就可以自己运行,TensorFlow 翻译过来就是向量在里面飞。

这个动图的解释就是,在输入层输入数据,然后数据飞到隐藏层飞到输出层,用梯度下降处理,梯度下降会对几个参数进行更新和完善,更新后的参数再次跑到隐藏层去学习,这样一直循环直到结果收敛。

tensors_flowing.gif

今天一口气把整个系列都学完了,先来一段完整的代码,然后解释重要的知识点!


1. 搭建神经网络基本流程

定义添加神经层的函数

1.训练的数据
2.定义节点准备接收数据
3.定义神经层:隐藏层和预测层
4.定义 loss 表达式
5.选择 optimizer 使 loss 达到最小

然后对所有变量进行初始化,通过 sess.run optimizer,迭代 1000 次进行学习:

import tensorflow as tf
import numpy as np

# 添加层
def add_layer(inputs, in_size, out_size, activation_function=None):
    # add one more layer and return the output of this layer
    Weights = tf.Variable(tf.random_normal([in_size, out_size]))
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
    Wx_plus_b = tf.matmul(inputs, Weights) + biases
    if activation_function is None:
        outputs = Wx_plus_b
    else:
        outputs = activation_function(Wx_plus_b)
    return outputs

# 1.训练的数据
# Make up some real data 
x_data = np.linspace(-1,1,300)[:, np.newaxis]
noise = np.random.normal(0, 0.05, x_data.shape)
y_data = np.square(x_data) - 0.5 + noise

# 2.定义节点准备接收数据
# define placeholder for inputs to network  
xs = tf.placeholder(tf.float32, [None, 1])
ys = tf.placeholder(tf.float32, [None, 1])

# 3.定义神经层:隐藏层和预测层
# add hidden layer 输入值是 xs,在隐藏层有 10 个神经元   
l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)
# add output layer 输入值是隐藏层 l1,在预测层输出 1 个结果
prediction = add_layer(l1, 10, 1, activation_function=None)

# 4.定义 loss 表达式
# the error between prediciton and real data    
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction),
                     reduction_indices=[1]))

# 5.选择 optimizer 使 loss 达到最小                   
# 这一行定义了用什么方式去减少 loss,学习率是 0.1       
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)


# important step 对所有变量进行初始化
init = tf.initialize_all_variables()
sess = tf.Session()
# 上面定义的都没有运算,直到 sess.run 才会开始运算
sess.run(init)

# 迭代 1000 次学习,sess.run optimizer
for i in range(1000):
    # training train_step 和 loss 都是由 placeholder 定义的运算,所以这里要用 feed 传入参数
    sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
    if i % 50 == 0:
        # to see the step improvement
        print(sess.run(loss, feed_dict={xs: x_data, ys: y_data}))

2. 主要步骤的解释:

  • 之前写过一篇文章 TensorFlow 入门 讲了 tensorflow 的安装,这里使用时直接导入:
import tensorflow as tf
import numpy as np
  • 导入或者随机定义训练的数据 x 和 y:
x_data = np.random.rand(100).astype(np.float32)
y_data = x_data*0.1 + 0.3
  • 先定义出参数 Weights,biases,拟合公式 y,误差公式 loss:
Weights = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
biases = tf.Variable(tf.zeros([1]))
y = Weights*x_data + biases
loss = tf.reduce_mean(tf.square(y-y_data))
  • 选择 Gradient Descent 这个最基本的 Optimizer:
optimizer = tf.train.GradientDescentOptimizer(0.5)
  • 神经网络的 key idea,就是让 loss 达到最小:
train = optimizer.minimize(loss)
  • 前面是定义,在运行模型前先要初始化所有变量:
init = tf.initialize_all_variables()
  • 接下来把结构激活,sesseion像一个指针指向要处理的地方:
sess = tf.Session()
  • init 就被激活了,不要忘记激活:
sess.run(init)
  • 训练201步:
for step in range(201):
  • 要训练 train,也就是 optimizer:
sess.run(train)
  • 每 20 步打印一下结果,sess.run 指向 Weights,biases 并被输出:
if step % 20 == 0:
print(step, sess.run(Weights), sess.run(biases))

所以关键的就是 y,loss,optimizer 是如何定义的。


3. TensorFlow 基本概念及代码:

TensorFlow 入门 也提到了几个基本概念,这里是几个常见的用法。

  • Session

矩阵乘法:tf.matmul

product = tf.matmul(matrix1, matrix2) # matrix multiply np.dot(m1, m2)

定义 Session,它是个对象,注意大写:

sess = tf.Session()

result 要去 sess.run 那里取结果:

result = sess.run(product)
  • Variable

用 tf.Variable 定义变量,与python不同的是,必须先定义它是一个变量,它才是一个变量,初始值为0,还可以给它一个名字 counter:

state = tf.Variable(0, name='counter')

将 new_value 加载到 state 上,counter就被更新:

update = tf.assign(state, new_value)

如果有变量就一定要做初始化:

init = tf.initialize_all_variables() # must have if define variable
  • placeholder:

要给节点输入数据时用 placeholder,在 TensorFlow 中用placeholder 来描述等待输入的节点,只需要指定类型即可,然后在执行节点的时候用一个字典来“喂”这些节点。相当于先把变量 hold 住,然后每次从外部传入data,注意 placeholder 和 feed_dict 是绑定用的。

这里简单提一下 feed 机制, 给 feed 提供数据,作为 run()
调用的参数, feed 只在调用它的方法内有效, 方法结束, feed 就会消失。

import tensorflow as tf

input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
ouput = tf.mul(input1, input2)

with tf.Session() as sess:
  print(sess.run(ouput, feed_dict={input1: [7.], input2: [2.]}))

4. 神经网络基本概念

  • 激励函数:

例如一个神经元对猫的眼睛敏感,那当它看到猫的眼睛的时候,就被激励了,相应的参数就会被调优,它的贡献就会越大。

下面是几种常见的激活函数:
x轴表示传递过来的值,y轴表示它传递出去的值:

激励函数在预测层,判断哪些值要被送到预测结果那里:

TensorFlow 常用的 activation function

  • 添加神经层:

输入参数有 inputs, in_size, out_size, 和 activation_function

import tensorflow as tf

def add_layer(inputs, in_size, out_size,  activation_function=None):

  Weights = tf.Variable(tf.random_normal([in_size, out_size]))
  biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
  Wx_plus_b = tf.matmul(inputs, Weights) + biases

  if activation_function is None:
    outputs = Wx_plus_b
  else:
    outputs = activation_function(Wx_plus_b)

return outputs
  • 分类问题的 loss 函数 cross_entropy :
# the error between prediction and real data
# loss 函数用 cross entropy
cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(prediction),
                                              reduction_indices=[1]))       # loss
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
  • overfitting:

下面第三个图就是 overfitting,就是过度准确地拟合了历史数据,而对新数据预测时就会有很大误差:

Tensorflow 有一个很好的工具, 叫做dropout, 只需要给予它一个不被 drop 掉的百分比,就能很好地降低 overfitting。

dropout 是指在深度学习网络的训练过程中,按照一定的概率将一部分神经网络单元暂时从网络中丢弃,相当于从原始的网络中找到一个更瘦的网络,这篇博客中讲的非常详细

代码实现就是在 add layer 函数里加上 dropout, keep_prob 就是保持多少不被 drop,在迭代时在 sess.run 中被 feed:

def add_layer(inputs, in_size, out_size, layer_name, activation_function=None, ):
    # add one more layer and return the output of this layer
    Weights = tf.Variable(tf.random_normal([in_size, out_size]))
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, )
    Wx_plus_b = tf.matmul(inputs, Weights) + biases

    # here to dropout
    # 在 Wx_plus_b 上drop掉一定比例
    # keep_prob 保持多少不被drop,在迭代时在 sess.run 中 feed
    Wx_plus_b = tf.nn.dropout(Wx_plus_b, keep_prob)

    if activation_function is None:
        outputs = Wx_plus_b
    else:
        outputs = activation_function(Wx_plus_b, )
    tf.histogram_summary(layer_name + '/outputs', outputs)  
    return outputs

5. 可视化 Tensorboard

Tensorflow 自带 tensorboard ,可以自动显示我们所建造的神经网络流程图:

就是用 with tf.name_scope 定义各个框架,注意看代码注释中的区别:

import tensorflow as tf


def add_layer(inputs, in_size, out_size, activation_function=None):
    # add one more layer and return the output of this layer
    # 区别:大框架,定义层 layer,里面有 小部件
    with tf.name_scope('layer'):
        # 区别:小部件
        with tf.name_scope('weights'):
            Weights = tf.Variable(tf.random_normal([in_size, out_size]), name='W')
        with tf.name_scope('biases'):
            biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, name='b')
        with tf.name_scope('Wx_plus_b'):
            Wx_plus_b = tf.add(tf.matmul(inputs, Weights), biases)
        if activation_function is None:
            outputs = Wx_plus_b
        else:
            outputs = activation_function(Wx_plus_b, )
        return outputs


# define placeholder for inputs to network
# 区别:大框架,里面有 inputs x,y
with tf.name_scope('inputs'):
    xs = tf.placeholder(tf.float32, [None, 1], name='x_input')
    ys = tf.placeholder(tf.float32, [None, 1], name='y_input')

# add hidden layer
l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)
# add output layer
prediction = add_layer(l1, 10, 1, activation_function=None)

# the error between prediciton and real data
# 区别:定义框架 loss
with tf.name_scope('loss'):
    loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction),
                                        reduction_indices=[1]))

# 区别:定义框架 train
with tf.name_scope('train'):
    train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)

sess = tf.Session()

# 区别:sess.graph 把所有框架加载到一个文件中放到文件夹"logs/"里 
# 接着打开terminal,进入你存放的文件夹地址上一层,运行命令 tensorboard --logdir='logs/'
# 会返回一个地址,然后用浏览器打开这个地址,在 graph 标签栏下打开
writer = tf.train.SummaryWriter("logs/", sess.graph)
# important step
sess.run(tf.initialize_all_variables())

运行完上面代码后,打开 terminal,进入你存放的文件夹地址上一层,运行命令 tensorboard –logdir=’logs/’ 后会返回一个地址,然后用浏览器打开这个地址,点击 graph 标签栏下就可以看到流程图了:


6. 保存和加载

训练好了一个神经网络后,可以保存起来下次使用时再次加载:

import tensorflow as tf
import numpy as np

## Save to file
# remember to define the same dtype and shape when restore
W = tf.Variable([[1,2,3],[3,4,5]], dtype=tf.float32, name='weights')
b = tf.Variable([[1,2,3]], dtype=tf.float32, name='biases')

init= tf.initialize_all_variables()

saver = tf.train.Saver()

# 用 saver 将所有的 variable 保存到定义的路径
with tf.Session() as sess:
   sess.run(init)
   save_path = saver.save(sess, "my_net/save_net.ckpt")
   print("Save to path: ", save_path)


################################################

# restore variables
# redefine the same shape and same type for your variables
W = tf.Variable(np.arange(6).reshape((2, 3)), dtype=tf.float32, name="weights")
b = tf.Variable(np.arange(3).reshape((1, 3)), dtype=tf.float32, name="biases")

# not need init step

saver = tf.train.Saver()
# 用 saver 从路径中将 save_net.ckpt 保存的 W 和 b restore 进来
with tf.Session() as sess:
    saver.restore(sess, "my_net/save_net.ckpt")
    print("weights:", sess.run(W))
    print("biases:", sess.run(b))

tensorflow 现在只能保存 variables,还不能保存整个神经网络的框架,所以再使用的时候,需要重新定义框架,然后把 variables 放进去学习。

 

文/不会停的蜗牛(简书作者)
原文链接:http://www.jianshu.com/p/e112012a4b2d
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

TensorFlow 入门

http://www.jianshu.com/p/6766fbcd43b9

CS224d-Day 2:

在 Day 1 里,先了解了一下 NLP 和 DP 的主要概念,对它们有了一个大体的印象,用向量去表示研究对象,用神经网络去学习,用 TensorFlow 去训练模型,基本的模型和算法包括 word2vec,softmax,RNN,LSTM,GRU,CNN,大型数据的 seq2seq,还有未来比较火热的研究方向 DMN,还有模型的调优。

今天先不直接进入理论学习,而是先学习一下 TensorFlow,在原课程里,这部分在第7讲,但是我觉得最高效地学习算法的方式,就是一边学理论,一边写代码,实践中才能理解更深刻。

Day 2 先认识 TensorFlow,了解一下基本用法,下一次就写代码来训练模型算法,以问题为导向,以项目为驱动。


本文结构:

  • 1. TensorFlow 是什么
  • 2. 为什么需要 TensorFlow
  • 3. TensorFlow 的优点
  • 4. TensorFlow 的工作原理
  • 5. 安装
  • 6. TensorFlow 基本用法
    • 要点
    • 例子
    • 概念
      • 张量
      • 会话

1. TensorFlow 是什么

是一个深度学习库,由 Google 开源,可以对定义在 Tensor(张量)上的函数自动求导。

Tensor(张量)意味着 N 维数组,Flow(流)意味着基于数据流图的计算,TensorFlow即为张量从图的一端流动到另一端。

它的一大亮点是支持异构设备分布式计算,它能够在各个平台上自动运行模型,从电话、单个CPU / GPU到成百上千GPU卡组成的分布式系统。

支持CNN、RNN和LSTM算法,是目前在 Image,NLP 最流行的深度神经网络模型。


2. 为什么需要 TensorFlow 等库

深度学习通常意味着建立具有很多层的大规模的神经网络。

除了输入X,函数还使用一系列参数,其中包括标量值、向量以及最昂贵的矩阵和高阶张量。

在训练网络之前,需要定义一个代价函数,常见的代价函数包括回归问题的方差以及分类时候的交叉熵。

训练时,需要连续的将多批新输入投入网络,对所有的参数求导后,代入代价函数,从而更新整个网络模型。

这个过程中有两个主要的问题:1. 较大的数字或者张量在一起相乘百万次的处理,使得整个模型代价非常大。2. 手动求导耗时非常久。

所以 TensorFlow 的对函数自动求导以及分布式计算,可以帮我们节省很多时间来训练模型。


3. TensorFlow 的优点

第一,基于Python,写的很快并且具有可读性。

第二,在多GPU系统上的运行更为顺畅。

第三,代码编译效率较高。

第四,社区发展的非常迅速并且活跃。

第五,能够生成显示网络拓扑结构和性能的可视化图。


4. TensorFlow 的工作原理

TensorFlow是用数据流图(data flow graphs)技术来进行数值计算的。

数据流图是描述有向图中的数值计算过程。

有向图中,节点通常代表数学运算,边表示节点之间的某种联系,它负责传输多维数据(Tensors)。

节点可以被分配到多个计算设备上,可以异步和并行地执行操作。因为是有向图,所以只有等到之前的入度节点们的计算状态完成后,当前节点才能执行操作。


5. 安装

极客学院有官方文档翻译版,讲的很清楚,有各种安装方式的讲解。

我选择基于 Anaconda 的安装,因为这个很方便。

Anaconda 是一个集成许多第三方科学计算库的 Python 科学计算环境,用 conda 作为自己的包管理工具,同时具有自己的计算环境,类似 Virtualenv。

  • 安装 Anaconda
    我之前已经安装过 Anaconda 了,直接从下面进行:
  • 建立一个 conda 计算环境
# 计算环境名字叫 tensorflow:
# Python 2.7
$ conda create -n tensorflow python=2.7
  • 激活环境,使用 conda 安装 TensorFlow
$ source activate tensorflow
(tensorflow)$  # Your prompt should change

# Mac OS X, CPU only:
(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/mac/tensorflow-0.8.0rc0-py2-none-any.whl
  • 安装成功后,每次使用 TensorFlow 的时候需要激活 conda 环境
  • conda 环境激活后,你可以测试是否成功,在终端进入 python,输入下面代码,没有提示错误,说明安装 TensorFlow 成功:
$ python
...
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
Hello, TensorFlow!
>>> a = tf.constant(10)
>>> b = tf.constant(32)
>>> print(sess.run(a + b))
42
>>>
  • 当你不用 TensorFlow 的时候,关闭环境:
(tensorflow)$ source deactivate

$  # Your prompt should change back
  • 再次使用的时候再激活:
$ source activate tensorflow
(tensorflow)$  # Run Python programs that use TensorFlow.
...

(tensorflow)$ source deactivate

在 Jupyter notebook 里用 TensorFlow
我在 (tensorflow)$ 直接输入 jupyter notebook 后,输入 import tensorflow as tf 是有错误的,可以参考这里


6. TensorFlow 基本用法

接下来按照官方文档中的具体代码,来看一下基本用法。

你需要理解在TensorFlow中,是如何:

  • 将计算流程表示成图;
  • 通过Sessions来执行图计算;
  • 将数据表示为tensors;
  • 使用Variables来保持状态信息;
  • 分别使用feeds和fetches来填充数据和抓取任意的操作结果;

先看个栗子:
例1,生成三维数据,然后用一个平面拟合它:

# (tensorflow)$ python   用 Python API 写 TensorFlow 示例代码

import tensorflow as tf
import numpy as np

# 用 NumPy 随机生成 100 个数据
x_data = np.float32(np.random.rand(2, 100)) 
y_data = np.dot([0.100, 0.200], x_data) + 0.300

# 构造一个线性模型
b = tf.Variable(tf.zeros([1]))
W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))
y = tf.matmul(W, x_data) + b

# 最小化方差
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)

# 初始化变量
init = tf.initialize_all_variables()

# 启动图 (graph)
sess = tf.Session()
sess.run(init)

# 拟合平面
for step in xrange(0, 201):
    sess.run(train)
    if step % 20 == 0:
        print step, sess.run(W), sess.run(b)

# 输出结果为:
0 [[-0.14751725  0.75113136]] [ 0.2857058]
20 [[ 0.06342752  0.32736415]] [ 0.24482927]
40 [[ 0.10146417  0.23744738]] [ 0.27712563]
60 [[ 0.10354312  0.21220125]] [ 0.290878]
80 [[ 0.10193551  0.20427427]] [ 0.2964265]
100 [[ 0.10085492  0.201565  ]] [ 0.298612]
120 [[ 0.10035028  0.20058727]] [ 0.29946309]
140 [[ 0.10013894  0.20022322]] [ 0.29979277]
160 [[ 0.1000543   0.20008542]] [ 0.29992008]
180 [[ 0.10002106  0.20003279]] [ 0.29996923]
200 [[ 0.10000814  0.20001261]] [ 0.29998815]

注意这几条代码:

W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))

y = tf.matmul(W, x_data) + b

init = tf.initialize_all_variables()

sess = tf.Session()
sess.run(init)

sess.run(train) 
print step, sess.run(W), sess.run(b)

接下来看具体概念:

  • TensorFlow 用图来表示计算任务,图中的节点被称之为operation,缩写成op。
  • 一个节点获得 0 个或者多个张量 tensor,执行计算,产生0个或多个张量。
  • 图必须在会话(Session)里被启动,会话(Session)将图的op分发到CPU或GPU之类的设备上,同时提供执行op的方法,这些方法执行后,将产生的张量(tensor)返回。

1. 构建图
例2,计算矩阵相乘:

import tensorflow as tf

# 创建一个 常量 op, 返回值 'matrix1' 代表这个 1x2 矩阵.
matrix1 = tf.constant([[3., 3.]])

# 创建另外一个 常量 op, 返回值 'matrix2' 代表这个 2x1 矩阵.
matrix2 = tf.constant([[2.],[2.]])

# 创建一个矩阵乘法 matmul op , 把 'matrix1''matrix2' 作为输入.
# 返回值 'product' 代表矩阵乘法的结果.
product = tf.matmul(matrix1, matrix2)

默认图有三个节点, 两个 constant() op, 和一个 matmul() op. 为了真正进行矩阵相乘运算, 并得到矩阵乘法的结果, 你必须在会话里启动这个图.

2. 张量 Tensor
从向量空间到实数域的多重线性映射(multilinear maps)(v是向量空间,v*是对偶空间)
例如代码中的 [[3., 3.]],Tensor 可以看作是一个 n 维的数组或列表。在 TensorFlow 中用 tensor 数据结构来代表所有的数据, 计算图中, 操作间传递的数据都是 tensor。

3. 在一个会话中启动图
创建一个 Session 对象, 如果无任何创建参数, 会话构造器将启动默认图。
会话负责传递 op 所需的全部输入,op 通常是并发执行的。

# 启动默认图.
sess = tf.Session()

# 调用 sess 的 'run()' 方法, 传入 'product' 作为该方法的参数,
# 触发了图中三个 op (两个常量 op 和一个矩阵乘法 op),
# 向方法表明, 我们希望取回矩阵乘法 op 的输出.
result = sess.run(product)

# 返回值 'result' 是一个 numpy `ndarray` 对象.
print result
# ==> [[ 12.]]

# 任务完成, 需要关闭会话以释放资源。
sess.close()

交互式使用
在 Python API 中,使用一个会话 Session 来 启动图, 并调用 Session.run() 方法执行操作.

为了便于在 IPython 等交互环境使用 TensorFlow,需要用 InteractiveSession 代替 Session 类, 使用 Tensor.eval() 和 Operation.run() 方法代替 Session.run()。

例3,计算 ‘x’ 减去 ‘a’:

# 进入一个交互式 TensorFlow 会话.
import tensorflow as tf
sess = tf.InteractiveSession()

x = tf.Variable([1.0, 2.0])
a = tf.constant([3.0, 3.0])

# 使用初始化器 initializer op 的 run() 方法初始化 'x' 
x.initializer.run()

# 增加一个减法 sub op, 从 'x' 减去 'a'. 运行减法 op, 输出结果 
sub = tf.sub(x, a)
print sub.eval()
# ==> [-2. -1.]

变量 Variable

上面用到的张量是常值张量(constant)。

变量 Variable,是维护图执行过程中的状态信息的. 需要它来保持和更新参数值,是需要动态调整的。

下面代码中有 tf.initialize_all_variables,是预先对变量初始化,
Tensorflow 的变量必须先初始化,然后才有值!而常值张量是不需要的。

下面的 assign() 操作和 add() 操作,在调用 run() 之前, 它并不会真正执行赋值和加和操作。

例4,使用变量实现一个简单的计数器:

# -创建一个变量, 初始化为标量 0.  初始化定义初值
state = tf.Variable(0, name="counter")

# 创建一个 op, 其作用是使 state 增加 1
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)

# 启动图后, 变量必须先经过`初始化` (init) op 初始化,
# 才真正通过Tensorflow的initialize_all_variables对这些变量赋初值
init_op = tf.initialize_all_variables()

# 启动默认图, 运行 op
with tf.Session() as sess:

  # 运行 'init' op
  sess.run(init_op)

  # 打印 'state' 的初始值
  # 取回操作的输出内容, 可以在使用 Session 对象的 run() 调用 执行图时, 
  # 传入一些 tensor, 这些 tensor 会帮助你取回结果. 
  # 此处只取回了单个节点 state,
  # 也可以在运行一次 op 时一起取回多个 tensor: 
  # result = sess.run([mul, intermed])
  print sess.run(state)

  # 运行 op, 更新 'state', 并打印 'state'
  for _ in range(3):
    sess.run(update)
    print sess.run(state)

# 输出:

# 0
# 1
# 2
# 3

上面的代码定义了一个如下的计算图:

Ok,总结一下,来一个清晰的代码:
过程就是:建图->启动图->运行取值

计算矩阵相乘:

import tensorflow as tf

# 建图
matrix1 = tf.constant([[3., 3.]])
matrix2 = tf.constant([[2.],[2.]])

product = tf.matmul(matrix1, matrix2)

# 启动图
sess = tf.Session()

# 取值
result = sess.run(product)
print result

sess.close()

上面的几个代码介绍了基本用法,通过观察,有没有觉得 tf 和 numpy 有点像呢。

TensorFlow和普通的Numpy的对比
cs224d的课件中有下面这个代码,来看一下二者之间的区别:

eval()

在 Python 中定义完 a 后,直接打印就可以看到 a。

In [37]: a = np.zeros((2,2))

In [39]: print(a)
[[ 0.  0.]
 [ 0.  0.]]

但是在 Tensorflow 中需要显式地输出(evaluation,也就是说借助eval()函数)!

In [38]: ta = tf.zeros((2,2))

In [40]: print(ta)
Tensor("zeros_1:0", shape=(2, 2), dtype=float32)

In [41]: print(ta.eval())
[[ 0.  0.]
[ 0. 0.]]

通过几个例子了解了基本的用法,feed 在上面的例子中还没有写到,下一次就能用到了,其他的可以查询这里


Day 1 宏观了解了 NLP,Day 2 搞定了工具,下次要直接先进入实战,训练模型,先从 Logistic 和 NN 开始,一边看模型一边写代码一边思考模型原理,这样理解才会更深刻!

 

文/不会停的蜗牛(简书作者)
原文链接:http://www.jianshu.com/p/6766fbcd43b9
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。