欢迎来我的个人Blog获得更好的阅读体验。
Tensorflow基础
Tensor
1.正如名称所示,TensorFlow 这一框架定义和运行涉及张量的计算。张量是对矢量和矩阵向潜在的更高维度的泛化。TensorFlow 在内部将张量表示为基本数据类型的 n 维数组。
2.tf.Tensor
具有以下属性:
数据类型(例如
float32
、int32
或string
)形状
3.张量中的每个元素都具有相同的数据类型,且该数据类型一定是已知的。形状,即张量的维数和每个维度的大小,可能只有部分已知。如果其输入的形状也完全已知,则大多数操作会生成形状完全已知的张量,但在某些情况下,只能在执行图时获得张量的形状。
4.如何按索引取多个值?
使用gather_nd
方法
a = tf.constant([[1,2],[3,4],[5,6]])
b = tf.Variable([[1],[2]])
tf.gather_nd(a, b)
OUT:
<tf.Tensor: id=11677, shape=(2, 2), dtype=int32, numpy=
array([[3, 4],
[5, 6]])>
Eager Execution
1.如何在enable_eager_execution
下求梯度?
导入import tensorflow.contrib.eager as tfe
,然后使用tfe.gradients_function()
进行求梯度。
def square(x, y):
return tf.multiply(x, x) + y
grad = tfe.gradients_function(square)
grad(2., 2.)
OUT:
[<tf.Tensor: id=595913, shape=(), dtype=float32, numpy=4.0>,
<tf.Tensor: id=595910, shape=(), dtype=float32, numpy=1.0>]
2.如何使用batch
数据集?
batched_dataset = tf.data.Dataset.from_tensor_slices((X, y)).batch(batch_size)
iterator = batched_dataset.make_one_shot_iterator()
for X, y in iterator:
do_something
线性回归
模型训练三要素:训练数据、损失函数、优化算法。
在线性回归中,的计算依赖于 和。也就是说,输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层(dense layer)。
在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:它每次返回
batch_size
(批量大小)个随机样本的特征和标签。也就是说batch_size
即为mini梯度下降的每次的数据量。
导包
import numpy as np
import tensorflow as tf
import tensorflow.contrib.eager as tfe
%matplotlib inline
from IPython import display
from matplotlib import pyplot as plt
import random
tf.enable_eager_execution() # 开启Eager Execution模式
手撸线性回归
构建数据集
我们构造一个简单的人工训练数据集,它可以使我们能够直观比较学到的参数和真实的模型参数的区别。设训练数据集样本数为1000,输入个数(特征数)为2。给定随机生成的批量样本特征,我们使用线性回归模型真实权重和偏差,以及一个随机噪声项来生成标签
其中噪声项服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中无意义的干扰。下面,让我们生成数据集。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = tf.random.normal(shape=(num_examples, num_inputs), stddev = 1)
labels = true_w[0] * features[:,0] + true_w[1] * features[:,1] + true_b
labels += tf.random.normal(stddev=0.01, shape=labels.shape) # 添加噪音
注意,features
的每一行是一个长度为2的向量,而labels
的每一行是一个长度为1的向量(标量)。
features[0], labels[0]
OUT:
(<tf.Tensor: id=10879, shape=(2,), dtype=float32, numpy=array([-0.7999102 , 0.21825652], dtype=float32)>,
<tf.Tensor: id=10883, shape=(), dtype=float32, numpy=1.8640134>)
接下来我们可以画图来更直观的看出他们的线性关系
def use_svg_display():
# 用矢量图显示
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
# 设置图的尺寸
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1); # 加分号只显示图
读取数据
在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:它每次返回batch_size
(批量大小)个随机样本的特征和标签。
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices) # 样本的读取顺序是随机的
for i in range(0, num_examples, batch_size):
j = tf.Variable(indices[i: min(i + batch_size, num_examples)])
j = tf.reshape(j, [-1,1])
yield tf.gather_nd(features, j), tf.gather_nd(labels, j)
让我们读取第一个小批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量大小和输入个数;标签形状为批量大小。
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
OUT:
tf.Tensor(
[[-0.227834 0.9596326 ]
[ 0.34192654 -0.66422266]
[ 0.3691396 -0.18605368]
[-1.1413608 0.8678634 ]
[ 0.58045554 0.3434902 ]
[-0.5410006 -0.42822808]
[ 0.9343072 1.4792926 ]
[-0.46756336 -0.6927819 ]
[ 0.08125348 -1.2476276 ]
[ 0.14274137 -0.79248554]], shape=(10, 2), dtype=float32) tf.Tensor(
[ 0.48526025 7.1483665 5.5596623 -1.0285498 4.194294 4.563314
1.0244439 5.611564 8.604012 7.180201 ], shape=(10,), dtype=float32)
初始化模型参数
我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。
w = tf.random.normal(stddev=0.01, shape=(num_inputs, 1))
b = tf.zeros(shape=(1,))
定义损失函数
我们使用平方误差作为损失函数
def squared_loss(X, w, b, y):
y_pred = tf.matmul(X, w) + b
return tf.reduce_sum((y_pred - tf.reshape(y, shape=y_pred.shape)) ** 2) / 2
定义优化算法
我们使用小批量随机梯度下降来定义优化算法,其实就是除了一个batch_size
def sgd(param, grad, lr, batch_size):
param = param - lr * grad / batch_size
return param
训练模型
我们训练三个epoch
,定义学习率为0.03
,batch_size
为10
,使用tfe.gradients_function
来求取梯度
num_epochs = 3
lr = 0.03
batch_size = 10
print("Initial loss: {:.3f}".format(squared_loss(X, w, b, y)))
for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
for X, y in data_iter(batch_size, features, labels):
grad = tfe.gradients_function(squared_loss)
grad_result = grad(X, w, b, y)
w = sgd(w, grad_result[1], lr, batch_size)
b = sgd(b, grad_result[2], lr, batch_size)
train_l = squared_loss(X, w, b, y)
print('epoch %d, loss %f' % (epoch + 1, train_l.numpy()))
OUT:
Initial loss: 170.409
epoch 1, loss 0.397634
epoch 2, loss 0.000867
epoch 3, loss 0.000623
我们来观察一下训练后的参数,可以发现他们是很接近的
true_w, w
([2, -3.4], <tf.Tensor: id=2451472, shape=(2, 1), dtype=float32, numpy=
array([[ 1.9991914],
[-3.399644 ]], dtype=float32)>)
true_b, b
(4.2,
<tf.Tensor: id=2451477, shape=(1,), dtype=float32, numpy=array([4.1997643], dtype=float32)>)
线性回归更简洁的实现
同样,我们定义好参数
num_epochs = 3
lr = 0.02
batch_size = 10
定义模型,这里注意要把w
与b
都要定义成Variable
,不然后面的梯度下降优化无法更新权值。
class Model(tf.keras.Model):
def __init__(self):
super(Model, self).__init__()
self.W = tf.Variable(tf.random.normal(stddev=0.01, shape=(num_inputs, 1)), name="weight")
self.B = tf.Variable(0., name='bias')
def call(self, inputs):
return tf.matmul(inputs, self.W) + self.B
定义损失函数,这里注意y_pred
的维度要与targets
的维度相同,不然相减可能会出问题,影响后面的结果,可能导致算法无法收敛。
def loss(model, inputs, targets):
y_pred = model(inputs)
error = y_pred - tf.reshape(targets, shape=y_pred.shape)
return tf.reduce_mean(tf.pow(error, 2)) / 2
定义梯度
def grad(model, inputs, targets):
with tf.GradientTape() as tape:
loss_value = loss(model, inputs, targets)
return tape.gradient(loss_value, [model.W, model.B])
初始化模型与优化方法,这里我们就用普通的梯度下降优化器
model = Model()
optimizer = tf.train.GradientDescentOptimizer(learning_rate=lr)
下面是用两种方法来做实验,一种是带batch_size
的小批量随机梯度下降,一种是不带batch_size
的批量梯度下降。
小批量:
这里使用到了Dataset
,可以直接读取数据,然后使用batch
方法生成一个批处理后的数据集,然后使用make_one_shot_iterator
生成一个迭代器,通过此对象,可以一次访问数据集中的一个元素。
print("Initial loss: {:.3f}".format(loss(model, features, labels)))
batched_dataset = tf.data.Dataset.from_tensor_slices((features, labels)).batch(batch_size)
for epoch in range(1, num_epochs + 1):
iterator = batched_dataset.make_one_shot_iterator()
for X, y in iterator:
grads = grad(model, X, y)
optimizer.apply_gradients(zip(grads, [model.W, model.B]))
# if epoch % 20 == 0:
print("Loss at epoch {:03d}: {:.8f}".format(epoch, loss(model, features, labels)))
print("Final loss: {:.3f}".format(loss(model, features, labels)))
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
OUT:
Initial loss: 16.771
Loss at epoch 001: 0.28207895
Loss at epoch 002: 0.00484521
Loss at epoch 003: 0.00013049
Final loss: 0.000
W = [[ 1.9972771]
[-3.3913243]], B = 4.190994739532471
批量:
print("Initial loss: {:.3f}".format(loss(model, features, labels)))
for i in range(200):
grads = grad(model, features, labels)
optimizer.apply_gradients(zip(grads, [model.W, model.B]),
global_step=tf.train.get_or_create_global_step())
if i % 20 == 0:
print("Loss at step {:03d}: {:.8f}".format(i, loss(model, features, labels)))
print("Final loss: {:.3f}".format(loss(model, features, labels)))
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
OUT:
Initial loss: 16.791
Loss at step 000: 16.11963463
Loss at step 020: 7.13295412
Loss at step 040: 3.15785861
Loss at step 060: 1.39871728
Loss at step 080: 0.61985403
Loss at step 100: 0.27484268
Loss at step 120: 0.12194006
Loss at step 140: 0.05414332
Loss at step 160: 0.02406745
Loss at step 180: 0.01071854
Final loss: 0.005
W = [[ 1.9749271]
[-3.3363655]], B = 4.128057956695557
线性回归更更简洁的实现
这里我们使用到Tensorflow
的最新科技Estimator
,一种可极大地简化机器学习编程的高阶 TensorFlow API。
从官方给的图我们就可以看出有多么的高阶,可以说是顶峰了。
[图片上传失败...(image-1f118e-1557193297372)]
编写一个或多个数据集导入函数
例如,您可以创建一个函数来导入训练集,并创建另一个函数来导入测试集。每个数据集导入函数都必须返回两个对象:
一个字典,其中键是特征名称,值是包含相应特征数据的张量(或 SparseTensor)
一个包含一个或多个标签的张量
例如,以下代码展示了输入函数的基本框架:
f0 = features[:,0].numpy()
f1 = features[:,1].numpy()
这里为什么要分开写呢?我到现在也不知道,希望知道的大神可以滴我一下。
def input_fn():
feature_dict = {}
feature_dict["w0"] = f0
feature_dict["w1"] = f1
return feature_dict, labels.numpy()
输入函数可以以您需要的任何方式生成 features
字典和 label
列表。不过,我们建议使用 TensorFlow 的 Dataset API,它可以解析各种数据。概括来讲,Dataset API 包含下列类:
def dataset_input_fn():
feature_dict = {}
feature_dict["w0"] = f0
feature_dict["w1"] = f1
dataset = tf.data.Dataset.from_tensors((feature_dict, labels.numpy()))
return dataset.shuffle(2000).repeat()
dataset_input_fn
是用了没有切片的结果
def dataset_slices_input_fn():
feature_dict = {}
feature_dict["w0"] = f0
feature_dict["w1"] = f1
dataset = tf.data.Dataset.from_tensor_slices((feature_dict, labels.numpy()))
return dataset.shuffle(100).repeat().batch(batch_size)
dataset_slices_input_fn
是用了切片的结果
这两种的区别就是dataset_slices_input_fn
函数会利用 tf.data.Dataset.from_tensor_slices
函数创建一个代表数组切片的 tf.data.Dataset
。系统会在第一个维度内对该数组进行切片。例如,一个包含MNIST
训练数据的数组的形状为 (60000, 28, 28)。将该数组传递给from_tensor_slices
会返回一个包含 60000 个切片的 Dataset
对象,其中每个切片都是一个 28x28 的图像。而这里就是每一行。
这里有个坑,就是使用了from_tensor_slices
,如果不用batch
直接训练会导致
ValueError: Feature (key: w0) cannot have rank 0. Give: Tensor("IteratorGetNext:0", shape=(), dtype=float32)
报错。
上面两个函数用到了几个对Dataset
的操作:
shuffle
方法使用一个固定大小的缓冲区,在条目经过时随机化处理条目。在这种情况下,buffer_size
大于Dataset
中样本的数量,确保数据完全被随机化处理。repeat
方法会在结束时重启Dataset
。要限制周期数量,请设置count
参数。batch
方法会收集大量样本并将它们堆叠起来以创建批次。这为批次的形状增加了一个维度。新的维度将添加为第一个维度。
定义特征列
每个 tf.feature_column
都标识了特征名称、特征类型和任何输入预处理操作。例如,这里就要按照上面input_fn
的返回值的feature_dict
的键名来命名特征名。
w0 = tf.feature_column.numeric_column('w0')
w1 = tf.feature_column.numeric_column('w1')
实例化相关的预创建的 Estimator
例如,下面是对名为 LinearRegressor
的预创建 Estimator 进行实例化的示例代码:
estimator = tf.estimator.LinearRegressor(
feature_columns=[w0, w1],
optimizer=tf.train.GradientDescentOptimizer(
learning_rate=0.0001,
))
调用训练、评估或推理方法。
1.使用input_fn()
函数
estimator.train(input_fn=lambda:input_fn(), steps=200)
OUT:
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpix5nedss\model.ckpt.
INFO:tensorflow:loss = 33480.66, step = 1
INFO:tensorflow:global_step/sec: 534.187
INFO:tensorflow:loss = 0.09687384, step = 101 (0.187 sec)
INFO:tensorflow:Saving checkpoints for 200 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpix5nedss\model.ckpt.
INFO:tensorflow:Loss for final step: 0.09687384.
最终loss
为0.09687384.
2.使用dataset_input_fn()
函数
estimator.train(input_fn=lambda:dataset_input_fn(), steps=200)
OUT:
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpt_lj1x_h\model.ckpt.
INFO:tensorflow:loss = 33480.66, step = 1
INFO:tensorflow:global_step/sec: 534.187
INFO:tensorflow:loss = 0.09687384, step = 101 (0.187 sec)
INFO:tensorflow:Saving checkpoints for 200 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpt_lj1x_h\model.ckpt.
INFO:tensorflow:Loss for final step: 0.09687384.
最终loss
为0.09687384.
和input_fn()
函数结果一样,因为我们都没有做任何改变,只是用了官方建议的Dataset
API去封装了我们的数据集。
3.使用dataset_slices_input_fn()
函数
这里我们要调整学习率,因为使用的是小批量梯度下降,所以学习率要高一点,不然收敛很慢。
重新定义estimator
estimator = tf.estimator.LinearRegressor(
feature_columns=[w0, w1],
optimizer=tf.train.GradientDescentOptimizer(
learning_rate=0.02,
))
训练
estimator.train(input_fn=lambda:dataset_slices_input_fn(), steps=200)
OUT:
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpli_pz5wh\model.ckpt.
INFO:tensorflow:loss = 207.14127, step = 1
INFO:tensorflow:global_step/sec: 493.095
INFO:tensorflow:loss = 0.0006978577, step = 101 (0.218 sec)
INFO:tensorflow:Saving checkpoints for 200 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmpli_pz5wh\model.ckpt.
INFO:tensorflow:Loss for final step: 0.000740177.
最终loss
为0.000740177.
效果果然好了很多
小trick
无意中发现,estimator
有个牛逼的功能,就是能够断点续训
,当然他不叫断点,官方的说法是Checkpoints
,翻译过来叫做检查点
,这真的太秀了,拿上面的dataset_slices_input_fn()
举个例子:
我故意把学习率调低
estimator = tf.estimator.LinearRegressor(
feature_columns=[w0, w1],
optimizer=tf.train.GradientDescentOptimizer(
learning_rate=0.0001,
))
然后训练
estimator.train(input_fn=lambda:dataset_slices_input_fn(), steps=200)
OUT:
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmplxsxz4hw\model.ckpt.
INFO:tensorflow:loss = 232.43555, step = 1
INFO:tensorflow:global_step/sec: 493.095
INFO:tensorflow:loss = 132.51915, step = 101 (0.218 sec)
INFO:tensorflow:Saving checkpoints for 200 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmplxsxz4hw\model.ckpt.
INFO:tensorflow:Loss for final step: 191.9462.
loss
为191.9462.很明显,没有收敛,于是我继续执行:
estimator.train(input_fn=lambda:dataset_slices_input_fn(), steps=200)
OUT:
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from C:\Users\ADMINI~1\AppData\Local\Temp\2\tmplxsxz4hw\model.ckpt-200
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 200 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmplxsxz4hw\model.ckpt.
INFO:tensorflow:loss = 78.7848, step = 201
INFO:tensorflow:global_step/sec: 457.875
INFO:tensorflow:loss = 128.2986, step = 301 (0.203 sec)
INFO:tensorflow:Saving checkpoints for 400 into C:\Users\ADMINI~1\AppData\Local\Temp\2\tmplxsxz4hw\model.ckpt.
INFO:tensorflow:Loss for final step: 70.70934.
他读取的缓存文件,继续训练了,这真的很优秀,再也不用重新训练了。