0%

Pandas数据炼金术:从原始数据到商业洞察的完整工作流

在数据科学领域,我们常说“垃圾进,垃圾出”(Garbage In, Garbage Out)。无论你的机器学习模型多么精妙,可视化图表多么炫酷,如果输入的数据质量低下,那么得出的结论也必然是错误的。据估算,一个数据科学家或分析师80%的时间都花费在数据清洗和准备上。因此,掌握一套高效、系统的数据处理方法,远比学习几个花哨的算法模型更为重要。

Pandas,这个Python数据分析的基石库,正是为我们解决这一核心痛点而生的。它不仅仅是一个工具库,更是一套完整的数据处理哲学。本文将带你跳出零散的函数记忆,从一个完整工作流的视角,重新审视Pandas的强大能力。我们将模拟一个真实的数据分析场景,一步步完成从数据检查、清洗、预处理,到提取、筛选、汇总、统计,最终输出成果的全过程。

第一阶段:数据诊断与检查

想象一下,你是一名医生,面前躺着一位病人(你的数据集)。在开药方(进行分析)之前,你必须先进行全面的体检,了解病人的基本状况和潜在问题。Pandas为我们提供了快速“诊断”数据的强大工具。

数据加载与概览

一切从加载数据开始。Pandas可以轻松读取CSV、Excel、SQL数据库等多种格式的数据。加载后,我们不应该一头扎进数据细节,而是要先看“全貌”。

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

# 模拟加载一份销售数据
df = pd.read_csv('sales_data.csv')

# .head() 查看前5行,快速了解数据结构
print(df.head())

# .tail() 查看后5行,检查数据末尾是否有异常
print(df.tail())

# .sample(5) 随机查看5行,避免数据排序带来的偏差
print(df.sample(5))

深度体检:info()与describe()

如果说head()是看一眼病人的气色,那么info()describe()就是一份详细的体检报告。

df.info()方法会告诉你数据集中有多少行、多少列,每一列的数据类型是什么(是整数、浮点数还是对象字符串),以及最关键的非空值数量。通过这个报告,你可以立刻发现哪些列存在大量的缺失值,哪些列的数据类型是错误的(例如,日期被读成了字符串)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print(df.info())

<class 'pandas.DataFrame'>
RangeIndex: 11 entries, 0 to 10
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 OrderID 11 non-null int64
1 Name 11 non-null str
2 Age 9 non-null float64
3 City 11 non-null str
4 OrderDate 11 non-null str
5 Category 11 non-null str
6 Quantity 11 non-null int64
7 UnitPrice 11 non-null float64
dtypes: float64(2), int64(2), str(4)
memory usage: 836.0 bytes
None

df.describe()方法则专注于数值型列,它会为你生成一份统计摘要,包括计数、均值、标准差、最小值、最大值以及四分位数。这能让你迅速了解数据的分布情况,比如价格是否在合理范围内,是否存在一些极端值。

1
2
3
4
5
6
7
8
9
10
11
12
print(df.describe())


OrderID Age Quantity UnitPrice
count 11.000000 9.000000 11.000000 11.000000
mean 1005.181818 48.777778 2.181818 48.999091
std 3.060006 56.949051 1.328020 49.314521
min 1001.000000 22.000000 1.000000 -10.000000
25% 1002.500000 28.000000 1.000000 17.750000
50% 1005.000000 30.000000 2.000000 30.000000
75% 1007.500000 35.000000 2.500000 65.000000
max 1010.000000 200.000000 5.000000 150.000000

第二阶段:数据清洗与预处理

体检完毕,问题浮出水面:缺失值、重复项、异常值、格式混乱。现在,是时候动手“治疗”了。

处理缺失值:填还是删?

阅读全文 »

全面掌握SQLAlchemy

SQLAlchemy 2.0并非简单的版本迭代,而是一次彻底的架构重塑。它统一了Core层与ORM层的查询接口,原生支持异步编程,并全面拥抱Python的类型提示系统。

