D2L(1) — 线性回归

作者:IguoChan日期:2025/11/30

0. 背景

虽然一直从事的是工程开发,但是目前从事的工作和算法、特别是大模型相关,因此想了解一下算法的相关基础,而d2l就是入门的教程,可参考dl2

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。 在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。

比如书中中的线性回归就是一个特别简单的例子,即根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。从简单的直观感受来说,房子的价格应该和面积成一定的正比,和房龄成反比。

其实在书中,已经将基本的原理介绍得很清楚了,但是有些地方对于年逾三十,离开大学很久的我来说理解起来不是那么直观,所以我这边会详细描述我所困在的点。

1. 基本推导

1.1 线性模型

这里的算法其实很简单,根据过往历史值求出根据某房屋面积和房龄的预测值,即可得以下公式:

price=warea×area+wage×age+bprice = w_{area} \times area + w_{age} \times age + bprice=warea​×area+wage​×age+b

这里{area,age}\{area, age\}{area,age}被称为特征,而{warea,wage}\{w_{area}, w_{age}\}{warea​,wage​}被称为权重,使用向量x={x1,x2}\mathbf{x} = \{ x_1, x_2 \}x={x1​,x2​}表征特征,向量w={w1,w2}\mathbf{w} = \{ w_1, w_2 \}w={w1​,w2​}表征权重,则针对于输入为 {x1,x2}\{ x_1, x_2\}{x1​,x2​}的预测值 y^\hat{y}y^​ 的计算公式如下:

y^=wTx+b\hat{y} = \mathbf{w}^\mathrm T\mathbf{x} + by^​=wTx+b

那对于一系列的特征集合X=(area1age1area2age2...areanagen)\mathbf{X} = \begin{pmatrix} area_1 & age_1 \\ area_2 & age_2 \\ ... \\ area_n & age_n \end{pmatrix}X=​area1​area2​...arean​​age1​age2​agen​​​,则整个预测值的预测曲线可以表征为:

y^=Xw+b\mathbf{\hat{y}} = \mathbf{X}\mathbf{w}+\mathbf{b}y^​=Xw+b

其中b={b,b,...,b}\mathbf{b} = \{b,b,...,b\}b={b,b,...,b}。

那其实线性回归本质上就是求取一组 w\mathbf{w}w 和 bbb ,使得预测值和样本值之间的误差尽可能的小。

所以本质问题变成了:

  • 怎么计算误差?
  • 怎么使得误差减小?

1.2 怎么计算误差(损失函数)

对于线性模型,我们可以很容易的使用平方差作为其损失函数:

L(w,b)=1n∑i=1n12(y^(i)−y(i))2=1n∑i=1n12(wTx(i)+b−y(i))2L(\mathbf{w}, b) = \frac{1}{n} \sum_{i=1}^{n} \frac{1}{2}(\hat{y}^{(i)} - y^{(i)})^2 = \frac{1}{n} \sum_{i=1}^{n} \frac{1}{2}(\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)})^2L(w,b)=n1​i=1∑n​21​(y^​(i)−y(i))2=n1​i=1∑n​21​(wTx(i)+b−y(i))2

1.3 怎么使得误差减小(随机梯度下降)

其实这里也是我最开始不太理解的地方:

  • 书中这个最终的公式是怎么推导的?
  • 怎么理解这个梯度下降的过程?

首先我们计算单个样本的损失:

l(i)(w,b)=12(y^(i)−y(i))2=12(wTx(i)+b−y(i))2l^{(i)}(\mathbf{w}, b) = \frac{1}{2}(\hat{y}^{(i)} - y^{(i)})^2 = \frac{1}{2}(\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)})^2l(i)(w,b)=21​(y^​(i)−y(i))2=21​(wTx(i)+b−y(i))2

对于 w\mathbf{w}w 中某个权重 wjw_jwj​ 梯度:

∂l(i)∂wj=∂l(i)∂y^(i)⋅∂y^(i)∂wj\frac{\partial l^{(i)}}{\partial w_j} = \frac{\partial l^{(i)}}{\partial \hat{y}^{(i)}} \cdot \frac{\partial \hat{y}^{(i)}}{\partial w_j}∂wj​∂l(i)​=∂y^​(i)∂l(i)​⋅∂wj​∂y^​(i)​

