《动手学习深度学习》- 从零开始实现线性回归模型

《动手学习深度学习》- 从零开始实现线性回归模型

Scroll Down

1. 线性回归的从零开始实现

  • 本节将介绍如何只利用Tensor和GradientTape来实现一个线性回归的训练。
  • 首先,导入本节中实验所需的包或模块,其中的matplotlib包可用于作图,且设置成嵌入显示。
%matplotlib inline
import tensorflow as tf
print(tf.__version__)
from matplotlib import pyplot as plt
import random
2.3.0

2. 生成数据集

  • 我们构造一个简单的人工训练数据集,它可以使我们能够直观比较学到的参数和真实的模型参数的区别。
  • 设训练数据集样本数为1000, 样本的输入特征数为2
  • 给定的随机批量样本特征 $X $
  • 注意 : Y += X (Y = Y + X)是减少内存开销的写法, 详见: 2.2.5 运算的内存开销
# 特征维度
w_input = 2
# 特征数量
w_examples = 1000
# 权重值
true_w = [2,-3.4]
# 偏差值
true_b = 4.2

# 生成特征值, 特征值为维度(1000,2)的标准差为1正太分布的随机数
features =tf.random.normal((w_examples,w_input), stddev=1)
# 生成标签
labels = true_w[0] * features[:,0] + true_w[1] * features[:,1] + true_b
# 下面的写法是为了减少内存开销的写法
labels += tf.random.normal(labels.shape, stddev=0.01)
  • 注意,features 的每一行是一个长度为2的向量,而 labels 的每一行是一个长度为1的向量(标量)
features[0],labels[0]
(<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-1.8004812, -0.4955389], dtype=float32)>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.2674139>)
  • 通过生成第二个特征features[:, 1]和标签 labels 的散点图,可以更直观地观察两者间的线性关系。
def set_figsize(figsize=(3.5, 2.5)):
    plt.rcParams['figure.figsize'] = figsize

set_figsize()
plt.scatter(features[:, 1], labels, 1)
<matplotlib.collections.PathCollection at 0x19b513f1e48>
png

3. 读取数据

  • 在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:它每次返回 batch_size(批量大小)个随机样本的特征和标签。
  • tf.gather() : 从索引中获取指定维度数据
    • param : 数据
    • indices : 索引
    • axis : 维度(坐标轴)
def  data_iter(batch_size, features, labels):
    """
        batch_size : 数据集批量大小
        features: 特征大小
        labels: 标签
    """
    nums_examples = len(features) # 获取特征元素数量
    # list(range(10)) : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    indices = list(range(nums_examples)) # 获取元素下标数组作为索引
    # shuffle : 混淆索引
    random.shuffle(indices)
    # 随机抽取数据
    for i in range(0, nums_examples, batch_size): # range(start, stop, step)
        j = indices[i: min(i+batch_size, nums_examples)] # 获取 i ~ batch_size 的下标数组
        # 根据下标从 features 和 labels 中获取数据
        yield tf.gather(params=features, axis=0, indices=j), tf.gather(params=labels, axis=0, indices=j), 
    
  • 读取小批量样本并打印
batch_size = 20

for x,y in data_iter(batch_size, features, labels):
    print(x,y)
    break

tf.Tensor(
[[-0.44567308 -0.46028626]
 [ 0.25810537 -0.1753419 ]
 [-1.2864676   1.0588208 ]
 [-0.4310032   0.14640251]
 [-0.21027322 -0.52261746]
 [ 0.3852463   0.17180958]
 [ 0.18383066 -1.8581442 ]
 [-0.18865168  0.517026  ]
 [ 0.66316545 -0.8758996 ]
 [-0.34125257  0.03255441]
 [ 0.3504944  -0.700806  ]
 [-1.0948176   0.34211987]
 [-1.4527543  -1.4194374 ]
 [-0.2180637  -0.9417918 ]
 [-0.46503007  0.61183107]
 [-1.213674    1.3021073 ]
 [ 0.60172164 -0.70607615]
 [ 0.6119027  -1.0931672 ]
 [ 0.21791288  0.8930051 ]
 [-1.4284779   0.4996903 ]], shape=(20, 2), dtype=float32) tf.Tensor(
[ 4.8678513   5.3239536  -1.9739721   2.8419168   5.5527067   4.39876
 10.897141    2.0665278   8.496226    3.423159    7.2897987   0.8599973
  6.140855    6.9794583   1.205171   -2.655914    7.7964063   9.117886
  1.5960008  -0.36231914], shape=(20,), dtype=float32)

