d2l 学习记录

数据处理

N维数组

  • 0-d 标量 一个类别

  • 1-d 向量 一个特征向量

  • 2-d 矩阵 一个样本-特征矩阵 每一行表示一个样本,每一列表示一个不同特征

  • 3-d RGB图片

  • 4-d 一个RGB图片批量

  • 5-d 视频

创建数组

创建数组需要三个要素:

  • 形状

  • 数据类型

  • 每个元素的值

访问元素

x[row,column] 规则与列表切片相同

数据操作

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
63
64
65
66
67
68
69
70
71
72
import torch

# 生成数组
torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
# 示例:
torch.arange(12) # tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
torch.arange(1, 10, 2) # tensor([1, 3, 5, 7, 9])

# 张量属性和方法
x = torch.arange(12)
x.shape # 返回张量的形状 torch.Size([12])
x.numel() # 返回张量元素的个数 12 (注意需要加括号)
x.reshape(3, 4) # 修改张量的形状为3×4矩阵
x.reshape(-1, 4) # -1表示自动计算该维度大小

# 使用全0、全1或随机采样的数字
torch.zeros((2, 3, 4)) # 创建2×3×4的全零张量
torch.ones((2, 3, 4)) # 创建2×3×4的全一张量
torch.randn((2, 3, 4)) # 创建2×3×4的随机张量(标准正态分布)
torch.rand((2, 3, 4)) # 创建2×3×4的随机张量(0-1均匀分布)

# 创建特定张量
torch.tensor([[2, 1, 3], [1, 2, 3], [3, 1, 2]]) # 从列表创建张量
torch.eye(3) # 创建3×3单位矩阵
torch.full((2, 3), 7) # 创建2×3的张量,所有元素为7

# 按元素运算
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y # 加法 tensor([3., 4., 6., 10.])
x - y # 减法 tensor([-1., 0., 2., 6.])
x * y # 乘法 tensor([2., 4., 8., 16.])
x / y # 除法 tensor([0.5, 1., 2., 4.])
x ** y # 幂运算 tensor([1., 4., 16., 64.])

# 数学函数
torch.exp(x) # 指数函数
torch.log(x) # 对数函数
torch.sqrt(x) # 平方根
torch.sin(x) # 正弦函数

# 连结张量
X = torch.arange(12, dtype=torch.float32).reshape((3, 4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0) # 沿行连接(垂直连接)
torch.cat((X, Y), dim=1) # 沿列连接(水平连接)

# 逻辑运算
X == Y # 逐元素比较,返回布尔张量
X < Y # 小于比较
X > Y # 大于比较

# 求和
X.sum() # 所有元素求和
X.sum(axis=0) # 沿行求和(每列的和)
X.sum(axis=1) # 沿列求和(每行的和)
X.sum(axis=[0, 1]) # 沿多个轴求和

# 广播机制
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a + b # 自动广播进行运算

# 节省内存的运算
Y = X + Y # 会分配新内存
Y[:] = X + Y # 或 Y += X,原地操作,节省内存

# 转换为其他Python对象
A = X.numpy() # 转换为NumPy数组
B = torch.tensor(A) # 从NumPy数组创建张量
a = torch.tensor([3.5])
a.item() # 将大小为1的张量转换为Python标量

数据预处理

  • 首先创建一个人工数据集作为例子
1
2
3
4
5
6
7
8
9
10
import os

os.makedirs(os.path.join('..','data'), exist_ok=True)
data_file = os.path.join('..','data','house_tiny.csv')
with open(data_file,'w') as f:
f.write('NumRooms,Alley,Price\n') # 列名
f.write('NA,Pave,127500\n')
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
  • 接下来使用pandas读取生成的.csv文件
1
2
3
4
import pandas as pd

data = pd.read_csv(data_file)
print(data)

终端会输出.csv文件的样式

1
2
3
4
5
    NumRooms Alley   Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
  • 现在处理缺失数据

常用的方法包括插值删除。这里使用插值。对于非数值的缺失值,我们可以选择将“NaN”视为一个类别。
下面是处理缺失值的代码:

1
2
3
4
5
6
7
8
# 将inputs和outputs分离
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]