生态定位:从全能巨舰到现代化引擎

SQLAlchemy无疑是Python生态中最成熟、功能最强大的ORM框架。在2.0版本之前,它已经凭借其独特的分层架构成为业界的”事实标准”。2.0版本的发布,标志着这个”全能巨舰”完成了向现代化引擎的转型。

SQLAlchemy 2.0的核心魅力在于其清晰的架构分层:Core层提供了数据库连接引擎、连接池、方言系统以及一套强大的SQL表达式语言;ORM层则在Core层之上,提供了声明式模型定义、对象关系映射和会话管理。这种分层设计使得SQLAlchemy 2.0既能满足大型复杂项目对功能完整性的苛刻要求,又能在需要极致性能的场景下,让开发者绕过ORM的抽象,直接使用Core层进行底层操作。

与Django ORM的”一体化套件”和Peewee的”轻量快艇”定位不同,SQLAlchemy 2.0更像是一艘配备了最新导航系统的豪华邮轮——既保留了强大的功能,又提供了现代化的开发体验。

核心变革:2.0版本的三大突破

SQLAlchemy 2.0的现代化转型主要体现在三个关键领域:统一的查询接口、原生异步支持和现代化的类型系统。

在SQLAlchemy 1.x时代,Core层使用select()构造查询,而ORM层使用session.query(),这种分裂的API设计让开发者需要在两种风格之间切换。2.0版本彻底统一了查询接口,现在无论是Core还是ORM,都使用select()作为标准的查询构造方式。

1
2
3
4
from sqlalchemy import select
# 2.0统一查询风格
stmt = select(User).where(User.age > 18)
users = session.execute(stmt).scalars().all()

随着高并发应用的普及,异步编程已成为现代Python开发的标配。SQLAlchemy 2.0原生支持了异步操作,配合asyncpg等异步数据库驱动,可以轻松集成到FastAPI等异步框架中。

1
2
3
4
5
6
7
8
9
10
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker

async_engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
AsyncSessionLocal = async_sessionmaker(async_engine, expire_on_commit=False)

async with AsyncSessionLocal() as session:
user = User(name="Alice")
session.add(user)
await session.commit()

SQLAlchemy 2.0全面拥抱Python的类型提示系统,通过Mapped类型和mapped_column函数,提供了更好的IDE支持和代码可读性。

1
2
3
4
5
6
7
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String

class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(50), unique=True)

快速上手:现代化模型定义

让我们通过一个完整的例子,体验SQLAlchemy 2.0的现代化开发流程。

首先,定义现代化的基类和模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from datetime import datetime
from typing import List, Optional
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import String, Integer, DateTime, ForeignKey, Boolean

class Base(DeclarativeBase):
"""所有数据库模型的基类"""
pass

class User(Base):
"""用户模型"""
__tablename__ = "users"

# 主键定义
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)

# 必填字段
username: Mapped[str] = mapped_column(
String(50),
unique=True,
nullable=False,
comment="用户名"
)

# 可选字段
email: Mapped[Optional[str]] = mapped_column(
String(100),
unique=True,
nullable=True
)

# 带默认值的字段
is_active: Mapped[bool] = mapped_column(
Boolean,
default=True,
comment="是否激活"
)

created_at: Mapped[datetime] = mapped_column(
DateTime,
default=datetime.utcnow
)

# 关系定义
posts: Mapped[List["Post"]] = relationship(
back_populates="author",
lazy="selectin"
)

def __repr__(self) -> str:
return f"<User(id={self.id}, username='{self.username}')>"

接下来,创建数据库引擎和会话:

1
2
3
4
5
6
7
8
9
10
11
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 创建数据库引擎
engine = create_engine("sqlite:///example.db", echo=True)

# 创建表结构
Base.metadata.create_all(engine)

# 创建会话工厂
SessionLocal = sessionmaker(bind=engine)

进阶技巧:关系映射与查询优化

