介绍
本章的目的是让你了解和运行 TensorFlow
在开始之前, 先看一段使用 Python API 撰写的 TensorFlow 示例代码, 对将要学习的内容有初步的印象.
这段很短的 Python 程序生成了一些三维数据, 然后用一个平面拟合它.
import tensorflow as tf
import numpy as np
# 使用 NumPy 生成假数据(phony data), 总共 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)
# 得到最佳拟合结果 W: [[0.100 0.200]], b: [0.300]
下载与安装
可以使用二进制包, 或者使用源代码, 安装 TensorFlow.
二进制安装
TensorFlow Python API 依赖 Python 2.7 版本. 在 Linux 和 Mac 下最简单的安装方式, 是使用 pip 安装.
Ubuntu/Linux
# 仅使用 CPU 的版本
$ pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.5.0-cp27-none-linux_x86_64.whl
# 开启 GPU 支持的版本 (安装该版本的前提是已经安装了 CUDA sdk)
$ pip install -i http://pypi.douban.com/simple https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow-0.5.0-cp27-none-linux_x86_64.whl
基于 VirtualEnv 的安装
推荐使用 virtualenv 创建一个隔离 的容器, 来安装 TensorFlow. 这是可选的, 但是这样做能使排查安装问题变得更容易.
首先, 安装所有必备工具:
# 在 Linux 上:
$ sudo apt-get install python-pip python-dev python-virtualenv
# 在 Mac 上:
$ sudo easy_install pip # 如果还没有安装 pip
$ sudo pip install --upgrade virtualenv
接下来, 建立一个全新的 virtualenv 环境. 为了将环境建在 ~/tensorflow 目录下, 执行:
$ virtualenv --system-site-packages ~/tensorflow
$ cd ~/tensorflow
然后, 激活 virtualenv:
$ source bin/activate # 如果使用 bash
(tensorflow)$ # 终端提示符应该发生变化
在 virtualenv 内, 安装 TensorFlow:
(tensorflow)$ pip install --upgrade <$url_to_binary.whl>
接下来, 使用类似命令运行 TensorFlow 程序:
(tensorflow)$ cd tensorflow/models/image/mnist
(tensorflow)$ python convolutional.py
# 当使用完 TensorFlow
(tensorflow)$ deactivate # 停用 virtualenv
$ # 你的命令提示符会恢复原样
TensorFlow基本使用
使用 TensorFlow, 你必须明白 TensorFlow:
- 使用图 (graph) 来表示计算任务.
- 在被称之为 会话 (Session) 的上下文 (context) 中执行图.
- 使用 tensor 表示数据.
- 通过 变量 (Variable) 维护状态.
- 使用 feed 和 fetch 为任意操作输入和输出数据.
计算流图
TensorFlow的程序是由两个阶段组成:一个是构建阶段,将算法构建成一个流图;一个是执行阶段,使用session将流图中的ops进行运行。
例如,通常在构建阶段创建一个流图用来表示或者训练一个神经网络,然后在执行阶段使用session反复地执行流图里面的一系列ops。
构建流图
TensorFlow Python的库拥有一个默认的流图,所有的新建的ops会自动地增加节点数。默认的流图对许多应用来说已经足够了。查阅Graph Class文档可以了解如何管理多张流图。
import tensorflow as tf
# 创建一个常量的op产生一个1x2的矩阵
matrix1 = tf.constant([[3., 3.]])
# 创建一个常量的op产生一个2x1的矩阵.
matrix2 = tf.constant([[2.],[2.]])
# 创建一个矩阵乘的op,返回matrix1和matrix2相乘结果
product = tf.matmul(matrix1, matrix2)
在上面的例子中,默认的Graph拥有三个节点:两个constant() ops和一个 matmul() op。为了能真正地实现矩阵相乘的操作,需要将上面创造的Graph录入到一个session中。
将Graph录入到session中
发送一张Graph,需要创建一个session对象,在没有参数的情况下,构造器会录入默认的Graph
# 创建一个session对象.
sess = tf.Session()
# 调用session对象的run()方法,执行Graph中的3个ops,返回值是一个numpy的‘ndarray’对象
result = sess.run(product)
print(result)
# ==> [[ 12.]]
# 关闭session
sess.close()
Session应该在执行完后关闭掉以释放资源,同样能创建Session使用“with”代码块,在这里session会自动地关闭。
with tf.Session() as sess:
result = sess.run([product])
print(result)
Tensors
TensorFlow在编程中使用Tensor的数据结构来表示所有的数据,只有Tensor才能在数据流图中的ops间传递,可以把TensorFlow中的Tensor认为是n维的数组或者列表。
Variables
变量在执行流图的时候会保持状态,下面的例子是用变量作为一个简单的计数器。
# 创建一个变量,初始化为0
state = tf.Variable(0, name="counter")
# 创建一个op constant,并将one添加到state上。
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)
# 变量在载入graph后必须运行一个“init” op进行初始化,首先将“init” op加入到graph上
init_op = tf.initialize_all_variables()
# 载入graph,运行ops.
with tf.Session() as sess:
# 运行“init” op
sess.run(init_op)
# 打印state的初始化值
print(sess.run(state))
# 运行op,更新“state”,并打印“state”.
for _ in range(3):
sess.run(update)
print(sess.run(state))
# 输出结果:
# 0
# 1
# 2
# 3
Fetches
取出ops的输出,调用Session对象的run方法执行Graph,并取回计算出的Tensor,你可以同时取出多个Tensors。
input1 = tf.constant(3.0)
input2 = tf.constant(2.0)
input3 = tf.constant(5.0)
intermed = tf.add(input2, input3)
mul = tf.mul(input1, intermed)
with tf.Session() as sess:
result = sess.run([mul, intermed])
print(result)
# 输出结果:
# [array([ 21.], dtype=float32), array([ 7.], dtype=float32)]
Feeds
Feeds机制使用一个Tensor值临时地替换op的输出结果,你需要在run方法中提供feed数据,这个feed数据只会在run方法运行时调用。
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.mul(input1, input2)
with tf.Session() as sess:
print(sess.run([output], feed_dict={input1:[7.], input2:[2.]}))
# output:
# [array([ 14.], dtype=float32)]
MNIST机器学习入门
MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片,它也包含每一张图片对应的标签,告诉我们这个是数字几。在此教程中将训练一个机器学习模型用于预测图片里面的数字。目的不是要设计一个世界一流的复杂模型, 而是要介绍下如何使用TensorFlow。所以,这里会从一个很简单的数学模型开始,它叫做Softmax Regression。
MNIST数据集
MNIST数据集的官网是Yann LeCun’s website。在这里,我们提供了一份python源代码用于自动下载和安装这个数据集。你可以下载这份代码,然后用下面的代码导入到你的项目里面,也可以直接复制粘贴到你的代码文件里面。
import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
下载下来的数据集被分成两部分:60000行的训练数据集(mnist.train
)和10000行的测试数据集(mnist.test
)。这样的切分很重要,在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能,从而更加容易把设计的模型推广到其他数据集上。
正如前面提到的一样,每一个MNIST数据单元有两部分组成:一张包含手写数字的图片和一个对应的标签。把这些图片设为“xs”,把这些标签设为“ys”。训练数据集和测试数据集都包含xs和ys,比如训练数据集的图片是 mnist.train.images
,训练数据集的标签是 mnist.train.labels
。
然后把这个数组展开成一个向量,长度是 28x28 = 784。如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开。从这个角度来看,MNIST数据集的图片就是在784维向量空间里面的点, 并且拥有比较复杂的结构 。
因此,在MNIST训练数据集中,mnist.train.images
是一个形状为 [60000, 784] 的张量,第一个维度数字用来索引图片,第二个维度数字用来索引每张图片中的像素点。在此张量里的每一个元素,都表示某张图片里的某个像素的强度值,值介于0和1之间。
相对应的MNIST数据集的标签是介于0到9的数字,用来描述给定图片里表示的数字。为了用于这个教程,我们使标签数据是”one-hot vectors”。 一个one-hot向量除了某一位的数字是1以外其余各维度数字都是0。所以在此教程中,数字n将表示成一个只有在第n维度(从0开始)数字为1的10维向量。比如,标签0将表示成([1,0,0,0,0,0,0,0,0,0,0])。因此, mnist.train.labels
是一个 [60000, 10] 的数字矩阵。
Softmax回归介绍(略)
实现回归模型
为了用python实现高效的数值计算,通常会使用函数库,比如NumPy,会把类似矩阵乘法这样的复杂运算使用其他外部语言实现。不幸的是,从外部计算切换回Python的每一个操作,仍然是一个很大的开销。如果你用GPU来进行外部计算,这样的开销会更大。用分布式的计算方式,也会花费更多的资源用来传输数据。
TensorFlow也把复杂的计算放在python之外完成,但是为了避免前面说的那些开销,它做了进一步完善。Tensorflow不单独地运行单一的复杂计算,而是让我们可以先用图描述一系列可交互的计算操作,然后全部一起在Python之外运行。(这样类似的运行方式,可以在不少的机器学习库中看到。)
使用TensorFlow之前,首先导入它:
import tensorflow as tf
我们通过操作符号变量来描述这些可交互的操作单元,可以用下面的方式创建一个:
x = tf.placeholder("float", [None, 784])
x
不是一个特定的值,而是一个占位符placeholder
,在TensorFlow运行计算时输入这个值。希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。用2维的浮点数张量来表示这些图,这个张量的形状是[None,784 ]。(这里的None表示此张量的第一个维度可以是任何长度的。 )
模型需要权重值和偏置量,当然可以把它们当做是另外的输入(使用占位符),但TensorFlow有一个更好的方法来表示它们:Variable
。 一个Variable
代表一个可修改的张量,存在在TensorFlow的用于描述交互性操作的图中。它们可以用于计算输入值,也可以在计算中被修改。对于各种机器学习应用,一般都会有模型参数,可以用Variable
表示。
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
赋予tf.Variable
不同的初值来创建不同的Variable
:在这里,都用全为零的张量来初始化W
和b
。因为我们要学习W
和b
的值,它们的初值可以随意设置。
现在,可以实现模型了。只需要一行代码!
y = tf.nn.softmax(tf.matmul(x,W) + b)
用tf.matmul(X,W)
表示x
乘以W
,对应之前等式里面的(Wx),这里x
是一个2维张量拥有多个输入。然后再加上b
,把和输入到f.nn.softmax
函数里面。
训练模型
为了训练模型,首先需要定义一个指标来评估这个模型是好的。其实,在机器学习,通常定义指标来表示一个模型是坏的,这个指标称为成本(cost)或损失(loss),然后尽量最小化这个指标。
一个非常常见的,非常漂亮的成本函数是“交叉熵”(cross-entropy)。交叉熵产生于信息论里面的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的重要技术手段。它的定义如下:
(y) 是我们预测的概率分布, (y’) 是实际的分布(我们输入的one-hot vector)。比较粗糙的理解是,交叉熵是用来衡量我们的预测用于描述真相的低效性。
为了计算交叉熵,首先需要添加一个新的占位符用于输入正确值:
y_ = tf.placeholder("float", [None,10])
然后可以用 (−∑y′log(y)) 计算交叉熵:
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
用 tf.log
计算 y
的每个元素的对数。接下来,我们把 y_
的每一个元素和 tf.log(y_)
的对应元素相乘。最后,用 tf.reduce_sum
计算张量的所有元素的总和。
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
在这里,要求TensorFlow用梯度下降算法(gradient descent algorithm)以0.01的学习速率最小化交叉熵。梯度下降算法(gradient descent algorithm)是一个简单的学习过程,TensorFlow只需将每个变量一点点地往使成本不断降低的方向移动。当然TensorFlow也提供了其他许多优化算法:只要简单地调整一行代码就可以使用其他的算法。
现在,已经设置好了模型。在运行计算之前,需要添加一个操作来初始化创建的变量:
init = tf.initialize_all_variables()
现在可以在一个Session里面启动模型,并且初始化变量:
sess = tf.Session()
sess.run(init)
然后开始训练模型,这里让模型循环训练1000次!
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
该循环的每个步骤中,会随机抓取训练数据中的100个批处理数据点,然后我们用这些数据点作为参数替换之前的占位符来运行train_step
。
评估模型
首先让找出那些预测正确的标签。tf.argmax
是一个非常有用的函数,它能给你在一个张量里沿着某条轴的最高条目的索引值。比如,tf.argmax(y,1)
是模型认为每个输入最有可能对应的那些标签,而 tf.argmax(y_,1)
代表正确的标签。 我们可以用 tf.equal
来检测我们的预测是否真实标签匹配。
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
这行代码会给我们一组布尔值。为了确定正确预测项的比例,可以把布尔值转换成浮点数,然后取平均值。例如,[True, False, True, True]
会变成 [1,0,1,1]
,取平均值后得到 0.75.
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
计算所学习到的模型在测试数据集上面的正确率。
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
深入MNIST
TensorFlow是一个做大规模数值计算的强大库。其中一个特点就是它能够实现和训练深度神经网络。 在这一小节里,将会在MNIST上构建深度卷积分类器
安装
在创建模型之前,我们会先加载MNIST数据集,然后启动一个TensorFlow的session。
加载MNIST数据
import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
这里,mnist
是一个轻量级的类。它包含Numpy数组格式的训练、校验和测试数据。它同时提供了一个函数,用于在迭代中获得minibatch,后面将会用到。
开始TensorFlow的交互会话
Tensorflow基于一个高效的C++模块进行运算。与这个模块的连接叫做session。一般而言,使用TensorFlow程序的流程是先创建一个图,然后在session中加载它。
这里,使用更加方便的InteractiveSession类。通过它,你可以更加灵活地构建你的代码。它能让你在运行图的时候,插入一些构建计算图的操作。这能给使用交互式文本shell如iPython带来便利。如果你没有使用InteractiveSession的话,你需要在开始session和加载图之前,构建整个计算图。
import tensorflow as tf
sess = tf.InteractiveSession()
构建Softmax Regression模型
占位符
先来创建计算图的输入(图片)和输出(类别)。
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
这里的x
和y
并不是具体值,他们是一个placeholder
,是一个变量,在TensorFlow运行计算的时候使用。
输入图片x是浮点数2维张量。这里,定义它的shape
为[None, 784]
,其中784是单张展开的MNIST图片的维度数。shape的第一维输入指代一个batch的大小,None
,可为任意值。输出值y_
也是一个2维张量,其中每一行为一个10维向量代表对应MNIST图片的分类。
虽然placeholder
的shape
参数是可选的,但有了它,TensorFlow能够自动捕捉因数据维度不一致导致的错误。
Variables
现在为模型定义权重W
和偏置b
。它们可以被视作是额外的输入量,但是TensorFlow有一个更好的方式来处理:Variable
。一个Variable
代表着在TensorFlow计算图中的一个值,它是能在计算过程中被读取和修改的。在机器学习的应用过程中,模型参数一般用Variable
来表示。
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
我们在调用tf.Variable
的时候传入初始值。在这个例子里,我们把W
和b
都初始化为零向量。W
是一个784x10的矩阵(因为我们有784个特征和10个输出值)。b
是一个10维的向量(因为我们有10个分类)。
Variable
需要在session之前初始化,才能在session中使用。初始化需要初始值(本例当中是全为零)传入并赋值给每一个Variable
。这个操作可以一次性完成。
sess.run(tf.initialize_all_variables())
预测分类与损失函数
现在可以实现regression模型了。这只需要一行!把图片x
和权重矩阵W
相乘,加上偏置b
,然后计算每个分类的softmax概率值。
y = tf.nn.softmax(tf.matmul(x,W) + b)
在训练中最小化损失函数同样很简单。我们这里的损失函数用目标分类和模型预测分类之间的交叉熵。
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
注意,tf.reduce_sum
把minibatch里的每张图片的交叉熵值都加起来了。我们计算的交叉熵是指整个minibatch的。
训练模型
已经定义好了模型和训练的时候用的损失函数,接下来使用TensorFlow来训练。因为TensorFlow知道整个计算图,它会用自动微分法来找到损失函数对于各个变量的梯度。TensorFlow有大量内置的优化算法. 这个例子中,我们用梯度下降法让交叉熵下降,步长为0.01.
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
这一行代码实际上是用来往计算图上添加一个新操作,其中包括计算梯度,计算每个参数的步长变化,并且计算出新的参数值。
train_step
这个操作,用梯度下降来更新权值。因此,整个模型的训练可以通过反复地运行train_step
来完成。
for i in range(1000):
batch = mnist.train.next_batch(50)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})
每一步迭代,我们都会加载50个训练样本,然后执行一次train_step
,使用feed_dict
,用训练数据替换placeholder
向量x
和y_
。
评估模型
首先,要先知道哪些label是预测正确了。tf.argmax
是一个非常有用的函数。它会返回一个张量某个维度中的最大值的索引。例如,tf.argmax(y,1)
表示我们模型对每个输入的最大概率分类的分类值。而 tf.argmax(y_,1)
表示真实分类值。我们可以用tf.equal
来判断我们的预测是否与真实分类一致。
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
这里返回一个布尔数组。为了计算我们分类的准确率,我们将布尔值转换为浮点数来代表对、错,然后取平均值。例如:[True, False, True, True]
变为[1,0,1,1]
,计算出平均值为0.75。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
最后,我们可以计算出在测试数据上的准确率,大概是91%。
print accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels})
构建一个多层卷积网络
权重初始化
在创建模型之前,先来创建权重和偏置。一般来说,初始化时应加入轻微噪声,来打破对称性,防止零梯度的问题。因为我们用的是ReLU,所以用稍大于0的值来初始化偏置能够避免节点输出恒为0的问题(dead neurons)。为了不在建立模型的时候反复做初始化操作,我们定义两个函数用于初始化。
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1) #截尾正太分布,stddev代表标准差
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
卷积和池化
TensorFlow在卷积和池化上有很强的灵活性。我们怎么处理边界?步长应该设多大?在这个实例里,我们会一直使用vanilla版本。我们的卷积使用1步长(stride size),0边距(padding size)的模板,保证输出和输入是同一个大小。我们的池化用简单传统的2x2大小的模板做max pooling。为了代码更简洁,把这部分抽象成一个函数。
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME')
第一层卷积
现在我们可以开始实现第一层了。它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征。权重是一个[5, 5, 1, 32]
的张量,前两个维度是patch的大小,接着是输入的通道数目,最后是输出的通道数目。输出对应一个同样大小的偏置向量。
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
为了用这一层,把x变成一个4d向量,第2、3维对应图片的宽高,最后一维代表颜色通道。
x_image = tf.reshape(x, [-1,28,28,1])
把x_image
和权值向量进行卷积相乘,加上偏置,使用ReLU激活函数,最后max pooling。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
第二层卷积
为了构建一个更深的网络,把几个类似的层堆叠起来。第二层中,每个5x5的patch会得到64个特征。
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
密集连接层
现在,图片降维到7x7,加入一个有1024个神经元的全连接层,用于处理整个图片。把池化层输出的张量reshape成一些向量,乘上权重矩阵,加上偏置,使用ReLU激活。
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
Dropout
为了减少过拟合,在输出层之前加入dropout。用一个placeholder
来代表一个神经元在dropout中被保留的概率。这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。 TensorFlow的tf.nn.dropout
操作会自动处理神经元输出值的scale。所以用dropout的时候可以不用考虑scale。
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
输出层
最后,我们添加一个softmax层,就像前面的单层softmax regression一样。
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
训练和评估模型
用更加复杂的ADAM优化器来做梯度最速下降,在feed_dict
中加入额外的参数keep_prob
来控制dropout比例。然后每100次迭代输出一次日志。
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.initialize_all_variables())
for i in range(20000):
batch = mnist.train.next_batch(50)
if i%100 == 0:
train_accuracy = accuracy.eval(feed_dict={
x:batch[0], y_: batch[1], keep_prob: 1.0})
print "step %d, training accuracy %g"%(i, train_accuracy)
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
print "test accuracy %g"%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})
TensorFlow运作方式入门
代码:下载
本篇教程的目的,是向大家展示如何利用TensorFlow使用MNIST数据集训练并评估一个用于识别手写数字的简易前馈神经网络(feed-forward neural network)。
教程使用的文件
文件 | 目的 |
---|---|
mnist.py | 构建一个完全连接(fully connected)的MINST模型所需的代码。 |
fully_connected_feed.py | 利用下载的数据集训练构建好的MNIST模型的主要代码,以数据反馈字典(feed dictionary)的形式作为输入模型。 |
只需要直接运行fully_connected_feed.py
文件,就可以开始训练:
python fully_connected_feed.py
准备数据
下载
在run_training()
方法的一开始,input_data.read_data_sets()
函数会确保你的本地训练文件夹中,已经下载了正确的数据,然后将这些数据解压并返回一个含有DataSet
实例的字典。
data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)
数据集 | 目的 |
---|---|
data_sets.train | 55000个图像和标签(labels),作为主要训练集。 |
data_sets.validation | 5000个图像和标签,用于迭代验证训练准确度。 |
data_sets.test | 10000个图像和标签,用于最终测试训练准确度(trained accuracy)。 |
输入与占位符(Inputs and Placeholders)
placeholder_inputs()
函数将生成两个tf.placeholder
操作,定义传入图表中的shape参数,shape参数中包括batch_size
值,后续还会将实际的训练用例传入图表。
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size, IMAGE_PIXELS))
labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
在训练循环(training loop)的后续步骤中,传入的整个图像和标签数据集会被切片,以符合每一个操作所设置的batch_size
值,占位符操作将会填补以符合这个batch_size
值。然后使用feed_dict
参数,将数据传入sess.run()
函数。
构建图表 (Build the Graph)
在为数据创建占位符之后,就可以运行mnist.py
文件,经过三阶段的模式函数操作:inference()
, loss()
,和training()
。图表就构建完成了。
1.inference()
—— 尽可能地构建好图表,满足促使神经网络向前反馈并做出预测的要求。
2.oss()
—— 往inference图表中添加生成损失(loss)所需要的操作(ops)。
3.training()
—— 往损失图表中添加计算并应用梯度(gradients)所需的操作。
推理(Inference)
inference()
函数会尽可能地构建图表,做到返回包含了预测结果(output prediction)的Tensor。
它接受图像占位符为输入,在此基础上借助ReLu(Rectified Linear Units)激活函数,构建一对完全连接层(layers),以及一个有着十个节点(node)、指明了输出logtis模型的线性层。
每一层都创建于一个唯一的tf.name_scope
之下,创建于该作用域之下的所有元素都将带有其前缀。
with tf.name_scope('hidden1') as scope:
在定义的作用域中,每一层所使用的权重和偏差都在tf.Variable实例中生成,并且包含了各自期望的shape。
weights = tf.Variable(
tf.truncated_normal([IMAGE_PIXELS, hidden1_units],
stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))),
name='weights')
biases = tf.Variable(tf.zeros([hidden1_units]),
name='biases')
例如,当这些层是在hidden1作用域下生成时,赋予权重变量的独特名称将会是”hidden1/weights
“。
每个变量在构建时,都会获得初始化操作(initializer ops)。
在这种最常见的情况下,通过tf.truncated_normal
函数初始化权重变量,给赋予的shape则是一个二维tensor,其中第一个维度代表该层中权重变量所连接(connect from)的单元数量,第二个维度代表该层中权重变量所连接到的(connect to)单元数量。对于名叫hidden1的第一层,相应的维度则是[IMAGE_PIXELS, hidden1_units]
,因为权重变量将图像输入连接到了hidden1
层。tf.truncated_normal
初始函数将根据所得到的均值和标准差,生成一个随机分布。
然后,通过tf.zeros
函数初始化偏差变量(biases),确保所有偏差的起始值都是0,而它们的shape则是其在该层中所接到的(connect to)单元数量。
图表的三个主要操作,分别是两个tf.nn.relu
操作,它们中嵌入了隐藏层所需的tf.matmul
;以及logits模型所需的另外一个tf.matmul
。三者依次生成,各自的tf.Variable
实例则与输入占位符或下一层的输出tensor所连接。
hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)
hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
logits = tf.matmul(hidden2, weights) + biases
最后,程序会返回包含了输出结果的logits
Tensor。
损失(Loss)
loss()
函数通过添加所需的损失操作,进一步构建图表。
首先,labels_placeholer
中的值,将被编码为一个含有1-hot values的Tensor。例如,如果类标识符为“3”,那么该值就会被转换为:
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
batch_size = tf.size(labels)
labels = tf.expand_dims(labels, 1)
indices = tf.expand_dims(tf.range(0, batch_size, 1), 1)
concated = tf.concat(1, [indices, labels])
onehot_labels = tf.sparse_to_dense(
concated, tf.pack([batch_size, NUM_CLASSES]), 1.0, 0.0)
之后,又添加一个tf.nn.softmax_cross_entropy_with_logits
操作,用来比较inference()
函数与1-hot标签所输出的logits Tensor。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits,
onehot_labels,
name='xentropy')
然后,使用tf.reduce_mean
函数,计算batch维度(第一维度)下交叉熵(cross entropy)的平均值,将将该值作为总损失。
loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
最后,程序会返回包含了损失值的Tensor。
训练
training()
函数添加了通过梯度下降(gradient descent)将损失最小化所需的操作。
首先,该函数从loss()
函数中获取损失Tensor,将其交给tf.scalar_summary
,后者在与SummaryWriter配合使用时,可以向事件文件(events file)中生成汇总值(summary values)。
tf.scalar_summary(loss.op.name, loss)
接下来,我们实例化一个tf.train.GradientDescentOptimizer
,负责按照所要求的学习效率(learning rate)应用梯度下降法(gradients)。
optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)
之后,我们生成一个变量用于保存全局训练步骤(global training step)的数值,并使用minimize()
函数更新系统中的三角权重(triangle weights)、增加全局步骤的操作。根据惯例,这个操作被称为 train_op
,是TensorFlow会话(session)诱发一个完整训练步骤所必须运行的操作(见下文)。
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)
最后,程序返回包含了训练操作(training op)输出结果的Tensor。
训练模型
一旦图表构建完毕,就通过fully_connected_feed.py
文件中的用户代码进行循环地迭代式训练和评估。
图表
在run_training()
这个函数的一开始,是一个Python语言中的with命令,这个命令表明所有已经构建的操作都要与默认的tf.Graph
全局实例关联起来。
with tf.Graph().as_default():
tf.Graph
实例是一系列可以作为整体执行的操作。TensorFlow的大部分场景只需要依赖默认图表一个实例即可。
会话
完成全部的构建准备、生成全部所需的操作之后,我们就可以创建一个tf.Session
,用于运行图表。
sess = tf.Session()
另外,也可以利用with
代码块生成Session
,限制作用域:
with tf.Session() as sess:
Session函数中没有传入参数,表明该代码将会依附于(如果还没有创建会话,则会创建新的会话)默认的本地会话。
生成会话之后,所有tf.Variable
实例都会立即通过调用各自初始化操作中的sess.run()
函数进行初始化。
init = tf.initialize_all_variables()
sess.run(init)
sess.run()
方法将会运行图表中与作为参数传入的操作相对应的完整子集。在初次调用时,init
操作只包含了变量初始化程序tf.group
。图表的其他部分不会在这里,而是在下面的训练循环运行。
训练循环
完成会话中变量的初始化之后,就可以开始训练了。
训练的每一步都是通过用户代码控制,而能实现有效训练的最简单循环就是:
for step in xrange(max_steps):
sess.run(train_op)
向图表提供反馈
执行每一步时,我们的代码会生成一个反馈字典(feed dictionary),其中包含对应步骤中训练所要使用的例子,这些例子的哈希键就是其所代表的占位符操作。
fill_feed_dict
函数会查询给定的DataSet
,索要下一批次batch_size
的图像和标签,与占位符相匹配的Tensor则会包含下一批次的图像和标签。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size)
然后,以占位符为哈希键,创建一个Python字典对象,键值则是其代表的反馈Tensor。
feed_dict = {
images_placeholder: images_feed,
labels_placeholder: labels_feed,
}
这个字典随后作为feed_dict
参数,传入sess.run()
函数中,为这一步的训练提供输入样例。
检查状态
在运行sess.run
函数时,要在代码中明确其需要获取的两个值:[train_op, loss]
。
for step in xrange(FLAGS.max_steps):
feed_dict = fill_feed_dict(data_sets.train,
images_placeholder,
labels_placeholder)
_, loss_value = sess.run([train_op, loss],
feed_dict=feed_dict)
因为要获取这两个值,sess.run()
会返回一个有两个元素的元组。其中每一个Tensor对象,对应了返回的元组中的numpy数组,而这些数组中包含了当前这步训练中对应Tensor的值。由于train_op
并不会产生输出,其在返回的元祖中的对应元素就是None
,所以会被抛弃。但是,如果模型在训练中出现偏差,loss
Tensor的值可能会变成NaN,所以我们要获取它的值,并记录下来。
假设训练一切正常,没有出现NaN,训练循环会每隔100个训练步骤,就打印一行简单的状态文本,告知用户当前的训练状态。
if step % 100 == 0:
print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)
状态可视化
为了释放TensorBoard所使用的事件文件(events file),所有的即时数据(在这里只有一个)都要在图表构建阶段合并至一个操作(op)中。
summary_op = tf.merge_all_summaries()
在创建好会话(session)之后,可以实例化一个tf.train.SummaryWriter
,用于写入包含了图表本身和即时数据具体值的事件文件。
summary_writer = tf.train.SummaryWriter(FLAGS.train_dir,
graph_def=sess.graph_def)
最后,每次运行summary_op
时,都会往事件文件中写入最新的即时数据,函数的输出会传入事件文件读写器(writer)的add_summary()
函数。
summary_str = sess.run(summary_op, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)
事件文件写入完毕之后,可以就训练文件夹打开一个TensorBoard,查看即时数据的情况。
保存检查点(checkpoint)
为了得到可以用来后续恢复模型以进一步训练或评估的检查点文件(checkpoint file),我们实例化一个tf.train.Saver。
saver = tf.train.Saver()
在训练循环中,将定期调用saver.save()
方法,向训练文件夹中写入包含了当前所有可训练变量值得检查点文件。
saver.save(sess, FLAGS.train_dir, global_step=step)
这样,我们以后就可以使用saver.restore()
方法,重载模型的参数,继续训练。
saver.restore(sess, FLAGS.train_dir)
评估模型
每隔一千个训练步骤,我们的代码会尝试使用训练数据集与测试数据集,对模型进行评估。do_eval
函数会被调用三次,分别使用训练数据集、验证数据集合测试数据集。
print 'Training Data Eval:'
do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_sets.train)
print 'Validation Data Eval:'
do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_sets.validation)
print 'Test Data Eval:'
do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_sets.test)
构建评估图表(Eval Graph)
在打开默认图表(Graph)之前,我们应该先调用get_data(train=False)
函数,抓取测试数据集。
test_all_images, test_all_labels = get_data(train=False)
在进入训练循环之前,我们应该先调用mnist.py
文件中的evaluation
函数,传入的logits和标签参数要与loss
函数的一致。这样做事为了先构建Eval操作。
eval_correct = mnist.evaluation(logits, labels_placeholder)
evaluation
函数会生成tf.nn.in_top_k
操作,如果在K个最有可能的预测中可以发现真的标签,那么这个操作就会将模型输出标记为正确。在本文中,我们把K的值设置为1,也就是只有在预测是真的标签时,才判定它是正确的。
eval_correct = tf.nn.in_top_k(logits, labels, 1)
评估图表的输出(Eval Output)
之后,创建一个循环,往其中添加feed_dict
,并在调用sess.run()
函数时传入val_correct
操作,目的就是用给定的数据集评估模型。
for step in xrange(steps_per_epoch):
feed_dict = fill_feed_dict(data_set,
images_placeholder,
labels_placeholder)
true_count += sess.run(eval_correct, feed_dict=feed_dict)
true_count
变量会累加所有in_top_k
操作判定为正确的预测之和。接下来,只需要将正确测试的总数,除以例子总数,就可以得出准确率了。
precision = float(true_count) / float(num_examples)
print ' Num examples: %d Num correct: %d Precision @ 1: %0.02f' % (
num_examples, true_count, precision)
卷积神经网络
概述
目标
本教程的目标是建立一个用于识别图像的相对较小的卷积神经网络,在这一过程中,本教程会:
- 着重于建立一个规范的网络组织结构,训练并进行评估;
- 为建立更大规模更加复杂的模型提供一个范例
选择CIFAR-10是因为它的复杂程度足以用来检验TensorFlow中的大部分功能,并可将其扩展为更大的模型。与此同时由于模型较小所以训练速度很快,比较适合用来测试新的想法,检验新的技术。
本教程的重点
CIFAR-10 教程演示了在TensorFlow上构建更大更复杂模型的几个种重要内容:
- 相关核心数学对象,如卷积、修正线性激活、最大池化以及局部响应归一化;
- 训练过程中一些网络行为的可视化,这些行为包括输入图像、损失情况、网络行为的分布情况以及梯度;
- 算法学习参数的移动平均值的计算函数,以及在评估阶段使用这些平均值提高预测性能;
- 实现了一种机制,使得学习率随着时间的推移而递减;
- 为输入数据设计预存取队列,将磁盘延迟和高开销的图像预处理操作与模型分离开来处理;
提供了模型的多GUP版本,用以表明:
- 可以配置模型后使其在多个GPU上并行的训练
- 可以在多个GPU之间共享和更新变量值
模型架构
本教程中的模型是一个多层架构,由卷积层和非线性层(nonlinearities)交替多次排列后构成。这些层最终通过全连通层对接到softmax分类器上。这一模型除了最顶部的几层外,基本跟Alex Krizhevsky提出的模型一致。
代码组织
本教程的代码位于:tensorflow/models/image/cifar10/.
文件 | 作用 |
---|---|
cifar10_input.py | 读取本地CIFAR-10的二进制文件格式的内容。 |
cifar10.py | 建立CIFAR-10的模型。 |
cifar10_train.py | 在CPU或GPU上训练CIFAR-10的模型。 |
cifar10_multi_gpu_train.py | 在多GPU上训练CIFAR-10的模型。 |
cifar10_eval.py | 评估CIFAR-10模型的预测性能。 |
CIFAR-10 模型
CIFAR-10 网络模型部分的代码位于 cifar10.py. 完整的训练图中包含约765个操作。但是我们发现通过下面的模块来构造训练图可以最大限度的提高代码复用率:
- 模型输入: 包括
inputs()
、distorted_inputs()
等一些操作,分别用于读取CIFAR的图像并进行预处理,做为后续评估和训练的输入; - 模型预测: 包括
inference()
等一些操作,用于进行统计计算,比如在提供的图像进行分类; adds operations
that perform inference, i.e. classification, on supplied images. - 模型训练: 包括
loss()
andtrain()
等一些操作,用于计算损失、计算梯度、进行变量更新以及呈现最终结果。
模型输入
输入模型是通过 inputs()
和distorted_inputs()
函数建立起来的,这2个函数会从CIFAR-10二进制文件中读取图片文件,由于每个图片的存储字节数是固定的,因此可以使用tf.FixedLengthRecordReader
函数。
图片文件的处理流程如下:
- 图片会被统一裁剪到24x24像素大小,裁剪中央区域用于评估或随机裁剪用于训练;
- 图片会进行近似的白化处理,使得模型对图片的动态范围变化不敏感。
对于训练,另外采取了一系列随机变换的方法来人为的增加数据集的大小:
- 对图像进行随机的左右翻转;
- 随机变换图像的亮度;
- 随机变换图像的对比度;
可以在Images页的列表中查看所有可用的变换,对于每个原始图我们还附带了一个image_summary
,以便于在TensorBoard中查看。这对于检查输入图像是否正确十分有用。
从磁盘上加载图像并进行变换需要花费不少的处理时间。为了避免这些操作减慢训练过程,在16个独立的线程中并行进行这些操作,这16个线程被连续的安排在一个TensorFlow队列中。
模型预测
模型的预测流程由inference()构造,该函数会添加必要的操作步骤用于计算预测值的 logits,其对应的模型组织方式如下所示:
Layer名称 | 描述 |
---|---|
conv1 | 实现卷积 以及 rectified linear activation. |
pool1 | max pooling. |
norm1 | 局部响应归一化. |
conv2 | 卷积 and rectified linear activation. |
norm2 | 局部响应归一化. |
pool2 | max pooling. |
local3 | 基于修正线性激活的全连接层. |
local4 | 基于修正线性激活的全连接层. |
softmax_linear | 进行线性变换以输出 logits. |
这里有一个由TensorBoard绘制的图形,用于描述模型建立过程中经过的步骤:
模型训练
训练一个可进行N维分类的网络的常用方法是使用多项式逻辑回归,又被叫做softmax 回归。Softmax 回归在网络的输出层上附加了一个softmax nonlinearity,并且计算归一化的预测值和label的1-hot encoding的交叉熵。在正则化过程中,我们会对所有学习变量应用权重衰减损失。模型的目标函数是求交叉熵损失和所有权重衰减项的和,loss()函数的返回值就是这个值。
在TensorBoard中使用scalar_summary来查看该值的变化情况:
使用标准的梯度下降算法来训练模型(也可以在Training中看看其他方法),其学习率随时间以指数形式衰减
train()
函数会添加一些操作使得目标函数最小化,这些操作包括计算梯度、更新学习变量。train()
函数最终会返回一个用以对一批图像执行所有计算的操作步骤,以便训练并更新模型。
开始执行并训练模型
已经把模型建立好了,现在通过执行脚本cifar10_train.py来启动训练过程。
python cifar10_train.py
脚本会在每10步训练过程后打印出总损失值,以及最后一批数据的处理速度。下面是几点注释:
- 第一批数据会非常的慢(大概要几分钟时间),因为预处理线程要把20,000个待处理的CIFAR图像填充到重排队列中;
- 打印出来的损失值是最近一批数据的损失值的均值。请记住损失值是交叉熵和权重衰减项的和;
- 上面打印结果中关于一批数据的处理速度是在Tesla K40C上统计出来的,如果你运行在CPU上,性能会比此要低;
cifar10_train.py
会周期性的在检查点文件中保存模型中的所有参数,但是不会对模型进行评估。cifar10_eval.py
会使用该检查点文件来测试预测性能
cifar10_train.py输出的终端信息中提供了关于模型如何训练的一些信息,但是我们可能希望了解更多关于模型训练时的信息,比如:
- 损失是真的在减小还是看到的只是噪声数据?
- 为模型提供的图片是否合适?
- 梯度、激活、权重的值是否合理?
- 当前的学习率是多少?
TensorBoard提供了该功能,可以通过cifar10_train.py中的SummaryWriter周期性的获取并显示这些数据。
比如可以在训练过程中查看local3
的激活情况,以及其特征维度的稀疏情况:
相比于总损失,在训练过程中的单项损失尤其值得人们的注意。但是由于训练中使用的数据批量比较小,损失值中夹杂了相当多的噪声。在实践过程中,也发现相比于原始值,损失值的移动平均值显得更为有意义。
评估模型
现在可以在另一部分数据集上来评估训练模型的性能。脚本文件cifar10_eval.py
对模型进行了评估,利用 inference()
函数重构模型,并使用了在评估数据集所有10,000张CIFAR-10图片进行测试。最终计算出的精度为1:N,N=预测值中置信度最高的一项与图片真实label匹配的频次。
为了监控模型在训练过程中的改进情况,评估用的脚本文件会周期性的在最新的检查点文件上运行,这些检查点文件是由cifar10_train.py
产生。
python cifar10_eval.py
Vector Representations of Words
在本教程我们来看一下Mikolov et al中提到的word2vec模型。该模型是用于学习文字的向量表示,称之为“word embedding”。
亮点
本教程意在展现出在TensorfLow中构建word2vec模型有趣、本质的部分。
- 从为何需要使用向量表示文字开始。
- 通过直观地例子观察模型背后的本质,以及它是如何训练的(通过一些数学方法评估)。
- 同时也展示了TensorFlow对该模型的简单实现。
- 最后,着眼于让给这个简单版本的模型表现更好。
在教程的推进中循序渐进地解释代码,但是如果你更希望直入主题,可以在 tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py查看到一个最简单的实现。这个基本的例子提供的代码可以完成下载一些数据,简单训练后展示结果。一旦你觉得已经完全掌握了这个简单版本,你可以查看 tensorflow/models/embedding/word2vec.py,这里提供了一些更复杂的实现,同时也展示了TensorFlow的一些更进阶的特性,比如如何更高效地使用线程将数据送入文本模型,再比如如何在训练中设置检查点等等。
但是首先,让我们来看一下为何需要学习word embeddings。如果你对word embeddings相关内容已经是个专家了,那么请安心跳过本节内容,直接深入细节干一些脏活吧。
动机: 为什么需要学习 Word Embeddings?
通常图像或音频系统处理的是由图片中所有单个原始像素点强度值或者音频中功率谱密度的强度值,把它们编码成丰富、高纬度的向量数据集。对于物体或语音识别这一类的任务,我们所需的全部信息已经都存储在原始数据中。然后,自然语言处理系统通常将词汇作为离散的单一符号,例如 “cat” 一词或可表示为 Id537 ,而 “dog” 一词或可表示为 Id143。这些符号编码毫无规律,无法提供不同词汇之间可能存在的关联信息。换句话说,在处理关于 “dogs” 一词的信息时,模型将无法利用已知的关于 “cats” 的信息(例如,它们都是动物,有四条腿,可作为宠物等等)。可见,将词汇表达为上述的独立离散符号将进一步导致数据稀疏,使我们在训练统计模型时不得不寻求更多的数据。而词汇的向量表示将克服上述的难题。
向量空间模型 (VSMs)将词汇表达(嵌套)于一个连续的向量空间中,语义近似的词汇被映射为相邻的数据点。向量空间模型在自然语言处理领域中有着漫长且丰富的历史,不过几乎所有利用这一模型的方法都依赖于 分布式假设,其核心思想为出现于上下文情景中的词汇都有相类似的语义。采用这一假设的研究方法大致分为以下两类:基于技术的方法 (e.g. 潜在语义分析), 和 预测方法 (e.g. 神经概率化语言模型).
其中它们的区别在如下论文中又详细阐述 Baroni et al.,不过简而言之:基于计数的方法计算某词汇与其邻近词汇在一个大型语料库中共同出现的频率及其他统计量,然后将这些统计量映射到一个小型且稠密的向量中。预测方法则试图直接从某词汇的邻近词汇对其进行预测,在此过程中利用已经学习到的小型且稠密的嵌套向量。
Word2vec是一种可以进行高效率词嵌套学习的预测模型。其两种变体分别为:连续词袋模型(CBOW)及Skip-Gram模型。从算法角度看,这两种方法非常相似,其区别为CBOW根据源词上下文词汇(’the cat sits on the’)来预测目标词汇(例如,‘mat’),而Skip-Gram模型做法相反,它通过目标词汇来预测源词汇。Skip-Gram模型采取CBOW的逆过程的动机在于:CBOW算法对于很多分布式信息进行了平滑处理(例如将一整段上下文信息视为一个单一观察量)。很多情况下,对于小型的数据集,这一处理是有帮助的。相形之下,Skip-Gram模型将每个“上下文-目标词汇”的组合视为一个新观察量,这种做法在大型数据集中会更为有效。本教程余下部分将着重讲解Skip-Gram模型。
处理噪声对比训练
神经概率化语言模型通常使用极大似然法 (ML) 进行训练,其中通过 softmax function 来最大化当提供前一个单词
h
(代表 “history”),后一个单词的概率
当 (score(wt,h)) 计算了文字 (wt) 和 上下文 (h) 的相容性(通常使用向量积)。我们使用对数似然函数来训练训练集的最大值,比如通过:
这里提出了一个解决语言概率模型的合适的通用方法。然而这个方法实际执行起来开销非常大,因为我们需要去计算并正则化当前上下文环境
(h)
中所有其他
(V)
单词
(w′)
的概率得分,在每一步训练迭代中。
从另一个角度来说,当使用word2vec模型时,我们并不需要对概率模型中的所有特征进行学习。而CBOW模型和Skip-Gram模型为了避免这种情况发生,使用一个二分类器(逻辑回归)在同一个上下文环境里从
k
虚构的 (噪声) 单词
从数学角度来说,我们的目标是对每个样本最大化: