• TensorFlow从1到2(一)续讲从锅炉工到AI专家


    引言

    原来引用过一个段子,这里还要再引用一次。是关于苹果的。大意是,苹果发布了新的开发语言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:UsersAdministrator>这样子。)

    如果希望使用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的支持下,对于同样的数据集和数学模型,变更学习算法会很容易。

    (待续...)

  • 相关阅读:
    虚拟机VirtualBox 共享挂载问题:mount: /mnt/xxx: wrong fs type, bad option, bad superblock on xxx
    git 设置和取消代理
    (转载)数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知
    关于golang中IO相关的Buffer类浅析
    (转)golang获取当前时间、时间戳和时间字符串及它们之间的相互转换
    FFmpeg常用命令
    go cmd nohup 的坑
    Nginx配置详解(转)
    记录一次go性能调试的过程
    github徽标引入
  • 原文地址:https://www.cnblogs.com/andrewwang/p/10664168.html
Copyright © 2020-2023  润新知