import torch
# 导入库
torch.cuda.is_available()

为了解释线性回归,我们举一个实际的例子: 我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)

目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下式子:

pytorch回归房价 pytorch房价预测模型_权重

pytorch回归房价 pytorch房价预测模型_权重_02pytorch回归房价 pytorch房价预测模型_线性回归_03称为权重(weight),权重决定了每个特征对我们预测值的影响。

b 称为偏置(bias)、偏移量(offset)或截距(intercept)。 偏置是指当所有特征都取值为0时,预测值应该为多少。 即使现实中不会有任何房子的面积是0或房龄正好是0年,我们仍然需要偏置项。 如果没有偏置项,我们模型的表达能力将受到限制。

Gradient descent summary

So far in this course, you have developed a linear model that predicts pytorch回归房价 pytorch房价预测模型_线性回归_04:
pytorch回归房价 pytorch房价预测模型_机器学习_05
In linear regression, you utilize input training data to fit the parameters pytorch回归房价 pytorch房价预测模型_权重_06,pytorch回归房价 pytorch房价预测模型_线性回归_07 by minimizing a measure of the error between our predictions pytorch回归房价 pytorch房价预测模型_线性回归_04 and the actual data pytorch回归房价 pytorch房价预测模型_权重_09. The measure is called the pytorch回归房价 pytorch房价预测模型_pytorch回归房价_10, pytorch回归房价 pytorch房价预测模型_权重_11. In training you measure the cost over all of our training samples pytorch回归房价 pytorch房价预测模型_机器学习_12
pytorch回归房价 pytorch房价预测模型_pytorch回归房价_13

In lecture, gradient descent was described as:

pytorch回归房价 pytorch房价预测模型_pytorch回归房价_14
where, parameters pytorch回归房价 pytorch房价预测模型_权重_06, pytorch回归房价 pytorch房价预测模型_线性回归_07 are updated simultaneously.
The gradient is defined as:
pytorch回归房价 pytorch房价预测模型_线性回归_17

**Here simultaniously means that you calculate the partial derivatives for all the parameters before updating any of the parameters. **

In lecture, gradient descent was described as:

pytorch回归房价 pytorch房价预测模型_pytorch回归房价_14
where, parameters pytorch回归房价 pytorch房价预测模型_权重_06, pytorch回归房价 pytorch房价预测模型_线性回归_07 are updated simultaneously.
The gradient is defined as:
pytorch回归房价 pytorch房价预测模型_线性回归_17

**Here simultaniously means that you calculate the partial derivatives for all the parameters before updating any of the parameters. **

首先我们自己生成一些数据,这些数据也许跟现实没有任何联系,但是可以用它们指代一个线性关系:即房屋面积以及房龄两个自变量与房价(因变量)之间存在的某种关系。

# 生成数据(使用线性模型参数 w = [ 2 , − 3.4 ] ⊤ b = 4.2 和噪声项 ϵ 生成数据集及其标签):
# y = X w + b + ϵ 
# w, b, 样本数
def synthetic_data(w, b, num_examples):
    # X 为矩阵,表示生成 样本数*len(w) 的(0,1)正态分布的矩阵。
    # len(w) 表示特征数
    X = torch.normal(0, 1, (num_examples, len(w)))
    # X matrix (1000,2) (1000个样例,2个输入特征:面积、房龄)
    # w vector (2)
    y = torch.matmul(X, w) + b
    # y vector (1000)
    # y = Xw + b + 噪声,噪声呈现标准差为 0.01 的正态分布。
    y += torch.normal(0, 0.01, y.shape)
    # (-1, 1) 代表作为列向量返回(指定列数为1,行数自动确定。)
    # 在torch中,如果要区分行向量与列向量的区别,则必须用矩阵表示,对于计算机来说,单纯的一列或一行都是一个一维数组
    return X, y.reshape((-1, 1)) 
# 预先设置好真实的参数w,b(现实中我们可能是不知道这个参数的,需要从大量数据中发现这个规律,从而可以预测新的数据)
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
print(f"features:{features[:3]},\n labels:{labels[:3]}")

查看前三个样本,其中第三个样本的含义就是,当房屋面积为1.3822、房龄为0.9644时,该房屋的房价为3.6739.
features:tensor([[-0.3011, -0.6250],
[ 0.5002, -0.3313],
[ 1.3822, 0.9644]]),
labels:tensor([[5.7323],
[6.3256],
[3.6739]])

