本文主要参考沐神的视频教程 https://www.bilibili.com/video/BV1K64y1Q7wu/?spm_id_from=333.788.recommend_more_video.0&vd_source=c7bfc6ce0ea0cbe43aa288ba2713e56d
文档教程 https://zh-v2.d2l.ai/
本文的主要内容对沐神提供的代码中个人不太理解的内容进行笔记记录,内容不会特别严谨仅供参考。
1.函数目录
python | 位置 |
---|---|
列表推导式 | 3.1 |
enumerate | 3.1 |
zip | 4.5 |
__getitem __() | 4.5 |
1.1 torchvision
torchvision 是 PyTorch 的一个重要子包,为计算机视觉任务提供了大量实用工具。它简化了数据集加载、预处理和模型定义的过程,使得开发计算机视觉应用更加高效和便捷。
- torchvision.datasets:
提供了许多常用的数据集,例如 MNIST、CIFAR-10、ImageNet 等。每个数据集类都实现了数据的下载、加载和预处理功能。 - torchvision.transforms:
提供了一系列数据预处理和增强方法,例如裁剪、缩放、归一化、随机旋转等。可以将这些方法组合起来,定义数据的预处理管道。 - torchvision.models:
提供了许多预训练模型,例如 ResNet、VGG、AlexNet 等。这些模型可以用于迁移学习,或者直接进行推理任务。 - torchvision.io:
提供了一些输入输出操作的工具,例如读取图像、视频文件等。 - torchvision.ops:
提供了一些通用的操作符和层,例如 RoIAlign、nms(非极大值抑制)等
torchvision | 位置 |
---|---|
torchvision.transforms.ToTensor | 3.1 |
torchvision.datasets.FashionMNIST() | 3.1 |
torchvision.transforms.Compose | 3.3 |
1.2 torch
torch | 位置 |
---|---|
torch.exp() | 4.1 |
torch.sum() | 4.1 |
索引 | 4.3 |
torch.argmax | 4.5 |
1.3 Tensor
Tensor | 位置 |
---|
2. 回归与分类
- 回归估计是一个连续值
- 分类预测一个离散类别
常用的分类数据集有:
MNIST:手写数字识别(10类)
ImageNet:自然物体分类(1000类)
2.1 从回归到多分类
回归 | 分类 |
---|---|
单连续值输出。自然区间 | 通常多个输出 |
跟真实值的区别作为损失 | 输出i是预测为第i类的置信度 |
- 对类别进行一位有效编码
y = [ y 1 , y 2 , . . . , y n ] T y = [y_1,y_2,...,y_n]^T y=[y1,y2,...,yn]T
y i = { 1 如果 i = y 0 o t h e r w i s e y_i=\left\{ \begin{aligned} 1 & & \ 如果 i=y \\ 0 & & \ otherwise \\ \end{aligned} \right. yi={10 如果i=y otherwise
y为真实值 - 使用均方损失训练
- 最大值作为预测
y ^ = arg max i o i \hat{y}=\mathop{\arg\max}\limits_{i} o_i y^=iargmaxoi
y ^ \hat{y} y^为预测值 - 输出匹配概率
y ^ = s o f t m a x ( o i ) \hat{y}=softmax(o_i) y^=softmax(oi)
y ^ i = e x p ( o i ) ∑ k e x p ( o k ) \hat{y}_i=\frac{exp(o_i)}{\sum_kexp(o_k)} y^i=∑kexp(ok)exp(oi)
线性层输出 | e x p ( o i ) exp(o_i) exp(oi) | ∑ k e x p ( o k ) \sum_kexp(o_k) ∑kexp(ok) | y ^ \hat{y} y^ | y |
---|---|---|---|---|
0.2 | 1.22 | 3.23 | 0.38 | 1 |
0.1 | 1.11 | 3.23 | 0.34 | 0 |
-0.1 | 0.90 | 3.23 | 0.28 | 0 |
此时
y
^
\hat{y}
y^中每个分类预测的概率均值为正值且所有概率的和为1。
假设第一个输出为正确分类,则y=[1, 0, 0]
2.2 Softmax与交叉熵损失
- 交叉熵损失
交叉熵损失函数常用来衡量两个概率的区别:
H ( p , q ) = ∑ i − p i ∗ l o g ( q i ) H(p,q)=\sum_i-p_i*log(q_i) H(p,q)=∑i−pi∗log(qi) - 将其作为损失:
l ( y , y ^ ) = − ∑ i y i l o g ( y i ^ ) = − l o g ( y y ^ ) l(y,\hat{y})=-\sum_iy_ilog(\hat{y_i})=-log(\hat{y_y}) l(y,y^)=−∑iyilog(yi^)=−log(yy^)
y ^ \hat{y} y^ | l o g ( y i ^ ) log(\hat{y_i}) log(yi^) | y i l o g ( y i ^ ) y_ilog(\hat{y_i}) yilog(yi^) |
---|---|---|
0.38 | -0.97 | -0.97 |
0.34 | -1.07 | 0 |
0.28 | -1.27 | 0 |
注意:在计算的过程中的log不是以10为底而是以e为底的
loss = -(-0.97)=0.97
import numpy as np
y = np.array([1, 0, 0])
z = np.array([0.2, 0.1, -0.1])
y_pred = np.exp(z) / np.exp(z).sum()# softmax计算层
loss = (- y * np.log(y_pred)).sum()# 交叉熵损失计算
print(loss)
0.9729189131256584
3. 图像分类数据集
3.1 加载数据集
3.1.1 transforms.ToTensor()
transforms.ToTensor() 是 torchvision.transforms 模块中的一个类,用于将图像数据从 PIL 图像或 numpy 数组转换为 PyTorch 张量,同时将像素值从 [0, 255] 范围缩放到 [0, 1] 范围。
3.1.2 datasets.FashionMNIST()
torchvision.datasets.FashionMNIST 类用于方便地下载、加载和预处理 Fashion-MNIST 数据集。通过设置不同的参数,可以控制数据集的行为,例如是否加载训练集、是否进行数据预处理和增强、是否下载数据等。
torchvision.datasets.FashionMNIST(root: Union[str, Path], train: bool = True, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, download: bool = False)
parameters:
- root (string): 数据集的根目录。数据将会被下载到这个目录下,如果已经下载则从这个目录加载数据。
- train (bool, optional): 如果设置为 True,则创建训练集,否则创建测试集。默认值为 True。
- transform (callable, optional): 一个函数或转换,它接受 PIL 图像并返回转换后的图像。用于对数据进行预处理和数据增强。
- target_transform (callable, optional): 一个函数或转换,它接受一个标签并返回转换后的标签。用于对标签进行预处理。
- download (bool, optional): 如果设置为 True,且数据集尚未下载,则从互联网下载数据并将其放在 root 目录下。否则不下载数据。默认值为 False。
Special-members:
# 通过整数索引访问对象时,返回一个包含任意类型的元组。
__getitem__(index: int) → Tuple[Any, Any]
-
Parameters参数:
index (int) – Index -
Returns:
(image, target) where target is index of the target class. -
Return type:
tuple
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True,transform=trans,
download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False,transform=trans,
download=True)
print(len(mnist_test))
print(len(mnist_train))
print(mnist_train[0][0]) #返回图像
print(mnist_train[0][1]) #返回类别
3.1.3 列表推导式
列表推导式是一种简洁而有效的语法,用于生成列表。它通过在一行代码中结合循环和条件操作,使得代码更加简洁和可读。了解列表推导式的语法和使用场景,有助于编写更高效的 Python 代码。
- 语法:
[expression for item in iterable if condition]
- expression:列表中的新元素,通常是对 item 进行某种操作后的结果。
- for item in iterable:遍历 iterable 中的每个 item。
- if condition:一个可选的条件表达式,只在条件为真时才包含 item。
numbers = [1, 2, 3, 4, 5]
squared_numbers = [x * 2 for x in numbers]
print(squared_numbers)
上面的代码等价于
numbers = [1, 2, 3, 4, 5]
s = []
for i in numbers:
s.append(i*2)
print(s)
# 将数字标签转换为对应的文本标签
def get_fashion_mnist_labels(labels): #@save
"""返回Fashion-MNIST数据集的文本标签"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
print(get_fashion_mnist_labels([9]))
3.1.4 matplotlib.pyplot.subplots()
subplots 是 Matplotlib 中一个常用的函数,用于创建一个包含一个或多个子图的图形(figure)对象。它返回一个包含图形和子图对象的元组,可以方便地在同一个图形窗口中排列和显示多个图表。
基本用法
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(6, 4))
- fig 是图形对象(figure),表示整个图形容器。可以用于设置图形级别的属性,如标题、大小等
- ax 是子图对象(axis),表示图形中的一个子图(轴对象)。
参数解释
- nrows:子图的行数。
- ncols:子图的列数。
- figsize:图形的宽度和高度,以英寸为单位。
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 3)
axs = axs.flatten()
for ax in axs:
ax.plot([1, 2, 3], [1, 4, 9])
plt.show()
# 创建多个子图
fig, axs = plt.subplots(2, 3, figsize=(9, 6))
for i in range(2):
for j in range(3):
axs[i, j].plot([1, 2, 3], [1, 4, 9])
plt.show()
3.1.5 enumerate
enumerate 是一个内置的 Python 函数,用于在遍历迭代对象时获取索引和值。它返回一个枚举对象,该对象生成包含索引和值的元组。
语法
enumerate(iterable, start=0)
- iterable:要枚举的对象(如列表、元组、字符串等)。
- start:指定索引的起始值,默认从 0 开始。
返回值
- enumerate 返回一个迭代器,该迭代器生成一系列元组,每个元组包含两个元素:当前元素的索引和该元素。
fruits = ['apple', 'banana', 'cherry']
for index, value in enumerate(fruits):
print(index, value)
0 apple
1 banana
2 cherry
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
figsize = (num_cols*scale, num_rows*scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
ax.imshow(img.numpy())
else:
ax.imshow(img)
#隐藏X坐标轴
ax.axes.get_xaxis().set_visible(False)
#隐藏Y坐标轴
ax.axes.get_yaxis().set_visible(False)
if titles:
#如果提供了 titles 参数,则为每个子图设置对应的标题。
ax.set_title(titles[i])
d2l.plt.show()
return axes
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18,28,28),2,9,titles=get_fashion_mnist_labels(y))
3.2 读取小批量
batch_size = 256
def get_dataloader_workers():
return 4
train_iter = data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers())
# 测试读取一次数据的速度
timer = d2l.Timer()
for X, y in train_iter:
continue
print(f'{timer.stop():.2f}sec')
3.3 整合所有组件
3.3.1 torchvision.transforms.Compose
torchvision.transforms.Compose 主要用于将一系列图像变换操作组合在一起,这样图像在被加载时就会依次应用这些变换。常见的变换操作包括调整图像大小、裁剪、归一化、随机水平翻转等。
常见的图像变换操作
- transforms.Resize(size): 调整图像大小到指定尺寸。
- transforms.CenterCrop(size): 从图像中心裁剪指定大小。
- transforms.RandomCrop(size): 随机裁剪图像。
- transforms.RandomHorizontalFlip (p): 以概率 p 随机水平翻转图像。
- transforms.ToTensor(): 将图像转换为 PyTorch 张量,并归一化到 [0, 1] 范围。
- transforms.Normalize(mean, std): 使用给定的均值和标准差对图像进行归一化。
import torchvision.transforms as transforms
# 定义图像变换操作
transform = transforms.Compose([
transforms.Resize((128, 128)), # 调整图像大小
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.ToTensor(), # 转换为张量
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) # 归一化
])
def load_data_fashion_mnist(batch_size, resize = None):
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans,
download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans,
download=True)
return (data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=get_dataloader_workers()))
train_iter, test_iter = load_data_fashion_mnist(32,resize=64)
for X, y in train_iter:
print(X.shape, X.dtype, y.shape, y.dtype)
break
3.4 完整代码
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
if __name__ == "__main__":
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True,transform=trans,
download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False,transform=trans,
download=True)
# print(len(mnist_test))
# print(len(mnist_train))
# print(mnist_train[0][0]) #返回图像
# print(mnist_train[0][1]) #返回类别
# 将数字标签转换为对应的文本标签
def get_fashion_mnist_labels(labels): #@save
#"""返回Fashion-MNIST数据集的文本标签"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
print(get_fashion_mnist_labels([9]))
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
figsize = (num_cols*scale, num_rows*scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
ax.imshow(img.numpy())
else:
ax.imshow(img)
#隐藏X坐标轴
ax.axes.get_xaxis().set_visible(False)
#隐藏Y坐标轴
ax.axes.get_yaxis().set_visible(False)
if titles:
#如果提供了 titles 参数,则为每个子图设置对应的标题。
ax.set_title(titles[i])
d2l.plt.show()
return axes
# X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
# show_images(X.reshape(18,28,28),2,9,titles=get_fashion_mnist_labels(y))
batch_size = 256
def get_dataloader_workers():
return 4
train_iter = data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers())
# 测试读取一次数据的速度
timer = d2l.Timer()
for X, y in train_iter:
continue
print(f'{timer.stop():.2f}sec')
def load_data_fashion_mnist(batch_size, resize = None):
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans,
download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans,
download=True)
return (data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=get_dataloader_workers()))
train_iter, test_iter = load_data_fashion_mnist(32,resize=64)
for X, y in train_iter:
print(X.shape, X.dtype, y.shape, y.dtype)
break
4.software回归的从零开始实现
4.1 初始化模型参数
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
#1. 初始化模型参数
num_inputs = 28*28
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
4.2 定义softmax操作
4.2.1 torch.exp()
torch.exp(input, *, out=None) → Tensor
返回一个具有输入张量input元素指数的新张量。
Returns a new tensor with the exponential of the elements of the input tensor input.
print(torch.exp(torch.Tensor([0, math.log(2)])))
tensor([1., 2.])
4.2.2 torch.sum()
torch.sum 是 PyTorch 中的一个函数,用于对输入张量进行求和操作。
torch.sum(input, dim=None, keepdim=False, *, dtype=None) -> Tensor
参数解释
- input: 要求和的输入张量。
- dim: 要沿着哪个维度进行求和。如果 dim 是一个整数,则在指定维度上进行求和;如果是一个元组,则在多个维度上进行求和。如果未指定,则对所有元素进行求和。
- keepdim: 布尔值,是否保留求和后的维度。如果设置为 True,则求和后输出张量会保留输入张量的维度,否则会减少求和的维度。
- dtype: 指定求和后结果的类型。如果未指定,则使用输入张量的类型。
# 沿指定维度求和
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
result_dim0 = torch.sum(x, dim=0)
result_dim1 = torch.sum(x, dim=1)
print(result_dim0) # 输出:tensor([5, 7, 9])
print(result_dim1) # 输出:tensor([ 6, 15])
#2. 定义softmax操作
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp/partition
X = torch.normal(0,1, (2,5))
X_prob = softmax(X)
print(X_prob, X_prob.sum(1))
4.3 定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
4.4 定义损失函数
4.4.1 索引
- 基本索引
a = torch.arange(12).reshape((3,4))
a
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
-访问单个元素、行、列
# 访问单个元素 第一行第一列元素
print(a[1,1]) # 输出:tensor(5)
# 访问整行或整列
print(a[1]) # 输出:tensor([4, 5, 6, 7])
print(a[:, 2]) # 输出:tensor([ 2, 6, 10])
- 切片(Slicing)
使用冒号(:)进行切片操作。
# 切片操作
print(x[0, :]) # 输出:tensor([0, 1, 2, 3])
print(x[:, 1:2]) # 输出:tensor([[ 1, 2],
# [ 5, 6],
# [ 9, 10]])
- 高级索引
高级索引允许使用整数或布尔数组来索引张量。
y = torch.tensor([0,2])
y_hat = torch.tensor([[0.1,0.8,0.1], [0.3, 0.2, 0.5]])
# [0,1]选中0行和1行
# 在0行[0.1,0.8,0.1]中选中0列 :0.1
# 在1行[0.3, 0.2, 0.5]中选中2列 :0.5
print(y_hat[[0,1],[0,2]])
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y)), y])
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.8, 0.1], [0.3, 0.2, 0.5]])
print(cross_entropy(y_hat, y))
4.5 分类精度
4.5.1 torch.argmax()
argmax()函数在PyTorch中用于返回指定维度上最大值的索引。这个函数非常有用,尤其在分类任务中,常用于获取预测类别的索引。
torch.argmax(input, dim=None, keepdim=False) → LongTensor
- 参数解释
- input: 输入张量。
- dim (int, optional): 指定计算最大值的维度。如果不指定,将会返回张量中所有元素的最大值的索引。
- keepdim (bool, optional): 是否保持输出张量的维度。如果设置为True,输出张量在dim维度上的尺寸将为1,否则dim维度将被压缩。
- 返回值
- 返回一个长整型张量,包含输入张量指定维度上最大值的索引。
y_hat = torch.tensor([[0.1, 0.8, 0.1], [0.3, 0.2, 0.5]])
print(y_hat.argmax(dim=1, keepdim=True))
tensor([[1],
[2]])
print(y_hat.argmax(dim=1, keepdim=False))
tensor([1, 2])
4.5.2 zip
在Python中,zip()函数用于将多个可迭代对象(如列表、元组、字符串等)打包成一个个元组,**然后返回由这些元组组成的迭代器。**该函数常用于同时迭代多个序列。
zip(*iterables)
- 参数
- iterables: 任意多个可迭代对象(如列表、元组、字符串等)。
- 返回值
- 返回一个由元组组成的迭代器。每个元组包含来自每个可迭代对象的对应元素。如果提供的可迭代对象长度不等,则返回列表的长度与最短的对象相同。
1.将两个列表打包成元组
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
zipped = zip(list1, list2)
print(list(zipped)) # 输出:[(1, 'a'), (2, 'b'), (3, 'c')]
2. 解压元组
zipped = [('a', 1), ('b', 2), ('c', 3)]
unzipped = zip(*zipped)
list1, list2 = list(unzipped)
print(list1) # 输出:('a', 'b', 'c')
print(list2) # 输出:(1, 2, 3)
3. 遍历多个序列
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
for num, char in zip(list1, list2):
print(f"{num} -> {char}")
4.5.3 __getitem __()
getitem 方法允许类实例像列表、元组或字典那样使用索引进行访问。这使得类的对象可以表现得像一个容器,可以通过键或索引来获取值。
class MyList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
# 使用示例
my_list = MyList([1, 2, 3, 4, 5])
print(my_list[2]) # 输出:3
这个示例中,自定义类MyList内部包含一个列表self.data,通过实现__getitem__方法,可以使用索引访问self.data中的元素。
def accuracy(y_hat, y):
if len(y_hat.shape)>1 and y_hat.shape[1]>1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
print(accuracy(y_hat, y)/len(y))
def accuracy(y_hat, y):
if len(y_hat.shape)>1 and y_hat.shape[1]>1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
print(accuracy(y_hat, y)/len(y))
class Accumulator:
'在n个变量上累加'
def __init__(self, n):
self.data = [0.0] * n #创建一个1*n的全0列表
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def evalution_accuracy(net, data_iter):
if isinstance(net, torch.nn.Module):
net.eval()
meteric = Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
meteric.add(accuracy(net(X), y), y.numel())
return meteric[0]/meteric[1]
print(evalution_accuracy(net, test_iter))