TensorFlow 学习笔记

TensorFlow 学习笔记

TensorFlow 是创建机器学习模型的 Python 库。

可以在Colab中练习:Colab

参考:TensorFlow 编程概念

首先是基本概念:

  • 张量:任意维度的数组
  • 指令:创建、销毁、操作张量的代码
  • 图:处理张量的流程结构,每个节点代表一个指令,张量流经图。
  • 会话:图在会话中运行。
import tensorflow as tf

g = tf.Graph()

with g.as_default():
  x = tf.constant(8, name="x_const")
  y = tf.constant(5, name="y_const")
  sum = tf.add(x, y, name="x_y_sum")

  with tf.Session() as sess:
    print(sum.eval())

创建和操控张量

矢量加法

tf.add 两个张量的相加

primes = tf.constant([2,3,5,7,11,13], dtype=tf.int32) # 一个包含质数的 primes 矢量。
ones = tf.ones([6], dtype=tf.int32)
just_beyond_primes = tf.add(primes, ones)
print(just_beyond_primes) # 打印结果 tf.Tensor([ 3  4  6  8 12 14], shape=(6,), dtype=int32)

# 使用tf.pow进行幂运算
just_under_primes_squared = tf.add(tf.pow(primes, 2), tf.constant(-1))
print(just_under_primes_squared)

some_matrix = tf.constant([[1, 2, 3], [4, 5, 6]], dtype=tf.int32)
print(some_matrix) # 会打印张量的值、形状、类型
print(some_matrix.numpy()) # 只打印值

张量形状

.shape.get_shape() 可以查看一个张量的形状,也就是各个维度的大小

scalar = tf.zero([]) # 零维度的张量
vector = tf.zeros([3]) # 第一维度大小为3,所以是3个元素的向量
matrix = tf.zeros([2,3]) #第一维度大小为2,第二个维度大小是3,所以是个2行3列的矩阵

print('vector has shape:', vector.shape)
print('matrix has shape:', matrix.shape)

广播

广播指的是增大较小的张量的形状,使它可以和较大的张量进行运算。 TensorFlow支持广播。

广播时从后边开始依次比较两个形状的两个维度,两个维度是兼容的当:

  1. 相同
  2. 其中一个是1

那么如果A和B的形状分别是 (8,1,6,1), (7,1,5), 运算后就会得到新的形状 (8,7,6,5)。如果对应维度不同且都不是1,就会有ValueError: frames are not aligned的报错。 广播就是这么个意思。

primes = tf.constant([2,3,5,7,11,13], dtype=tf.int32) 
ones = tf.constant(1, dtype=tf.int32)
just_beyond_primes = tf.add(primes, one) # 因为ones是1维,所以

矩阵乘法

tf.matmul计算矩阵乘法。需要满足线性代数中,第一个矩阵列数等于第二个矩阵的行数。

# 3X4的矩阵
x = tf.constant([[5, 2, 4, 3], [5, 1, 6, -2], [-1, 3, -1, -2]],
                dtype=tf.int32)
# 4X2 的矩阵
y = tf.constant([[2, 2], [3, 5], [4, 5], [1, 6]], dtype=tf.int32)

matrix_multiply_result = tf.matmul(x, y)
print(matrix_multiply_result) # 得到3X2的矩阵

张量变形

tf.reshape改变张量的形状。

mat_8x2 = tf.constant(
    [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16]],
    dtype=tf.int32)
mat_2x8 = tf.reshape(mat_8x2, [2, 8])
mat_4x4 = tf.reshape(mat_8x2, [4, 4])
print(mat_8x2.numpy())
print(mat_2x8.numpy())
print(mat_4x4.numpy())

还可以变形为不同维度:

tensor_2x2x4 = tf.reshape(mat_8x2, [2, 2, 4])
one_d_vector = tf.reshape(mat_8x2, [16])
print(tensor_2x2x4.numpy())
print(one_d_vector.numpy())

拼接张量

vec1 = tf.constant(tf.random_uniform([3, 1], minval=1, maxval=7, dtype=tf.int32))
vec2 = tf.constant(tf.random_uniform([3, 1], minval=1, maxval=7, dtype=tf.int32))
print(vec1.numpy())
print(vec2.numpy())
mat = tf.concat([vec1, vec2], axis=1)
print(mat.numpy())