注意,上述数据都是伪造的,包括真实的权重参数w和b,接下来我们的任务就是:假设我们不知道真实的权重参数,根据这些大量的样本,如何推断出权重参数(或者尽可能地逼近真实值),这就是线性回归的任务。

首先我们将w,b参数初始化为0

# 随机初始化参数,并计算梯度。
# 划重点: 需要 w,b 进行更新,所以才将 requires_grad设置为 True
w = torch.tensor([0., 0.], requires_grad=True)
b = torch.tensor([0.], requires_grad=True)
w,b

定义平方损失函数

# 计算样本总的损失
def calc_cost(X,w,b,labels,m):
    # 批量梯度下降,求取全部样本的损失相加
    prices = torch.matmul(X,w)+b
    cost = 0
    # 对标上面的公式(2)
    for i in range(m):
        cost += (prices[i] - labels[i])**2
    total_cost = 1 / (2 * m) * cost     

    return total_cost

定义批量梯度下降算法

# 批量梯度下降算法,参数,学习率
def sgd(params, lr):  
    # 更新时不参与梯度计算(simultaneously update,同时对w和b求偏导,然后再更新参数w,b)(对应公式(3))
    with torch.no_grad(): # 当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存
        for param in params:
            param -= lr * param.grad
            print(f"参数更新为:{param}")
            param.grad.zero_()  # 每一个批次都要对每个参数的梯度进行清零(这里的一个批次就是全部样本)

训练过程如下,可以自行更改迭代次数和学习率。

# 训练
# 学习率
lr = 0.008
# 迭代次数
num_epochs = 600
loss = calc_cost

for epoch in range(num_epochs):
    total_cost = calc_cost(features,w,b,labels,len(labels))  # 批量总损失(对标公式(2))
    print(f"第{epoch+1}轮迭代的总损失:{total_cost}") 
    total_cost.backward() # 自动求导(对标公式(4)(5))
    print(f"梯度分别为:{w.grad},{b.grad}") # 临时偏导数(梯度)
    sgd([w,b],lr) # 根据梯度,更新参数(对应公式(3))
    print(f"面积权重、房龄权重的真实参数为:{true_w}\n偏置的真实参数为:{true_b}\n")
第597轮迭代的总损失:tensor([0.0014], grad_fn=<MulBackward0>)
梯度分别为:tensor([-0.0192,  0.0320]),tensor([-0.0352])
参数更新为:tensor([ 1.9793, -3.3672], requires_grad=True)
参数更新为:tensor([4.1645], requires_grad=True)
面积权重、房龄权重的真实参数为:tensor([ 2.0000, -3.4000])
偏置的真实参数为:4.2

第598轮迭代的总损失:tensor([0.0014], grad_fn=<MulBackward0>)
梯度分别为:tensor([-0.0190,  0.0318]),tensor([-0.0350])
参数更新为:tensor([ 1.9795, -3.3674], requires_grad=True)
参数更新为:tensor([4.1648], requires_grad=True)
面积权重、房龄权重的真实参数为:tensor([ 2.0000, -3.4000])
偏置的真实参数为:4.2

第599轮迭代的总损失:tensor([0.0013], grad_fn=<MulBackward0>)
梯度分别为:tensor([-0.0189,  0.0315]),tensor([-0.0347])
参数更新为:tensor([ 1.9796, -3.3677], requires_grad=True)
参数更新为:tensor([4.1651], requires_grad=True)
面积权重、房龄权重的真实参数为:tensor([ 2.0000, -3.4000])
偏置的真实参数为:4.2

第600轮迭代的总损失:tensor([0.0013], grad_fn=<MulBackward0>)
梯度分别为:tensor([-0.0188,  0.0313]),tensor([-0.0344])
参数更新为:tensor([ 1.9798, -3.3679], requires_grad=True)
参数更新为:tensor([4.1653], requires_grad=True)
面积权重、房龄权重的真实参数为:tensor([ 2.0000, -3.4000])
偏置的真实参数为:4.2

可以看到,在训练的过程中,w,b参数在不断地根据梯度下降的方向进行调整,损失也随之不断减小,最终损失下降到接近0,w,b参数也调整至接近真实参数。