# 处理缺失的数值数据:使用均值填充NaN数值
inputs = inputs.fillna(inputs.mean())

# 处理缺失的非数值数据:将缺失值也视为一个单独的类别
inputs = pd.get_dummies(inputs, dummy_na=True)

.iloc是pandas的索引器,通过位置选择数据(0:2表示选择前两列)。

.fillna方法用于填充缺失值,可以使用均值、中位数等替代NA值。

pd.get_dummies将分类变量转换为独热编码,dummy_na=True参数使NA值也被视为一个独立类别。

  • 最后转换为张量格式
1
2
3
4
import torch

X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))

pytorch使用

Dataset 和 Dataload 类

PyTorch中的DatasetDataLoader类是数据加载和预处理的关键组件,它们能有效地处理各种类型的数据。

Dataset是抽象类,用于表示数据集,它有两种主要类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 映射式数据集:通过索引访问数据
class MyMapDataset(torch.utils.data.Dataset):
def __init__(self, data_tensor, target_tensor):
self.data = data_tensor
self.target = target_tensor

def __getitem__(self, index):
return self.data[index], self.target[index]

def __len__(self):
return len(self.data)

# 迭代式数据集:只能顺序访问
class MyIterableDataset(torch.utils.data.IterableDataset):
def __init__(self, data_tensor, target_tensor):
self.data = data_tensor
self.target = target_tensor

def __iter__(self):
for i in range(len(self.data)):
yield self.data[i], self.target[i]

DataLoader是对Dataset的包装,提供了批处理、打乱数据、并行加载等功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本用法
dataloader = torch.utils.data.DataLoader(
dataset, # 数据集对象
batch_size=32, # 每批次样本数
shuffle=True, # 是否打乱数据
num_workers=4, # 并行加载的进程数
drop_last=False, # 是否丢弃最后不完整的批次
pin_memory=True # 是否将数据复制到CUDA固定内存中
)

# 使用DataLoader遍历数据
for batch_data, batch_labels in dataloader:
# 模型训练代码
outputs = model(batch_data)
loss = criterion(outputs, batch_labels)
# ...

Dataset可以与数据变换函数结合,实现数据增强:

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
from torchvision import transforms

# 图像数据转换示例
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 在自定义Dataset中使用
class ImageDataset(torch.utils.data.Dataset):
def __init__(self, file_list, labels, transform=None):
self.file_list = file_list
self.labels = labels
self.transform = transform

def __getitem__(self, index):
img = load_image(self.file_list[index]) # 自定义图像加载函数
label = self.labels[index]

if self.transform:
img = self.transform(img)

return img, label

def __len__(self):
return len(self.file_list)

当批处理中的样本无法简单堆叠时,可以定义自定义收集函数:

1
2
3
4
5
6
7
8
9
def my_collate_fn(batch):
# 处理可变长度序列
data = [item[0] for item in batch]
target = [item[1] for item in batch]
data = pad_sequence(data, batch_first=True) # 填充到相同长度
return data, torch.tensor(target)

# 使用自定义收集函数
dataloader = DataLoader(dataset, batch_size=32, collate_fn=my_collate_fn)

自动求导

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
import torch

# 创建一个需要求导的张量
x = torch.tensor([2.0], requires_grad=True)
y = torch.tensor([3.0], requires_grad=True)

# 计算一个复杂表达式
z = x**2 + y**3

# 计算梯度
z.backward()

# 查看梯度值
print(f'x的梯度: {x.grad}') # 应该是2*x = 4.0
print(f'y的梯度: {y.grad}') # 应该是3*y^2 = 27.0

# 多次调用backward()需要指定retain_graph或清除梯度
x.grad.zero_() # 清除梯度
y.grad.zero_()

# 分离计算图
a = torch.tensor([2.0], requires_grad=True)
b = a * 3
c = b.detach() # 分离c,把c看作一个常数
d = c * 2
e = a * d
e.backward()
print(f'a的梯度: {a.grad}') # 只包含e=a*d中的梯度,不含d=c*2的梯度路径

# 使用with torch.no_grad()阻止跟踪
with torch.no_grad():
w = x * 2