SQLAlchemy 2.0在关系映射方面提供了更强大的功能。通过relationship()函数,可以优雅地处理表与表之间的关联。

1
2
3
4
5
6
7
8
class Post(Base):
__tablename__ = 'posts'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(100))
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))

# 定义与User模型的关系
author: Mapped["User"] = relationship(back_populates="posts")

N+1查询问题依然是性能优化的关键。SQLAlchemy 2.0推荐使用selectinload来解决一对多关系的N+1问题:

1
2
3
4
5
from sqlalchemy.orm import selectinload

# 使用selectinload优化查询
stmt = select(User).options(selectinload(User.posts))
users_with_posts = session.execute(stmt).scalars().all()

生产级实践:异步、事务与迁移

在实际项目中,需要考虑性能、稳定性和可维护性。

异步编程
SQLAlchemy 2.0的异步支持让高并发应用开发变得更加简单:

1
2
3
4
5
6
7
8
9
10
11
12
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.asyncio import async_sessionmaker

async_engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
AsyncSessionLocal = async_sessionmaker(async_engine, expire_on_commit=False)

async def create_user(name: str):
async with AsyncSessionLocal() as session:
user = User(name=name)
session.add(user)
await session.commit()
return user

事务管理
对于涉及多个数据库操作的复杂业务逻辑,必须使用事务来保证数据的一致性:

1
2
3
4
5
6
7
8
try:
# 执行一系列操作
session.add(order)
session.delete(cart_item)
await session.commit()
except Exception:
await session.rollback()
raise

数据库迁移
Alembic作为SQLAlchemy官方配套的迁移工具,可以自动检测模型变化并生成迁移脚本,让数据库结构管理变得像代码版本控制一样简单。

从简单的增删改查到复杂的关联查询,再到生产环境的异步支持与性能优化,SQLAlchemy 2.0提供了一条清晰而强大的现代化路径。掌握它,意味着你不仅能高效地操作数据,更能构建出健壮、可维护且性能卓越的数据驱动应用。

从PyTorch的积木到AI的引擎:一文读懂Transformer入门

掌握了PyTorch的核心——张量、自动微分、损失函数和优化器。已经能用torch.nn.Linear搭建网络,理解backward()如何计算梯度。是时候从“玩具模型”迈向真正驱动现代人工智能的引擎了。这个引擎,就是Transformer。

Transformer不是一种新的编程语言,也不是一个独立的软件。它是一个模型架构,一套精妙的神经网络设计蓝图。它由Google Brain团队在2017年的里程碑论文《Attention Is All You Need》中提出,并彻底颠覆了人工智能的格局。

要理解Transformer,我们必须先回到它出现之前的时代,看看它究竟解决了什么痛点。

在Transformer之前,处理序列数据(如一句话、一段音频)的主流是循环神经网络(RNN)及其变体LSTM。RNN的工作方式像一个勤奋的朗读者,它按顺序一个字一个字地处理句子。当它读到第N个词时,它只能依靠之前N-1个词留下的“记忆”(隐藏状态)来理解上下文。

这种方式有两个致命缺陷:

  • 无法并行:因为必须等前一个词处理完才能处理下一个,所以训练速度非常慢,无法充分利用GPU的强大算力。
  • 长距离依赖问题:当句子很长时,RNN很难记住开头的信息。就像你读一本长篇小说,读到结尾时可能已经忘了主角的名字。

Transformer的出现,一举解决了这两个问题。它完全摒弃了循环结构,转而拥抱一个名为自注意力(Self-Attention)的机制。这个机制的核心思想非常直观:让句子中的每个词都去“关注”句子中的所有其他词,从而直接捕捉任意两个词之间的关系,无论它们相距多远。

这种设计带来了两大革命性优势:

  • 极致并行:模型可以一次性看到整个句子,所有词的计算都可以同时进行,训练效率得到指数级提升。
  • 强大的长程建模能力:通过自注意力,句子开头的词和结尾的词可以直接“对话”,完美解决了长距离依赖问题。

