参考视频:b站,李沐老师,动手学习深度学习pytorch
链接:https
线性回归是一个最基础的模型
以预测房价为例子
我们假设影响房价的关键因素是卧室个数、卫生间个数和居住面积,记为 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3
假设我们的房价y是关键因素的加权和
y
=
w
1
x
1
+
w
2
x
2
+
w
3
x
3
+
b
y=w_1x_1+w_2x_2+w_3x_3+b
y=w1x1+w2x2+w3x3+b
其中的w和b分别是权重和偏差。(后面再决定)
那么把这个拓展为一个一般化的线性模型,如图所示

那么这个线性模型就是一个简单的模型,可以看作一个单层的神经网络。
输入层就是 x 1 , x 2 , x 3 . . . x_1,x_2,x_3... x1,x2,x3...,输出层就是房价y。
有了模型之后,我们要做预测,那么我们还需要评估我们的预测的质量,也就是比较真实值和预估值。
假设y是真实值,
y
^
\widehat{y}
y
是预估值,我们用如下函数来评价预测的质量(这是常用的一个平方差)
φ
(
y
,
y
^
)
=
1
2
(
y
−
y
^
)
2
\varphi(y,\widehat{y})=\frac{1}{2} (y-\widehat{y})^2
φ(y,y
)=21(y−y
)2
至于前面提到的权重和偏差如何决定呢?我们通过一些大量的数据来决定。
比如,我们收集过去几个月的房子成交数据,我们称为训练数据。

这个大写的 X X X就是我们有n个样本,每一行都是一个向量,如 x 1 x_1 x1就是一个向量,就是一个成交房子的数据,也就是我们前面说的诸多关键因素, y y y也是n组数据,每一个 y 1 y_1 y1都是一个实数的数值,也就是房子的成交价。
那么对应我们之前说的评估函数,我们称为损失函数:
前面我们说了,我们的预测房价
y
=
<
x
,
w
>
+
b
y=

那么这个损失函数就是评估了预测值与真实值的差距,那么我们当然是期望我们预测的越来越准,所以我们的目标就是求出这个损失函数的最小值。
怎么求最小值呢,就是不断地调整w和b的值,我们的目标就是找到一个w和b,使得我们的整个损失函数取值最小
那么这个过程也是我们确定w和b的取值的过程,这就是我们的参数学习过程,在这个训练过程中,不断学习,调整权重和偏差。

因为我们这是一个线性模型,所以我们是有显式解的,也就是能直接表示出他的解。
他的显式解怎么求呢?
首先,我们把偏差b,全部设为1,然后把他加入到W中去。
这样就直接写作 y=Wx,不要b了。然后对这个损失函数求偏导,可以直接求出来W

那么这就是一个完整的线性回归的模型。
在没有显式解的情况下,我们一般采用的办法是梯度下降法:
就是随机的选择一个初试参数: W 0 W_0 W0

这个方法的大概意思就是:比如当前时刻的参数值是 W t W_t Wt ,上一时刻的参数值是 W t − 1 W_{t-1} Wt−1
那么 W t − 1 W_{t-1} Wt−1 求导,就是求出 W t − 1 W_{t-1} Wt−1 的梯度(就是这个点上函数值下降最快的方向),
这个梯度取一个负号,负的梯度,就是下降的方向。
这个 η \eta η是学习率,就是一次下降程度大概是多少,这俩相乘,用 W t − 1 W_{t-1} Wt−1 加上这个结果,就是下一时刻的 W t W_{t} Wt

如图所示,就是这样不断下降,不断靠近最优解(最小值)
这个学习率不应过大(一次下降太多,来回震荡),也不应过小(下降太慢)
然后这个梯度下降,我们每次下降,都要对我们的整个损失函数求导,而这个损失函数是对整个样本的损失。
对所有的数据,都计算一遍,那耗时太久。
所以,我们还会采用“小批量随机梯度下降”
每次采样一部分数据进行下降

