目录
线性回归
一些术语
在机器学习的术语中,该数据集称为训练数据集(training data set)或训练集(training set)
每行数据(比如一次房屋交易相对应的数据)称为样本(sample),也可以称为数据点(data point)或数据样本(data instance)
我们把试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)
预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)
一些概念解释
线性模型
损失函数
损失函数是一个数学函数,它接受模型的预测值和真实值作为输入,并输出一个标量值,表示预测值与真实值之间的 “距离” 或 “误差”。其主要作用有以下几点:
- 评估模型性能:损失函数的值越小,说明模型的预测值越接近真实值,模型的性能越好。通过不断优化损失函数,我们可以提高模型的准确性和泛化能力。
- 指导模型训练:在模型训练过程中,损失函数的梯度被用于更新模型的参数。通过最小化损失函数,我们可以调整模型的参数,使得模型的预测值逐渐接近真实值
损失函数常见类型:
1.均方误差(Mean Squared Error,MSE)
2.平均绝对误差(Mean Absolute Error,MAE)
3.交叉熵损失(Cross Entropy Loss)(常用于分类问题)
4.Hinge 损失
解析解(最优解)
解析解是对问题的精确描述,它能给出问题的准确答案,没有任何近似。
梯度下降(一种优化算法)
随机取样(随机梯度下降)
与传统的梯度下降算法不同,SGD 在每次迭代中不是使用整个数据集来计算梯度,而是随机选择一个或一小批样本(mini-batch)来计算梯度。这样可以大大减少计算量,特别是对于大规模数据集。
线性回归的从零实现
线性模型实现
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w, b, num_examples):
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)
print('features:', features[999],'\nlabel:', labels[999])
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
代码解释:
读取数据集
# 定义一个data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成
# 大小为batch_size的小批量。每个小批量包含一组特征和标签。
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 squared_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 = 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) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
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}')
# epoch 1, loss 0.042790
# epoch 2, loss 0.000162
# epoch 3, loss 0.000051
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
对于训练过程的一些解释
训练结果:
对于线性回归的个人总结
首先创建线性函数synthetic_data为 特征和目标值构造线性函数关系,接着初始化偏移量 权重(真实值)和特征矩阵,计算出目标值向量(真实值),函数构造完毕,然后开始读取数据集,采用随机小批量读取的方法(每个批量大小为batch_size),将读取的批量数据(特征和目标)记录,预测偏移量和权重大小并初始化,初始化学习率和轮数为以后优化准备,将预测的权重,偏移量和特征矩阵带入线性函数,求出预测的目标值,将预测目标值和真实值作为参数导入损失函数计算损失大小并记录,经过多次优化计算权重和偏移量误差大小
softmax回归
回归和的分类的简单区分
回归估计一个连续值
分类预计一个离散类别
Softmax回归其实是一个分类问题
对一些概念的理解
似然函数
简单理解就是 对于某事件发生既定事实预测与其相关另一件事发生概率的合理性
交叉熵
交叉熵值的意义:交叉熵越小,两个概率分布越接近
直观理解:
想象你有两个袋子,袋子里装着不同颜色的球,每个袋子中不同颜色球的比例代表一个概率分布。如果两个袋子中球的颜色比例非常相似,那么从一个袋子中随机取出一个球,猜测它来自另一个袋子的难度就比较小。这就对应着两个概率分布很接近,交叉熵也会比较小。
反之,如果两个袋子中球的颜色比例差异很大,那么猜测的难度就大,交叉熵就会比较大。
数据集
定义:数据集是一组数据的集合,它是机器学习和数据分析任务的基础。这些数据通常由多个样本组成,每个样本包含一个或多个特征以及一个对应的标签(在有监督学习中)。
分类:将数据集划分为训练集、验证集和测试集。训练集用于训练模型,验证集用于调整模型的超参数,测试集用于评估模型在未见过数据上的性能。这样可以避免模型过度拟合训练数据,从而提高模型的泛化能力。
MNIST数据集(LeCunetal.,1998)是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。我们将使用类似但更复杂的Fashion‐MNIST数据集(Xiaoetal.,2017)。
对数据集的读取
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0~1之间
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)
len(mnist_train), len(mnist_test)
对数据集的可视化处理
def get_fashion_mnist_labels(labels):
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
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)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
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))
处理之后的运行结果:
同样的,为了对数据方便快速处理 不消耗太多内存,对于softmax的实现 我们仍然使用小批量数据进行训练
对于小批量的读取
batch_size = 256
def get_dataloader_workers(): #@save
"""使用4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
timer = d2l.Timer()
for X, y in train_iter:
continue
f'{timer.stop():.2f} sec'
- 这段代码主要用于设置
Fashion - MNIST
训练数据的数据加载参数,创建数据加载器,然后测量使用该数据加载器遍历整个训练集数据所花费的时间。这有助于评估数据加载的效率,以便在模型训练时合理配置数据加载相关的参数。
对于数据集的进一步处理
#整合所有组件
# 这段代码的主要目的是方便地加载和预处理 Fashion-MNIST 数据集,并提供了一种简单的方式来查看加载的数据的形状和类型。
# 这对于深度学习模型的开发和调试非常有用,可以确保数据以正确的格式被加载和处理。
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
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, shuffle=True,
num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, 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
这段代码的总体目的是为了方便地获取Fashion - MNIST
数据集,并对其进行预处理、加载以及快速查看数据的基本信息(形状和数据类型),以便后续在深度学习模型的开发和调试中使用。
运行结果:
- 其中
torch.Size([32, 1, 64, 64])
表示一个批次中的图像数据的形状。32 是批次大小,即这个批次中有 32 张图像;1 表示图像是单通道的(因为Fashion - MNIST
是灰度图像数据集);64×64 表示图像经过Resize
操作后的尺寸为 64×64 像素。torch.float32
是图像数据的数据类型,因为经过ToTensor
操作后图像数据被转换为float32
类型。torch.Size([32])
表示标签数据的形状,即这个批次中有 32 个标签,每个标签对应一张图像,torch.int64
是标签数据的数据类型。- 结果的意义
- 这个输出结果有助于确认数据是否按照预期被加载和预处理。在开发和调试深度学习模型时,模型的输入数据需要满足一定的格式要求,例如正确的形状和数据类型。通过查看这个输出,可以快速验证数据加载和预处理步骤是否正确设置,从而避免在后续模型训练过程中出现因数据格式问题导致的错误。
softmax回归从零实现
import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
#初始化模型参数
num_inputs = 784
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)
#定义softmax操作
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
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)
X_prob, X_prob.sum(1)
#定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
#定义损失函数
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
#分类精度
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
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())
accuracy(y_hat, y) / len(y)
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
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]
evaluate_accuracy(net, test_iter)
#训练
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
class Animator: #@save
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
把这些数据(训练损失、训练准确率、测试准确率)添加到画图的东西里,这样就可以看到随着训练周期的增加,这些指标是怎么变化的。
运行结果:
代码解释:
数据准备与加载
- 首先,代码设定了每次从数据集中拿数据的数量(
batch_size = 256
),然后用这个batch_size
去加载Fashion - MNIST
数据集,这个数据集就像一堆衣服的图片,被分成了训练集(用来让模型学习怎么分类衣服)和测试集(用来看看模型学得好不好)。模型搭建基础
- 因为这些衣服图片是 28×28 像素的灰度图,把它展开成一条线的话就有 784 个数字(这就是每个图片的特征数量,也就是
num_inputs
),而这些衣服一共分为 10 个类别(比如 T 恤、裤子之类的,这就是num_outputs
)。然后,代码随机初始化了两个重要的东西,一个是W
(可以想象成一个大表格,用来调整每个特征对每个类别的影响),另一个是b
(可以当成每个类别的一个初始分数),这两个东西都是模型的参数,就像调整收音机的旋钮一样,通过调整它们来让模型更好地工作。定义 Softmax 和模型
- Softmax 部分
- 定义了
softmax
函数,这个函数就像是一个打分机器,它把模型计算出来的一些数字(比如对每个类别的初始判断分数)变成每个类别的概率。它的做法是先把这些数字变成指数形式(就像把分数放大了),然后把每个样本(这里的样本就是一张衣服图片)对应的这些指数数字加起来,再用每个指数数字除以这个和,这样就得到了每个类别的概率,这些概率加起来就是 1,就像把 100% 的可能性分配到 10 个类别里。- 模型部分
- 定义了一个叫
net
的模型,这个模型做的事情就是把输入的图片数据(先把图片拉平成一条线),用前面的W
和b
进行计算(先做乘法再加上偏置),然后把结果送到softmax
函数里,得到这张图片属于每个类别的概率。定义损失和准确率计算
- 损失函数
- 定义了
cross_entropy
这个损失函数,它是用来衡量模型预测得有多差的。如果模型预测某个衣服是某个类别的概率很高,但是实际上不是这个类别,那这个损失就会很大。它是通过取预测概率的对数(只取真实类别对应的那个概率)然后再取负得到的。- 准确率计算
- 有一个
accuracy
函数,它是看模型预测正确的比例是多少。如果模型预测的类别和真实类别一样,那就是预测正确了。对于模型输出的概率结果,如果是多个类别的概率,就看概率最大的那个类别是不是真实类别。还有一个evaluate_accuracy
函数,这个函数是计算模型在整个数据集(比如测试集)上预测正确的比例,它会把数据集中的所有数据都过一遍模型,然后统计正确的数量除以总数得到准确率。模型训练相关
- 单个周期训练
train_epoch_ch3
函数是用来训练模型一次的。在这个过程中,把模型设置成训练模式(如果是那种特殊的模型结构的话),然后对训练集中的每个批次数据进行操作。它会先让模型预测,然后计算损失,根据不同的更新方式(是用PyTorch
自带的优化器还是自定义的)来调整模型的W
和b
这两个参数,同时还会累加这个批次的损失、正确预测的数量和这个批次的样本数量,最后算出这个周期的平均损失和准确率。- 更新函数
updater
函数就是用来更新模型参数的,这里用的是随机梯度下降(sgd
)的方法,就是根据损失函数的梯度(可以理解为损失函数变化的方向)来调整W
和b
,每次调整的幅度由学习率(这里是lr = 0.1
)决定。- 多个周期训练与可视化
train_ch3
函数是用来进行多个周期(这里是 10 个周期)的训练。它会创建一个能画图的东西(Animator
),在每个周期里,调用train_epoch_ch3
函数得到这个周期的训练结果,再计算这个周期模型在测试集上的准确率,然后把这些数据(训练损失、训练准确率、测试准确率)添加到画图的东西里,这样就可以看到随着训练周期的增加,这些指标是怎么变化的。最后,还会检查一下训练的结果是不是合理,比如训练损失不能太大,训练准确率和测试准确率要在合理的范围内。
部分数据预测
# 预测
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
predict_ch3(net, test_iter)
- 这个
predict_ch3
函数的主要目的是使用给定的神经网络net
对test_iter
中的部分数据进行预测,并可视化预测结果。它将真实标签和预测标签组合成标题,与对应的图像一起展示,这样可以直观地查看模型预测的准确性。
预测结果可视化输出:
预测结果由两部分组成:真实标签(trues
)和预测标签(preds
)。