一、TF常量、变量和占位符详解
最基本的TensorFlow提供了一个库来定义和执行对张量的各种数学运算。张量,可理解为一个 n 维矩阵,所有类型的数据,包括标量、矢量和矩阵等都是特殊类型的张量。(对TF张量的理解非常有趣,[1]是标量;[1,2,3]、[1,2]是向量,类似于坐标向量(x1,y1,z1);实际二维矩阵是TF的矩阵;实际高维矩阵是TF的高维张量)
TensorFlow 支持以下三种类型的张量:常量:常量是其值不能改变的张量。
变量:当一个量在会话中的值需要更新时,使用变量来表示。例如,在神经网络中,权重和偏置需要在训练期间更新,可以通过将权重声明为变量来实现。变量在使用前需要被显式初始化(TF中,变量的定义和初始化是明确分离的)。另外需要注意的是,常量存储在计算图的定义中,每次加载图时都会加载相关变量。换句话说,它们是占用内存的。另一方面,变量又是分开存储的。它们可以存储在参数服务器上。
占位符:用于将值输入 TensorFlow 图中。它们可以和 feed_dict 一起使用来输入数据。在训练神经网络时,它们通常用于提供新的训练样本。在会话中运行计算图时,可以为占位符赋值。这样在构建一个计算图时不需要真正地输入数据。需要注意的是,占位符不包含任何数据,因此不需要初始化它们。
3.1 TensorFlow常量
声明一个标量常量:
t_1 = tf.constant(4)
一个形如 [1,3] 的常量向量可以用如下代码声明:
t_2 = tf.constant([4,3,2])
要创建一个所有元素为零的张量,可以使用tf.zeros() 函数。这个语句可以创建一个形如 [M,N] 的零元素矩阵,数据类型(dtype)可以是int8、 int32、float32 (注意,别忘记写8、32,int,float的写法在TF中是错误的,已实践)等:
tf.zeros([M,N],tf.dtype)
例如:
zero_t = tf.zeros([2,3],tf.int32)
# Results in an 2x3 array of zeros:[[0 0 0],[0 0 0]]
利用tf.zeros_like()和tf.ones_like()还可以创建与现有 Numpy 数组或张量常量具有相同形状的张量常量,如下所示:
t_3 = tf.zeros_like(t_2)
# creat a zero matrix of same shape as t_2
t_4 = tf.ones_like(t_2)
# creat a ones matrix of same shape as t_2
# Results in is: [0 0 0] and [1 1 1]
tf.ones()创建一个所有元素都设为 1 的张量。下面的语句即创建一个形如[M,N]、元素均为 1 的矩阵:
tf.ones([M,N],tf.dtype)
例如:
ones_t = tf.ones([2,3], tf.int32)
# Results in an 2x3 array of ones:[[1 1 1],[1 1 1]]
更进一步,还有以下语句:tf.linspace(start,stop,num)在一定范围内生成一个从初值到终值等差排布的序列。相应的值为 (stop-start)/(num-1)。例如:
range_t = tf.linspace(2.0,5.0,5)
#We get:[2. 2.75 3.5 4.25 5.]
tf.range(start,limit,delta)从开始(默认值=0)生成一个数字序列,增量为 delta(默认值=1),直到终值(但不包括终值):
range_t = tf.range(10)
#Result:[0 1 2 3 4 5 6 7 8 9]
TensorFlow 允许创建具有不同分布的随机张量:
(1)使用tf.random_normal()创建一个具有一定均值(默认值=0.0)和标准差(默认值=1.0)、形状为 [M,N] 的正态分布随机数组:
t_random_normal=tf.random_normal([2,3],mean=2,stddev=4,seed=12)
# result: [[ 0.25347447 5.37991 1.9527606 ], [-1.5376031 1.2588985 2.8478067 ]]
(2) 使用tf.truncatd_normal()创建一个具有一定均值(默认值=0.0)和标准差(默认值=1.0)、形状为 [M,N] 的截尾正态分布随机数组:我在pycharm上尝试了tf.truncatd_normal()命令,但是报错说没有该属性。网上看也有类似案例,似乎是安装版本的问题?以后再看吧
(3) tf.random_uniform()在种子的 [minval(default=0),maxval] 范围内创建形状为 [M,N] 的给定伽马分布随机数组:
t_random= tf.random_uniform([2,3],maxval=4,seed=12)
# result: [[2.54461 3.6963658 2.7051091], [2.0085006 3.8445983 3.5426888]]
(4) 要将给定的张量随机裁剪为指定的大小,使用以下语句:
tf.random_crop(t_random,[2,5],seed=12)
这里,t_random 是一个已经定义好的张量。这将导致随机从张量 t_random 中裁剪出一个大小为 [2,5] 的张量。
很多时候需要以随机的顺序来呈现训练样本,可以使用 tf.random_shuffle() 来沿着它的第一维(即重新调整行的位置)随机排列张量。如果 t_random 是想要重新排序的张量,使用下面的代码:
tf.random_shuffle(t_random)
(5) 随机生成的张量受初始种子值的影响。要在多次运行或会话中获得相同的随机数,应该将种子设置为一个常数值。当使用大量的随机张量时,可以使用 tf.set_random_seed() 来为所有随机产生的张量设置种子。以下命令将所有会话的随机张量的种子设置为54:
tf.set_random_seed(54)
TIP:种子只能有整数值。(种子值的作用在于,帮助我们生成可区别或相同的随机张量。对于同样的语句,53和54就会生成不同的随机张量;而53和53,执行两次,生成依旧是相同的随机张量)
3.2 TensorFlow变量
它们通过使用变量类来创建。完整定义一个变量包括:用来初始化变量的常量/随机张量模具+tf.Variable()语句。(要对这句话理解透彻,模具用来给变量定型并使其具有初始值,但是这里变量的定义也仅仅是定义,即只是准备好了模具和对应的变量,使用模具给变量烙上形状,是由会话Session()执行Initializer()操作实现的,见下文可知)下面的代码中创建了两个不同的张量变量 t_a 和 t_b。两者将被初始化为形状为 [50,50] 的随机均匀分布,最小值=0,最大值=10:
rand_t= tf.random_uniform([50,50],0,10,seed=0)
t_a = tf.Variable(rand_t)
t_a = tf.Variable(rand_t)
注意:变量通常在神经网络中表示权重和偏置。(因为机器学习最终都是为了得到某个回归模型,例如y=ax+b,那么方程形式,权重a和偏置b就是我们要得到的东西)
下面的代码中定义了两个变量的权重和偏置。权重变量使用正态分布随机初始化,均值为 0,标准差为 2,权重大小为 100×100。偏置由 100 个元素组成,每个元素初始化为 0。在这里也使用了可选参数名以给计算图中定义的变量命名:
weights=tf.Variable(tf.random_normal([100,100]), stddev=2)
bias=tf.Variable(tf.zeros[100], name='biases')
在前面的例子中,都是利用一些常量来初始化变量,也可以指定一个变量来初始化另一个变量。下面的语句将利用前面定义的权重来初始化 weight2:
补充:以上类似这样weights=tf.Variable(rand_t, name='w2')的语句所涉及的变量命名很迷,已经指定了名称为weights,又来个name='w2',很奇怪。我自己实践后发现,这种混乱的情况,以会话时初始化采用的名称为准。如sess.run(weight2.initializer),那名称就是weight2
变量的定义将指定变量如何被初始化,但是必须显式初始化所有的声明变量。在计算图的定义中通过声明初始化操作对象来实现(下面语句一次初始化全部变量):
每个变量也可以在运行图中单独使用 tf.Variable.initializer 来初始化(一次初始化一个变量):
保存变量:使用 Saver 类来保存变量,定义一个 Saver 操作对象,详细参见:
saver = tf.train.Saver()
#定义之后,主要使用以下两个语句保存和恢复:
save_path = saver.save(sess, save_path, global_step=1)
saver.restore(sess, save_path + '-'+ str(1))
3.3 TensorFlow 占位符
介绍完常量和变量之后,我们来讲解最重要的元素——占位符,它们用于将数据提供给计算图。可以使用以下方法定义一个占位符:
tf.placeholder(dtype,shape=None,name=None)
dtype 定义占位符的数据类型,并且必须在声明占位符时指定。在这里,为 x 定义一个占位符并计算 y=2*x,使用 feed_dict 输入一个随机的 4×5 矩阵:
我在这里补充了两组对比,充分证明TF中定义和执行(会话)是严格分开的,没有sess之前,data只是定义式,没有值,sess之后才有值。牢牢记住这点,才能明白为什么定义了中转量xdata(非必需),才能避免犯“将定义式赋值/传递给sess后的语句”这类错误。
3.4 解读分析
需要注意的是,所有常量、变量和占位符将在代码的计算图部分中定义。如果在定义部分使用 print 语句,只会得到有关张量类型的信息,而不是它的值。为了得到相关的值,需要创建会话图并对需要提取的张量显式使用运行命令,如下所示:(如上面两张图所证明的)
print(sess.run(t_1))
#Will print the value of t_1 defined in step 1
3.5 拓展阅读
很多时候需要大规模的常量张量对象;在这种情况下,为了优化内存,最好将它们声明为一个可训练标志设置为 False 的变量:(具体是怎么使用呢?留待以后)
t_large = tf.Varible(large_array,trainable = False)
TensorFlow 被设计成与 Numpy 配合运行,因此所有的 TensorFlow 数据类型都是基于 Numpy 的。使用 tf.convert_to_tensor() 可以将给定的值转换为张量类型,并将其与 TensorFlow 函数和运算符一起使用。该函数接受 Numpy 数组、Python 列表和 Python 标量,并允许与张量对象互操作。
下表列出了 TensorFlow 支持的常见的数据类型:
请注意,与 Python/Numpy 序列不同,TensorFlow 序列不可迭代。试试下面的代码:
for i in tf.range(10)
你会得到一个错误提示:
#typeError("'Tensor'object id not iterable.")
二、TF超参数详解
超参数是程序中经常用来规范模型性能的一些参数,如学习率、epoch、min_error等
n_input = 784
n_classes = 10
max_epochs = 1000
learn_rate = 0.5
batch_size = 10
n_hidden = 30
正如你目前所看到的,神经网络的性能非常依赖超参数。因此,了解这些参数如何影响网络变得至关重要。常见的超参数是学习率、正则化器、正则化系数、隐藏层的维数、初始权重值,甚至选择什么样的优化器优化权重和偏置。
超参数调整过程调整超参数的第一步是构建模型。与之前一样,在 TensorFlow 中构建模型。
添加一种方法将模型保存在 model_file 中。在 TensorFlow 中,可以使用 Saver 对象来完成。然后保存在会话中:
...
saver=tf.train.Saver()
...
with tf.Session() as sess:
...
save_path=saver.save(sess,'.../../model.ckpt')
# 事实上1.x版本已经不使用ckpt文件,而是.data和.index文件,详细参见我关于saver的帖子
print('Model saved in file:%s' %save_path)
3. 确定要调整的超参数,并为超参数选择可能的值。在这里,你可以做随机的选择、固定间隔值或手动选择。三者分别称为随机搜索、网格搜索和手动搜索(叫什么搜索,就叫选择就行了,又没什么特别的东西)。例如,下面是用来调节学习率的代码:
4. 选择对损失函数给出最佳响应的参数。所以,可以在开始时将损失函数的最大值定义为 best_loss(如果是精度,可以选择将自己期望得到的准确率设为模型的最低精度):
5. 把你的模型放在 for 循环中,然后保存任何能更好估计损失的模型:
以下代码使用的model系列函数,应该是Keras函数式API,留待以后钻研吧
... # load and preprocess data
... # heperparameters
tuning_epoch = [50,60,70]
batches = [5,10,20]
rsme_min = 0.04
for epoch in epochs:
for batch in batches:
model = get_model()
model.compile(loss='mean_squared_error',optimizer='adam')
model.fit(X_train, Y_train, validation_data=(X_test,Y_test),epochs=epoch,batch_size=batch,verbose=1)
Y_test_pred = model.predict(X_test)
rmse = mean_squared_error(Y_test,Y_test_pred)
if rmse < rmse_min:
rmse_min = rmse
# serialize model to json
model_json = model.to_json()
with open('model.json','w') as json_file:
json_file.write(model_json)
# serialize weights to HDF5
model.save_weights('model.hdf5')
print('saved model to disk')
除此之外,贝叶斯优化也可以用来调整超参数。其中,用高斯过程定义了一个采集函数。高斯过程使用一组先前评估的参数和得出的精度来假定未观察到的参数。采集函数使用这一信息来推测下一组参数。
拓展阅读