接触Tensorflow也有好一段时间了,但总是对这个项目庞大的代码量和复杂的设计结构望而生畏,一直觉得它可以和某些原生的编程语言相提并论,所以大多时候也都是照着一些现成的模板去套用然后再不断调试,对内部的一些基本概念其实并没有太多了解。最近想着好好把官方给的get started和tutorial过一遍,对以后使用应该也更有帮助。
Tensors
张量就是高维数组的另一种表达形式,数组是几维的,张量的rank就是几。比如[1,2,3]是一维向量,rank=1;[[1,2,3], [2,3,4]]是二维矩阵,rank=2;[[[1,2,3], [2,3,4]]]是三维张量,rank=3,就这么简单。
Computational Graph
有了tensor,要形成flow,就必须借助图,张量和图起来就基本构成了Tensorflow的基本框架。图中的每个node可以是tensor,也可以是加减乘除之类的算子。其实回想一下,神经网络本质上就是这种神经元之间互相有向连接的结构,所以在我们定义好每个node后把这些node连接成图,理论上就可以搭建任何模式的结构。但是如果要查看某个图最终node的输出结果,还必须要通过一个session的运行环境来run。下面举个简单的小例子就会一目了然:
|
|
上面的code只是把node1和node2定义为tf.constant()的常量形式,更普遍的我们将输入定义为类似于函数形式的变量,这时就需要利用到tf.placeholder()方法,可以理解为占位符,这类似于python中lambda匿名函数的形式,占位符就是传入匿名函数的输入参数,再看例子:
|
|
上面提到的tensor都是不可变,但是在大多machine learning的应用中需要通过不断的迭代来调整参数,比如网络中的weight和bias,这时需要利用tf.Variable()来给定初始值并定义这些需要train的变量。有了trainable的参数和输入数据,最后一步就是定义loss,我们以linear regression为例,定义square loss 为$loss = (Wx + b - \hat y)^2$,大多数常见的loss在Tensorflow中都已经被封装好,直接调用即可。接下来的代码相对来说复杂一点,但逻辑在Tensorflow的框架下十分清晰:
|
|
Train
到现在为止,我们还只是简单的手动构建了图,做了几个简单运算,还没有涉及到神经网路的梯度反向传播、参数更新过程。Tensorflow的强大之处在于,在用户构建完成网络结构(图)后,就可以通过简单的函数调用来自动计算梯度。这里我们以最常见的梯度下降为例:
|
|
Reading Data
Tensorflow读取数据的方式大体上说有三种,第一种是和上述例子中一样的方式,先在图中定义好placeholder,再通过feed_dict方式在run session时传入placeholder对应的数据;第二种适用于小数据量,直接将数据定义为constant或variable,个人感觉本质上和第一种没有太大区别;第三种在数据量比较大时候十分常用,就是直接从文件中按照队列的方式进行读取,以目前一般企业级机器学习的数据量来看,前两种方式会受限于内存和图的大小,因此简单介绍下这种比较通用的方式。
首先将要读取的小文件名形成一个列表,然后通过tf.train.string_input_producer函数建立一个关于这些文件名的队列queue。接下来会有若干个reader实体去从这个queue中读取文件名,然后对于不同形式的文件(比如csv、二进制等)再通过不同的decoder对当前文件名下的内容进行解析,这样就可以不断的从文件列表中的每个文件读数,下面是一个涉及到batch read的例子。
|
|
一般来说牵扯到了queue,我们就必须要在tf.Session()中在任务run之前利用tf.train.start_queue_runners和tf.train.Coordinator来填充队列和管理线程,不然read进程就会因为从queue中等待读取文件名而发生阻塞。
上面只是对tf做了一个非常非常浅显的介绍,但烂熟于心之后基本上也可以无障碍阅读github上大多数的tensorflow tutorial,而在实际的程序中依然有大量的坑和N多封装好的方法等待被发掘。