而:

∂l(i)∂y^(i)=y^(i)−y(i)\frac{\partial l^{(i)}}{\partial \hat{y}^{(i)}} = \hat{y}^{(i)} - y^{(i)}∂y^​(i)∂l(i)​=y^​(i)−y(i)

又因为 y^=w1x1+w2x2+...+wnxn+b\hat{y} = w_1x_1 + w_2x_2+ ... +w_nx_n + by^​=w1​x1​+w2​x2​+...+wn​xn​+b,所以:

∂y^(i)∂wj=xj(i)\frac{\partial \hat{y}^{(i)}}{\partial w_j} = x_j^{(i)}∂wj​∂y^​(i)​=xj(i)​

所以单个样本的损失:

∂l(i)∂wj=(y^(i)−y(i))⋅xj(i)=(wTx(i)+b−y(i))⋅xj(i)\frac{\partial l^{(i)}}{\partial w_j} = (\hat{y}^{(i)} - y^{(i)}) \cdot x_j^{(i)} = (\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)}) \cdot x_j^{(i)}∂wj​∂l(i)​=(y^​(i)−y(i))⋅xj(i)​=(wTx(i)+b−y(i))⋅xj(i)​

所以对于所有样本的损失:

∂L∂wj=1n∑i=1n(y^(i)−y(i))⋅xj(i)=1n∑i=1n(wTx(i)+b−y(i))⋅xj(i)\frac{\partial L}{\partial w_j} = \frac{1}{n} \sum_{i=1}^{n}(\hat{y}^{(i)} - y^{(i)}) \cdot x_j^{(i)} = \frac{1}{n} \sum_{i=1}^{n}(\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)}) \cdot x_j^{(i)}∂wj​∂L​=n1​i=1∑n​(y^​(i)−y(i))⋅xj(i)​=n1​i=1∑n​(wTx(i)+b−y(i))⋅xj(i)​

那么针对所有的权重,其梯度向量为:

∇wL={∂L∂w1,∂L∂w2,...,∂L∂wn}=1nXT(y^−y)\nabla_wL = \{\frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2},...,\frac{\partial L}{\partial w_n}\} = \frac{1}{n}\mathbf{X}^\mathrm T(\hat{y} - y)∇w​L={∂w1​∂L​,∂w2​∂L​,...,∂wn​∂L​}=n1​XT(y^​−y)

我们来简单理解一下上面冰冷的公式:∂L∂wj\frac{\partial L}{\partial w_j}∂wj​∂L​ 梯度表示在wjw_jwj​这个方向上,在wjw_jwj​这个位置上的斜率,那么我们要找到使得函数更小的点,就应该朝着反方向挪动一步,使其向极小值收敛。

w←w−ηm⋅∑i=1mx(i)(wTx(i)+b−y(i))\mathbf{w} \leftarrow \mathbf{w} - \frac{\eta}{m} \cdot \sum_{i=1}^{m} \mathbf{x}^{(i)} (\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)})w←w−mη​⋅i=1∑m​x(i)(wTx(i)+b−y(i))

其中,η\etaη 表示挪动的一小步,即学习率;mmm 表示取样的小批量。同理:

b←b−ηm⋅∑i=1m(wTx(i)+b−y(i))b \leftarrow b - \frac{\eta}{m} \cdot \sum_{i=1}^{m} (\mathbf{w}^\mathrm T\mathbf{x}^{(i)} + b - y^{(i)})b←b−mη​⋅i=1∑m​(wTx(i)+b−y(i))

2. 实际验证

书上的3.2. 线性回归的从零开始实现 写的十分的详细,这里我就不复述相关操作了,然后我们画一张图更加形象地表征一下我们梯度逐步减小的过程。

为了方便验证,我在sgd中做如下改动:

1def sgd(params, lr, batch_size):  #@save
2    """小批量随机梯度下降"""
3    with torch.no_grad():
4        for param in params:
5            if param.shape[0] == 2:
6                print("w: ", param)
7                print("w.grad: ", param.grad)
8            param -= lr * param.grad / batch_size
9            param.grad.zero_()
10

然后我们在jupyter的最后加上如下的代码,再将以上打印的数据复制到下面:(或者优雅一点将打印输出到文件,这里再从文件读出)