4. 初始化模型参数

  • 将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。
w = tf.Variable(tf.random.normal((w_input, 1), stddev=1))
b = tf.Variable(tf.zeros((1,)))

w,b
(<tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy=
 array([[0.41561303],
        [0.04117949]], dtype=float32)>,
 <tf.Variable 'Variable:0' shape=(1,) dtype=float32, numpy=array([0.], dtype=float32)>)

5. 定义模型

  • 定义线性回归的表达式

$$
y = X\omega + b + \epsilon
$$

def linear_reg(X, w, b):
    return tf.matmul(X,w) + b

6. 定义损失函数

  • 我们使用平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值y变形成预测值y_hat的形状。以下函数返回的结果也将和y_hat的形状相同。
  • 定义损失函数的表达式

$$
\zeta{(i)}(w_1,w_2,b) = \dfrac{1}{2} (\hat{(i)} - y{(i)})2
$$

def loss(y_hat, y):
    return (y_hat - tf.reshape(y, y_hat.shape)) ** 2 /2

7. 定义优化函数

  • 先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch),然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。
  • tf.assign_sub(ref, value)
    1. 作用: ref - value
    2. 要求 value 必须是 Tensor
        b = tf.Variable(1, dtype=tf.int64)
        res_b = b.assign_sub(1)
        res_b.numpy()
    
def sgd(params, lr, batch_size, grid):
    """
        params : 模型参数
        lr: 学习率
        batch_size: 批量数据大小
        grid: 自动求导的数据值
    """
    # enumerate(list/dict) 作用是返回集合中的元素和下标
    for i, param in enumerate(params):
        # tf.assign_sub(ref, value) : ref - value
        param.assign_sub((lr * grid[i] / batch_size))

9. 训练模型

  • 在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的小批量数据样本(特征X和标签y),通过调用反向函数 t.gradients 计算小批量随机梯度,并调用优化算法 sgd 迭代模型参数。由于我们之前设批量大小 batch_size 为10,每个小批量的损失l的形状为 (10, 1)。回忆一下自动求梯度一节。由于变量l并不是一个标量,所以我们可以调用 reduce_sum() 将其求和得到一个标量,再运行 t.gradients 得到该变量有关模型参数的梯度。注意在每次更新完参数后不要忘了将参数的梯度清零。

  • 在一个迭代周期(epoch)中,我们将完整遍历一遍 data_iter 函数,并对训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。这里的迭代周期个数 num_epochs 和学习率 lr 都是超参数,分别设 3 和 0.03 。在实践中,大多超参数都需要通过反复试错来不断调节。虽然迭代周期数设得越大模型可能越有效,但是训练时间可能过长


lr = 0.01 # 学习率
num_epochs = 20 # 迭代批次
net = linear_reg # 线性回归表达式
loss = loss # 损失函数


for epoch in range(num_epochs):
    # 获取批量数据
    for x,y in data_iter(batch_size, features, labels):
        # 计算梯度
        with tf.GradientTape() as t:
            t.watch([w,b])
            # 对误差值求和
            l = tf.reduce_sum(loss(net(x, w, b),y))
        # 计算梯度
        # gradient(y, x)
        grid = t.gradient(l, [w,b])
        # 优化参数
        sgd([w,b], lr, batch_size, grid)
    # 使用新的损失函数值
    # 使用优化后的参数计算损失值
    train_l = loss(net(features, w,b), labels)
    print('epoch %d, loss %f' % (epoch + 1, tf.reduce_mean(train_l)))
epoch 1, loss 0.000050
epoch 2, loss 0.000050
epoch 3, loss 0.000050
epoch 4, loss 0.000050
epoch 5, loss 0.000050
epoch 6, loss 0.000050
epoch 7, loss 0.000050
epoch 8, loss 0.000050
epoch 9, loss 0.000050
epoch 10, loss 0.000050
epoch 11, loss 0.000050
epoch 12, loss 0.000050
epoch 13, loss 0.000050
epoch 14, loss 0.000050
epoch 15, loss 0.000050
epoch 16, loss 0.000050
epoch 17, loss 0.000050
epoch 18, loss 0.000050
epoch 19, loss 0.000050
epoch 20, loss 0.000050
  • 比较学到的参数和用来生成训练集的真实参数
print(true_w, w)
print(true_b, b)
[2, -3.4] <tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy=
array([[ 2.000148 ],
       [-3.3994756]], dtype=float32)>
4.2 <tf.Variable 'Variable:0' shape=(1,) dtype=float32, numpy=array([4.199801], dtype=float32)>