原来引用过一个段子, 这里还要再引用一次. 是关于苹果的. 大意是, 苹果发布了新的开发语言 Swift, 有非常多优秀的特征, 于是很多时髦的程序员入坑学习. 不料, 经过一段头脑体操一般的勤学苦练, 发现使用 Swift 做开发, 不仅要学习 Swift, 还要学习 Swift2,Swift3,Swift4...
后来我发现, 这个段子很有普遍性, 并非仅仅苹果如此, 今天的 TensorFlow 2.0 也有点这样的趋势. 以至于我不得不专门写一个课程的续集, 来面对使用新版本软件开始机器学习的读者.
事实上大多具有革命性的公司都是这样, 一方面带来令人兴奋的新特征, 另一方面则是高企不落的学习成本.
《从锅炉工到 AI 专家》一文中, 已经对机器学习的基本概念做了很详细的介绍. 所以在这里我们就省掉闲言絮语, 直接从 TensorFlow2.0 讲起.
当然即便没有看过这个系列, 假设你对 TensorFlow 1.x 很熟悉, 也可以直接通过阅读本文, 了解从 TensorFlow 1.x 迁移至 2.x 的知识.
如果你不了解机器学习的概念, 试图通过直接学习 TensorFlow 2.0 开始 AI 开发, 那可能比较困难. TensorFlow 只是工具. 没有技能, 只凭工具, 你恐怕无法踏上旅程.
安装
截至本文写作的时候, TensorFlow 2.0 尚未正式的发布. pip 仓库中仍然是 1.13 稳定版. 所以如果想开始 TensorFlow 2.0 的学习, 需要指定版本号来安装. 此外由于 Python2 系列将于 2020 年元月停止官方维护, 本文的示例使用 Python3 的代码来演示:
- $ pip3 install tensorflow==2.0.0-alpha0
- (注: 上面 $ 是 Mac/Linux 的提示符, 假如用 Windows, 你看到的提示符应当类似 C:\Users\Administrator > 这样子.)
如果希望使用 GPU 计算, 安装的预先准备会麻烦一些, 请参考这篇文档: https://www.tensorflow.org/install/gpu . 主要是安装 CUDA/cuDNN 等计算平台的工具包. 其中 CUDA 可以使用安装程序直接安装. cuDNN 是压缩包, 如果不打算自己编译 TensorFlow 的话, 放置到 CUDA 相同目录会比较省事.
这里提醒一下, 除非自己编译 TensorFlow, 否则一定使用 CUDA 10.0 的版本, 低了, 高了都不成, 因为官方的 2.0.0-alpha0 使用了 CUDA 10.0 的版本编译.
此外 TensorFlow 的安装请使用如下命令:
$ pip3 install tensorflow-gpu==2.0.0-alpha0
安装完成后, 可以在 Python 的交互模式, 来确认 TensorFlow 正常工作:
- $ python3
- Python 3.7.2 (default, Feb 13 2019, 13:59:29)
- [Clang 10.0.0 (clang-1000.11.45.5)] on darwin
- Type "help", "copyright", "credits" or "license" for more information.
- >>> import tensorflow as tf
- >>> tf.__version__
- '2.0.0-alpha0'
- >>>
本文中还会用到几个第三方的 python 扩展库, 也是在机器学习中非常常用的, 建议一起安装:
$ pip3 install numpy matplotlib pillow pandas seaborn sklearn
第一个例子: 房价预测
本示例中的源码来自于《从锅炉工到 AI 专家》系列 2, 使用了最简单的线性函数来做房价预测. 原始 TensorFlow 1.x/ Python 2.x 代码如下:
- #!/usr/bin/env python
- # -*- coding=UTF-8 -*-
- # 本代码在 Mac 电脑, python2.7 环境测试通过
- # 第一行是 Mac/Linux 系统脚本程序的标志, 表示从环境参量中寻找 python 程序解释器来执行本脚本
- # 省去了每次在命令行使用 python <脚本名> 这样的执行方式
- # 第二行表示本脚本文本文件存盘使用的代码是 utf-8, 并且字符串使用的编码也是 utf-8,
- # 在本源码中, 这一点其实没有什么区别, 但如果需要中文输出的时候, 这一行就必须要加了.
- # 引入 TensorFlow 库
- import tensorflow as tf
- # 引入数值计算库
- import numpy as np
- # 使用 NumPy 生成假数据集 x, 代表房间的平米数, 这里的取值范围是 0-1 的浮点数,
- # 原因请看正文中的说明, 属于是 "规范化" 之后的数据
- # 生成的数据共 100 个, 式样是 100 行, 每行 1 个数据
- x = np.float32(np.random.rand(100,1))
- # 我们假设每平米 0.5 万元, 基础费用 0.7 万, 这个数值也是规范化之后的, 仅供示例
- # 最终运行的结果, 应当求出来 0.5/0.7 这两个值代表计算成功
- # 计算最终房价 y,x 和 y 一同当做我们的样本数据
- # np.dot 的意思就是向量 x * 0.5
- y = np.dot(x,0.5) + 0.7
- #--------------------------------- 数据集准备完成
- # 以下使用 TensorFlow 构建数学模型, 在这个过程中,
- # 直到调用. run 之前, 实际上都是构造模型, 而没有真正的运行.
- # 这跟上面的 numpy 库每一次都是真正执行是截然不同的区别
- # 请参考正文, 我们假定房价的公式为: y=a*x+b
- #tf.Variable 是在 TensorFlow 中定义一个变量的意思
- # 我们这里简单起见, 人为给 a/b 两个初始值, 都是 0.3, 注意这也是相当于规范化之后的数值
- b = tf.Variable(np.float32(0.3))
- a = tf.Variable(np.float32(0.3))
- # 这是定义主要的数学模型, 模型来自于上面的公式
- # 注意这里必须使用 tf 的公式, 这样的公式才是模型
- # 上面使用 np 的是直接计算, 而不是定义模型
- # TensorFlow 的函数名基本就是完整英文, 你应当能读懂
- y_value = tf.multiply(x,a) + b
- # 这里是代价函数, 同我们文中所讲的唯一区别是用平方来取代求绝对值,
- # 目标都是为了得到一个正数值, 功能完全相同,
- # 平方计算起来会更快更容易, 这种方式也称为 "方差"
- loss = tf.reduce_mean(tf.square(y_value - y))
- # TensorFlow 内置的梯度下降算法, 每步长 0.5
- optimizer = tf.train.GradientDescentOptimizer(0.5)
- # 代价函数值最小化的时候, 代表求得解
- train = optimizer.minimize(loss)
- # 初始化所有变量, 也就是上面定义的 a/b 两个变量
- init = tf.global_variables_initializer()
- # 启动图
- sess = tf.Session()
- # 真正的执行初始化变量, 还是老话, 上面只是定义模型, 并没有真正开始执行
- sess.run(init)
- # 重复梯度下降 200 次, 每隔 5 次打印一次结果
- for step in xrange(0, 200):
- sess.run(train)
- if step % 5 == 0:
- print step, sess.run(loss),sess.run(a), sess.run(b)
代码保留了原始的注释, 希望如果概念已经没有问题的话, 可以让你不用跑回原文去看详细讲解.
程序使用 numpy 生成了一组样本集, 样本集是使用线性函数生成的. 随后使用 TensorFlow 学习这些样本, 从而得到线性函数中未知的权重 (Weight) 和偏移 (Bias) 值.
原文中已经说了, 这个例子并没有什么实用价值, 只是为了从基础开始讲解 "机器学习" 的基本原理.
使用 2.0 中的 v1 兼容包来沿用 1.x 代码
TensorFlow 2.0 中提供了 tensorflow.compat.v1 代码包来兼容原有 1.x 的代码, 可以做到几乎不加修改的运行. 社区的 contrib 库因为涉及大量直接的 TensorFlow 引用代码或者自己写的 Python 扩展包, 所以无法使用这种模式. TensorFlow 2.0 中也已经移除了 contrib 库, 这让人很有点小遗憾的.
使用这种方式升级原有代码, 只需要把原有程序开始的 TensorFlow 引用:
import tensorflow as tf
替换为以下两行就可以正常的继续使用:
- import tensorflow.compat.v1 as tf
- tf.disable_v2_behavior()
其它代码无需修改. 个人觉得, 如果是稳定使用中, 并且没有重构意愿的代码, 这种方式算的上首选.
使用迁移工具来自动迁移 1.x 代码到 2.0
TensorFlow 2.0 中提供了命令行迁移工具, 来自动的把 1.x 的代码转换为 2.0 的代码. 工具使用方法如下(假设我们的程序文件名称为 first-tf.py):
tf_upgrade_v2 --infile first-tf.py --outfile first-tf-v2.py
迁移工具还可以对整个文件夹的程序做升级, 请参考工具自身的帮助文档.
使用迁移工具升级的代码, 实质上也是使用了 tensorflow.compat.v1 兼容包来提供在 TensorFlow 2.0 环境中执行 1.x 的代码. 这里贴出自动转换后的新代码供你对比参考:
- # 引入 TensorFlow 库
- import tensorflow as tf
- #import tensorflow.compat.v1 as tf
- tf.compat.v1.disable_v2_behavior()
- # 引入数值计算库
- import numpy as np
- # 使用 NumPy 生成假数据集 x, 代表房间的平米数, 这里的取值范围是 0-1 的浮点数,
- # 原因请看正文中的说明, 属于是 "规范化" 之后的数据
- # 生成的数据共 100 个, 式样是 100 行, 每行 1 个数据
- x = np.float32(np.random.rand(100,1))
- # 我们假设每平米 0.5 万元, 基础费用 0.7 万, 这个数值也是规范化之后的, 仅供示例
- # 最终运行的结果, 应当求出来 0.5/0.7 这两个值代表计算成功
- # 计算最终房价 y,x 和 y 一同当做我们的样本数据
- # np.dot 的意思就是向量 x * 0.5
- y = np.dot(x,0.5) + 0.7
- #--------------------------------- 数据集准备完成
- # 以下使用 TensorFlow 构建数学模型, 在这个过程中,
- # 直到调用. run 之前, 实际上都是构造模型, 而没有真正的运行.
- # 这跟上面的 numpy 库每一次都是真正执行是截然不同的区别
- # 请参考正文, 我们假定房价的公式为: y=a*x+b
- #tf.Variable 是在 TensorFlow 中定义一个变量的意思
- # 我们这里简单起见, 人为给 a/b 两个初始值, 都是 0.3, 注意这也是相当于规范化之后的数值
- b = tf.Variable(np.float32(0.3))
- a = tf.Variable(np.float32(0.3))
- # 这是定义主要的数学模型, 模型来自于上面的公式
- # 注意这里必须使用 tf 的公式, 这样的公式才是模型
- # 上面使用 np 的是直接计算, 而不是定义模型
- # TensorFlow 的函数名基本就是完整英文, 你应当能读懂
- y_value = tf.multiply(x,a) + b
- # 这里是代价函数, 同我们文中所讲的唯一区别是用平方来取代求绝对值,
- # 目标都是为了得到一个正数值, 功能完全相同,
- # 平方计算起来会更快更容易, 这种方式也称为 "方差"
- loss = tf.reduce_mean(input_tensor=tf.square(y_value - y))
- # TensorFlow 内置的梯度下降算法, 每步长 0.5
- optimizer = tf.compat.v1.train.GradientDescentOptimizer(0.5)
- # 代价函数值最小化的时候, 代表求得解
- train = optimizer.minimize(loss)
- # 初始化所有变量, 也就是上面定义的 a/b 两个变量
- init = tf.compat.v1.global_variables_initializer()
- # 启动图
- sess = tf.compat.v1.Session()
- # 真正的执行初始化变量, 还是老话, 上面只是定义模型, 并没有真正开始执行
- sess.run(init)
- # 重复梯度下降 200 次, 每隔 5 次打印一次结果
- for step in range(0, 200):
- sess.run(train)
- if step % 5 == 0:
- print(step, sess.run(loss),sess.run(a), sess.run(b))
转换之后, 代码中的注释部分会完美的保留, 喜欢用代码来代替文档的程序员可以放心. 所有 2.0 中变更了的类或者方法, 转换工具将使用 tensorflow.compat.v1 中的对应类或方法来替代, 比如:
- optimizer = tf.compat.v1.train.GradientDescentOptimizer(0.5)
- train = optimizer.minimize(loss)
- init = tf.compat.v1.global_variables_initializer()
所以从本质上, 这种方式跟第一种方法, 采用 tensorflow.compat.v1 包作为 tensorflow 替代包的方式是完全相同的.
编写原生的 TensorFlow 2.0 程序
推荐的演进方式, 当然还是学习 TensorFlow 2.0 的相关特征, 重构原有代码为新版本代码才是正路. 平心而论, 毕竟绝大多数系统的升级都是为了提供更多功能和降低使用门槛. TensorFlow 2.0 也是大幅的降低了使用门槛的. 大多数的工作比起 1.x 版本来, 都能使用更少的代码量来完成.
首先了解一下 TensorFlow 2.0 同 1.x 之间的重要区别:
在 API 层面的类, 方法有了较大的变化, 这个需要在使用中慢慢熟悉
取消了 Session 机制, 每一条命令直接执行, 而不需要等到 Session.run
因为取消了 Session 机制, 原有的数学模型定义, 改为使用 Python 函数编写. 原来的 feed_dict 和 tf.placeholder, 成为了函数的输入部分; 原来的 fetches, 则成为了函数的返回值.
使用 keras 的模型体系对原有的 TensorFlow API 进行高度的抽象, 使用更容易
使用 tf.keras.Model.fit 来替代原有的训练循环.
正常情况下, 最后一项 tf.keras.Model.fit 能够大大的降低训练循环的代码量. 但在本例中, 我们模拟了一个现实中并不适用的例子, keras 中并未对这种情形进行优化. 所以在本例中反而无法使用 tf.keras.Model.fit(实际上一定要使用也是可以的, 不过要自定义模型, 工作量更不划算). 因此本例中仍然要自己编写训练循环. 并且因为 2.0 中 API 的变化, 代码更复杂了. 不过相信我, 等到比较正式应用中, 使用神经网络, 卷积等常用算法, 代码就极大的简化了.
使用 TensorFlow 2.0 原生代码的程序代码如下:
- #!/usr/bin/env python3
- # 上面一行改为使用 python3 解释本代码
- # 引入 python 新版本的语言特征
- from __future__ import absolute_import, division, print_function
- # 引入 TensorFlow 库, 版本 2.0
- import tensorflow as tf
- # 引入数值计算库
- import numpy as np
- # 使用 NumPy 生成假数据集 x, 代表房间的平米数, 这里的取值范围是 0-1 的浮点数,
- # 原因请看正文中的说明, 属于是 "规范化" 之后的数据
- # 生成的数据共 100 个, 式样是 100 行, 每行 1 个数据
- x = np.float32(np.random.rand(100,1))
- # 我们假设每平米 0.5 万元, 基础费用 0.7 万, 这个数值也是规范化之后的, 仅供示例
- # 最终运行的结果, 应当求出来 0.5/0.7 这两个值代表计算成功
- # 计算最终房价 y,x 和 y 一同当做我们的样本数据
- y = np.dot(x,0.5) + 0.7
- #--------------------------------- 数据集准备完成
- # 请参考正文, 我们假定房价的公式为: y=a*x+b
- # 定义 tensorflow 变量, a 是权重, b 是偏移
- b = tf.Variable(np.float32(0.3))
- a = tf.Variable(np.float32(0.3))
- # 以上代码基本同 tensorflow1.x 版本一致
- # 以下有了区别
- # 使用 python 语言定义数学模型, 模型来自于上面的公式
- # 上面使用 np 的是直接计算得到训练样本, 而不是定义模型
- # 模型中并非必须使用 tensorflow 的计算函数来代替 python 的乘法运算
- @tf.function
- def model(x):
- return a*x+b
- # 定义代价函数, 也是 python 函数
- def loss(predicted_y, desired_y):
- return tf.reduce_sum(tf.square(predicted_y - desired_y))
- # TensorFlow 内置 Adam 算法, 每步长 0.1
- optimizer = tf.optimizers.Adam(0.1)
- # 还可以选用 TensorFlow 内置 SGD(随机最速下降)算法, 每步长 0.001
- # 不同算法要使用适当的步长, 步长过大会导致模型无法收敛
- #optimizer = tf.optimizers.SGD(0.001)
- # 重复梯度下降 200 次, 每隔 5 次打印一次结果
- for step in range(0, 200):
- with tf.GradientTape() as t:
- outputs = model(x) #进行一次计算
- current_loss = loss(outputs, y) #得到当前损失值
- grads = t.gradient(current_loss, [a, b]) #调整模型中的权重, 偏移值
- optimizer.apply_gradients(zip(grads,[a, b])) #调整之后的值代回到模型
- if step % 5 == 0: #每 5 次迭代显示一次结果
- print( "Step:%d loss:%%%2.5f weight:%2.7f bias:%2.7f" %
- (step,current_loss.numpy(), a.numpy(), b.numpy()))
程序在升级中所做的修改和特殊的处理, 都使用注释保留在了源码中. 我觉得这种方式比打散摘出来讲解的能更透彻.
最后看一下新版程序的执行结果:
- Step:0 loss:%25.78244 weight:0.4000000 bias:0.4000000
- Step:5 loss:%2.71975 weight:0.7611420 bias:0.7740188
- Step:10 loss:%3.09600 weight:0.6725605 bias:0.7224629
- Step:15 loss:%0.87834 weight:0.4931822 bias:0.5800986
- Step:20 loss:%1.24737 weight:0.4960071 bias:0.6186275
- Step:25 loss:%0.22444 weight:0.5730734 bias:0.7264798
- Step:30 loss:%0.47145 weight:0.5464076 bias:0.7252067
- Step:35 loss:%0.09156 weight:0.4736322 bias:0.6712209
- Step:40 loss:%0.14845 weight:0.4771673 bias:0.6866464
- Step:45 loss:%0.06199 weight:0.5101752 bias:0.7255269
- Step:50 loss:%0.03108 weight:0.4946054 bias:0.7112849
- Step:55 loss:%0.04115 weight:0.4770990 bias:0.6918764
- Step:60 loss:%0.00145 weight:0.4950625 bias:0.7060429
- Step:65 loss:%0.01781 weight:0.5029647 bias:0.7096580
- Step:70 loss:%0.00211 weight:0.4934593 bias:0.6963260
- Step:75 loss:%0.00298 weight:0.4983235 bias:0.6982682
- Step:80 loss:%0.00345 weight:0.5049748 bias:0.7031375
- Step:85 loss:%0.00004 weight:0.5001755 bias:0.6976562
- Step:90 loss:%0.00102 weight:0.5002422 bias:0.6978318
- Step:95 loss:%0.00065 weight:0.5029225 bias:0.7010939
- Step:100 loss:%0.00001 weight:0.5000774 bias:0.6990223
- Step:105 loss:%0.00021 weight:0.4996552 bias:0.6993059
- Step:110 loss:%0.00015 weight:0.5007215 bias:0.7008768
- Step:115 loss:%0.00000 weight:0.4993480 bias:0.6997767
- Step:120 loss:%0.00003 weight:0.4995552 bias:0.7000407
- Step:125 loss:%0.00004 weight:0.5001000 bias:0.7004969
- Step:130 loss:%0.00001 weight:0.4995880 bias:0.6998325
- Step:135 loss:%0.00000 weight:0.4999941 bias:0.7000810
- Step:140 loss:%0.00001 weight:0.5001197 bias:0.7000892
- Step:145 loss:%0.00000 weight:0.4999250 bias:0.6998329
- Step:150 loss:%0.00000 weight:0.5001498 bias:0.7000451
- Step:155 loss:%0.00000 weight:0.5000388 bias:0.6999565
- Step:160 loss:%0.00000 weight:0.4999948 bias:0.6999494
- Step:165 loss:%0.00000 weight:0.5000526 bias:0.7000424
- Step:170 loss:%0.00000 weight:0.4999576 bias:0.6999717
- Step:175 loss:%0.00000 weight:0.4999971 bias:0.7000214
- Step:180 loss:%0.00000 weight:0.4999900 bias:0.7000131
- Step:185 loss:%0.00000 weight:0.4999775 bias:0.6999928
- Step:190 loss:%0.00000 weight:0.5000094 bias:0.7000152
- Step:195 loss:%0.00000 weight:0.4999923 bias:0.6999906
模型通过学习后, 得到的结果是很接近我们的预设值的.
程序中还可以考虑使用随机快速下降算法(SGD), 你可以把当前的 Adam 算法使用注释符屏蔽上, 打开 SGD 算法的注释屏蔽来尝试一下. 对于本例中的数据集来讲, SGD 的下降步长需要的更小, 同样循环次数下, 求解的精度会低一些. 可以看出对于本例, Adam 算法显然是更有效率的. 而在 TensorFlow 的支持下, 对于同样的数据集和数学模型, 变更学习算法会很容易.
(待续...)
来源: https://www.cnblogs.com/andrewwang/p/10664168.html