个人原文:www.hijerry.cn/p/26959.htm…html
这是一次使用python进行机器学习的实验。python
一是总结本身学过的各类python包,二是了解一下使用python进行机器学习大概是什么样的,不过此次使用主要的目的仍是熟悉Tensorflow的使用。git
本次实验使用到的python包及其版本:程序员
机器环境是:macOS 10.13github
这里只介绍本次实验所用到的Tensorflow的关键知识、概念,想了解详情能够参考官方文档:https://tensorflow.google.cn/programmers_guide/low_level_intro 。web
TensorFlow 中的核心数据单位是张量。一个张量由一组造成阵列(任意维数)的原始值组成。张量的阶是它的维数,而它的形状是一个整数元组,指定了阵列每一个维度的长度。如下是张量值的一些示例:shell
3. # 0阶张量;也叫标量;形状是[]
[1., 2., 3.] # 1阶张量;也叫向量;形状是[3]
[[1., 2., 3.], [4., 5., 6.]] # 2阶张量;也叫矩阵;形状是[2, 3]
[[[1., 2., 3.]], [[7., 8., 9.]]] # 3阶张量;形状是[2, 1, 3]
复制代码
TensorFlow 使用 numpy 阵列来表示张量值。编程
您能够将 TensorFlow Core 程序看做由两个互相独立的部分组成:api
tf.Graph
)。tf.Session
)。计算图是排列成一个图的一系列 TensorFlow 指令。图由两种类型的对象组成。数组
重要提示:tf.Tensors
不具备值,它们只是计算图中元素的手柄。
咱们来构建一个简单的计算图。最基本的指令是一个常量。构建指令的 Python 函数将一个张量值做为输入值。生成的指令不须要输入值。它在运行时输出的是被传递给构造函数的值。咱们能够建立以下所示的两个浮点数常量 a
和 b
:
a = tf.constant(3.0, dtype=tf.float32)
b = tf.constant(4.0) # 默认dtype=tf.float32
total = a + b
print(a)
print(b)
print(total)
复制代码
打印语句会生成:
Tensor("Const:0", shape=(), dtype=float32)
Tensor("Const_1:0", shape=(), dtype=float32)
Tensor("add:0", shape=(), dtype=float32)
复制代码
请注意,打印张量并不会如您可能预期的那样输出值 3.0
、4.0
和 7.0
。上述语句只会构建计算图。这些 tf.Tensor
对象仅表明将要运行的指令的结果。
图中的每一个指令都拥有惟一的名称。这个名称不一样于使用 Python 分配给相应对象的名称。张量是根据生成它们的指令命名的,后面跟着输出索引,如上文的 "add:0"
所示。
要评估张量,您须要实例化一个 tf.Session
对象(一般被称为会话)。会话会封装 TensorFlow 运行时的状态,并运行 TensorFlow 指令。若是说 tf.Graph
像一个 .py
文件,那么 tf.Session
就像一个可执行的 python
。
下面的代码会建立一个 tf.Session
对象,而后调用其 run
方法来评估咱们在上文中建立的 total
张量:
sess = tf.Session()
print(sess.run(total))
复制代码
当您使用 Session.run
请求输出节点时,TensorFlow 会回溯整个图,并流经提供了所请求的输出节点对应的输入值的全部节点。所以此指令会打印预期的值 7.0:
7.0
复制代码
您能够将多个张量传递给 tf.Session.run
。run
方法以透明方式处理元组或字典的任何组合,以下例所示:
print(sess.run({'ab':(a, b), 'total':total}))
复制代码
它返回的结果拥有相同的布局结构:
{'total': 7.0, 'ab': (3.0, 4.0)}
复制代码
在调用 tf.Session.run
期间,任何 tf.Tensor
都只有单个值。例如,如下代码调用 tf.random_uniform
来生成一个 tf.Tensor
,后者会生成随机的三元素矢量(值位于 [0,1)
):
vec = tf.random_uniform(shape=(3,))
out1 = vec + 1
out2 = vec + 2
print(sess.run(vec))
print(sess.run(vec))
print(sess.run((out1, out2)))
复制代码
每次调用 run
时,结果都会显示不一样的随机值,但在单个 run
期间(out1
和 out2
接收到相同的随机输入值),结果显示的值是一致的:
[ 0.52917576 0.64076328 0.68353939]
[ 0.66192627 0.89126778 0.06254101]
(
array([ 1.88408756, 1.87149239, 1.84057522], dtype=float32),
array([ 2.88408756, 2.87149239, 2.84057522], dtype=float32)
)
复制代码
部分 TensorFlow 函数会返回 tf.Operations
,而不是 tf.Tensors
。对指令调用 run
的结果是 None
。您运行指令是为了产生反作用,而不是为了检索一个值。这方面的例子包括稍后将演示的[初始化](https://tensorflow.google.cn/programmers_guide/low_level_intro#Initializing Layers)和训练指令。
目前来说,这个图不是特别有趣,由于它老是生成一个常量结果。图能够参数化以便接受外部输入,也称为占位符。占位符表示承诺在稍后提供值,它就像函数参数。
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
z = x + y
复制代码
前面三行有点像函数。咱们定义了这个函数的两个输入参数(x
和 y
),而后对它们运行指令。咱们可使用 run 方法的 feed_dict
参数来为占位符提供真正的值,从而经过多个输入值来评估这个图:
print(sess.run(z, feed_dict={x: 3, y: 4.5}))
print(sess.run(z, feed_dict={x: [1, 3], y: [2, 4]}))
复制代码
上述操做的结果是输出如下内容:
7.5
[ 3. 7.]
复制代码
另请注意,feed_dict
参数可用于覆盖图中的任何张量。占位符和其余 tf.Tensors
的惟一不一样之处在于若是没有提供值给它们,那么占位符会显示错误。
它是一种可极大地简化机器学习编程的高阶 TensorFlow API。Estimator 会封装下列操做:
为了更好的理解它是个啥,请看后面的 Premade Estimator
和 Custom Estimator
章节。
直接看线性模型的目标是什么吧,如下图为例:
模型的目标是找到一条直线(图红色直线),让每个蓝色点到与直线的y距离最小。
下面来更数学化一点的介绍:
给定一个大小为n的点集 ,
线性模型的目标就是寻找一组 和
构成的直线
,
使得全部点的损失值 越小越好。
由于若是咱们找到了这么一组 和
,咱们就能够预测某一个
的
值。
这里我想多说几句,线性模型在实际应用中不必定能很好的预测 的值,这是由于实际的数据分布也许不是线性的,多是二次、三次、圆形甚至无规则,因此判断何时能用线性模型很重要。一个比较好的实践方法是,先用matplotlib画出数据分布,观察一下看看,就比如上面的蓝色点,一看就知道是线性分布,因此能够用线性模型来作。可是这种方法在大部分状况下也不能用,由于数据不少状况下有多个特征(多元),一元、二元都还好,能看出来,到了三元、四元数据,可能连图都画不出来。。这时候又怎么办呢?也很简单,用线性模型和其余模型一块儿套一下,评估、对比看看结果如何。
那么如今问题是,怎么让 loss
最小呢?请接着往下看。
废话很少说,直接上写好的代码:
def fit_linear_model(data, num_steps, alpha):
""" train with the machine learning :param data: training data :param num_steps: training steps :param alpha: learning rate :return: W and b of trained linear model """
# variables
W = tf.Variable(1, dtype=tf.float64)
b = tf.Variable(1, dtype=tf.float64)
x = tf.placeholder(tf.float64)
y = tf.placeholder(tf.float64)
# predict
pred = W * x + b
# loss
loss = tf.reduce_sum(tf.square(pred - y))
# optimizer
optimizer = tf.train.GradientDescentOptimizer(alpha)
# train
train = optimizer.minimize(loss)
train_set, test_set = split_test_set(data, frac=0.3, random=True)
sess = tf.Session()
sess.run(tf.global_variables_initializer())
for i in range(num_steps):
sess.run(train, {x: train_set['x'], y: train_set['y']})
final_W, final_b = sess.run([W, b], {x: train_set['x'], y: train_set['y']})
# evaluate
final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)
print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))
return final_W, final_b
复制代码
下面一步一步讲解代码:
1
;loss
GradientDescentOptimizer
来优化模型,减少loss
,这个类的原理是梯度降低,能够看到咱们传递了学习速率 alpha
即 α
。能够好奇的是,咱们没有计算梯度,而是调用了minimize
方法,这个方法分两步进行,第一步是使用 compute_gradients
计算梯度,第二步是使用 apply_gradients
更新参数值,凭借经验能够知道,第一步其实就是在计算偏导数,那么tensorflow是怎么作到,能够计算任意元线性模型的偏导数的呢,我大概扫描了一下源码,猜想应该是用了计算图
。split_test_set
将数据集划分为 训练集:测试集= 7:3,即有30%的数据用做测试集。global_variables_initializer
是个神魔恋,几乎tensorflow应用都要执行这个东西,看看文档就知道它会建立初始化程序时图中就存在的变量好比代码中的 variables_initializer(global_variables())
的缩写。run
方法获取到。讲道理应该能够经过相似 get_variable
的方法拿到值,看了下貌似没有这个方法。evaluate
评估模型,计算模型在训练集、测试集上的损失值。这是个封装好的函数,只要传入数据集、训练步数、学习率就能够获得训练好的模型了(即 和
)。
须要提醒一下的是,第3三、35行的 {x: train_set['x'], y: train_set['y']}
不能把 key
写成单引号的 {'x': train_set['x'], 'y': train_set['y']}
。
那么下面是须要拿到数据,这里我用的是随机生成的数据:
def linear_data(data_size, devi_degree):
""" Make random linear data :param data_size: data size :param devi_degree: degree of deviation :return: linear data with x and y """
# standard linear function
x = np.array(range(data_size), dtype=np.float64)
y = 3 * x + 0.6
# make deviation
y += np.random.randn(data_size) * devi_degree
data = pd.DataFrame({'x': x, 'y': y})
return data
复制代码
作法是:
data_size
的标准一元一次函数点集devi_degree
再来划分训练集和测试集:
def split_test_set(df, frac=0.3, random=True):
""" Split DataFrame to train set and test set :param df: :param frac: :param random: :return: """
test_size = int(len(df) * min(frac, 1))
if random:
df = df.sample(frac=1).reset_index(drop=True)
return df[test_size:].reset_index(drop=True), df[:test_size].reset_index(drop=True)
复制代码
def evaluate(train_set, test_set, W, b):
""" Evaluate the model's loss :param train_set: :param test_set: :param W: :param b: :return: train_loss, evaluate_loss """
x = tf.placeholder(tf.float64)
y = tf.placeholder(tf.float64)
# predict
pred = W * x + b
# loss
loss = tf.reduce_sum(tf.square(pred - y))
sess = tf.Session()
sess.run(tf.global_variables_initializer())
train_loss = sess.run(loss, {x: train_set['x'], y: train_set['y']})
evaluate_loss = sess.run(loss, {x: test_set['x'], y: test_set['y']})
sess.close()
return train_loss, evaluate_loss
复制代码
这里的loss评估是使用预测值-实际值平方再求和,与 理论基础
章节描述的 loss
同样。
这部分就简单了,把点画一下,把直线画一下就能够了,注意直线最好和点不同的颜色,因此标红。
def print_linear_model(data, W, b):
""" print the data and the predictions of linear model :param data: :param W: W of linear model :param b: b of linear model """
x = np.array(data['x'])
y = np.array(data['y'])
pred = np.array(W * x + b)
plt.scatter(x, y, linewidths=1)
plt.plot(x, pred, color='red')
plt.show()
复制代码
每一部分的函数写完啦,如今要作的是将这几个函数组合起来用,上代码。
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--data_size', default=50, type=int, help='data size')
parser.add_argument('--num_steps', default=1000, type=int, help='number of trannig steps')
parser.add_argument('--devi_degree', default=10, type=int, help='Degree of deviation in stand linear data')
parser.add_argument('--alpha', default=0.00001, type=float, help='learning rate of gradient decent')
def main(argv):
args = parser.parse_args(argv[1:])
data = linear_data(args.data_size, args.devi_degree)
W, b = fit_linear_model(data, args.num_steps, args.alpha)
print_linear_model(data, W, b)
if __name__ == '__main__':
tf.logging.set_verbosity(tf.logging.INFO)
tf.app.run(main)
复制代码
这里用到了 argparse
包来处理传入的参数
把我上面给的全部代码,都复制到tf-linear.py
文件里,接着运行:
python tf-linear.py --data_size=100 --alpha=0.000001 --devi_degree=30 --num_steps=5000
复制代码
便可看到以前那个图啦。
这里须要注意一下,alpha
参数不要设太大了。。否则会梯度爆炸。。程序员运行不出结果( 和
都会变成
nan
)
若是你看懂了基础版本的代码。。。那这部分就简单的很了,仍是同样,直接上代码:
def fit_estimator(data, num_steps):
""" train with estimator :param data: :param num_steps: :return: """
feature_columns = [
tf.feature_column.numeric_column('x')
]
estimator = tf.estimator.LinearRegressor(feature_columns=feature_columns)
train_set, test_set = split_test_set(data, frac=0.3, random=True)
input_fn = tf.estimator.inputs.numpy_input_fn(
{'x': train_set['x']}, train_set['y'], batch_size=4, num_epochs=None, shuffle=True
)
estimator.train(input_fn=input_fn, steps=num_steps)
W = estimator.get_variable_value('linear/linear_model/x/weights')
b = estimator.get_variable_value('linear/linear_model/bias_weights')
final_W, final_b = float(W), float(b)
final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)
print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))
return final_W, final_b
复制代码
仍是按步骤来解释吧:
线性回归器
,也就是 estimator
split_test_set
将数据集划分为 训练集:测试集= 7:3,即有30%的数据用做测试集。线性回归器
dir
命令才知道,能够用这种方法获取到参数,若是有其余优雅的方法能够告诉我哦。evaluate
评估模型,计算模型在训练集、测试集上的损失值。能够看到,比我们本身手撸模型要简单多了。首先,预测函数不用本身写了,优化器也不用本身建立了,甚至连for-loop也不用了,直接调用 train
方法就万事大吉了。
这里须要注意一下,LinearRegressor
有个 weight_column
参数,若是不给的话,初始值是1
,形状是 (1,)
,能够理解为是 np.array([1.])
,因此才能把他转成 float
,若是大于1阶,估计会转换失败。
数据、绘图都不变,只须要修改 main
方法为以下便可:
def main(argv):
args = parser.parse_args(argv[1:])
data = linear_data(args.data_size, args.devi_degree)
# W, b = fit_linear_model(data, args.num_steps, args.alpha)
W, b = fit_estimator(data, args.num_steps)
print_linear_model(data, W, b)
复制代码
接着仍是运行:
python tf-linear.py --data_size=100 --alpha=0.000001 --devi_degree=30 --num_steps=5000
复制代码
而后能够看到和以前那个图差很少的样子啦。
依然先上代码:
def fit_custom_estimator(data, num_steps, alpha):
""" train with custom estimator :param data: :param num_steps: :param alpha: :return: """
def model_fn(features, labels, mode):
W = tf.get_variable('W', 1., dtype=tf.float64)
b = tf.get_variable('b', 1., dtype=tf.float64)
# predict
pred = W * tf.cast(features['x'], dtype=tf.float64) + b
# loss
loss = tf.reduce_sum(tf.square(pred - labels))
# optimizer
optimizer = tf.train.GradientDescentOptimizer(alpha)
# global step
global_step = tf.train.get_global_step()
# train
train = tf.group(
optimizer.minimize(loss),
tf.assign_add(global_step, 1)
)
return tf.estimator.EstimatorSpec(
mode=mode,
predictions=pred,
loss=loss,
train_op=train
)
feature_columns = [
tf.feature_column.numeric_column('x')
]
estimator = tf.estimator.Estimator(
model_fn=model_fn
)
train_set, test_set = split_test_set(data, frac=0.3, random=True)
input_fn = tf.estimator.inputs.numpy_input_fn(
{'x': train_set['x']}, train_set['y'], batch_size=4, num_epochs=None, shuffle=True
)
estimator.train(input_fn=input_fn, steps=num_steps)
W = estimator.get_variable_value('W')
b = estimator.get_variable_value('b')
final_W, final_b = float(W), float(b)
final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)
print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))
return final_W, final_b
复制代码
能够分为两部分,第一部分是编写 model_fn
,第二部分是调用自定义的estimator。
先看第一部分吧,也就是11行~38行。
1
loss
。GradientDescentOptimizer
类,用于优化模型。global_step
即当前步数(这个步数是全局的)。GradientDescentOptimizer
优化模型,而且把 global_step
加一。group
方法表示把多个操做放在一个节点里,这个方法没有返回值。Estimator
类,并把一些必要参数传给它。第二部分调用自定义的estimator大部分代码和调用预约义的estimator同样,就是在第44~46行,咱们把本身写的 model_fn
做为参数传入到实例化的 Estimator
里。
在咱们调用estimator的 train
方法时,tensorflow就会在内部调用咱们写的 model_fn
方法来计算loss、prediction以及训练模型。
model_fn
的 mode
参数是啥?它其实有三个值分别是:ModeKeys.TRAIN、ModeKeys.EVAL、ModeKeys.PREDICT,用来指示本次调用的目的是训练、评估仍是预测。不过在本次实验中,咱们写了本身的evaluate
方法,因此就不区分这三种状况了。
def main(argv):
args = parser.parse_args(argv[1:])
data = linear_data(args.data_size, args.devi_degree)
# W, b = fit_linear_model(data, args.num_steps, args.alpha)
# W, b = fit_estimator(data, args.num_steps)
W, b = fit_custom_estimator(data, args.num_steps, args.alpha)
print_linear_model(data, W, b)
复制代码
在使用 Estimator
训练模型的时候,tensorflow会自动记录训练过程当中某些数据的变化,好比 loss
值,拿premade estimator来讲,在训练完成后,控制会打印出如图所示的一些信息:
能够看到有一些数据被存到了:
/var/folders/4c/14xc6rkj1ndgw2x5kw5sw8hr0000gn/T/tmpo5gg328l/model.ckpt.
复制代码
查看这个目录发现里面还有不少其余文件
下面运行Tensorboard,来查看此次训练中的数据变化:
tensorboard --logdir /var/folders/4c/14xc6rkj1ndgw2x5kw5sw8hr0000gn/T/tmpo5gg328l
复制代码
这会启动一个web服务,点击终端提示的网址便可打开,个人是在 6006
端口。
本次实验的开源github地址:https://github.com/JerryCheese/tensorflow-study
参考的文档:
[1] TensorFlow 完整的TensorFlow入门教程, https://blog.csdn.net/lengguoxing/article/details/78456279
[2] TensorFlow 使用入门, https://tensorflow.google.cn/get_started/premade_estimators