变量

前面是常数(tf.constant),接下来介绍变量,就是 Variable对象,它的值是可改变的。

#创建一个初始值为3的变量
v = tf.contrib.eager.Variable([3])
#创建一个形状为[1,4]的vector,从均值为1,标准差为0.35的正态分布中抽样
w = tf.contrib.eager.Variable(tf.random_normal([1, 4], mean=1.0, stddev=0.35))
print("v:", v.numpy())
print("w:", w.numpy())
# 可以赋予新值,但是不能改变形状
v.assign([5])
print("v:", v.numpy())

tf.estimator、线性回归

参考:使用 TensorFlow 的起始步骤

设置

#加载库
import math

from matplotlib import cm
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from sklearn import metrics
import tensorflow as tf
from tensorflow.python.data import Dataset

pd.options.display.max_rows = 10
pd.options.display.float_format = '{:.1f}'.format

# 加载数据
california_housing = pd.read_csv("https://download.mlcc.google.cn/mledu-datasets/california_housing_train.csv", sep=",")

#随机排列
california_housing = california_housing.reindex(np.random.permutation(california_housing.index))
#调整数据单位
california_housing["median_house_value"] /= 1000.0
california_housing.head()

检查数据

可以看到样本数、均值、标准偏差、最大值、最小值和各种分位数。

california_housing.describe()

构建模型

# 输入的特征
my_feature = california_housing[["total_rooms"]]
# 特征列是一种存储了特征数据的描述的数据结构,不包含数据本身
feature_columns = [tf.feature_column.numeric_column("total_rooms")]
# 目标
targets = california_housing["median_house_value"]

# 用梯度下降法作为优化器
my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.0000001)
# 用梯度裁剪来确保梯度大小在训练期间不会变得过大,梯度过大会导致梯度下降法失败
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)

# 用 LinearRegressor 创建线性回归模型,这里传入了特征列和优化器
linear_regressor = tf.estimator.LinearRegressor(
    feature_columns=feature_columns,
    optimizer=my_optimizer
)

定义输入函数

输入函数是构建了一个迭代器,它会做一些数据处理,最后返回下一批数据给 LinearRegressor。

更详细文档:

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
    """
    Args:
      features: pandas DataFrame of features
      targets: pandas DataFrame of targets
      batch_size: Size of batches to be passed to the model
      shuffle: True or False. Whether to shuffle the data.
      num_epochs: Number of epochs for which data should be repeated. None = repeat indefinitely
    Returns:
      Tuple of (features, labels) for next data batch
    """

    # 从DataFrame转为一个Dict: {'total_rooms': array([ 829., 2396., 1268., ..., 1575., 1513., 2154.])}
    features = {key:np.array(value) for key,value in dict(features).items()}                                           

    # 构建数据集并配置 batch size (每次训练多少个样本)和 epoch num(重复训练多少次)。
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)

    # 重排数据
    if shuffle:
      ds = ds.shuffle(buffer_size=10000)

    # 返回下一个 batch 的数据
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

模型训练

调用train()来训练模型。训练100步,训练一步表示计算一批(batch_size个)样本产生的损失,并据此修改模型的权重。

linear_regressor.train(
    input_fn = lambda:my_input_fn(my_feature, targets),
    steps=100
)

评估模型

这里用训练数据评估一下模型的拟合情况。

# 创建预测用的输入函数
prediction_input_fn =lambda: my_input_fn(my_feature, targets, num_epochs=1, shuffle=False)
# 调用predict() 获取预测结果,此时返回的是一个 generator
predictions = linear_regressor.predict(input_fn=prediction_input_fn)
# 将预测结果转为NumPy数组
predictions = np.array([item['predictions'][0] for item in predictions])
# 计算预测结果与真实结果的MSE和RMSE两种误差
mean_squared_error = metrics.mean_squared_error(predictions, targets)
root_mean_squared_error = math.sqrt(mean_squared_error)

print("Mean Squared Error (on training data): %0.3f" % mean_squared_error)
print("Root Mean Squared Error (on training data): %0.3f" % root_mean_squared_error)

RMSE是在MSE的基础上开了个平方,它比MSE更好解读,因为它和目标在同一个数量级。

\[ RMSE = \sqrt{\frac 1 n \sum_{i=1}^n (y_i-\bar y_i)^2}\]