1import torch
2import numpy as np
3import matplotlib.pyplot as plt
4from mpl_toolkits.mplot3d import Axes3D
5import re
6
7# 1. 解析打印日志,提取w的更新路径
8def parse_w_trajectory(log_text):
9    """从打印日志中解析w的更新轨迹"""
10    pattern = r"w:\s*tensor\(\[\[(.*?)\],\s*\[(.*?)\]\]"
11    matches = re.findall(pattern, log_text)
12    
13    w_trajectory = []
14    for match in matches:
15        w1 = float(match[0].strip())
16        w2 = float(match[1].strip())
17        w_trajectory.append([w1, w2])
18    
19    return np.array(w_trajectory)
20
21# 2. 生成模拟数据 (与之前相同)
22def synthetic_data(w_true, b_true, num_examples):
23    X = torch.normal(0, 1, (num_examples, len(w_true)))
24    y = torch.matmul(X, w_true) + b_true
25    y += torch.normal(0, 0.01, y.shape)
26    return X, y.reshape((-1, 1))
27
28true_w = torch.tensor([2.0, -3.4])
29true_b = 4.2
30features, labels = synthetic_data(true_w, true_b, 1000)
31
32def compute_mse(X, y, w, b):
33    y_hat = linear_regression(X, w, b)
34    return squared_loss(y_hat, y).mean()
35
36# 4. 创建网格计算损失曲面 (与之前相同)
37b_fixed = true_b
38w1_range = torch.linspace(0.0, 5.0, 50)
39w2_range = torch.linspace(-5.0, 0.0, 50)
40W1, W2 = torch.meshgrid(w1_range, w2_range, indexing='ij')
41Z = torch.zeros_like(W1)
42
43for i in range(len(w1_range)):
44    for j in range(len(w2_range)):
45        w_current = torch.tensor([W1[i, j], W2[i, j]], dtype=torch.float32)
46        Z[i, j] = compute_mse(features, labels, w_current, b_fixed)
47
48# 5. 解析打印日志中的w轨迹
49# 这里需要将您的打印日志内容粘贴到log_text中
50log_text = """
51您提供的完整打印日志内容在这里
52"""
53
54w_trajectory = parse_w_trajectory(log_text)
55
56# 6. 计算轨迹上每个点的损失值
57trajectory_losses = []
58for w_point in w_trajectory:
59    w_tensor = torch.tensor(w_point, dtype=torch.float32)
60    loss_val = compute_mse(features, labels, w_tensor, b_fixed)
61    trajectory_losses.append(loss_val.item())
62
63trajectory_losses = np.array(trajectory_losses)
64
65# 7. 创建可视化图形
66fig = plt.figure(figsize=(12, 5))
67
68# 7a. 三维曲面图
69ax1 = fig.add_subplot(121, projection='3d')
70surf = ax1.plot_surface(W1.numpy(), W2.numpy(), Z.numpy(), 
71                       cmap='coolwarm', alpha=0.6, linewidth=0, 
72                       antialiased=True)
73
74# 绘制优化路径
75ax1.plot(w_trajectory[:, 0], w_trajectory[:, 1], trajectory_losses, 
76         'r-', linewidth=2, label='Optimization Path')
77ax1.scatter(w_trajectory[:, 0], w_trajectory[:, 1], trajectory_losses, 
78            c='r', s=20, alpha=0.6)
79
80# 标记起点和终点
81ax1.scatter([w_trajectory[0, 0]], [w_trajectory[0, 1]], [trajectory_losses[0]], 
82            c='g', s=100, marker='o', label='Start')
83ax1.scatter([w_trajectory[-1, 0]], [w_trajectory[-1, 1]], [trajectory_losses[-1]], 
84            c='b', s=100, marker='*', label='End')
85
86ax1.set_xlabel('Weight w1')
87ax1.set_ylabel('Weight w2')
88ax1.set_zlabel('Loss')
89ax1.set_title('3D Loss Surface with Optimization Path')
90ax1.legend()
91fig.colorbar(surf, ax=ax1, shrink=0.5, aspect=20)
92
93# 7b. 等高线图
94ax2 = fig.add_subplot(122)
95contour = ax2.contourf(W1.numpy(), W2.numpy(), Z.numpy(), levels=20, cmap='coolwarm')
96
97# 绘制优化路径
98ax2.plot(w_trajectory[:, 0], w_trajectory[:, 1], 'r-', linewidth=2, label='Optimization Path')
99ax2.scatter(w_trajectory[:, 0], w_trajectory[:, 1], c='r', s=20, alpha=0.6)
100
101# 标记起点和终点
102ax2.scatter(w_trajectory[0, 0], w_trajectory[0, 1], c='g', s=100, marker='o', label='Start')
103ax2.scatter(w_trajectory[-1, 0], w_trajectory[-1, 1], c='b', s=100, marker='*', label='End')
104
105# 添加箭头显示方向
106for i in range(0, len(w_trajectory)-1, max(1, len(w_trajectory)//20)):
107    dx = w_trajectory[i+1, 0] - w_trajectory[i, 0]
108    dy = w_trajectory[i+1, 1] - w_trajectory[i, 1]
109    ax2.arrow(w_trajectory[i, 0], w_trajectory[i, 1], dx, dy, 
110              shape='full', lw=0, length_includes_head=True, 
111              head_width=0.05, head_length=0.05, color='red')
112
113ax2.set_xlabel('Weight w1')
114ax2.set_ylabel('Weight w2')
115ax2.set_title('Loss Contour with Optimization Path')
116ax2.legend()
117fig.colorbar(contour, ax=ax2, shrink=0.5, aspect=20)
118
119plt.tight_layout()
120plt.show()
121
122# 8. 打印优化过程摘要
123print(f"优化过程摘要:")
124print(f"  初始权重: w1={w_trajectory[0, 0]:.4f}, w2={w_trajectory[0, 1]:.4f}")
125print(f"  最终权重: w1={w_trajectory[-1, 0]:.4f}, w2={w_trajectory[-1, 1]:.4f}")
126print(f"  真实权重: w1={true_w[0]:.1f}, w2={true_w[1]:.1f}")
127print(f"  初始损失: {trajectory_losses[0]:.4f}")
128print(f"  最终损失: {trajectory_losses[-1]:.4f}")
129print(f"  迭代步数: {len(w_trajectory)}")
130

可以发现,当我们不考虑 bbb 的情形下,从任意起点开始,梯度下降会逐渐收缩到整个曲面的最低点附近,这也就使得梯度下降更易于理解了。

4. 参考文献

3. 线性神经网络


D2L(1) — 线性回归》 是转载文章,点击查看原文


相关推荐


Git Worktree + Claude Code:让你的开发效率翻倍的秘密武器
去码头整点薯片2025/11/27

前言 作为一名前端开发者,你是否遇到过这样的场景: 正在开发新功能 A,突然产品经理要你紧急修复一个 bug 想要同时开发两个独立的功能,但频繁的 git stash 和分支切换让你头疼 需要对比两个分支的代码差异,却要来回切换分支 想要同时运行多个分支的开发服务器,却因为端口冲突而束手无策 如果你的答案是"是的",那么这篇文章将为你介绍一个革命性的解决方案:Git Worktree + Claude Code。 本文将从零基础开始,带你深入了解 Git Worktree 的概念,以及如何通


通过Amazon Q CLI 集成DynamoDB MCP 实现游戏场景智能数据建模
亚马逊云开发者2025/11/25

前言 Amazon DynamoDB 是一项完全托管的 NoSQL 数据库服务,提供快速、可预测且可扩展的性能。作为一种无服务器数据库,DynamoDB 让开发者无需担心服务器管理、硬件配置或容量规划等基础设施问题,可以专注于应用程序开发。对于游戏行业而言,DynamoDB 的设计特性尤为适合:其低延迟数据访问(通常以个位数毫秒计)能够支持游戏中的实时交互;自动扩展功能可以轻松应对游戏上线或特殊活动期间的流量高峰;全球表功能支持多区域部署,为全球玩家提供一致的低延迟体验,而按需容量模式则使游戏开


一款基于 .NET + 计算机视觉技术开源免费、功能强大的原神智能辅助工具,一键解放双手!
追逐时光者2025/11/23

前言 今天大姚给大家分享一款基于 .NET + 计算机视觉技术完全开源免费(GPL-3.0 license)、功能强大的原神智能辅助自动化工具,意图让原神变的更好的项目:better-genshin-impact。 项目介绍 better-genshin-impact 是一款基于 .NET + 计算机视觉技术完全开源免费(GPL-3.0 license)、功能强大的原神智能辅助自动化工具,意图让原神变的更好的项目,包含:自动剧情、全自动钓鱼(AI)、全自动七圣召唤、自动伐木、自动刷本、自动采集/


Redis(139)Redis的Cluster是如何实现的?
Victor3562025/11/22

Redis Cluster 是 Redis 的一种分布式架构,允许将数据分布到多个节点上以实现数据的自动分片、负载均衡和高可用性。Redis Cluster 通过分片、复制、故障检测和自动故障转移等机制实现这些功能。以下是 Redis Cluster 的详细实现及其关键组件和代码示例。 核心概念 1. 数据分片 Redis Cluster 将数据键空间分为 16384 个哈希槽(hash slots)。每个键根据其 CRC16 校验和被映射到其中一个哈希槽。 2. 节点角色 Redis Clus


15:00开始面试,15:06就出来了,问的问题有点变态。。。
测试界晓晓2025/11/20

从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到8月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%,这下搞的饭都吃不起了。 还在有个朋友内推我去了一家互联网公司,兴冲冲见面试官,没想到一道题把我给问死了: 如果模块请求http改为了https,测试方案应该如何制定,修改? 感觉好简单的题,硬是没有答出来,早知道好好看看一大佬软件测试面试宝典了。 通过大数据总结发现,其实软件测试岗的面试都是差不多


Java集合框架概述Java:ArrayList的使用Java:LinkedList的使用Java:HashSet的使用Java:TreeSet的使用Java:HashMap的使用
熙客2025/11/19

目录 1、概念 2、核心体系结构 3、Collection接口及实现 3.1 List接口(有序) 3.2 Set接口(去重) 3.3 Queue接口(队列) 4、Map接口及实现 5、迭代器和工具类 5.1 迭代器 (Iterator) 5.2 比较器(Comparator) 5.3 工具类(Collections) 6、集合选择 1、概念 Java集合框架是一个统一的架构,用于表示和操作集合。它包含: 接口:代表不同类型的集合,如 List, Set,


Python 的内置函数 str
IMPYLH2025/11/17

Python 内建函数列表 > Python 的内置函数 str Python 的内置函数 str() 是一个非常重要的类型转换函数,用于将其他数据类型转换为字符串类型。它在 Python 编程中有广泛的应用场景,下面详细介绍其功能和用法: 基本功能 str() 函数的主要作用是将给定的对象转换为字符串表示形式。当调用 str() 时,它会尝试调用对象的 __str__() 方法(如果存在)来获取其字符串表示。 语法格式 str(object='', encoding='utf-8',


PC微信协议之AES-192-GCM算法
AiXed2025/11/16

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.backends import default_backend import os import binascii # ============================


Lua 的简介与环境配置
hubenchang05152025/11/15

#Lua 的简介与环境配置 Lua 是一个简洁、轻量、可扩展的脚本语言;有着相对简单的 C 语言 API,因而而很容易嵌入应用中。很多应用程序使用 Lua 作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 #安装 Lua Lua 官方仅以源码形式进行发布,因为其使用纯 ISO C 实现,编译非常轻松。 首先从 Lua 的官方网站 下载源码,以下是部分历史版本的下载链接: Lua 版本发布日期哈希值(sha256)lua-5.4.82025-05-214f18ddae154e793e46e


【AI应用探索】-10- Cursor实战:小程序&APP - 下
bblb2025/11/14

【AI应用探索】-10- Cursor实战:小程序&APP - 下 1 开发商城微信小程序1.1 微信小程序发布流程准备1.2 Cursor需求设计1.3 数据库准备1.4 生成对应代码1.4.1 小程序前端1.4.2 小程序后端API1.4.3 商家后端 2 开发安卓APP2.1 创建项目2.2 Cursor介入2.3 源码开放 1 开发商城微信小程序 1.1 微信小程序发布流程准备 因为我之前开发过微信小程序,所以上一个小程序的信息还在这里留存着,所以有这些信息

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读