Tensorflow学习笔记(一)

因为博士研究方向主要集中在运筹优化方向,希望博士毕业之后工作转向人工智能方向。因此决定在未来的半年内逐渐丰富自己的AI相关技术栈,集中于深度学、GAN等非监督学习技术、强化学习等方面。

Tensorflow作为google开源的一款优秀的深度学习框架,吸引了很多关注,也成为和FB的pytorch并驾齐驱的框架。因此有必要对Tensorflow进行深入的研究。本学习笔记主要参考了《Tensorflow:实战Google深度学习框架》这本书,其对于入门来说非常合适,因此本系列文章主要是此书的最重要内容的境界和个人的一些理解。

Tensorflow的安装和配置

如果是在个人PC上配置Tensorflow,推荐安装pycharm破解版+anaconda的python环境,很容易利用pycharm的包管理器添加Tensorflow的功能。如果在服务器上,可以参考网上的配置教程,这不是本文的重点,相关安装可参考点我配置。

Tensorflow的主要思想

  1. 张量和图
    Tensorflow最重要的概念就是张量(tensor),一个张量可以理解为一个多维数组,如果是标量则定义为0维。Tensorflow第一步需要做的事情就是定义一个图(graph),图的顶点由张量构成,这些张量之间存在一些运算关系,这种运算关系则相当于顶点之间的边。当定义好图后,就相当于定义好了所有张量的一个计算流
    值得注意的是,定义好的张量其本质是一个指针,而不是具体数值,直接打印定义好的张量,会打印出张量的数据结构信息,考虑如下代码
import tensorflow as tf
a = tf.constant([1,0, 2.0], name='a')
print(a)

其输出结果并不是预想的[1.0, 2.0],而是

>>Tensor("a:0", shape=(3,), dtype=float32)
  1. 会话
    因此,Tensorflow的第三个重要的概念就是会话(session),只有当图运行在会话上时,才能使得张量计算得到具体值。如下代码给出了一个最简单的使用tensorflow计算加法的demo。
import tensorflow as tf
a = tf.constant([1.0, 2.0], name='a', dtype=tf.float32)
b = tf.constant([3.0, 4.0], name='b', dtype=tf.float32)
result = a + b
with tf.Session() as sess:
    print(sess.run(result))

上面的代码with ... as 实际上是一个局部空间,能够保证tf.Session()在使用完后会话关闭,若tensor是某定义好的张量节点,则运行sess.run(tensor)可以得到其具体值。同时注意到tf.Session()会运行默认图,只有当传入tf.Session(graph=g1)才能保证该会话运行指定的图g1。可以按照如下方式定义图且运行指定图:

g1 = tf.Graph()
with g1.as_default():
    u = tf.Variable(initial_value=tf.random_normal(\
    shape=[200, 100], stddev=0.1), name='u')
with tf.Session(graph=g1) as sess:
    tf.initialize_all_variables().run()
    with tf.variable_scope("", reuse=True):
        print(sess.run(tf.get_variable('u')))   ##打印图g1中张量u的计算结果

如果要定义默认会话,可以使用tf.InteractiveSession(),例如下面可以直接计算变量结果的代码

result = tf.constant(1,name='result')
sess=tf.InteractiveSession()
print(result.eval())
sess.close()
---------------------
>>1
  1. 变量
    变量是tensorflow重要的一种张量,在深度学习训练中,变量可用来存储可迭代更新的待优化的参数。常见的两种声明变量的方法包括
w1 = tf.Variable(initial_value=tf.random(\
shape[1,1],stddev=0.1), name='weights')
w2 =  tf.get_variable(name = 'weights', shape=[1, 1],\
initializer=tf.random_normal_initializer(stddev=0.1))

采用这两种方式定义变量是等价的。tf.Variable需要传入初始值,而tf.get_variable则要求传入一个initializer的初始化器。在run session的之前init这些变量将会按照代码中的方式进行初始化。tf.Variable和tf.get_variable存在一些细微的差别:

  • tf.Variable在出现变量的name冲突时会系统自动处理加上后缀,而tf.get_variable则会系统报错,不允许重名。
import tensorflow as tf
w_1 = tf.Variable(1,name="w_1")
w_2 = tf.Variable(2,name="w_1")
print(w_1.name)
print(w_2.name)
#输出
>>w_1:0
>>w_1_1:0
-----------------------------------
import tensorflow as tf
w_1 = tf.get_variable(name="w_1",initializer=1)
w_2 = tf.get_variable(name="w_1",initializer=2)
#错误信息
#ValueError: Variable w_1 already exists, disallowed. Did
#you mean to set reuse=True in VarScope?
  • 当需要共享变量的时候,需要使用tf.get_variable()。为了方便变量管理,tensorflow有一个变量管理器,叫做tf.variable_scope,类似于C++中的命名空间,在不同命名空间内,tf.get_variable()可以重名。但tf.get_variable()在同一个命名空间内共享一个变量的时候需要声明reuse,否则报错,reuse代表是指向同一个变量。如下为例:
import tensorflow as tf
with tf.variable_scope("scope1"): # scopename is scope1
    w1 = tf.get_variable("w1", shape=[])
    w2 = tf.Variable(0.0, name="w2")
with tf.variable_scope("scope1", reuse=True):
    w1_p = tf.get_variable("w1", shape=[])
    w2_p = tf.Variable(1.0, name="w2")
with tf.variable_scope("scope2"):
    w1_pp = tf.get_variable("w1", shape=[])
print(w1 is w1_p, w2 is w2_p, w1 is w1_pp)  # True False False