RMSE越小说明拟合得越好,现在的情况是它和目标值的最大最小值的差值的一半差不多大:

min_house_value = california_housing["median_house_value"].min()
max_house_value = california_housing["median_house_value"].max()
min_max_difference = max_house_value - min_house_value
print("Difference between Min. and Max.: %0.3f" % min_max_difference)
print("Root Mean Squared Error: %0.3f" % root_mean_squared_error)
# Difference between Min. and Max.: 485.002
# Root Mean Squared Error: 237.417

说明效果不太好。我们需要降低模型的误差。为了方便调整模型的超参数,我们实现一个函数来完成创建、训练、评估模型的过程。

def train_model(learning_rate, steps, batch_size, input_feature="total_rooms"):
  """
  Args:
    learning_rate: A `float`, the learning rate.
    steps: A non-zero `int`, the total number of training steps.
    batch_size: A non-zero `int`, the batch size.
    input_feature: A `string` specifying a column from `california_housing` to use as input feature.
  """

  periods = 10
  steps_per_period = steps / periods

  #  设置
  my_feature = input_feature
  my_feature_data = california_housing[[my_feature]]
  my_label = "median_house_value"
  targets = california_housing[my_label]

  feature_columns = [tf.feature_column.numeric_column(my_feature)]

  training_input_fn = lambda:my_input_fn(my_feature_data, targets, batch_size=batch_size)
  prediction_input_fn = lambda: my_input_fn(my_feature_data, targets, num_epochs=1, shuffle=False)

  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=feature_columns,
      optimizer=my_optimizer
  )

  # 绘图
  plt.figure(figsize=(15, 6))
  plt.subplot(1, 2, 1)
  plt.title("Learned Line by Period")
  plt.ylabel(my_label)
  plt.xlabel(my_feature)
  sample = california_housing.sample(n=300)
  plt.scatter(sample[my_feature], sample[my_label])
  colors = [cm.coolwarm(x) for x in np.linspace(-1, 1, periods)]

  # 训练
  print("Training model...")
  print("RMSE (on training data):")
  root_mean_squared_errors = []

  for period in range (periods):
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period
    )

    # 预测
    predictions = linear_regressor.predict(input_fn=prediction_input_fn)
    predictions = np.array([item['predictions'][0] for item in predictions])

    # 评估
    root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(predictions, targets))
    root_mean_squared_errors.append(root_mean_squared_error)
    print("  period %d : %0.2f" % (period, root_mean_squared_error))

    weight = linear_regressor.get_variable_value('linear/linear_model/%s/weights' % input_feature)[0]
    bias = linear_regressor.get_variable_value('linear/linear_model/bias_weights')

    # 确保数据和线条能美观地显示
    y_extents = np.array([0, sample[my_label].max()])    
    x_extents = (y_extents - bias) / weight
    x_extents = np.maximum(np.minimum(x_extents,
                                      sample[my_feature].max()),
                           sample[my_feature].min())
    y_extents = weight * x_extents + bias
    plt.plot(x_extents, y_extents, color=colors[period]) 

  print("Model training finished.")

  # 绘制 loss 指数的变化图
  plt.subplot(1, 2, 2)
  plt.ylabel('RMSE')
  plt.xlabel('Periods')
  plt.title("Root Mean Squared Error vs. Periods")
  plt.tight_layout()
  plt.plot(root_mean_squared_errors)

  print("Final RMSE (on training data): %0.2f" % root_mean_squared_error)

然后我们可以尝试不同的超参,不同的特征:

train_model(
    learning_rate=0.00002,
    steps=500,
    batch_size=5
)

常见用法解释

tf.placeholder

待填充的Tensor,要用session.run的feed_dict参数填充。

x = tf.placeholder(tf.int32)
z = tf.add(x, x)

with tf.Session() as sess:
  print(sess.run(z, feed_dict={x: 3}))

tf.nn.embedding_lookup

传入一个tensor,和一堆索引,返回这个tensor对应索引的值。

L = tf.Variable([1,3,5,7,11,13,17,19,23], trainable=False)
embedding = tf.nn.embedding_lookup(L, [1,5])

with tf.Session() as sess:
  sess.run(tf.initialize_all_variables())
  print(sess.run(embedding))

Previous Post Next Post