凭借其无与伦比的效率和性能,Transformer迅速从自然语言处理(NLP)领域扩张到计算机视觉(CV)、语音处理乃至多模态领域,成为当今所有大语言模型(如GPT、BERT)的绝对基石。

理解了Transformer的“为什么”,我们再来拆解它的“怎么做”。一个标准的Transformer模型,其内部结构精巧而优雅,主要由以下几个核心组件构成。

在将文本输入模型之前,首先要将每个词(或子词)转换成一个计算机可以理解的数字向量,这个过程叫词嵌入(Word Embedding)。例如,“国王”和“王后”的向量在空间中的距离会很近,而“苹果”的向量则会离它们很远。

但Transformer是并行处理所有词的,它本身并不理解词的顺序。为了让模型知道“我打他”和“他打我”的区别,我们必须为每个词的嵌入向量加上一个位置编码(Positional Encoding)。这个编码是一个根据词在句子中的位置生成的独特向量,它告诉模型每个词的先后顺序。

这是Transformer的“心脏”。它的任务是让模型学会关注句子中最重要的部分。我们可以把它想象成一个信息检索系统,包含三个角色:

  • Query(查询):代表“我当前想找什么信息”。
  • Key(键):代表“我身上有什么信息”。
  • Value(值):代表“我实际包含的信息内容”。

模型通过计算Query和所有Key的相似度,来决定从对应的Value中提取多少信息。这个过程会生成一个全新的、融合了上下文信息的表示向量。

为了从不同角度理解句子,Transformer会使用多头注意力(Multi-Head Attention)。它相当于开了多个“观察视角”,有的头可能关注语法结构,有的头关注指代关系,最后将所有头的观察结果综合起来,形成一个更全面、更丰富的理解。

在注意力层之后,模型还会通过一个前馈网络(Feed-Forward Network)。这是一个简单的全连接网络,独立地对每个位置的向量进行非线性变换,进一步提取和整合特征。

一个完整的Transformer模型通常采用编码器-解码器(Encoder-Decoder)架构。

  • 编码器(Encoder):负责“阅读理解”。它接收输入句子,通过多层堆叠的自注意力和前馈网络,将其转换成一个富含上下文信息的特征表示。
  • 解码器(Decoder):负责“写作生成”。它接收编码器的输出,并以自回归的方式(即一个词接一个词地)生成目标句子。在生成每个词时,它会通过交叉注意力(Cross-Attention)机制,回头去“查阅”编码器提供的原文信息,确保翻译或生成的准确性。

从理论到实践,我们来看看如何用PyTorch这个强大的工具箱,将Transformer的设计蓝图变为现实。这就像用乐高积木,按照图纸一步步搭建出宏伟的城堡。

首先,我们需要定义模型的核心——自注意力模块。这个过程完全由PyTorch的张量运算构成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch
import torch.nn as nn
import math

class ScaledDotProductAttention(nn.Module):
def __init__(self, d_k):
super().__init__()
self.d_k = d_k # 键向量的维度
# 将一组原始分数(logits)转换成概率分布。把任意数值变成 0 到 1 之间的概率值,并且保证这些概率值加起来等于 1
self.softmax = nn.Softmax(dim=-1)

def forward(self, Q, K, V):
# 1. 计算Q和K的相似度 (矩阵乘法)
scores = torch.matmul(Q, K.transpose(-1, -2))

# 2. 缩放,防止点积过大导致Softmax梯度消失
scores = scores / math.sqrt(self.d_k)

# 3. 应用Softmax,得到注意力权重
attention_weights = self.softmax(scores)

# 4. 用权重对V加权求和,得到最终输出
output = torch.matmul(attention_weights, V)
return output

这段代码精确地实现了自注意力的数学公式。Q, K, V都是PyTorch张量,torch.matmul是矩阵乘法,nn.Softmax是激活函数。你看,复杂的注意力机制,底层依然是你最熟悉的PyTorch操作。