这个样本也是一样,不能取太大,也不能取太小。
torch.normal(mean=0.,std=1.,size=(2,2))
该函数返回从单独的正态分布中提取的随机数的张量,该正态分布的均值是mean,标准差是std。
torch.matmul(X, w)做乘法
detach就是说从梯度计算的计算图摘出来,不需要做梯度计算了。
reshape(行,列)
with torch.no_grad():下面的代码都不要梯度计算
import torch
import random
import matplotlib.pyplot as plt
# 三个参数 我们给定真实的 w 和 b 以及我们要生成的数据量num_examples
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
# 仿照给定的 w 生成n行数据
X = torch.normal(0, 1, (num_examples, len(w)))
# torch.matmul 做乘法
y = torch.matmul(X, w) + b
# 再加一个噪声
y += torch.normal(0, 0.01, y.shape)
# reshape(行,列) -1 代表未给定
return X, y.reshape((-1, 1))
# 这个函数就是传入一些数据 我每次给你返回随机的一批样本 大小由你指定为batch_size
# 该函数接收特征矩阵和标签向量作为输入,给定一个batch_size,生成大小为batch_size的小批量
# 接收一组样本 features labels 大小为batch_size
def data_iter(batch_size, features, labels):
# 先看下数据一共多少个
num_examples = len(features)
# 生成n个下标
indices = list(range(num_examples))
# 把这些下标打乱顺序 这样待会可以随机访问
random.shuffle(indices)
# 遍历 i从0到n 每次间隔batch_size个大小
for i in range(0, num_examples, batch_size):
# 这个就是取出这个批量的数据
# 比如batch_size=10 循环时候i=0,10,20,30...
# 进入循环就是取0-10的这10个数据 防止超出范围 所以用一个min
batch_indices = torch.tensor(indices[i:min(i +batch_size, num_examples)])
# yield就相当于一个迭代器 他使得这个函数可以被迭代
# 就是每次调用都会返回一组大小为batch_size的样本
yield features[batch_indices], labels[batch_indices]
def linreg(X, w, b):
"""线性回归模型。"""
return torch.matmul(X, w) + b
def squared_loss(y_hat, y):
"""均方损失。y_hat是真实值 y是预测值 相减 再平方 再1/2 """
return (y_hat - y.reshape(y_hat.shape))**2 / 2
def sgd(params, lr, batch_size):
"""小批量随机梯度下降。给定参数 学习率 batch_size"""
with torch.no_grad():
for param in params:
# 更新每一个参数 求损失的时候没有做均值 所以这里做均值
param -= lr * param.grad / batch_size
# 梯度清0
param.grad.zero_()
if __name__ == '__main__':
# 给定真实的w 和 b
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 生成1000组 X是特征 y是标注
features, labels = synthetic_data(true_w, true_b, 1000)
# plt.scatter(features[:, 1].detach().numpy(),labels.detach().numpy(), 1);
# plt.show()
lr = 0.03 # 学习率
num_epochs = 3 # 迭代次数
net = linreg # 网络模型
loss = squared_loss # 损失函数
batch_size = 10 # 批量大小
# 初始化我们的参数模型
# w随机初始化为均值=0 方差=0.01 大小2行1列的向量
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# 偏差直接给0
b = torch.zeros(1, requires_grad=True)
for epoch in range(num_epochs):
# 每次拿出batch_size的数据
for X, y in data_iter(batch_size, features, labels):
# 计算损失
l = loss(net(X, w, b), y)
# 求和 sgd里面做了平均
# backward() 求梯度
l.sum().backward()
# 用参数去优化
sgd([w, b], lr, batch_size)
with torch.no_grad():
# net(features, w, b)是对整个数据的预测 与 labels 求个损失
# 就是评价一下预测结果
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
# 因为我们是自己建的数据集 所以我们是知道真实的w和b的
# 看一下我们学习得到的w和b 与 真实值的差距
'''
整个过程其实就是 我们先假设一个真实的w和b
然后呢 再生成一组随机的向量 合在一起叫做X
再结合 y=wX+b 这个线性函数 得到一组y(为了增加难度 我们还加了一组噪音)
然后 我们把 X 和 y 看作我们的训练数据
然后开始完整的机器学习求解过程
我们面对 X 和 y ,使用我们定义好的线性模型
目的是求解出w和b 我们随机的初始化一个w和b 然后使用梯度下降法
不断的更新我们的w和b 经过多次训练 求解得到一个 w和b
'''
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
输出结果:
epoch 1, loss 0.034254
epoch 2, loss 0.000125
epoch 3, loss 0.000049
w的估计误差: tensor([ 0.0004, -0.0002], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0011], grad_fn=<RsubBackward1>)
Process finished with exit code 0
那么我们把上面那个模型,使用pytorch提供的一些函数,来快速方便的实现一下:
import random
import matplotlib.pyplot as plt
import numpy as np
import torch
from torch import nn
from torch.utils import data
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器。"""
# 把所有数据送进来
dataset = data.TensorDataset(*data_arrays)
# 生成一个batch_size大小的批量数据 shuffle代表顺序随机
return data.DataLoader(dataset, batch_size, shuffle=is_train)
if __name__ == '__main__':
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 生成数据集
features, labels = synthetic_data(true_w, true_b, 1000)
# 指定 batch_size 创建数据迭代器
batch_size = 10
data_iter = load_array((features, labels), batch_size)
# 定义网络 Sequential 就是把网络的每个层依次放在这里面
net = nn.Sequential(nn.Linear(2, 1))
# 初始化 网络参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# 损失函数
loss = nn.MSELoss()
# 优化器
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# 开始训练
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
# 计算损失
l = loss(net(X), y)
# 梯度清0
trainer.zero_grad()
# 前向传播 求梯度
l.backward()
# 更新权重和偏差
trainer.step()
# 训练一代之后 评估一下
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
# 输出训练结果与真实值的差
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
输出结果:
epoch 1, loss 0.000214
epoch 2, loss 0.000100
epoch 3, loss 0.000100
w的估计误差: tensor([0.0003, 0.0003])
b的估计误差: tensor([-0.0002])