在此记录TensorFlow(TF)的基本概念、使用方法,以及用一段别人写好的代码展示其应用。
1 背景知识
“一个计算图是被组织到图节点上的一系列 TF 计算” 。—— TensorFlow Manual
1.1 组成要素
- 计算图 (Graph)
- 通过 节点 及 链接 构造的有向图来构造出 数据流图 。
- 在节点与链接之间,传递的是多维数组。
- 节点与变量
- 节点代表各种不同的操作。
- 每个分枝则作为底层的变量输入。
- 会话 (Session)
- 实例化计算图,并运行计算图的方法。
1.2 变量构成
TF种的变量类型由如下三种形式构成:
-
常量(Constant)定义后维度与数值皆不可变。一般用于存取超参数或其他结构信息。
-
变量(Variable)定义后维度不可变而数值可变。一般用于存取权重值或其他相关信息的矩阵。
-
占位符(PlaceHolder)不需要初始值,只分配必要的内存。
1.3 结构流程
创建图,实施算 。
即,先定义“计算图”,再进行图的“计算”。
2 方法实现
以 Anaconda3-5.1.0-Linux-x86_64, Python3.6.4 为版本环境。
2.1 简单实现
直接定义 a,b 两个节点,c 作为操作符(add):
1 import tensorflow as tf 2 a = 2 3 b = 3 4 c = tf.add(a, b, name='Add') 5 print(c)
结果为:“Tensor("Add:0", shape=(), dtype=int32)”
运行 session 并进行计算:
1 sess = tf.Session() 2 print("按c的定义形式确定的tf.add计算结果:{0}".format(sess.run(c))) 3 sess.close()
结果为:“按c的定义形式确定的tf.add计算结果:5”
2.2 变量定义与实现
2.2.1 常量
采用如下的形式进行声明:
#Create a constant.
c = tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)
定义两个常量:aa,bb,并开展 cc=aa+bb 的运算:
1 aa=tf.constant(100) 2 bb=tf.constant(200) 4 cc = aa + bb 5 # launch the graph in a session 6 with tf.Session() as sess: 7 print(sess.run(cc))
结果为:“300”
2.2.2 变量
采用如下的形式进行声明:
#Create a variable.
w = tf.Variable(<initial-value>, name=<optional-name>)
1 # 如定义一个 2x3 的服从标准差为 1 正态分布的随机矩阵: 2 w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1)) 3 with tf.Session() as sess: 4 # now let's evaluate their value 5 sess.run(tf.global_variables_initializer()) 6 ## 注意 tf.global_variables_initializer() 对于 tf.Variable() 的使用是必须的,下同 7 print(sess.run(w1))
结果为:“[[-0.8113182 1.4845988 0.06532937] [-2.4427042 0.0992484 0.5912243 ]]”
(因是随机数,不同的机器结果或不同)
2.2.3 占位符
占位符只占分配内存,并通过 feed_dict 馈送数据:
# Create a placeHolder.
b = tf.placeholder(<value-type>, shape=<value-shape>, name=<optional-name>)
1 # 如定义一个常量(aaa)和一个占位符(bbb),并进行相加运算: 2 aaa = tf.constant([5, 5, 5], tf.float32)#, name='A') # 如果对名字空间不严格,name 用不用都可以 3 bbb = tf.placeholder(tf.float32, shape=[3])#, name='B') 4 ccc = tf.add(aaa, bbb)#, name="Add") # 如果对名字空间不严格,name 用不用都可以 5 6 with tf.Session() as sess: 7 # create a dictionary: 8 # 注意这里对于 placeHolder 的字典型定义: 9 fb_4_bbb = {bbb: [1, 2, 6]} 10 # feed it to the placeholder 11 # 注意这里 sess.run 中,对于 ddd 的 feed_dict 调用: 12 print(sess.run(ccc, feed_dict=fb_4_bbb))
结果为:“[ 6. 7. 11.]”
2.2.4 tf.get_varaible 声明方式
新的声明方式 get_varialble 被用于对变量进行声明及定义。
该方法的声明方式:
tf.get_variable(name,
shape=None,
dtype=None,
initializer=None,
regularizer=None,
trainable=True,
collections=None,
caching_device=None,
partitioner=None,
validate_shape=True,
use_resource=None,
custom_getter=None,
constraint=None)
1 # 使用 get_variable 重复上面的过程: 2 3 ## 声明“常量”: 4 5 # get_variable() 中,如果没有 shape 参数,则报错: 6 # 注意,后面的 add 操作需要 aGV 与 bGV 数据类型一致,因此在 constant_init 中 给出为 20.0,且dtype=‘float’ 7 aGV = tf.get_variable(name="var_1", shape=[1], dtype='float', initializer=tf.constant_initializer(20.0)) 8 9 # tf.get_variable 的使用在同一个环境下、第二次使用时,将会报错:“Variable xxx already exists, disallowed.”
继续声明其他变量:
1 ## 声明“变量”: 2 # tf.get_variable 的使用在同一个环境下、第二次使用时,将会报错:“Variable xxx already exists, disallowed.” 3 bGV = tf.get_variable(name="var_2", shape=[2,3], initializer=tf.random_normal_initializer(mean=0, stddev=1)) 4 5 ## 声明“占位符”: 6 # get_variable 中没有tf.placeholder_initializer 之类的,因此沿用之前的 tf.placeholder() 7 phrGV = tf.placeholder(tf.float32, shape=[2,3]) 8 9 # 注意 tf.add 中的各个参数类型(如 int 或 float)必须一致 10 cGV = tf.add(aGV, bGV)#, name="Add1") ## 同样的,name="Add1" 在名字空间不敏感时也可不要
声明变量完成后,进行TF运行,即 sess.run():
1 # launch the graph in a session 2 with tf.Session() as sess: 3 # now let's evaluate their value 4 sess.run(tf.global_variables_initializer()) ## tf.global_variables_initializer 对于 tf.Variable 必须 5 print("`aGV` is : \n {}".format(sess.run(aGV))) 6 print("`bGV` is : \n {}".format(sess.run(bGV))) 7 print("`aGV+bGV` is: \n {}".format(sess.run(cGV))) 8 9 # 用 placeholder 的值进行加合 10 print("\n Next is Placeholder: \n") 11 # 按 placeholder 的使用方法,采用字典方式进行声明,并在 sess.run中使用 feed_dict进行赋值 12 fb_4_phrGV = {phrGV: [[2,4,6],[1,3,5]]} 13 print("`phrGV` is : \n {}".format(sess.run(phrGV, feed_dict=fb_4_phrGV))) 14 15 # cGV 与 phrGV 相加 16 cAddphr = tf.add(cGV, phrGV)#, name="Add2") ## 同样的,name="Add2" 在名字空间不敏感时也可不要 17 ## 上句与此句等价 --->>> cAddphr = tf.add(tf.add(aGV, bGV), phrGV) 18 19 # 注意,上面的 sess.run(xx, feed_dict=yyy) 结束后,placeholder 仍置空 20 # 因此此句仍须在 sess.run() 中进行 feed,注意此处是作用在 cAddphr 中: 21 print("`aGV+bGV+phrGV` is : \n {}".format(sess.run(cAddphr, feed_dict=fb_4_phrGV)))
结果为:(因部分变量为随机数,因此不同电脑结果或不同)
`aGV` is : [20.] `bGV` is : [[-0.26086393 1.0050383 -0.22036408] [-1.6718161 1.1273892 1.616446 ]] `aGV+bGV` is: [[19.739136 21.00504 19.779636] [18.328184 21.12739 21.616446]] Next is Placeholder: `phrGV` is : [[2. 4. 6.] [1. 3. 5.]] `aGV+bGV+phrGV` is : [[21.739136 25.00504 25.779636] [19.328184 24.12739 26.616446]]
3 应用案例
参考前述链接的代码如下:
1 import tensorflow as tf 2 from numpy.random import RandomState 3 4 batch_size=10 5 # 生成一个 [2,3] 的正态随机矩阵,第一层权重系数 w1: 6 w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1)) 7 # 生成一个 [3,1] 的正态随机矩阵,第一层权重系数 w2: 8 w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1)) 9 10 # None 可以根据 batch 大小确定维度,在shape的一个维度上使用None 11 ### 构造 x, y ,x应是输入,y应是输出 12 x=tf.placeholder(tf.float32,shape=[None,2]) 13 y=tf.placeholder(tf.float32,shape=[None,1]) 14 15 #激活函数使用 ReLU (tf.nn.relu()) 16 ### 构造了两层网络 (矩阵乘法使用 tf.matmul) 17 a=tf.nn.relu(tf.matmul(x,w1)) ### 第一层计算:x*w1 = a 18 yhat=tf.nn.relu(tf.matmul(a,w2)) ### 第二层计算: a*w2 = x*w1*w2 = y 19 20 #定义交叉熵为损失函数,训练过程使用Adam算法最小化交叉熵 21 ### 使用 tf.clip_by_value 将 yhat 的最大最小值限定在 [1e-10, 1.0] 之间,越过区间的设定为上下限, 22 ### 再使用 tf.log 取 log_e 值,之后与 y 相乘,再取平均值,以此作为交叉熵 (cross_entropy) 23 cross_entropy=-tf.reduce_mean(y*tf.log(tf.clip_by_value(yhat,1e-10,1.0))) 24 # 使用 tf.train.AdamOptimizer (选择参数为 0.001) 进行训练,并最小化交叉熵 25 ### 注意 tf.train 中 AdamOptimizer 与 minimize 的用法 26 train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy) 27 28 rdm=RandomState(1) 29 data_size=516 30 31 ### 定义 输入变量 X 及目标值 Y 32 #生成两个特征,共data_size个样本 33 X=rdm.rand(data_size,2) 34 #定义规则给出样本标签,所有x1+x2<1的样本认为是正样本,其他为负样本。 35 ### x1+x2 满足 <1 的条件时,Y 为1,即为正样本 36 Y = [[int(x1+x2 < 1)] for (x1, x2) in X] 37 38 with tf.Session() as sess: 39 sess.run(tf.global_variables_initializer()) ### 生成之前使用 tf 定义的所有变量 40 print(sess.run(w1)) 41 print(sess.run(w2)) 42 steps=11000 43 for i in range(steps): 44 45 #选定每一个批量读取的首尾位置,确保在1个epoch内采样训练 46 start = i * batch_size % data_size 47 end = min(start + batch_size,data_size) 48 ### 注意此处 placeholder 的 feed_dict 用法, 49 ### 直接使用函数 train_step 中嵌套使用的 cross_entropy 函数中的 x,y 变量的字典定义, 50 ### 亦即,即使在几层函数的嵌套使用中,sess.run(xxx, feed_dict = {..}) 依然有效 51 sess.run(train_step,feed_dict={x:X[start:end],y:Y[start:end]}) 52 if i % 1000 == 0: 53 training_loss= sess.run(cross_entropy,feed_dict={x:X,y:Y}) 54 print("在迭代 %d 次后,训练损失为 %g"%(i,training_loss))
结果为:(因部分变量为随机数,因此不同电脑结果或不同)
[[-0.8113182 1.4845988 0.06532937] [-2.4427042 0.0992484 0.5912243 ]] [[-0.8113182 ] [ 1.4845988 ] [ 0.06532937]] 在迭代 0 次后,训练损失为 0.309702 在迭代 1000 次后,训练损失为 0.0393322 在迭代 2000 次后,训练损失为 0.0173816 在迭代 3000 次后,训练损失为 0.0102881 在迭代 4000 次后,训练损失为 0.00676341 在迭代 5000 次后,训练损失为 0.00446996 在迭代 6000 次后,训练损失为 0.00297459 在迭代 7000 次后,训练损失为 0.0021837 在迭代 8000 次后,训练损失为 0.00179786 在迭代 9000 次后,训练损失为 0.00132818 在迭代 10000 次后,训练损失为 0.000957028
总结:
- 网络为双层。w1/w2 声明为两层网络的权重,x为输入,维度[Nx2]的2列多行向量,y为输出,维度[Nx1]的1列多行向量。
- 网络的计算结构为: y=x.w1.w2, 相应的维度关系是:[N,2]x[2,3]x[3,1] = [N,1] ,因此得到 y 的值。
程序的整体运行方式是:
a. 定义各个变量(x,y,w1,w2);
b. 定义相互的计算关系,即 Net 的结构(a=x.w1, y=a.w2);
c. 定义优化目标,即 交叉熵:cross_entropy;
d. 定义优化方法,即 train_step,其中具体实现采用: tf.train.AdamOptimizer.minimize(交叉熵);
e. 进行数据整理,并运行 TF,即 sess.run()。