先说句题外话, 这段时间一直研究爬虫技术,主要目的是为将来爬取训练数据做准备,同时学习python编程。这一研究才发现,python的开发资源实在是太丰富了,所有你能想到的应用都有对应的开发库提供支持,简直是无所不能。举一个简单的例子,以前认为比较难办的验证码输入,python竟然提供了多个库供我们选择以实现自动识别验证码、并自动输入,这对于爬取需输入验证码的网站非常有用。另外,对人脸识别、自然语音处理、数据挖掘等AI领域的支持也相当强大。目前主流的算法像线性回归、支持向量机、聚类、决策树、kNN等python实现比较容易,要是用C/C++可就麻烦多了,因为这些算法要用到大量的矩阵运算,python的数学库NumPy对此支持相当好。同时,像tensorflow这样的开源的深度学习库其提供的开发例程也是以python为主。所以,经过这段时间的学习,我相信python在将来一定会成为大部分程序员必须掌握的一门语言之一。当然,从现在看python的热度也已经在GitHub、StackOverflow等网站上表现出强劲的增长势头。
       本周4结束了爬虫技术的学习,自己开发了一个自动登录京东、自动下订单购买的爬虫程序作为检验自己学习成果的结业项目,感觉很不错,以后再抢东西,肯定比别人手快了,不用再熬夜抢了。现在转过头开始看tensorflow的文档,小有收获,理解了tensorflow的基本开发架构。有一点需要特别注意的是,使用tensorflow之前,必须确定自己已经安装了NumPy库,大量的算法实现都围绕这个库完成,核心就是矩阵运算,也就是线性代数要学习的内容。
        tensorflow的基本开发架构参见如下开发例程:

#-*- coding: utf-8 -*-

import tensorflow as tf
import numpy as np

#在tensorflow中,所有变量必须用tf.Variable定义,0为初始值,name为该变量名称(可选)
state = tf.Variable(0, name = 'counter')
#print(state.name)
one = tf.constant(1)

#与new_vale = state + 1等价,tf中必须用专有函数操作,且下述语句仅是定义该操作,并不执行
new_value = tf.add(state, one)
#指定将new_value的值更新到state,这里依然是事先指定这个操作给变量update,并不会执行
update = tf.assign(state, new_value)

#指定tf启动session时要进行所有变量初始化操作,这里依然只是指定初始化操作给init,并不实际执行
#换句话说,只要上面存在tf.Variable()的调用,就必须调用init操作
init = tf.global_variables_initializer()

#启动tf会话并执行
with tf.Session() as sess:
    sess.run(init) #所有tf.Vaiable()定义的变量被真正初始化
    print('-' * 8, sess.run(state))
    #测试变量更新操作,执行多次
    for _ in range(10):
        sess.run(update)        #变量更新操作
        print(sess.run(state))  #输出state值,注意tf中要查看某个变量的值同样需要用sess.run()函数

从语句state = tf.Variable(...)开始到 init = tf.global_variables_initializer()结束,这属于tensorflow基本输入结构的定义区,所有要训练的输入数据或要使用的训练算法都在这里定义好,然后通过tf.Session()启动一个会话并通过run()函数开始执行上面的基本输入结构提前定义好的操作。查看执行结果也是通过run函数,像上面这个例子我们通过语句sess.run(state)来查看其操作结果,如下:

Tensorflow基本开发架构

通过这个例子可以看出,tensorflow其基本开发架构不难,难点还是在于对核心实现算法的理解。像下面这个例子,利用逻辑回归算法我们求得了一组测试数据的回归系数和偏置量:

#-*- coding: utf-8 -*-

import tensorflow as tf
import numpy as np

#使用NumPy生成测试数据,共100个点
x_data = np.float32(np.random.rand(2, 100))
y_data = np.dot([0.100, 0.700], x_data) + 0.300 #其实就是线性方程: y = 0.1 * x2 + 0.7 * x2 + 0.3
                                                #最后预测数据看是不是0.1、0.2(W: weights, 权重)和0.3(b: baises, 偏置系数)
print(x_data)
print(y_data)

#构造一个线性模型
b = tf.Variable(tf.zeros(1))                          #偏置系数初始为0
W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0)) #产生一个1行2列的矩阵,数值从-1到1之间,为权重的初始值
y = tf.matmul(W, x_data) + b                          #生成训练数据

#最小化方差
loss = tf.reduce_mean(tf.square(y - y_data))          #与真实数据(y_data)之间的方差
optimizer = tf.train.GradientDescentOptimizer(0.5)    #梯度下降步长
train = optimizer.minimize(loss)                      #使用梯度下降算法开始优化数据使其变到最小

#初始化变量,tf中只要定义变量就必须初始化变量
init = tf.global_variables_initializer()