接下来,我们将多个注意力头组合起来,并加上残差连接和层归一化,构建一个完整的编码器层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.d_model = d_model # 词向量的总维度,比如512
self.num_heads = num_heads # “头”的数量,比如8个
self.d_k = d_model // num_heads # 每个“头”的维度,512/8=64

# 这三个线性层负责把输入 x 转换成 Q(查询), K(键), V(值)
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
# 这个线性层负责把所有“头”的结果融合起来
self.W_o = nn.Linear(d_model, d_model)
# 计算注意力的“工具”
self.attention = ScaledDotProductAttention(self.d_k)

def forward(self, x):
batch_size, seq_len, _ = x.shape

# 1. 线性投影并分割成多个头
# 用线性层把输入 x 转换成查询向量 Q。此时 Q 的形状是 (batch_size, seq_len, d_model)
# view().transpose(1, 2): 它把 Q 的最后一个维度(比如512)切分成 num_heads 个(比如8个)小向量,每个小向量的维度是 d_k(比如64)。然后通过转置,把“头”的维度放到前面。
# 意义是:原来我们只有一个大的查询向量,现在我们把它分成了8个“小专家”(头)。每个“小专家”都从自己的视角去观察整个句子
Q = self.W_q(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)

# 2. 并行计算多头注意力
head_outputs = []
# 让每一个“小专家”(头)独立工作
for i in range(self.num_heads):
head_output = self.attention(Q[:, i], K[:, i], V[:, i])
# 每个头都会输出一个结果,我们把所有头的结果都收集到 head_outputs 列表里
head_outputs.append(head_output)

# 3. 把他们的成果拼接起来。torch.cat(..., dim=-1) 会在最后一个维度上进行拼接,把8个维度为 d_k (64) 的向量,重新拼成一个维度为 d_model (8 * 64 = 512) 的大向量。
concatenated = torch.cat(head_outputs, dim=-1)

# 4. 最终线性变换。把所有“小专家”的观点综合起来,形成一个最终的、更全面的理解
output = self.W_o(concatenated)
return output

# 一个完整的编码器层还包括前馈网络和残差连接
class EncoderLayer(nn.Module):
def __init__(self, d_model, ffn_hidden, num_heads):
super().__init__()
self.attention = MultiHeadAttention(d_model, num_heads)
# 前馈网络,一个简单的两层全连接网络
self.ffn = nn.Sequential(
nn.Linear(d_model, ffn_hidden), # 先放大维度
nn.ReLU(), # 加一个非线性激活函数
nn.Linear(ffn_hidden, d_model) # 再变回原来的维度
)
# 两个层归一化,用来稳定训练
self.layer_norm1 = nn.LayerNorm(d_model)
self.layer_norm2 = nn.LayerNorm(d_model)

def forward(self, x):
# 残差连接 + 层归一化
x = self.layer_norm1(x + self.attention(x))
x = self.layer_norm2(x + self.ffn(x))
return x

通过组合这些模块,我们就可以搭建出完整的Transformer模型。这个过程让你深刻理解到,看似神秘的“大模型”,其内部是由一个个清晰、可解释的PyTorch模块精密协作而成的。

当然,在现实中,我们很少从零开始写代码。Hugging Face的transformers库提供了成千上万预训练好的Transformer模型,让我们能像搭积木一样快速应用。

1
2
3
4
5
6
7
8
9
10
11
12
import os
# 使用国内镜像
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from transformers import pipeline

# 一行代码加载一个情感分析模型
classifier = pipeline("sentiment-analysis")

# 直接使用
result = classifier("I love learning about Transformer with PyTorch!")
print(result)
# 输出: [{'label': 'POSITIVE', 'score': 0.9998}]

这行简洁代码的背后,是Hugging Face库用PyTorch实现的、结构极其复杂的BERT或GPT模型。它已经学习了海量文本,我们只需调用它,就能获得强大的AI能力。

从PyTorch的nn.Linearbackward(),到Transformer的自注意力机制,再到Hugging Face的预训练模型,你已经走完了一条从基础到前沿的完整路径。

