1. tf的一般流程
我把tf的一般流程普适性地分成以下三个部分:数据集的处理、神经网络的搭建、开始训练。其中前面两个部分,数据集以及神经网络则需要我们脱离tf工具去考虑问题。以下使用《tensorflow实战:Google深度学习框架》里面的最开始例子来说明如何通过一般流程来编程。
2. 例子一
例子一要学习这样的特征:如果(x1 + x2) < 1,则为正样本y=1;否则为负样本y=0。首先我们定义数据集,使用随机数作为数据集,训练集10000,测试集100。其次我们考虑神经网络结构,因为该特征是一个简单的线性二分类模型,所以我们的神经网络仅需要较少层数,即可学习这样的特征。看下面的代码,即可了解本文说的三个流程。
import tensorflow as tf
from numpy.random import RandomState
# 1. 数据集的制作
# 训练集
train_set_size = 10000
train_set_x = RandomState(1).rand(train_set_size, 2) #以1为随机数种子
train_set_label = [[int(x1+x2<1)] for (x1, x2) in train_set_x]
# 测试集
test_set_size = 200
test_set_x = RandomState(2).rand(test_set_size, 2)
test_set_label = [[int(x1+x2<1)] for (x1, x2) in test_set_x]
# 2. 定义神经网络结构
# 三层神经元。第一层2个,第二层3个,第三层1个
# ph是一种张量,张量具有类型,维度,名字三个基本属性。其优势是作为输入输出时可以只指定第二个维度
input_node = tf.placeholder(tf.float32, shape = (None, 2), name = "input_node")
# 定义成变量,tf才会对权重进行优化
w1 = tf.Variable(tf.random_normal([2, 3], stddev = 1, seed = 1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev = 1, seed = 1))
label_node = tf.placeholder(tf.float32, shape = (None, 1), name = "label_node")
# 定义前向传播
a = tf.matmul(input_node, w1)
b = tf.matmul(a, w2)
output_node = tf.sigmoid(b)
# 定义优化方法
# 交叉熵
# 注意对于二分类,不可以仅仅用交叉熵作为损失函数,因为有可能全部预测成1,那么log(1)为0,这时候损失函数为0,但并没有意义。可以考虑加上正则项
cross_entropy = -tf.reduce_mean(label_node * tf.log(tf.clip_by_value(output_node, 1e-10, 1.0)))
#mse = tf.reduce_mean(tf.square(label_node - output_node))
train_step = tf.train.AdamOptimizer(0.001).minimize(C..E..)
# 3. 开始训练
batch_size = 8
with tf.Session() as sess :
# 初始化变量
init_op = tf.global_variables_initializer()
sess.run(init_op)
steps = train_set_size//batch_size * 100 # 对整个数据集训练100次
for i in range(steps) :
start = (i * batch_size) % train_set_size
end = start + batch_size
# 通过选取的样本训练神经网络并更新参数
sess.run(train_step, feed_dict = {input_node : train_set_x[start : end], label_node : train_set_label[start : end]})
if i % 10000 == 0:
# 每隔一段时间计算再所有数据上的交叉熵并输出
total_CE = sess.run(mse, feed_dict = {input_node : train_set_x, label_node : train_set_label})
print("After %d training step(s), cross entropy on all data is %g" % (i, total_CE))
# 测试集上准确率
right_test = 0
for i in range(test_set_size) :
v = sess.run(output_node, feed_dict = {input_node : test_set_x[i : i + 1]})
if (v < 0.5 and test_set_label[i] == [0]) or (v > 0.5 and test_set_label[i] == [1]) :
right_test += 1
else :
print("{} : not right. v is {} and x is {}, label is {}".format(i, v, test_set_x[i], test_set_label[i]))
print("Total accuracy on test set : {}".format(right_test/test_set_size))
3. 对例子一的反思
cross_entropy = -tf.reduce_mean(label_node * tf.log(tf.clip_by_value(output_node, 1e-10, 1.0)))
cross_entropy = -tf.reduce_mean(label_node * tf.log(tf.clip_by_value(output_node, 1e-10, 1.0)) + (1 - label_node) * tf.log(tf.clip_by_value(1 - output_node, 1e-10, 1.0)))
在网上找交叉熵的写法,找到了第一条,但是细细思考之后,这样的写法放在本分类问题中是大错特错的。这样的写法只是考虑了那些正例,而对于负类,却没有任何约束作用。因此,极有可能最终预测方式为全部都为正类,按照损失函数的定义,此时损失函数为0。因此,需要考虑那些负类,也就有了后面那一项。两项一起表示的损失函数才具有意义。
此外,在损失函数的选择上,分类问题使用交叉熵(CE),而回归问题使用MSE。
为什么分类问题不能使用MSE呢?网上许多文章说是因为分类常用的sig函数和MSE连用出现K(1-K)项,可能会导致函数在非常接近1或者非常接近0时,学习得很慢,所以不能用。我对这种说法存在疑问,K(1-K)出现的本质是sig求导,难道就不能用sig了吗?但是使用CE作为损失函数时,和sig链式求导可以使得其中一项消去,比MSE更方便学习。
另外,以上程序跑出来准确率结果很差,不明白是哪里出现错误。目前我认为是参数调的不对,而不是损失函数或者tf使用上的问题。
4. 例子二
第二个例子是经典的神经网络训练入门例子MNIST。在此我们选用三层神经网络:第一层神经网络,即输入层784个神经元;第二层500个神经元,激活函数为relu函数;第三层,即输入层10个神经元。这里是分类问题,所以使用交叉熵作为损失函数,并且注意10个输出神经元应该输出对应的概率,所以需要softmax。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 数据集
mnist = input_data.read_data_sets(r"E:\Learn\Codes\tensorflow实战Google深度学习框架\MNIST\dataset", one_hot = True)
# 网络结构
num_input_node = 784
num_lay1_node = 500
num_output_node = 10
input_node = tf.placeholder(tf.float32, (None, num_input_node), name = "input_node")
output_node = tf.placeholder(tf.float32, (None, num_output_node), name = "output_node")
label_node = tf.placeholder(tf.float32, (None, num_output_node), name = "label_node")
w1 = tf.Variable(tf.random_normal([num_input_node, num_lay1_node], stddev = 1, seed = 1))
b1 = tf.Variable(tf.constant(0.1, shape = [num_lay1_node]))
w2 = tf.Variable(tf.random_normal([num_lay1_node, num_output_node], stddev = 1, seed = 1))
b2 = tf.Variable(tf.constant(0.1, shape = [num_output_node]))
a = tf.nn.relu(tf.matmul(input_node, w1) + b1)
logits = tf.matmul(a, w2) + b2
cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits = logits, labels = tf.argmax(label_node, 1)))
train_step = tf.train.AdamOptimizer(0.01).minimize(cross_entropy)
# 开始训练
correction_prediction_average = tf.equal(tf.argmax(logits, 1), tf.argmax(label_node, 1))
# 将布尔类型转成实数,转换后的平均值就是准确率
accuracy = tf.reduce_mean(tf.cast(correction_prediction_average, tf.float32))
batch_size = 100
num_train_step = 30000
with tf.Session() as sess :
init_op = tf.global_variables_initializer()
sess.run(init_op)
validate_feed = {input_node : mnist.validation.images, label_node : mnist.validation.labels}
test_feed = {input_node : mnist.test.images, label_node : mnist.test.labels}
for i in range(num_train_step) :
# 生成一个batch数据
xs, ys = mnist.train.next_batch(batch_size)
sess.run(train_step, feed_dict = {input_node : xs, label_node : ys})
if i % 1000 == 0 :
validate_acc, total_cross_entropy = sess.run([accuracy, cross_entropy], feed_dict = validate_feed)
print("After %d training step(s), validation accuracy is %g, and loss is %g" % (i, validate_acc, total_cross_entropy))
test_acc = sess.run(accuracy, feed_dict = test_feed)
print("THE LAST : this model %g in test dataset" % test_acc)
本博文的主要目的在于建立一个普适性的tf框架,以上两个例子虽然简单,但是也足够说明目的。但是显然我们还可以在这个框架上加上更深入的思考,例如选用什么激活函数,什么损失函数,网络结构怎么样,需不需要正则化等等。考虑成熟之后,就可以进入tf的函数调用,这些都脱离了本博文的内容,因此也就不再叙述。