《动手学深度学习》之线性回归的从零实现(含个人理解)

线性回归的从零实现

尽管强⼤的深度学习框架可以减少⼤量重复性⼯作,但若过于依赖它提供的便利,会导致我们很难深⼊理解深度学习是如何⼯作的。因此,本节将介绍如何只利⽤NDArray和autograd来实现⼀个线性回归的训练。

⾸先,导⼊本节中实验所需的包或模块

from IPython import display
from mxnet import autograd, nd
import random

1.生成数据集

我们构造⼀个简单的⼈⼯训练数据集,它可以使我们能够直观⽐较学到的参数和真实的模型参数的区别。设训练数据集样本数为1000,输⼊个数(特征数)为2。给定随机⽣成的批量样本特征X ∈ R1000×2,我们使⽤线性回归模型真实权重w = [2, -3.4]和偏差b = 4.2,以及⼀个随机噪声项ϵ来⽣成标签:

y = X w + b + ϵ y = Xw + b + ϵ y=Xw+b+ϵ

实际上这个公式可以写成这样以便理解:

y = X y = X y=X1 w w w 1 + X +X +X2 w w w2 + b + ϵ + b + ϵ +b+ϵ

其中噪声项ϵ服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中⽆意义的⼲扰。下⾯,让我们⽣成数据集。

num_inputs = 2 #输入个数(特征数)
num_example = 1000 #样本数
true_w = [2, -3.4] #设置真实权重w
true_b = 4.2 #设置真实偏差b
#features为数据集,即生成了行数为1000,列数为2的服从均值为0、标准差为1的正态分布的随机数据
features = nd.random.normal(scale=1, shape=(num_example, num_inputs))
#labels是正确的符合y = Xw + b + ϵ的y值矩阵
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
#我们为这个y值加上一个很小的噪声项ϵ,生成了有些许偏差的标签y值矩阵
labels += nd.random.normal(scale=0.01, shape=labels.shape)

为labels加上噪声项ϵ的意义
从代码中我们可以看到,最开始的lables是通过真实的权重 w w w和偏差 b b b,利用 y = X w + b y = Xw + b y=Xw+b求出来的,为的是让lables跟 w w w具有线性关系。而我们线性回归模型的任务是求出根据labels去求出 w w w b b b,现实中的数据不可能完美地符合线性回归,所及加上噪声项去模拟有些许偏差的测试数据。

注意
features的每⼀⾏是⼀个⻓度为2的向量,而labels的每⼀⾏是⼀个⻓度为1的向量(标量)

2.读取数据

在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这⾥我们定义⼀个函数:
它每次返回batch_size(批量⼤小)个随机样本的特征和标签。

def data_iter(batch_size, features, labels):
    num_example = len(features)
    indices = list(range(num_example))
 # 样本的读取顺序是随机的,这里之后indices就变成了一个包含打乱了顺序的0-999数字的集合
    random.shuffle(indices) 
    for i in range(0, num_example, batch_size):
        j = nd.array(indices[i:min(i + batch_size, num_example)])
        yield features.take(j), labels.take(j)  # take函数根据索引返回对应元素

batch_size = 10;#先在这里定义好批量大小为10

每个批量的特征形状为(10, 2),分别对应批量⼤小和输⼊个数;标签形状为批量⼤小。

3.初始化模型参数

我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。

# 初始化模型参数
w = nd.random.normal(scale=0.01, shape=(num_inputs, 1))
b = nd.zeros(shape=(1,))

注意这个 w w w b b b与上面的定义的true_w和true_b的差别
这里的是我们初始化的w和b,即我们在不知道真实 w w w b b b时随机设置的初始值,在后面的训练中 w w w b b b会不断接近真实的true_w和true_b。

之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们需要创建它们的梯度。

#创建w和b的梯度
w.attach_grad()
b.attach_grad()

4.定义模型

下⾯是线性回归的⽮量计算表达式的实现。我们使⽤dot函数做矩阵乘法。

def linreg(X, w, b):
    return nd.dot(X, w) + b

理解这个函数
这个函数需要X,w,b三个参数,X即我们的特征矩阵features,w和b是我们自己定义的参数(而不是true_w和true_b),这样做了dot矩阵乘法后,return出来的就是经过 X X X1 w w w 1 + X +X +X2 w w w2 + b +b +b计算得到的 y y y值矩阵

5.定义损失函数

我们定义一个损失函数,用来计算我们的线性回归模型计算出来的 y y y值和真实的 y y y值(含有噪声项)之间的差距(即损失),后面需要对这个损失求梯度。

# 定义损失函数
#y_hat是预测值,y是真实值
def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

上面提到,真实值y(即labels)是一个标量,而预测值y_hat是由矩阵经过计算得到的,是一个向量,两者无法进行数学运算,所以我们需要把真实值y变形成预测值y_hat的形状,就相当于把标量变成了向量。函数返回的结果也将和y_hat的形状相同(即也是向量)。

6.定义优化算法