Transformer并非遥不可及的黑箱,它是由你熟悉的PyTorch积木搭建而成的宏伟建筑。理解它的工作原理,不仅让你能更好地使用现有的大模型,更为你未来探索和创新打开了大门。现在,你已经手握开启AI新世界的钥匙。

Python 装饰器:从入门到工程实践

在 Python 的众多特性中,装饰器(Decorator)无疑是最具魅力但也最容易让人困惑的概念之一。很多初学者在第一次看到 @ 符号时,往往觉得它像是一种“魔法”。但实际上,装饰器并非魔法,而是一种优雅的设计模式,它允许我们在不修改原函数代码的情况下,动态地增强或修改函数的行为。

本文将从基础原理出发,带你深入理解装饰器的本质,并探讨如何在实际工程中编写高质量、可维护的装饰器。

类似于java的AOP

装饰器的本质:语法糖与闭包

要理解装饰器,首先要理解 Python 中的“函数是一等公民”。这意味着函数可以像变量一样被传递、作为参数,甚至作为返回值。

装饰器本质上是一个高阶函数:它接收一个函数作为参数,并返回一个新的函数。

让我们看一个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
def my_decorator(func):
def wrapper(*args, **kwargs):
print("在函数执行之前...")
result = func(*args, **kwargs)
print("在函数执行之后...")
return result
return wrapper

@my_decorator
def say_hello():
print("Hello!")

say_hello()

当你运行这段代码时,输出会显示“在函数执行之前…”,然后是“Hello!”,最后是“在函数执行之后…”。

这里的 @my_decorator 实际上就是 say_hello = my_decorator(say_hello) 的语法糖。

核心机制解析:

  1. 闭包wrapper 函数定义在 my_decorator 内部,它“记住”了外部传入的 func。这就是闭包。
  2. 代理模式wrapper 并没有改变 say_hello 的逻辑,它只是“包裹”了原函数,在调用前后插入了额外的逻辑(日志记录)。
阅读全文 »

PyTorch微分与梯度:从原理到实战

在深度学习的宏大叙事中,PyTorch 的 torch.autograd 模块无疑是那个最沉默却最强大的幕后推手。它实现了自动微分机制,让复杂的神经网络训练变得像搭积木一样简单。很多开发者习惯于调用 .backward(),却未必真正理解其背后的数学原理与工程细节。

今天,我们就剥开 PyTorch 的“黑盒”,深入探讨它是如何通过计算图追踪梯度,以及反向传播与梯度下降是如何协同工作,让模型从“无知”变得“睿智”。

计算图与自动微分:PyTorch 的“记忆”

PyTorch 的微分机制基于动态计算图。当你使用 Tensor 进行运算时,PyTorch 不仅在计算数值,还在后台构建一个有向无环图,记录数据是如何流动的。

要让一个张量进入这个“追踪系统”,只需设置 requires_grad=True。一旦开启,PyTorch 就会记录所有作用在该张量上的操作。

1
2
3
4
5
6
7
8
9
10
11
import torch

# 定义一个需要追踪梯度的张量
x = torch.tensor(2.0, requires_grad=True)

# 进行一系列运算:y = x^2 + 3x + 1
y = x**2 + 3*x + 1

# 此时,y 拥有一个 grad_fn 属性,指向创建它的函数
# grad_fn 属性:它指向创建该张量的函数对象。这个对象记录了前向传播的操作细节,以便在反向传播时能够利用链式法则计算梯度
print(f"计算图节点: {y.grad_fn}")

在这个例子中,y 不仅仅是一个数值 11,它还持有着一条“线索”,指向它是如何通过 x 计算得来的。这就是反向传播的基础。

反向传播:链式法则的自动化

当我们得到最终的输出(通常是损失值 Loss)后,调用 .backward() 方法,PyTorch 就会启动自动微分引擎。它利用链式法则,从输出端向输入端反向遍历计算图,计算梯度。

阅读全文 »