阅读书籍为《Hands-On Machine Learning with Scikit-Learn & TensorFlow》王静源等翻译的中文译版《机器学习实战,基于 Scikit-Learn 和 TensorFlow》,本文中所有图片均来自于书籍相关部分截图。
文章目录
TensorFlow是什么鬼
是什么: 是一个用于数值计算的开源软件库,或者框架。
为什么叫TensorFlow: TF中的操作可以接受任意数量的输入,也可以产生任意数量的输出。当输入和输出都是多维数组的时候被称为张量Tensor。
基本的原理: 首先再python中定义一个用来计算的图,然后TF使用,并且使用优化过的C++代码进行执行。
特点: 设计清晰,灵活性,扩展性还有完善的文档。
优势: TF可以将一个计算图划分为多个子图,然后进行分布式并行计算。这个优势使得TF非常适用于在庞大的(数十亿的数据量)数据集上训练巨大的(上百万的特征量)神经网络。
如何使用TensorFlow
-
构造一个节点树:
import tensorflow as tf x = tf.Variable(3, name="X") y = tf.Variable(4, name="Y") f = x*x*x + y + 2 init = tf.global_variables_initializer()
-
什么是会话:当前运行的一次过程,有两种启动方式
#1 使用with with tf.Session() as sess: init.run() result = f.eval() print(result)
#2 不使用with,这个要记得手动关闭会话 sess = tf.InteractiveSession() init.run() re = f.eval() print(re) sess.close()
-
节点会自己加入结点树吗:会!
x1 = tf.Variable(1) a = x1.graph is tf.get_default_graph() print(a) #will print true
-
设置默认结点:两种方法
#1 graph = tf.Graph() with graph.as_default(): x2 = tf.Variable(2) print(x2.graph is graph) # will print ture
#2 tf.get_default_graph() #可以用来直接重置默认结点 print(x2.graph is tf.get_default_graph()) #will print flase
-
一个节点的生命周期:
节点值每次执行后都会被抛弃;
变量值由会话维护,从初始化器的执行开始到关闭会话结束;w = tf.constant(3) x = w + 2 y = x + 5 z = x * 3 with tf.Session() as sess: print(y.eval()) print(z.eval())
此段代码描述了一个简单的计算图,执行阶段
1.启动一个会话(with那行);
2.求值Y, 检测到y依赖x,x依赖w, 所以先计算w,再计算x,最后合成y。(第一个print那行);
3.求值z, 检测到z依赖x,x依赖w,所以先计算w,再计算x,最后合成z。(第二个print那行);
过程中可见,W和X被计算两次,此处体现了,节点值每次执行后都会被抛弃;
用加州的房价数据集实现一个线性回归
import numpy as np
from sklearn.datasets import fetch_california_housing
import tensorflow as tf
# import data
housing = fetch_california_housing()
m, n = housing.data.shape
housing_data_plus_bias = np.c_[np.ones((m, 1)), housing.data]
# prepare array
X = tf.constant(housing_data_plus_bias, dtype=tf.float32, name="X")
Y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="Y")
XT = tf.transpose(X)
# formula computing theta
theta = tf.matmul(tf.matmul(tf.matmul_inverse(tf.matmul(XT, X)), XT), Y)
# do
with tf.Session() as sess:
theta_value = theta.eval()
print(theta_value)
结果如下:
与直接使用numpy计算相比,TF会自动把计算任务发给不同的CPU或者GPU,快得很。
这里theta参数集使用正规方程计算:
实现一个梯度下降
TF有个强大的功能就是可以自动微分从而自动计算梯度。除此之外还有众多的优化器可以帮助更好的实现梯度下降。
我们将对比手工,自动微分,和优化器的三种不同梯度下降方法:
-
手工实现
import numpy as np from sklearn.datasets import fetch_california_housing import tensorflow as tf housing = fetch_california_housing() m, n = housing.data.shape #梯度下降前应该对数据进行特征向量归一化,这里没有进行归一化所以结果有误 housing_data_plus_bias = np.c_[np.ones((m, 1)), housing.data] n_epochs = 1000 L_rate = 0.01 X = tf.constant(housing_data_plus_bias, dtype=tf.float32, name="X") Y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="Y") theta = tf.Variable(tf.random_uniform([n+1, 1], -1.0, 1.0), name="theta") y_pred = tf.matmul(X, theta, name="pred") error = y_pred - Y mse = tf.reduce_mean(tf.square(error), name="mse") grad = 2/m * tf.matmul(tf.transpose(X), error) training_op = tf.assign(theta, theta-L_rate*grad) init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) for epoch in range(n_epochs): if epoch % 100 == 0: print("epoch", epoch, "MSE=", mse.eval()) sess.run(training_op) best_theta = theta.eval() print(best_theta)
-
自动微分实现
# grad = 2/m * tf.matmul(tf.transpose(X), error) 替换为下列语句即可。 grad = tf.gradients(mse, [theta])[0]
自动计算梯度主要有四种方法:
-
优化器实现
# training_op = tf.assign(theta, theta - L_rate * grad)替换为下列两个语句。 op = tf.train.GradientDescentOptimizer(learning_rate=L_rate) training_op = op.minimize(mse)
怎样保存和恢复模型
TF提供了非常便捷的模型保存和恢复语句:
- 保存模型参数, 调用 save()方法:
[....] theta = ..... [...] init = tf.global_variables_initializer() saver = tf.train.Saver() with tf.Session() as sess: sess.run(init) for epoch in range(n_epochs): if epoch % 100 == 0: print("epoch", epoch, "MSE=", mse.eval()) sess.run(training_op) best_theta = theta.eval() save_path = saver.save(sess, "相对路径字符串")
- 恢复模型参数,调用restore()方法:
#前面一样 [.......] with tf.Session() as sess: saver.restore(sess, "相对路径字符串") [.....]
TensorBoard又是什么鬼
是一个更好地可视化训练过程的库,我们可以用它来查看所定义的计算图, 可以查看训练曲线,可以查看MSE等重要的训练信息。相比我们之前依赖print来监控训练过程好的太多了。
其他:
命名作用域
随着我们计算的问题越来越复杂,图就很容易变得杂乱而庞大,一些相关的结点就有可能绕在一起混淆我们的思维。为了避免这个问题,我们可以建议命名作用域来在不使用时将相关结点收起来。具体操作如下:
#通过在变量命名前为其规定命名空间将其“收纳”
with tf.name_scope("loss") as scope:
error = y_pred - Y
mse = tf.reduce_mean(tf.square(error), name="mse")
图就会变成如下模样(loss在一般情况下自行收起,使图面看起来整洁清爽):
模块化
在很多项目的代码编写中我们经常会用到相同的语句比如下图所示:
这种操作产生的问题有:难以维护,复制过程中出错不易发现。TF为我们提供了避免这种情况产生的语句:
具体操作原理如下:
当TF创建一个结点的时候, 会先检查这个名字是否已经存在,如果存在,则会为其添加一个下划线和一个索引以保证结点的唯一性。然后TF会发现这种命名规律,然后将其归类到一个命名空间以避免界面的混乱,如下图:
如何实现归类到命名空间,具体实现如下:
共享变量
当我们想在图的不同组建中共享变量的时候,最简单的方法是先创建,然后将其作为参数传给需要他的函数。
不过当共享的参数特别多的时候,这样的办法就不再使用,这种情况下,有人创建一个包含模型所需要的所有变量的字典,然后传递给每一个函数。有人为每一个模块都创建一个类。还有一种方法是在第一次调用的时候将共享变量设置为relu()函数中的一个属性,像下面做的那样:
除过这三种方法之外,TF还提供另一个可以让代码看起来更清晰,更加模块化的选择,这种方法将在很多项目中都会频繁使用。
方法的实现如下(这段我并没有读懂, 希望以后使用的过程中慢慢理解吧,好累):
如果共享变量不存在,先用get_variable()函数创建共享变量;如果已经存在,复制它;复制与否的行为通过variable_scope()函数中的一个属性控制到底是复制还是创建。