#启动图
sess = tf.Session()
sess.run(init)      #执行变量初始化

#拟合平面,201次循环执行sess.run(train),使方差最小
for step in range(0, 201):
    sess.run(train)
    if step % 20 == 0:
        print(step, sess.run(W), sess.run(b))

上面这个例子的执行流程很清晰,比较容易理解,主要难点在于这段代码干了啥事?这段代码首先生成了一段训练数据x_data和y_data,这段数据为两个矩阵x和y,其关系可以用二元一次方程式表示:
y = 0.1 * X1  + 0.7 * X2 + 0.3

其中语句y_data = np.dot(...) 即是对这个方程式的代码实现。np.dot()函数实现了矩阵的标准乘运算。其中,x_data为一个二维矩阵:2行100列,然后我们用一个1行2列的矩阵[0.100, 0.700]与之相乘,得到一个1行100列的新矩阵,然后新矩阵每个向量加0.3得到y_data。这个过程完全利用numpy库完成了上述矩阵运算,实现了y_data与x_data之间由上述二元一次方程式表示的向量关系。
然后我们开始构建逻辑回归(Logistic回归)模型,首先将偏置系数初始值置为0(意思就是从0开始找,直至找到合适的偏置系数0.3),然后再利用随机数构建一个1行2列的矩阵,这个矩阵的作用与[0.100, 0.700]完全相同。在这里用随机数生成只是因为模型尚且不知道哪两个系数与x_data相乘能得到y_data:
y = tf.matmul(W, x_data) + b

所以程序要找的就是根据上式能够得到或者最接近y_data的W和b的最优值。因此,我们用随机数生成了回归系数矩阵W的向量值作为初始值,从这两个值开始计算查找直至找到0.1和0.7为止。逻辑回归模型的真正作用亦在于此,其能够根据一组给定的多维数值,找到它们之间的逻辑关系。在程序中其表现就是,从上述两个初始值开始迭代执行梯度下降算法:

#最小化方差
loss = tf.reduce_mean(tf.square(y - y_data))          #与真实数据(y_data)之间的方差
optimizer = tf.train.GradientDescentOptimizer(0.5)    #梯度下降步长
train = optimizer.minimize(loss)                      #使用梯度下降算法开始优化数据使其变到最小
使得loss,也就是与真实数据之间的方差最小(用方差的原因是去掉正、负号对计算结果的影响),从而得到最优的回归系数与偏置系数。

        最后的for循环即是启动tensorflow执行上述的梯度下降算法,在这里sess.run(train)共迭代执行201次,程序每隔20次输出当前的回归系数与偏置系数,这样可以让我们看到训练是否正在向预期的数值回归。下面是这个程序的输出结果:

Tensorflow基本开发架构

图中,从第0次开始,W和b逐渐接近[0.1 ,0.7]、0.3,到第200次的时候,W等于[0.10000169, 0.70000368] ,b等于0.29999697,与实际数值基本相同,算法验证成功。

Tensorflow基本开发架构

tensorflow的实际作用即在于此,我们不用考虑复杂的数学公式以及如何编码实现它,我们直接使用封装好的算法即可完成数据训练得到我们想要的模型,这一点非常重要。

那么接下来的问题是,上述算法实现到底能有啥实际用处呢? 引用机器学习领域最著名的波士顿房价问题来解释这个。在波士顿,有经验的房屋销售人员可以根据房屋地段、面积、户型给出估算的房屋售价,基本这个售价不会偏离实际售出价太多,但是对于刚入行的人来说这个就很难了。那么,我们怎样能够让所有销售人员能够比较准确的预测要出售房屋的售价呢?这就是上述算法要大展身手的地方了。假如我们已经有了最近几年的已经销售房屋的样本数据,包括地段、房屋面积、房屋户型以及实际售价。那么我们将上述数据输给上面那个程序,让它找出地段、面积、户型这三组数据与实际售价之间的逻辑关系,最终经过N次迭代执行,我们会得到三个W值,一个b值。这个过程我们称之为数据训练(也就是机器学习)。于是,有了这个模型,销售人员只需输入地段、面积、户型三个已知数值,就可以得到预测的房价。只要样本数据训练足够多(当然实际样本数据类型也应该不止这三种),那么预测房价就会逐渐趋近实际售价,模型的准确率就会越来越高。
当然房价预测只是上述算法的其中一个应用而已,更重要的是,这个算法不用任何改动,其就可以用到其它领域的数值预测中,而我们要做的只是把训练数据换一下而已,机器学习的优势在这方面体现的淋漓尽致。另外,监督学习中的各种分类问题上述算法亦用处多多。其实,这个算法在tensorflow中只是冰山一角,其丰富的算法库可以帮助我们解决诸如图像识别、数据挖掘、自然语言处理等诸多领域的实际应用问题。