最后再简单说一下变量的初始化,如果已经声明了两个变量w1和w2,那么下面几种初始化方式等价:

sess.run(w1.initializer)
sess.run(w2.initializer)
-----------------------------
sess.run(tf.initialize_all_variables())
-----------------------------
tf.initialize_all_variables().run()

值得注意的是,在最新版本的python和tensorflow中,函数tf.initialize_all_variables被替换成了tf.global_variables_initializer,但前者还可以兼容使用,只不过会报警告。

  1. 赋值操作
    tensorflow为张量节点提供了赋值运算的操作tf.assign,可以将一个变量赋给另外一个变量,但是要求变量的dtype是一样的,维数shape不同的时候还需要声明validate_shape=False。举例如下
## 尝试赋不同类型
w1 = tf.Variable(tf.random_normal([2,3], stddev=1), name='w1')
w2 = tf.Variable(tf.random_normal([3,4], dtype=tf.float64, stddev=1), name='w2')
w1.assign(w2)
>>TypeError: Input 'value' of 'Assign' Op has type float64 that does not match type float32 of argument 'ref'.
## 赋不同shape
w1 = tf.Variable(tf.zeros([2,2]), name='w1')
w2 = tf.Variable(tf.zeros([2,3]), name='w2')
tf.assign(w1, w2)
>> ValueError: Dimension 1 in both shapes must be equal, but are 2 and 3. Shapes are [2,2] and [2,3]. for 'Assign' (op: 'Assign') with input shapes: [2,2], [2,3].
# 这一句可以成功执行
tf.assign(w1, w2, validate_shape=False)
  1. 占位张量(placeholder)
    通常而言,定义一个计算图会使用常量tf.constant,但是频繁使用tf.constant会增大网络图的规模,带来急速的计算膨胀。因此,tensorflow里面引入了placeholder占位符用来在循环结构中不断接收常量。例如在前向传播算法中一次迭代用多个样本(一个batch)来计算输出值,迭代1000次,如果不用placeholder去接收batch,而改成声明常量的方法,必然导致计算图的规模急剧膨胀。因此做法是为每个batch声明一个固定的placeholder作为占位张量节点,然后每次迭代将具体的样本数据feed进去。placeholder必须要有固定的数据类型,但是维度可以是变化的,比如可设定shape=(none, 2),则第一维可以变化。在sess.run()的时候需要将所有必须的placeholder都填上具体的数据,例如
x = tf.placeholder(shape=[1,2], dtype=tf.float32, name='x-input')
y = tf.const([0.5,0.5])
result = x+y
with tf.Session() as sess:
    print(sess.run(result, feed_dict={x:[0.5,0.5]}))
>> [1.0, 1.0]

使用tensorflow编写最简单的神经网络

熟悉了上一节介绍的tensorflow思想,我们就可以写一个最简单的神经网络的代码了

import tensorflow as tf
#Numpy是一个科学计算的工具包,这里通过Numpy工具包生成模拟数据集
from numpy.random import RandomState
 
#定义训练数据batch的大小
batch_size = 8
 
#定义神经网络的参数
w1 = tf.Variable(tf.random_normal([2, 3], stddev = 1, seed = 1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev = 1, seed = 1))
 
'''
在shape的一个维度上使用None可以方便的使用不同的batch大小。在训练时把数据分成
比较小的batch,但是在测试时,可以一次性使用全部的数据。当数据集比较小时这样比较
方便测试。但是数据集比较大时,将大量数据放入一个batch可能造成内存溢出。
'''
x = tf.placeholder(tf.float32, shape=(None, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
 
#定义神经网络的前向传播结构
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
 
#定义损失函数和反向传播算法
y = tf.sigmoid(y)
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0))
                                + (1-y)*tf.log(tf.clip_by_value(1-y, 1e-10, 1.0)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
 
#通过随机数生成一个模拟数据集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
 
'''
定义规则来给出样本的标签。在这里所有x1+x2<1的样例都被认为是正样本(比如零件合格),
而其他为负样本(比如零件不合格)。和tensorflow游乐场中的表示法不太一样的地方是,
在这里使用0表示负样本,1表示正样本。大部分解决分类问题的神经网络都会采用0和1表示法。
'''
Y = [[int(x1+x2<1)] for (x1, x2) in X]
 
#创建一个会话来运行tensorflow程序
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    #初始化变量
    sess.run(init_op)
 
    #打印训练前神经网络参数的值
    print(sess.run(w1))
    print(sess.run(w2))
 
    #设定训练的轮数
    STEPS = 5000
    for i in range(STEPS):
        #每次选取batch个样本进行训练
        start = (i * batch_size) % dataset_size
        end = min(start+batch_size, dataset_size)
 
        #通过选取的样本训练神经网络并更新参数
        sess.run(train_step, feed_dict={x:X[start:end], y_:Y[start:end]})
        if i % 1000 == 0:
            #每隔一段时间计算所在数据上的交叉熵并输出
            total_cross_entropy = sess.run(cross_entropy, feed_dict={x:X, y_:Y})
            print("After %d training steps(s), cross entropy on all data is %g" %
                  (i, total_cross_entropy))
    print(sess.run(w1))
    print(sess.run(w2))

可以总结撰写神经网络的大致步骤 :

  1. placeholder用于传入样本数据,作为神经网络的输入;(trainable)variables用于作为待训练参数
  2. 定义神经网络结构图和前向传播算法的输出结果
  3. 定义损失函数及反向传播的优化算法,形成一个完整的train_step
  4. 定义一个会话tf.Session(),多次执行train_step(核心是反响传播算法),不断更新variables

你可能感兴趣的:(Tensorflow学习笔记(一))