你可能知道tensorflow的核心是用c++构建的,然而只有python的api才能获得多种便利。
当我写上一篇文章时,目标是仅使用tensorflow的c ++ api实现相同的dnn(深度神经网络),然后仅使用cudnn。从我入手tensorflow的c ++版本开始,我意识到即使对于简单dnn来说,也有很多东西被忽略了。
上一篇:
在osx上使用brew就可以了:
brewinstall bazel
我们将从tensorflow源文件开始构建:
mkdir /path/tensorflow
cd /path/tensorflow
git clone https://github/tensorflow/tensorflow.git
然后你必须对安装进行配置,如选择是否启用gpu,你要运行以下配置脚本:
cd /path/tensorflow
./configure
现在我们创建接收我们模型的代码并首次构建tensorflow的文件。请注意,第一次构建需要相当长的时间(10 – 15分钟)。
非核心的c ++ tensorflow代码位于/tensorflow/cc中,这是我们创建模型文件的地方,我们还需要一个build文件,以便bazel可以建立model.cc。
mkdir /path/tensorflow/model
cd /path/tensorflow/model
touchmodel.cc
touchbuild
我们将bazel指令添加到build文件中:
基本上它会使用model.cc建立一个模型二进制文件。我们现在准备编写我们的模型。
读取数据
这些数据是从法国网站leboncoin.fr中截取,然后清理和归一化并保存到csv文件中。我们的目标是读取这些数据。用于归一化数据的元数据被保存到csv文件的第一行,我们需要他们重新构建网络输出的价格。我创建了一个data_set.h和data_set.cc文件以保持代码清洁。他们从csv文件中产生一个浮点型二维数组,馈送给我们的网络。我把代码粘贴在这里,但这无关紧要,你不需要花时间阅读。
data_set.h
data_set.cc
我们还必须在我们的bazel build文件中添加这两个文件。
load(//tensorflow:tensorflow.bzl, tf_cc_binary)
tf_cc_binary(
name = model,
srcs = [
model.cc,
data_set.h,
data_set.cc
],
deps = [
//tensorflow/cc:gradients,
//tensorflow/cc:grad_ops,
//tensorflow/cc:cc_ops,
//tensorflow/cc:client_session,
//tensorflow/core:tensorflow
],
)
建立模型
第一步是读取csv文件加入两个张量:x表示输入,y表示预期的结果。我们使用之前定义的dataset类。访问下方链接下载csv数据集。
要定义一个张量,我们需要它的类型和形状。在data_set对象中,x数据以平坦(flat)的方式保存,所以我们要将尺寸缩减成3(每辆车有3个特征)。然后,我们正在使用std::copy_n将数据从data_set对象复制到张量(eigen::tensormap)的底层数据结构。我们现在将数据作为tensorflow数据结构,开始构建模型。
你可以使用以下方法调试张量:
log(info)
c ++ api的独特之处在于,你将需要一个scope对象来保存图形构造的状态,并将该对象传递给每个操作。
scope scope = scope::newrootscope;
我们将有两个占位符,x包含汽车的特征和y表示每辆车相应的价格。
auto x = placeholder(scope, dt_float);
auto y = placeholder(scope, dt_float);
我们的网络有两个隐藏层,因此我们将有三个权重矩阵和三个偏置矩阵。而在python中,它是在底层完成的,在c++中你必须定义一个变量,然后定义一个assign节点,以便为该变量分配一个默认值。我们使用randomnormal来初始化我们的变量,这将给我们一个正态分布的随机值。
然后我们使用tanh作为激活函数来构建我们的三个层。
添加l2正则化。
最后,我们计算损失,我们的预测和实际价格之间y的差异,并且将正则化加入损失。
至此,我们完成了前向传播,并准备做反向传播部分。第一步是使用一个函数调用将前向操作的梯度添加到图中。
所有操作必须计算关于每个变量被添加到图中的损失的梯度,关于,我们初始化一个空的grad_outputs向量,它会tensorflow会话使用时填充了为变量提供梯度的节点,grad_outputs[0]会给我们关于w1, grad_outputs[1]损失的梯度和关于w2的损失梯度,它顺序为{w1, w2, w3, b1, b2, b3},变量的顺序传递给addsymbolicgradients。
现在我们在grad_outputs中有一个节点列表。当在tensorflow会话中使用时,每个节点计算一个变量的损失梯度。我们用它来更新变量。我们将为每个变量设置一行,在这里我们使用最简单的梯度下降进行更新。
cast操作实际上是学习速率参数,在我们的例子中为0.01。
我们的网络已准备好在会话中启动,python中的optimizersapi的最小化函数基本上封装了在函数调用中计算和应用梯度。这就是我在pr#11377中所做的。
pr#11377:https://github/tensorflow/tensorflow/pull/11377
我们初始化一个clientsession和一个名为outputs的张量向量,它将接收我们网络的输出。
然后我们初始化我们的变量,在python中调用tf.global_variables_initializer就可以了,因为在构建图的过程中我们保留了所有变量的列表。在c ++中,我们必须列出变量。每个randomnormal输出将被分配给assign节点中定义的变量。
在这一点上,我们可以按训练步骤的数量循环。在本例中,我们做5000步。首先使用loss节点运行前向传播部分,输出网络的损失。每隔100步记录一次损失值,减少损失是活动网络的强制性属性。然后我们必须计算我们的梯度节点并更新变量。我们的梯度节点被用作applygradientdescent节点的输入,所以运行我们的apply_节点将首先计算梯度,然后将其应用于正确的变量。
到这里,我们的网络训练完成,可以试着预测(或者说推理)一辆车的价格。我们尝试预测一辆使用7年的宝马1系车的价格,这辆车是柴油发动机里程为11万公里。我们运行我们的layer_3节点吧汽车数据输入x,它本质上是一个前向传播步骤。因为我们已经训练过网络5000步,所以权重有一个学习值,所产生的结果不会是随机的。我们不能直接使用汽车属性,因为我们的网络从归一化的属性中学习的,它们必须经过相同的归一化化过程。dataset类有一个input方法,使用csv读取期间加载的数据集的元数据来处理该步骤。
我们的网络产生一个介于0和1之间的值,data_set的output方法还会使用数据集元数据将该值转换为可读的价格。该模型可以使用命令bazel run -c opt //tensorflow/cc/models:model运行,如果最近编译了tensorflow,你会很快看到如下输出:
它展示了汽车预计价格13377.7欧元。每次运行模型都会得到不同的结果,有时差异很大(8000—17000)。这是由于我们只用三个属性来描述汽车,而我们的网络架构也相对简单。