# 控制梯度计算
def get_gradient(x, y):
# 复杂计算
with torch.enable_grad():
z = x**2 + y**2
z.backward()
return x.grad, y.grad

自动求导系统需要注意几个关键点:

  1. 计算图:PyTorch构建动态计算图,记录操作历史,以便反向传播。

  2. 梯度累积:每次调用.backward()会累积梯度,除非手动清零:

    1
    2
    3
    4
    optimizer.zero_grad()  # 优化器方式
    # 或
    for param in model.parameters():
    param.grad = None

  3. 高阶梯度:PyTorch支持计算高阶导数:

    1
    2
    3
    4
    5
    x = torch.tensor([1.0], requires_grad=True)
    y = x**3
    y.backward(create_graph=True) # 保留计算图以计算二阶导数
    x.grad.backward() # 计算二阶导数
    print(f'二阶导数: {x.grad.grad}') # 应为6.0

  4. 矢量计算加速:自动求导系统针对矢量运算进行了优化,所以通常应避免使用Python for循环。

线性神经网络

线性回归

  • 样本(sample):每行数据

  • 标签(label):预测的目标

  • 特征(feature):预测依据的自变量

一般通常使用n来表示数据集的样本数。对索引为i的样本,其输入表示为x(i) = [x1(i), x2(i)]T,其对应的标签为y(i)

线性回归模型

有两个假设:

  • 假设一:影响预测目标的有几个关键因素,记为x1, x2, x3

  • 假设二:预测目标是关键因素的加权和:y = w1x1 + w2x2 + w3x3 + b

用向量表示就是:

  • 给定一个n维输入:x = [x1, x2, ..., xn]T

  • 线性模型有一个n维权重和一个标量偏差:w = [w1, w2, ..., wn], b

  • 输出时输入的加权和:y =  < w, x > +b

线性模型可以看做是单层线性网络。

损失函数

我们通过损失函数来衡量预估质量

假设y是真实值,是估计值,我们可以比较:

这个叫做平方损失

代入求解可以得到训练损失函数为:

我们这里就要最小化损失来学习参数

基础优化方法

梯度下降

当没有办法求出解析解的时候,就可以使用梯度下降的方法来优化

  • 挑选一个初始值 w0

  • 重复迭代参数 t=1,2,3

  • 沿梯度方向将增加损失函数值

  • 学习率:步长的超参数。学习率不能太小,也不能太大。太小会导致梯度下降轮次过多,太大会导致函数震荡无法逼近最优解。

小批量随机梯度下降

在整个训练集上做梯度下降耗费时间太长。一般选择随机采样b个样本来近似损失。

b是批量大小,是一个重要的超参数。

梯度下降就是不断沿着梯度反方向更新参数求解;
小批量随机梯度下降是深度学习默认的求解算法;
两个重要的超参数是批量大小和学习率

从零构建线性回归

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
63
%matplotlib inline
import random
import torch
import matplotlib.pyplot as plt
import matplotlib as mpl
# 构造人造数据集
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, 2000)

def data_iter(batch_size, features, labels):
num_examples = len(features)
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

for X,y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break

w = torch.normal(0, 0.01, size=(2,1), requires_grad = True)
b = torch.zeros(1, requires_grad = True)

def linreg(X, w, b): #@save
return torch.matmul(X, w) + b

def square_loss(y_hat, y): #@save
return (y_hat - y.reshape(y_hat.shape))**2 / 2

def sgd(params, lr, batch_size): #@save
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()

lr = 0.03
num_epochs = 3
net = linreg
loss = square_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.sum().backward()
sgd([w, b], lr, batch_size)
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}')

用框架构建线性回归

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
import numpy as np
import torch
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))

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

def load_array(data_arrays, batch_size, is_train=True): #@save
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)

next(iter(data_iter))


from torch import nn
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 = 6
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
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)

softmax回归

softmax回归其实是分类问题。回归估计一个连续值,分类预测一个离散类别


d2l 学习记录
https://beetlesliu.github.io/2025/04/29/d2l-学习记录/
作者
Beetles
发布于
2025年4月29日
更新于
2026年4月15日
许可协议