CoderXL's Blog

Back

线性回归模型Blur image

简述#

线性回归是十分基础的预测模型。它对输入进行加权和,再加上一个偏差来预测因变量。

线性回归可以看作单层神经网络。

符号化表述#

单个样本#

单个样本的自变量:x\vec x 单个样本的因变量:yy 学习到的权重:w\vec w 学习到的偏差:bb

那么预测模型的预测结果就是 y^=xw+b\hat y = \vec x \cdot \vec w + b 此次预测的损失(常用平方损失)是:(y,y^)=12(yy^)2\ell(y, \hat y)= {1\over 2}(y-\hat y)^2

  • 此处使用系数 121\over 2 是为了求导方便

训练#

我们一般将大量样本打包成矩阵 X\boldsymbol{X}, 其中每个样本自变量 xi\vec x_i 作为一个行向量。 然后再将每个样本的因变量打包成列向量 y\vec y。 这样,可以统一地计算损失 (X,y,w,b)=12nyXwb2\ell(\boldsymbol{X},\vec y,\vec w, b) = {1 \over 2n} ||\vec y - \boldsymbol{X}\vec w - b||^2

  • 其中 bb 与向量的减法涉及了广播,即向量的每个分量都减去 bb

为了能一并训练偏差 bb,我们实践中会将 bb 融合到 w\vec w 中,方法是给 w\vec w 加一行 w[wb]\displaystyle{\vec w \leftarrow \left[\begin{matrix}\vec w \\ b\end{matrix}\right]},然后再给 X\boldsymbol{X} 加一个全 11X[X,1]\displaystyle{\boldsymbol{X} \leftarrow [\boldsymbol{X}, \boldsymbol{1}]} 这样损失函数可以简化为 (X,y,w)=12nyXw2=12n(yXw)(yXw)\ell(\boldsymbol{X},\vec y, \vec w) = {1\over 2n} ||\vec y - \boldsymbol{X}\vec w||^2 = {1\over 2n} (\vec y - \boldsymbol{X}\vec w) \cdot (\vec y - \boldsymbol{X}\vec w)

显式解#

我们计算线性回归模型的损失函数的梯度:

w(X,y,w)=1n(yXw)TX{\partial \over \partial \vec w} \ell(\boldsymbol{X},\vec y, \vec w) = {1\over n}(\vec y - \boldsymbol{X} \vec w)^T\boldsymbol{X}

由于是个线性回归的损失函数是个二次型,因此它是凸函数(也是我们将会学习的所有模型中唯一的凸函数,因为凸函数对应的是 p 问题,而深度学习主要解决 np 问题,这些问题难以抽象成凸函数),存在显式最优解(可遇不可求,以后不会再有了)。 解

 w(X,y,w)=0 1n(yXw)TX=0 w=(XTX)1Xy\begin{aligned} &~\begin{aligned} {\partial \over \partial \vec w} \ell(\boldsymbol{X},\vec y, \vec w) &= 0\\ \Leftrightarrow ~ {1\over n}(\vec y - \boldsymbol{X} \vec w)^T\boldsymbol{X} &= 0\\ \end{aligned}\\ &\Leftrightarrow ~ \vec w^* = (\boldsymbol{X}^T \boldsymbol{X})^{-1}\boldsymbol{X} \vec y \end{aligned}

代码实现#

手动代码实现(来自 d2l)#

import torch
import random

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)

# 样本真实参数
true_w = torch.tensor([2, -3.4])
true_b = 4.2 # No need to use torch.tensor
features, labels = synthetic_data(true_w, true_b, 1000)

def data_iter(batch_size, features, labels):
    num_examples = labels.numel()
    indices = list(range(num_examples))
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

batch_size = 10

# 随机化初始参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

def linreg(X, w, b):  
    """线性回归模型"""
    return torch.matmul(X, w) + b

def squared_loss(y_hat, y):  
    """均方损失"""
    return ((y_hat - y.reshape(y_hat.shape)) ** 2 / 2).sum()/y.numel();

def sgd(params, lr):  
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad
            param.grad.zero_()

# 训练过程

lr = 0.03	# 学习率 learning rate
num_epochs = 3 	# 轮数
# 通用 alias 方便移植
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)
        l.backward()
        sgd([w, b], lr)
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
py

借助 torch.nn 的代码实现(来自 d2l)#

import torch
from torch.utils import data
from torch import nn

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)

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

def load_array(data_arrays, batch_size, is_train=True):
	dataset = data.TensorDataset(*data_arrays)
	return data.DataLoader(dataset, batch_size, shuffle=is_train)
	
batch_size = 10
data_iter = load_array((features, labels), batch_size)

net = nn.Sequential(nn.Linear(2, 1))

# 下面这样直接原位修改会报错
# RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.
# net[0].weight.normal_(0, 0.01)
# net[0].bias.fill_(0)

# 下面的写法是较早版本的做法,现在不推荐
# net[0].weight.data.normal_(0, 0.01)
# net[0].bias.data.fill_(0)

# 下面是标准做法:在 no_grad 上下文中原位修改参数,进行初始化
with torch.no_grad():
	net[0].weight.normal_(0, 0.01)
	net[0].bias.fill_(0)
	
# Mean Squared Error 平方均值误差
loss = nn.MSELoss()
# 将网络的参数通过 .parameters() 返回的 iterator 传递给 trainer
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)    # 此处 nn.MSELoss 已经自动完成求和与求均值了
		trainer.zero_grad()    # 清零梯度
		l.backward()
		trainer.step()         # 按照学习率进行梯度下降
	# 在每轮 epoch 评估的时候暂停自动求导功能,节省性能
	with torch.no_grad():
	    l = loss(net(features), labels)
	    print(f'epoch {epoch + 1}, loss {l:f}')

w = net[0].weight.detach()
b = net[0].bias.detach()
print('w的估计误差:', true_w - w.reshape(true_w.shape))
print('b的估计误差:', true_b - b)
py
线性回归模型
https://blog.leosrealms.top/blog/2025-11-11-linear-regression-model
Author CoderXL
Published at 2025年11月11日
Comment seems to stuck. Try to refresh?✨