以下的sgd函数实现了小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这⾥⾃动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。我们将它除以批量⼤小来得到平均值。

def sgd(params, lr, batch_size):
    for param in params:
        param[:] = param - lr * param.grad / batch_size

7.训练模型

在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的小批量数据样本(特征X和标签y),通过调⽤反向函数backward计算小批量随机梯度,并调⽤优化算法sgd迭代模型参数。由于我们之前设批量⼤小batch_size为10,每个小批量的损失l的形状为(10, 1)。由于变量l并不是⼀个标量,运⾏l.backward()将对l中元素求和得到新的变量,再求该变量有关模型参数的梯度。

在⼀个迭代周期(epoch)中,我们将完整遍历⼀遍data_iter函数,并对训练数据集中所有样本都使⽤⼀次(假设样本数能够被批量⼤小整除)。这⾥的迭代周期个数num_epochs和学习率lr都是超参数,分别设3和0.03。在实践中,⼤多超参数都需要通过反复试错来不断调节,这里直接给出了一个较为准确的超参数以减少工作量,学习率对模型的影响将在以后介绍。虽然迭代周期数设得越⼤模型可能越有效,但是训练时间可能过⻓。

# 训练模型
lr = 0.03
num_epochs = 3
net = linreg #将模型函数重命名为net
loss = squared_loss #将损失函数重命名为loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        with autograd.record():
            l = loss(net(X, w, b), y)  # l是有关⼩批量X和y的损失
        l.backward()  # ⼩批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)  # 使⽤⼩批量随机梯度下降迭代模型参数
        train_l = loss(net(features, w, b), labels)
        print('epoch%d,loss%f' % (epoch + 1, train_l.mean().asnumpy()))

训练过程部分打印信息如下:

epoch1,loss15.373573
epoch1,loss14.425648
epoch1,loss13.684246
......
epoch2,loss0.007572
epoch2,loss0.007226
epoch2,loss0.006662
......
epoch3,loss0.000049
epoch3,loss0.000049
epoch3,loss0.000049

可以看到,在训练过程中,损失一直在减少。打印出学来的参数w和b与真实参数来比较它们之间的差距(因为有噪声项的干扰,两者必然不相等):

代码:

print(true_b, b)
print(true_w, w)

结果:

<NDArray 1 @cpu(0)>
[2, -3.4] #真实参数w
[[ 1.9999706]
 [-3.3994975]] #训练之后的模型参数w

4.2  #真实参数b 
[4.19931] #训练之后的模型参数b

可以看到两者十分的接近!

8.完整代码

# ==========本节将介绍如何只利⽤NDArray和autograd来实现⼀个线性回归的训练。=============
from IPython import display
from mxnet import autograd, nd
import random

# 生成数据集
num_inputs = 2
num_example = 1000
true_w = [2, -3.4]
true_b = 4.2
features = nd.random.normal(scale=1, shape=(num_example, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)


# 读取数据
# 在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。
# 这⾥我们定义⼀个函数:它每次返回batch_size(批量⼤小)个随机样本的特征和标签。
def data_iter(batch_size, features, labels):
    num_example = len(features)
    indices = list(range(num_example))
    random.shuffle(indices)  # 样本的读取顺序是随机的
    for i in range(0, num_example, batch_size):
        j = nd.array(indices[i:min(i + batch_size, num_example)])
        yield features.take(j), labels.take(j)  # take函数根据索引返回对应元素


batch_size = 10;

# 初始化模型参数
w = nd.random.normal(scale=0.01, shape=(num_inputs, 1))
b = nd.zeros(shape=(1,))
w.attach_grad()
b.attach_grad()


# 定义模型
# 下⾯是线性回归的⽮量计算表达式的实现。我们使⽤dot函数做矩阵乘法。
def linreg(X, w, b):
    return nd.dot(X, w) + b


# 定义损失函数
def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2


# 定义优化算法
# 以下的sgd函数实现了上⼀节中介绍的小批量随机梯度下降算法。
# 它通过不断迭代模型参数来优化损失函数。
# 这⾥⾃动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。我们将它除以批量⼤小来得到平均值。
def sgd(params, lr, batch_size):
    for param in params:
        param[:] = param - lr * param.grad / batch_size


# 训练模型
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
# 训练模型⼀共需要num_epochs个迭代周期
# 在每⼀个迭代周期中,会使⽤训练数据集中所有样本⼀次(假设样本数能够被批量⼤⼩整除)。
# X和y分别是⼩批量样本的特征和标签
for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        with autograd.record():
            l = loss(net(X, w, b), y)  # l是有关⼩批量X和y的损失
        l.backward()  # ⼩批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)  # 使⽤⼩批量随机梯度下降迭代模型参数
        train_l = loss(net(features, w, b), labels)
        print('epoch%d,loss%f' % (epoch + 1, train_l.mean().asnumpy()))

print(true_b, b)
print(true_w, w)

你可能感兴趣的:(深度学习,深度学习,python)