1.Tensorflow基础TensorFlow与其他数学计算库(如numpy)的根本区别在于,在TensorFlow中,操作是符号化的。这是一个强大的想法,它允许TensorFlow做任何事情(例如自动微分),而这对于像numpy这样的命令式库来说是不可能的。但是这也导致了随之而来的代价,就是掌握这个库会比较困难。在本文中,作者将尝试揭开TensorFlow的神秘面纱,并提供一些有关有效使用TensorFlow的指南和实际示例。让我们从一个简单的例子开始,我们想要将两个随机矩阵相乘。首先,让我们看一下这个使用numpy的例子:importnumpyasnpx=np.random.normal(size=[10,10])y=np.random.normal(size=[10,10])z=np.dot(x像,y)print(z)这样的计算在TensorFlow中会是什么样子?结果如下:importtensorflowastfx=tf.random_normal([10,10])y=tf.random_normal([10,10])z=tf.matmul(x,y)sess=tf.Session()z_val=sess.run(z)print(z_val)不同于numpy的直接进行计算并将结果复制到变量z的思路。TensorFlow仅表示图中的结果。节点提供操作。如果我们直接打印z的值,我们将得到以下信息:Tensor("MatMul:0",shape=(10,10),dtype=float32)由于两个输入矩阵都有完全定义的维度,TensorFlow还能够指定张量的数据类型及其维度。为了计算张量的值,我们需要使用Session.run()函数来创建会话。提示:使用Jupyter笔记本时,请务必在开始时调用tf.reset_default()函数以清除符号图,然后再定义新节点。要了解符号计算的强大功能,让我们看另一个例子。假设我们在一条曲线上有一些样本点(比如曲线是f(x)=5x^2+3),但是我们想在不知道参数的情况下估计这个函数f(x)。我们定义一个参数化函数g(x,w)=w0x^2+w1x+w2,它是输入数据x和隐藏参数w的函数。我们的目标是找到这组隐藏参数,使得g(x,w)≈f(x)。我们可以通过最小化以下损失函数L(w)来做到这一点:L(w)=(f(x)-g(x,w))^2。虽然这个简单的问题已经有一个封闭的解决方案,但我们选择使用一种更通用的方法,该方法可以应用于任何可微函数,使用随机梯度下降。我们简单地计算一组样本点上损失函数L(w)相对于w的平均梯度,然后在梯度的相反方向上改变参数w。下面展示这个方法在TensorFlow中是如何实现的:importnumpyasnpimporttensorflowastf#使用占位符将参数值从python传递给TensorFlow算子。我们在这里定义了两个占位符,其中一个用于存储输入特征x,另一个用于存储输出y.x=tf.placeholder(tf.float32)y=tf.placeholder(tf.float32)#假设我们知道所需函数是二次多项式,我们分配一个具有3个元素的向量来表示参数。这些变量是随机初始化的。w=tf.get_variable("w",shape=[3,1])#我们将yhat定义为我们对y的估计f=tf.stack([tf.square(x),x,tf.ones_like(x)],1)yhat=tf.squeeze(tf.matmul(f,w),1)#损失函数定义为y的估计值与真实值的l2距离。我们还附加了一个收缩项,以确保生成的权重更小。loss=tf.nn.l2_loss(yhat-y)+0.1*tf.nn.l2_loss(w)#我们使用学习率为0.1的Adam优化器来最小化损失函数。train_op=tf.train.AdamOptimizer(0.1).minimize(loss)defgenerate_data():x_val=np.random.uniform(-10.0,10.0,size=100)y_val=5*np.square(x_val)+3returnx_val,y_valsess=tf.Session()#因为要使用这些变量,所以需要先初始化sess.run(tf.global_variables_initializer())for_inrange(1000):x_val,y_val=generate_data()_,loss_val=sess.run([train_op,loss],{x:x_val,y:y_val})print(loss_val)print(sess.run([w]))运行这段代码后,我得到的参数结果是:[4.98605919,-0.00187828875e-04,3.8395009]以上是编辑运行后的结果,其对应的损失值为17.6175。每次的具体结果都会不同,但最终的结果都非常接近预期的函数值。下面是原作者提供的数值。[4.9924135,0.00040895029,3.4504161]这非常接近所需参数。这只是TensorFlow可以做的事情的冰山一角。许多问题,例如优化具有数百万个参数的大型神经网络,都可以在TensorFlow中用很少的代码高效地实现。同时,TensorFlow开发团队也在致力于多设备、多线程、多平台的支持。为了简单起见,在大多数例子中我们都是手动创建session,并没有保存和加载checkpoints,但这是我们在实战中经常需要做的。您可能正在考虑使用EstimationAPI进行会话管理和日志记录。我们在代码/框架路径下提供了一个简单的可扩展架构,作为使用TensorFlow训练神经网络的实用架构示例。理解静态维度和动态维度TensorFlow中的张量具有静态维度的属性,静态维度是在构建图时确定的。静态维度也可能是不确定的。例如,我们可以定义一个维度为[None,128]的张量。importtensorflowastfa=tf.placeholder([None,128])这意味着第一个维度可以是任意大小,并且将在Session.run()期间动态确定。在表示静态张量时,TensorFlow有一个相当丑陋的API:static_shape=a.get_shape().as_list()#returns[None,128](这应该写成a,shape()的形式,但是这里有人把它太不方便定义了。)要得到张量的动态形式,可以调用tf.shape函数,它返回一个表示给定张量形状的张量:dynamic_shape=tf.shape(a)一个张量的静态维度可以使用Tensor.set_shape()函数进行设置。a.set_shape([32,128])只有当你知道自己在做什么时才使用这个函数,事实上,使用tf.reshape()更安全。a=tf.reshape(a,[32,128])如果有一个函数在方便时返回静态尺寸,在可用时返回动态尺寸,这将很方便。下面定义了这样一个函数:defget_shape(tensor):static_shape=tensor.get_shape().as_list()dynamic_shape=tf.unstack(tf.shape(tensor))dims=[s[1]ifs[0]isNoneelses[0]forsinzip(static_shape,dynamic_shape)]returndims现在想象一下:我们想通过折叠第二个和第三个维度,将一个3维矩阵转换为一个二维矩阵。我们可以使用上面的get_shape()函数来完成这个:b=placeholder([None,10,32])shape=get_shape(tensor)b=tf.reshape(b,[shape[0],shape[1]*shape[2]])请注意,无论矩阵是否为静态,这都有效。事实上,我们可以编写一个具有更通用目标的函数来折叠任意几个维度:[dims])elifall([isinstance(shape[d],int)fordindims]):dims_prod.append(np.prod([shape[d]fordindims]))else:dims_prod.append(tf.prod([shape[d]fordindims]))tensor=tf.reshape(tensor,dims_prod)returntensor然后折叠二维变得很容易。b=placeholder([None,10,32])b=tf.reshape(b,[0,[1,2]])广播操作TensorFlow支持逐元素广播操作。通常,当您要执行加法和乘法等运算时,需要确保运算符的维度匹配。例如,您不能将维度[3,2]的张量添加到维度[3,4]的张量。但在特殊情况下,您可以使用不寻常的尺寸。TensorFlow会隐式调整张量的异常维度以匹配另一个算子的维度,以实现维度兼容。所以给一个[3,1]维的张量加上一个[3,2]维的张量是合法的。importtensorflowastfa=tf.constant([[1.,2.],[3.,4.]])b=tf.constant([[1.],[2.]])#c=a+tf.tile(a,[1,2])c=a+广播允许我们执行隐式调整,这导致更短的代码和更高效的内存使用,因为我们不需要存储调整操作的中间结果的内存开销。这种方法可以用于一种场景:即组合不同长度的特征。为了连接不同长度的特征,我们通常调整输入张量的大小,然后连接结果并应用一些非线性处理。这是许多神经网络中的常用方法。a=tf.random_uniform([5,3,5])b=tf.random_uniform([5,1,6])#concataandbandapplynonlinearitytiled_b=tf.tile(b,[1,3,1])c=tf.concat([a,tiled_b],2)d=tf.layers.dense(c,10,activation=tf.nn.relu)但这可以通过广播更有效地完成。我们可以利用f(m(x+y))等价于f(mx+my)这一事实。所以我们可以把线性运算分开,然后用广播的方式做隐式连接。pa=tf.layers.dense(a,10,activation=None)pb=tf.layers.dense(b,10,activation=None)d=tf.nn.relu(pa+pb)其实这段代码就是很一般,它可以用于任何维度的张量,只要张量可以在它们之间广播。deftile_concat_dense(a,b,units,activation=tf.nn.relu):pa=tf.layers.dense(a,units,activation=None)pb=tf.layers.dense(b,units,activation=None)c=pa+pbifactivationisnotNone:c=activation(c)returnc到目前为止我们已经讨论了广播操作好的一面。那么你问它的缺点是什么?隐含的假设总是使调试更加困难。看看下面的例子:a=tf.constant([[1.],[2.]])b=tf.constant([1.,2.])c=tf.reduce_sum(a+b)你认为c的值是多少?如果你说6,那你就错了。结果会是12。这是因为当两个张量的秩不匹配时,TensorFlow会自动将第一个维度的大小用较低的维度扩展,所以相加的结果会变成[[2,3],[3,4]],因此所有参数的总和为12。避免此问题的方法是尽可能明确。如果我们明确指定要求和的维度,解决这个问题就变得容易了。a=tf.constant([[1.],[2.]])b=tf.constant([1.,2.])c=tf.reduce_sum(a+b,0)现在c的值将是[5,7],给定输出的维度,我们立即想知道是否出了问题。一般的经验法则是在求和和使用tf.squeeze()时始终指定维度。Python运行下的原型内核和高度可视化为了更高的效率,TensorFlow的计算内核是用C++编写的。但是用C++编写TensorFlow内核是一件痛苦的事情。因此,在实现内核之前,您可能希望快速实现原型系统。借助tf.py_func()函数,您可以将任何一段Python代码转换为TensorFlow操作。例如,下面的示例展示了如何使用Pythonops在TensorFlow中实现一个简单的ReLU非线性内核。importnumpyasnpimporttensorflowastfimportuuiddefrelu(inputs):#Definetheopinpythondef_relu(x):returnp.maximum(x,0.)#Definetheop的gradientinpythondef_relu_grad(x):returnnp.float32(x>0)#AnadapterthatdefinesagradientopcompatiblewithTensorflowdef_relu_grad_op(op.,grad_op):x0]x_grad=grad*tf.py_func(_relu_grad,[x],tf.float32)返回x_grad#Registerthegradientwithauniqueidgrad_name="MyReluGrad_"+str(uuid.uuid4())tf.RegisterGradient(grad_name)(_relu_grad_op)#Overridethegradienttoftthecustomget_default_graph()withg.gradient_override_map({"PyFunc":grad_name}):output=tf.py_func(_relu,[inputs],tf.float32)returnoutput您可以使用TensorFlow的梯度检查器来验证梯度是否正确:x=tf。random_normal([10])y=relu(x*x)withtf.Session():diff=tf.test.compute_gradient_error(x,[10],y,[10])print(diff)functioncompute_gradient_error()accounting计算梯度的大小并返回与给定梯度相比的差异。我所期望的是一个小差距。请注意,此实现相当低效且仅对原型设计有用,因为Python代码不可并行化且无法在GPU上运行。一旦你验证了你的想法,你肯定会想把它写成一个C++内核。在实践中,我们通常在Tensorboard上使用Python操作来进行可视化。假设您正在构建一个图像分类模型,并希望在训练期间可视化模型的预测。TensorFlow允许使用tf.summary.image()函数进行可视化。image=tf.placeholder(tf.float32)tf.summary.image("image",image)但这仅可视化输入图像。为了将预测结果可视化,你必须找到一种方法来做图像标注,这在现有操作中几乎不存在。更简单的方法是用Python绘制图形,然后用Python操作将其包装起来。importioimportmatplotlib.pyplotaspltimportnumpyasnpimportPILimporttensorflowastfdefvisualize_labeled_images(images,labels,max_outputs=3,name='image'):def_visualize_image(image,label):#Dotheactualdrawinginpythonfig=plt.figure(figsize=(3,3),dpi=80)ax=fig.add_subplot(111)ax.imshow(image[::-1,...])ax.text(0,0,str(label),horizo??ntalalignment='left',verticalalignment='top')fig.canvas.draw()#Writetheplotasamemoryfile.buf=io.BytesIO()data=fig.savefig(buf,format='png')buf.seek(0)#Readtheimageandconverttonumpyarrayimg=PIL.Image.open(buf)returnnp.array(img.getdata()).reshape(img.size[0],img.size[1],-1)def_visualize_images(images,labels):#Onlydisplaythegivennumberofexamplesinthebatchoutputs=[]foriinrange(max_outputs):output=_visualize_image(图像[i],标签[i])outputs.append(output)returnp.array(outputs,dtype=np.uint8)#Runthepythonop.figs=tf.py_func(_visualize_images,[images,labels],tf.uint8)returntf.summary.image(名称,figs)要注意,因为这里的总结通常都会隔一段时间才评价一次(并不是每一步),所以这个方法很实用,不用担心由此带来的效率问题原文:https://github.com/vahidk/EffectiveTensorflow【本文为机器之心专栏原文翻译,微信公众号《机器之心(id:almosthuman2014)》】点此查看本作者更多好文
