今年以来,人工智能成为一个时代热点,同时 TensorFlow 1.0 的发布后,我也想蹭蹭时代的热点,初步学习一下神经网络和机器学习,在这里把成果以初学者的方式记录下来。 学习一个新东西,不可避免会遇到很多坑,很多教程都是一个有经验,熟悉的人写的,那样其实并不是特别接地气,因为很多坑在作者写文章的时候都忘了,这篇文章也是记录一下我掉的坑,做一个备忘的作用。 阅读这一系列文章,你需要做好以下准备,或者有以下技能:
我也只是一个刚接触机器学习的初学者,所以你不用太过于担心这篇文章难度问题,我会尽量用高中生能够看懂的方式来叙述问题,希望能够帮助到大部分初学者。TensorFlow 介绍性的话就不多说了,直奔主题。
这篇文章是我正式使用 TensorFlow 第一天写的,在这之前,我阅读了一些关于机器学习的理论知识,在阅读完官网的 入门教程后,我发现 TensorFlow 最基础的应用可以用来拟合方程,即给出 N 个点(x,y),这些点符合一定规律,我们希望推导出其他符合这个规律的 x 对应的 y 值。
最简单的情况是线性的,我们希望能够使用一条直线拟合这几个点,得到方程式完整的内容,即假设 y = a *
x b,我们只需要求得 a 和 b 的值就好了,在初中数学中,只需要提供 2 组(x,y),即可通过消元法求得 a 和 b,这是一个很简单的数学问题。但是如果想用 TensorFlow 的比较通用的方式解决这个问题,就不能教机器这样解了,我们必须让机器通过不断尝试的方式,来获得 a 和 b 的值。
我们来实际操作一下,假设我们现在有一个方程 y = 5 * x 13,我们需要让机器通过一些(x,y) 来推导出 a = 5,b = 13。 首先要使用 TensorFlow,需要 import tensorflow 和数学库 numpy,TensorFlow 的数学计算是以 numpy 为基础的(这点我还不是特别确认,可能也可以有其他的数学库,不过他们之间关系紧密):
import tensorflow as tf
import numpy as np
我们要提供一系列已经存在的(x,y) 组合,这个叫做训练集,我们先用代码生成 5 组训练集,先随机生成 5 组 x 的值,命名为 t_x(t 为训练 train 的意思),其中使用 np.random.random([5]) 来生成 0 到 1 之间的随机数,将其乘以 10 可以获得 0 到 10 之间的随机数,最后使用 np.floor 函数对其进行取整,并且令数据类型为浮点数 np.float32 以便于计算:
t_x = np.floor(10 * np.random.random([5]),dtype=np.float32)
print t_x
结果如下,其中随机数每次执行会不一样:
[ 4. 2. 3. 2. 9.]
然后根据公式求得 t_y 的值:
t_y = t_x * 3.0 8.0
print t_y
结果为:
[ 20. 14. 17. 14. 35.]
这样我们就能够得到 5 组训练集了,可以开始使用 TensorFlow 求解了,关于 TensorFlow 的基本用法我现在也没办法讲的太清楚,可以简单参考下 官网基础教程,我以我的理解,重新简单讲解一下,TensorFlow 所有的执行流程会在一个 Session 中执行,可以把它暂时看做执行计算的一个载体。我们要在执行前,构造计算的规则,对于计算量的表示,目前只需要知道 2 种,一种是输入量,在 TensorFlow 中以占位符 placeholder 表示,另一种是变量,以 Variable 表示。我们的训练集是在计算过程中以输入表示,因此将其定义为占位符,它的类型为浮点类型 tf.float32:
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
而需要用于输出的 a 和 b 的值,我们将其定义为变量,初始化为浮点数 0.0,这个初始值并不是特别重要,因为 TensorFlow 在训练的过程中,会不断调整这两个值,这个后面会详细说明:
a = tf.Variable(0.0)
b = tf.Variable(0.0)
而在 TensorFlow 的 Session 内部,我们需要根据内部 x,a,b 的值,求得当前的 y 的值 curr_y,因此按照线性公式使用 x,a,b 定义 curr_y,这行语句执行时并不会进行真正的计算,只是仅仅描述他们的关系:
curr_y = x * a b
得到了当前的 y 值 curr_y,我们就要和我们提供的训练集中对应的 y 值进行比较,使得差异最小,这个差异在机器学习中称为损失函数(Loss function),当损失函数值最低时,就可以认为找到了一个比较好的值,当然实际应用中会有一些局部最小值,这个本例不涉及到,就不讨论了。一般来说,可以使用他们的方差来描述损失函数,因为 TensorFlow 能够很好的支持矩阵运算,而 curr_y 和 y 都可以看做是 1 行 5 列的一个矩阵,因此损失函数可以定义为矩阵各元素之差的平方之和:
loss = tf.reduce_sum(tf.square(curr_y - y)) #损失函数,实际输出数据和训练输出数据的方差
重温一下我们训练的目的,是通过不断调整变量 a 和 b 的值,来达到损失函数值最小的目的。而调整 a 和 b 的值的方法,我们采用一个叫做梯度下降(Gradient descent)的方法,简单来说,可以看做做 loss = fun(a,b) 形成了一个三维曲面,大概如下图所示(图来源文末参考资料):
梯度下降可以看做是一个小球,沿着曲面滚动,它距离地面的面积,就是 loss 函数的值,当它滚动到最低点时,也就找到了损失函数最小的位置(关于局部最小值和梯度下降更深入的内容可以参考原文)。 TensorFlow 中能够很方便地定义梯度下降的训练方法以及描述求损失函数最小值的目的:
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss) #训练的结果是使得损失函数最小
其中梯度下降的参数 0.001 是我调出来的,我目前并不清楚这个值如何更好的调整,太大可能找不到局部最小点,太小会导致训练过慢,也许这就是机器学习工程师有时候被戏称为"调参工程师"的原因? 至此,我们的 TensorFlow 描述部分已经完成了,可以开始进入执行流程了,首先,我们要创建一个 Session 用于执行对于变量,我们需要进行初始化操作,内部会对变量进行内存的分配操作,这个内存会在 Session 关闭时被释放:
sess = tf.Session()
sess.run(tf.global_variables_initializer())
然后我们可以对数据开始训练,第一个参数是训练的内容 train,第二个参数是指定变量 x 和 y 对应的实际值:
sess.run(train, {x:t_x, y:t_y})
一般,训练次数和准确度是有关系的,我通过"调参",确定训练 10000 次,在每次训练后,把当前的 a,b 和损失函数 loss 的值打印出来,需要注意的是,TensorFlow 中的值需要在 sess.run 中执行才能看到结果,如果需要得到多个值,可以将其放到一个数组 []中,因此打印 a,b 和 loss 的值需要放到 sess.run 中执行,同时也要将 t_x 和 t_y 传入:
for i in range(10000):
sess.run(train, {x:t_x, y:t_y})
print sess.run([a,b,loss],{x:t_x, y:t_y})
完整的 Python 代码如下:
#!/usr/bin/python
#coding=utf-8
import tensorflow as tf
import numpy as np
tf.logging.set_verbosity(tf.logging.ERROR) #日志级别设置成 ERROR,避免干扰
np.set_printoptions(threshold='nan') #打印内容不限制长度
t_x = np.floor(10 * np.random.random([5]),dtype=np.float32)
print t_x
t_y = t_x * 3.0 8.0
print t_y
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
a = tf.Variable(0.0)
b = tf.Variable(0.0)
curr_y = x * a b
loss = tf.reduce_sum(tf.square(curr_y - y)) #损失函数,实际输出数据和训练输出数据的方差
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss) #训练的结果是使得损失函数最小
sess = tf.Session() #创建 Session
sess.run(tf.global_variables_initializer()) #变量初始化
for i in range(10000):
sess.run(train, {x:t_x, y:t_y})
print sess.run([a,b,loss],{x:t_x, y:t_y})
exit(0)
好了,现在可以执行一下,如果环境没有问题的话,你应该能看到屏幕上不断出现的值,我稍微截取头尾一部分看看:
$ python ./test1.py
[ 4. 2. 3. 2. 9.]
[ 20. 14. 17. 14. 35.]
[1.0040001, 0.2, 1381.1299]
[1.7710881, 0.35784, 839.84033]
[2.3569665, 0.48341811, 522.96967]
[2.8042414, 0.58430529, 337.39871]
[3.1455021, 0.66629255, 228.64702]
[3.4056759, 0.73380953, 164.84021]
[3.6038294, 0.7902444, 127.32993]
[3.7545466, 0.83818877, 105.2057]
[3.8689826, 0.87962502, 92.084335]
[3.9556696, 0.91606945, 84.231171]
[4.0211344, 0.94868195, 79.461243]
[4.0703683, 0.97834975, 76.496269]
[4.1071901, 1.0057515, 74.588219]
[4.1345205, 1.0314064, 73.299591]
...
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
[3.0000157, 7.9999132, 1.0950316e-08]
在刚开始执行的时候,代码打印出训练集 t_x 和 t_y 的值,然后开始进行训练,a 和 b 的值快速增长,损失函数也在不断减少,最后 a 的值停留在 3.0000157,b 的值停留在 7.9999132,损失函数则为 1.0950316e-08,可见与结果 a=3,b=8 已经非常接近了,如果要更加接近结果,可以尝试降低梯度下降学习速率参数。这样就达到了求 a 和 b 的值的目的。
上面的例子如果能完成,结合官网的资料和其他博主的资料,我相信你已经算入了个门了,后面能不能通过修改上面的例子进行解决更加复杂的问题呢?再看看下一个问题,如果有一个值,它受到 N 个参数的影响,但是每个参数的权重我们并不清楚,我们希望能用刚刚学到的 TensorFlow 来解决这个问题。 首先建立一个模型,表示 N 组数据,具体点,先实现 5 个变量的求解,生成 10 个数据集,我们可以很容易联想到使用大小为 [10,5]的矩阵表示 t_x,使用大小为 [5,1]的矩阵表示参数权重 t_w,使用大小为 [10,1]的矩阵表示结果 t_y,即 t_y = t_x *
t_w。 当然,为了更加通用,变量的数量和数据集的数量可以使用常量来表示,矩阵的向量乘法在 numpy 库中使用 dot 函数实现:
test_count = 10 #数据集数量
param_count = 5 #变量数
t_x = np.floor(1000 * np.random.random([test_count,param_count]),dtype=np.float32)
#要求的值
t_w = np.floor(1000 * np.random.random([param_count,1]),dtype=np.float32)
#根据公式 t_y = t_x * t_w 算出值 t_y
t_y = t_x.dot(t_w)
print t_x
print t_w
print t_y
与上面的例子一样,我们以 TensorFlow 占位符形式定义输入训练集 x 和 y,矩阵大小可以使用 shape 参数来定义:
#x 是输入量,对应 t_x,用于训练输入,在训练过程中,由外部提供,因此是 placeholder 类型
x = tf.placeholder(tf.float32,shape=[test_count,param_count])
y = tf.placeholder(tf.float32,shape=[test_count,1])
以 TensorFlow 变量形式定义结果 w:
#w 是要求的各个参数的权重,是目标输出,对应 t_w
w = tf.Variable(np.zeros(param_count,dtype=np.float32).reshape((param_count,1)), tf.float32)
定义 TensorFlow 计算结果 y、损失函数 loss 和训练方法:
curr_y = tf.matmul(x, w) #实际输出数据
loss = tf.reduce_sum(tf.square(t_y - curr_y)) #损失函数,实际输出数据和训练输出数据的方差之和
optimizer = tf.train.GradientDescentOptimizer(0.0000001)
train = optimizer.minimize(loss) #训练的结果是使得损失函数最小
针对训练次数的问题,我们可以优化一下之前的方式,设定当 loss 函数值低于一定值或者不再变化的时候停止,因为 loss 函数需要在 Session 中使用,它需要使用 TensorFlow 的常量表示:
LOSS_MIN_VALUE = tf.constant(1e-5) #达到此精度的时候结束训练
好了,模型已经建立完毕,开始训练,我们使用变量 run_count 来记录训练的次数,以 last_loss 记录上一次训练的损失函数的值,初始值为 0。
sess = tf.Session()
sess.run(tf.global_variables_initializer())
run_count = 0
last_loss = 0
训练主循环,将当前的 loss 函数值保存在 curr_loss 中,与上一次相比,如果相同,则退出训练,另外如果 loss 函数低于设定的精度 LOSS_MIN_VALUE,也会退出训练:
while True:
run_count = 1
sess.run(train, {x:t_x, y:t_y})
curr_loss,is_ok = sess.run([loss,loss < LOSS_MIN_VALUE],{x:t_x, y:t_y})
print "运行%d 次,loss=%s" % (run_count,curr_loss)
if last_loss == curr_loss:
break
last_loss = curr_loss
if is_ok:
break
最后打印结果,由于我们知道 t_w 的值是整数,因此将得到的结果四舍五入的值 fix_w 也打印出来,再看看 fix_w 与 t_w 的差距 fix_w_loss 是多少:
curr_W, curr_loss = sess.run([w, loss], {x:t_x,y:t_y})
print("t_w: %snw: %snfix_w: %snloss: %snfix_w_loss:%s" % (t_w, curr_W, np.round(curr_W), curr_loss, np.sum(np.square(t_w - np.round(curr_W)))))
exit(0)
完整代码如下:
#!/usr/bin/python
#coding=utf-8
import tensorflow as tf
import numpy as np
tf.logging.set_verbosity(tf.logging.ERROR) #日志级别设置成 ERROR,避免干扰
np.set_printoptions(threshold='nan') #打印内容不限制长度
test_count = 10 #数据集数量
param_count = 5 #变量数
t_x = np.floor(1000 * np.random.random([test_count,param_count]),dtype=np.float32)
#要求的值
t_w = np.floor(1000 * np.random.random([param_count,1]),dtype=np.float32)
#根据公式 t_y = t_x * t_w 算出值 t_y
t_y = t_x.dot(t_w)
print t_x
print t_w
print t_y
#x 是输入量,对应 t_x,用于训练输入,在训练过程中,由外部提供,因此是 placeholder 类型
x = tf.placeholder(tf.float32,shape=[test_count,param_count])
y = tf.placeholder(tf.float32,shape=[test_count,1])
#w 是要求的各个参数的权重,是目标输出,对应 t_w
w = tf.Variable(np.zeros(param_count,dtype=np.float32).reshape((param_count,1)), tf.float32)
curr_y = tf.matmul(x, w) #实际输出数据
loss = tf.reduce_sum(tf.square(t_y - curr_y)) #损失函数,实际输出数据和训练输出数据的方差之和
optimizer = tf.train.GradientDescentOptimizer(0.00000001)
train = optimizer.minimize(loss) #训练的结果是使得损失函数最小
LOSS_MIN_VALUE = tf.constant(1e-5) #达到此精度的时候结束训练
sess = tf.Session()
sess.run(tf.global_variables_initializer())
run_count = 0
last_loss = 0
while True:
run_count = 1
sess.run(train, {x:t_x, y:t_y})
curr_loss,is_ok = sess.run([loss,loss < LOSS_MIN_VALUE],{x:t_x, y:t_y})
print "运行%d 次,loss=%s" % (run_count,curr_loss)
if last_loss == curr_loss:
break
last_loss = curr_loss
if is_ok:
break
curr_W, curr_loss = sess.run([w, loss], {x:t_x,y:t_y})
print("t_w: %snw: %snfix_w: %snloss: %snfix_w_loss:%s" % (t_w, curr_W, np.round(curr_W), curr_loss, np.sum(np.square(t_w - np.round(curr_W)))))
exit(0)
运行一下,仍然把头尾的部分记录下来,中间部分太多就省略掉:
$ python ./test1.py
[[ 842. 453. 586. 919. 91.]
[ 867. 600. 156. 993. 558.]
[ 795. 809. 146. 793. 118.]
[ 202. 184. 125. 132. 450.]
[ 214. 36. 436. 118. 290.]
[ 207. 916. 757. 647. 670.]
[ 679. 176. 872. 522. 927.]
[ 552. 602. 981. 563. 937.]
[ 31. 519. 718. 226. 178.]
[ 571. 464. 289. 141. 769.]]
[[ 42.]
[ 465.]
[ 890.]
[ 84.]
[ 488.]]
[[ 889153.]
[ 809970.]
[ 663711.]
[ 435982.]
[ 565200.]
[ 1489672.]
[ 1382662.]
[ 1680752.]
[ 987505.]
[ 884068.]]
运行 1 次,loss=3.30516e 13
运行 2 次,loss=1.02875e 14
运行 3 次,loss=3.22531e 14
运行 4 次,loss=1.01237e 15
运行 5 次,loss=3.17825e 15
运行 6 次,loss=9.97822e 15
运行 7 次,loss=3.13272e 16
运行 8 次,loss=9.83534e 16
运行 9 次,loss=3.08786e 17
运行 10 次,loss=9.69452e 17
运行 11 次,loss=3.04365e 18
运行 12 次,loss=9.55571e 18
运行 13 次,loss=3.00007e 19
运行 14 次,loss=9.41889e 19
运行 15 次,loss=2.95712e 20
...
运行 2821 次,loss=6839.32
运行 2822 次,loss=6780.68
运行 2823 次,loss=6767.86
运行 2824 次,loss=6735.09
运行 2825 次,loss=6709.06
运行 2826 次,loss=6662.66
运行 2827 次,loss=6637.81
运行 2828 次,loss=6637.81
t_w: [[ 117.]
[ 642.]
[ 662.]
[ 318.]
[ 771.]]
w: [[ 117.0872879 ]
[ 641.80706787]
[ 662.05078125]
[ 318.10388184]
[ 771.01501465]]
fix_w: [[ 117.]
[ 642.]
[ 662.]
[ 318.]
[ 771.]]
loss: 6637.81
fix_loss:0.0
可见,这次在执行了 2828 次之后,loss 函数从 3.30516e 13 降低到 6637.81 后不再变动,看起来有点大,但是实际上我们的 y 值也是非常大的,最后求得的结果与实际值有大约不到千分之一的差距,要缩小这个差距,可以通过减少梯度下降学习速率,同时增加训练次数来解决,而 fix_w 的值已经等于 t_w 的值了。 目前这个代码也可以修改一下训练集的数量以及变量的数量,然后通过调梯度下降学习速率参数来进行训练,如果学习速率过大,可能就会得到 loss 函数为 inf 值,这样就无法得到结果了,具体原因我还得继续研究一下。
能够解决这样的问题,基本上就能够初步使用机器学习的思维来解决问题了,比如预测股票下个交易日涨跌幅,参数可以是昨日开盘价,昨日收盘价,昨日涨跌幅,昨日成交量等。不过先别激动,股票的模型也不是简单的线性模型,如果想建立股票预测模型,还需要使用更加复杂的方法才行,有兴趣的读者可以继续深入研究,比如使用多元多次方程来进行数据的拟合,只要建立起这个思想,这篇文章的目的就达到了。
相关推荐
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。