1. 写在最前面
1.1 机器学习如何入门
关于机器学习如何入门可以参考机器学习该怎么入门?,相关课程可以参考吴恩达机器学习系列课程。
1.2 关于框架的选择
无论是TensorFlow还是PyTorch都是非常优秀的框架,关于两者如何选择,可以参2021 年了,TensorFlow 和 PyTorch 两个深度学习框架地位又有什么变化吗?,最高赞的结论是:
2021年学术圈 Pytorch会继续称霸天下,而Tensorflow引以为傲的工业界地位会进一步衰退,Pytorch会借助ONNX所带来的落地能力在工业界逐渐抢走主导地位。
但是因为项目中使用的是TensorFlow, 所以这里选择的是TensorFlow,每天是身在tf营,心在torch。关于Pytorch,可以参考PyTorch 中文教程 & 文档。
1.3 tf1、tf2、keras与tf.keras
tf1与tf2的区别可以参考: https://www.tensorflow.org/guide/migrate/tf1_vs_tf2
- TensorFlow 1.X需要用户使用tf.*里的API手动构建计算图,然后用session.run()传入输入tensor并且计算某些输出tensor。
- TensorFlow 2.X默认是Eager执行模式,我们在定义一个Operation的时候会动态构造计算图并且马上计算。这样的好处就是我们的代码就像在执行普通的Python代码,Graph和Session等实现细节概念都被隐藏在后面了。
- Keras可以看成是一种深度学习框架的高阶接口规范,它帮助用户以更简洁的形式定义和训练深度学习网络。随着谷歌对Keras的收购,Keras库2.3.0版本后也将不再进行更新,用户应当使用tf.keras而不是使用pip安装的Keras。
- tf.keras是在TensorFlow中以TensorFlow低阶API为基础实现的这种高阶接口,它是Tensorflow的一个子模块。
1.4 Tensorflow接口层次结构
最底层为硬件层,TensorFlow支持CPU、GPU或TPU加入计算资源池;
第二层为C++实现的内核,kernel可以跨平台分布运行;
第三层为Python实现的操作符,提供了封装C++内核的低级API指令,主要包括各种张量操作算子、计算图、自动微分。如tf.Variable,tf.constant,tf.function, tf.GradientTape, tf.nn.softmax... 如果把模型比作一个房子,那么第三层API就是【模型之砖】;
第四层为Python实现的模型组件,对低级API进行了函数封装,主要包括各种模型层,损失函数,优化器,数据管道,特征列等等。 如tf.keras.layers, tf.keras.losses, tf.keras.metrics, tf.keras.optimizers,tf.data.DataSet, tf.feature_column... 如果把模型比作一个房子,那么第四层API就是【模型之墙】。
第五层为Python实现的模型成品,一般为按照OOP方式封装的高级API,主要为tf.keras.models提供的模型的类接口。 如果把模型比作一个房子,那么第五层API就是模型本身,即【模型之屋】。
2. Tensorflow基本概念
TensorFlow,顾名思义就是流动着的Tensor。首先,我们要搞清楚什么是tensor,它在哪里以何种方式流动?用最简单的话来说,Tensor实际上就是一个多维数组(multidimensional array),是TF的主要数据结构。它们在一个或多个由节点(nodes)和边(edges)组成的图(graphs)中流动。边代表的是tensors,节点代表的是对tensors的操作(operations)。tensors在图中从一个节点流向另一个节点,每次经过一个节点都会接受一次操作。
2.1 Tensor
1. tensor基本概念
tensor有两个属性:
- dtype: Tensor 存储的数据的类型,可以为tf.float32、tf.int32、tf.string…
- shape: Tensor 存储的多维数组中每个维度的数组中元素的个数
此外还有一个rank,是shape的另一种表达方式,即维数
# 0阶张量,即标量,shape=0
t0 = tf.constant(3, dtype=tf.int32)
# 1阶张量,即矢量,shape=[2]
t1 = tf.constant([3., 4.1, -.2], dtype=tf.float32)
# 2阶张量,二维数组,shape=[2, 3]
t2 = tf.constant([['A', 'B'], ['C', 'D']], dtype=tf.string)
# 3阶张量,三维数组,shape=[2, 1, 3],数据默认类型为整形
t3 = tf.constant([[[1, 3]], [[5, 7]]])
print
打印tensor的属性定义,tf.print(tensor)
打印tensor的值,tf.print
如果数量太多默认会省略中间部分,如果不想省略可以增加参数summarize=-1
。
> print(t0)
tf.Tensor(3, shape=(), dtype=int32)
> print(t1)
tf.Tensor([ 3. 4.1 -0.2], shape=(3,), dtype=float32)
> print(t2)
tf.Tensor(
[[b'A' b'B']
[b'C' b'D']], shape=(2, 2), dtype=string)
> print(t3)
tf.Tensor(
[[[1 3]]
[[5 7]]], shape=(2, 1, 2), dtype=int32)
> print(t2.get_shape())
(2, 2)
> tf.print(t0)
3
> tf.print(t1)
[3 4.1 -0.2]
> tf.print(t2)
[["A" "B"]
["C" "D"]]
> tf.print(t3)
[[[1 3]]
[[5 7]]]
> tf.print(tf.range(1, 11, delta=1, dtype=tf.float32))
[1 2 3 ... 8 9 10]
> tf.print(tf.range(1, 11, delta=1, dtype=tf.float32), summarize=-1)
[1 2 3 4 5 6 7 8 9 10]
2. tensor的创建
创建tensor的方法可以分为2种
- 使用tf自带的函数直接创建,例如:
# create a zero filled tensor
t0 = tf.zeros([1, 2])
# [[0 0]]
# create a one filled tensor
t1 = tf.ones([1, 2])
# [[1 1]]
# create a constant filled tensor
t2 = tf.fill([1, 2], 42)
# [[42 42]]
# create a tensor out of an existing constant
t3 = tf.constant([1, 2, 3])
# [1 2 3]
# generate random numbers from a uniform distribution
t4 = tf.random.uniform([1, 2], minval=0, maxval=1)
# [[0.809502 0.184664249]]
# generate random numbers from a normal distribution
t5 = tf.random.normal([1, 2], mean=0.0, stddev=1.0)
# [[-0.717701077 -1.32147753]]
- 将Python对象(Numpy arrays, Python lists,Python scalars)转成tensor,例如:
import numpy as np
x_data = np.array([[1., 2., 3.], [3., 2., 6.]])
tf.convert_to_tensor(x_data, dtype=tf.float32)
# [[1 2 3]
# [3 2 6]]
2.2 operation
operation代表对tensor的操作,如下图所示:节点a接收了一个1-D tensor,该tensor从节点a流出后,分别流向了节点b和c,节点b执行的是prod操作(5*3),节点c执行的是sum操作(5+3)。当tensor从节点b流出时变成了15,从节点c流出时变成了8。此时,2个tensor又同时流入节点d,接受的是add操作(15+8),最后从节点d流出的tensor就是23。
相应的代码为:
import tensorflow as tf
a = tf.constant([5, 3], name='input_a')
b = tf.reduce_prod(a, name='prod_b')
c = tf.reduce_sum(a, name='sum_c')
d = tf.add(b, c, name='add_d')
3. Tensorflow入门示例
正如大部分的编程语言入门从hello world!
开始, tensorflow的入门以最简单线性回归(Linear Regression)开始。
线性回归就是在坐标系中有很多点, 线性回归的目的就是找到一条线使得这些点都在这条直线上或者直线的周围。我们要对其进行线性回归,则建立线性数学模型:
y=W∗x+b
那么如何评估我们线性回归出来的直线方程呢?我们需要建立一个损失模型(loss model)来评估模型的合理性:
loss=Σ(yn−y′n)2
3.1 构造数据集
利用函数Y = 2*X + 3
并增加正态随机构造数据集,相关代码如下所示
import tensorflow as tf
w = 2
b = 3
X = tf.range(1, 11, delta=1, dtype=tf.float32)
Y = X * w + b + tf.random.normal([10,], mean = 0.0, stddev= 1.0)
构造好的数据集如下所示
X = [1 2 3 4 5 6 7 8 9 10]
Y = [5.69277859 5.68863153 7.6934948 10.7271547 13.6772203 14.6714172 15.488286 20.2158928 21.2844448 24.5952759]
如下图所示,将Y = 2*X + 3
(绿线部分)和X、Y(蓝点)画在同一图像上,画图代码如下所示:
from matplotlib import pyplot as plt
plt.plot(X, X * w + b, color='green')
plt.scatter(X, Y, color='blue')
plt.xlabel('x')
plt.ylabel('y')
plt.title("Data")
plt.ylim(ymin=0)
plt.show()
将数据集拆分为训练数据集和评估数据集
# 训练数据
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# 评估数据
x_eval = [2.0, 4.0, 6.0, 8.0, 10.0]
y_eval = [5.68863153, 10.7271547, 14.6714172, 20.2158928, 24.5952759]
3.2 训练
训练代码如下所示
import tensorflow as tf
# create data
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# create tensorflow structure
W = tf.Variable(tf.random.uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
# alias: tf.losses.mse
loss = lambda: tf.keras.losses.MSE(y_train, W * x_train + b)
# alias: tf.optimizers.SGDS
optimizer = tf.keras.optimizers.SGD(learning_rate=0.004)
num_epoch = 6001
for step in range(num_epoch):
optimizer.minimize(loss, var_list=[W, b])
if step % 100 == 0:
print( "{} step, weights = {}, biases = {}, loss = {}".format(step, W.read_value(), b.read_value(), loss()) )
迭代过程如下所示
0 step, weights = [0.6796642], biases = [0.09973339], loss = 99.73298645019531
100 step, weights = [2.285599], biases = [0.8164981], loss = 2.1074492931365967
200 step, weights = [2.2274106], biases = [1.1977823], loss = 1.7347787618637085
300 step, weights = [2.1792789], biases = [1.5131716], loss = 1.4797898530960083
400 step, weights = [2.139465], biases = [1.7740546], loss = 1.3053206205368042
500 step, weights = [2.106532], biases = [1.989852], loss = 1.1859443187713623
600 step, weights = [2.0792909], biases = [2.1683528], loss = 1.104265809059143
700 step, weights = [2.0567577], biases = [2.3160055], loss = 1.0483779907226562
800 step, weights = [2.0381184], biases = [2.4381416], loss = 1.010138750076294
900 step, weights = [2.0227005], biases = [2.5391688], loss = 0.9839746356010437
1000 step, weights = [2.009947], biases = [2.622736], loss = 0.9660736322402954
1100 step, weights = [1.999398], biases = [2.6918607], loss = 0.9538244009017944
1200 step, weights = [1.9906719], biases = [2.74904], loss = 0.9454426765441895
1300 step, weights = [1.9834539], biases = [2.7963367], loss = 0.9397082328796387
……
5300 step, weights = [1.9489253], biases = [3.02259], loss = 0.9272836446762085
5400 step, weights = [1.9489214], biases = [3.0226138], loss = 0.9272834658622742
5500 step, weights = [1.948918], biases = [3.0226376], loss = 0.927282989025116
5600 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5700 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5800 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5900 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
6000 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
可以看到迭代到5600步时,loss已经收敛了, 最终训练出的结果是:W = 1.9489162
, b = 3.0226493
,与构造的数据用的 W = 2
,b = 3
已经很接近了。将评估训练集带入模型,发现最终的损失也很小,说明训练出的模型还是有很好的泛化性
import tensorflow as tf
x_eval = [2.0, 4.0, 6.0, 8.0, 10.0]
y_eval = [5.68863153, 10.7271547, 14.6714172, 20.2158928, 24.5952759]
W = tf.constant(1.9489162, dtype=tf.float32)
b = tf.constant(3.0226493, dtype=tf.float32)
tf.print(tf.keras.losses.MSE(y_eval, W * x_eval + b))
# 1.6869427
3.3 TensorBoad
TensorBoad是Google为TensorFlow开发了一款可视化工具,使用流程如下
- 建立文件夹存放 TensorBoard 的记录文件;
- 实例化记录器;
# 日志目录
logdir = "/tmp/tensorflow"
# create the file writer object
writer = tf.summary.create_file_writer(logdir)
- 将参数(一般是标量)记录到指定的记录器中;
with writer.as_default():
tf.summary.scalar('training loss', loss(), step=step+1)
- 访问 TensorBoard 的可视界面。
tensorboard --logdir /tmp/tensorflow
根据提示访问http://localhost:6006/,就可以看到如下界面,可以看到前1000步之前loss急剧下降,然后loss下降趋于平缓。
TensorBoad完整代码请参考附录1
参考文献:
附录
1. LinearRegression.py
import tensorflow as tf
# 日志目录
logdir = "/tmp/tensorflow"
# create the file writer object
writer = tf.summary.create_file_writer(logdir)
# create data
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# create tensorflow structure
W = tf.Variable(tf.random.uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
# alias: tf.losses.mse
loss = lambda: tf.keras.losses.MSE(y_train, W * x_train + b)
# alias: tf.optimizers.SGDS
optimizer = tf.keras.optimizers.SGD(learning_rate=0.004)
num_epoch = 6001
for step in range(num_epoch):
optimizer.minimize(loss, var_list=[W, b])
# write the loss value
with writer.as_default():
tf.summary.scalar('training loss', loss(), step=step+1)
if step % 100 == 0:
print( "{} step, weights = {}, biases = {}, loss = {}".format(step, W.read_value(), b.read_value(), loss()))