深度学习(线性回归&softmax回归)

目录

线性回归

一些术语

一些概念解释

线性模型

​损失函数

解析解(最优解)

梯度下降(一种优化算法)

随机取样(随机梯度下降)

线性回归的从零实现

线性模型实现

代码解释:

读取数据集

代码解释:

读取结果

小批量随机梯度下降训练过程

对于训练过程的一些解释

对于线性回归的个人总结 

softmax回归

回归和的分类的简单区分

对一些概念的理解

似然函数

交叉熵

数据集

对数据集的读取

对数据集的可视化处理

对于小批量的读取

对于数据集的进一步处理

softmax回归从零实现

部分数据预测


线性回归

一些术语

 在机器学习的术语中,该数据集称为训练数据集(training data set)或训练集(training set)
 每行数据(比如一次房屋交易相对应的数据)称为样本(sample),也可以称为数据点(data   point)或数据样本(data instance)
 我们把试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)
 预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)

一些概念解释

线性模型

损失函数

损失函数是一个数学函数,它接受模型的预测值和真实值作为输入,并输出一个标量值,表示预测值与真实值之间的 “距离” 或 “误差”。其主要作用有以下几点:

  1. 评估模型性能:损失函数的值越小,说明模型的预测值越接近真实值,模型的性能越好。通过不断优化损失函数,我们可以提高模型的准确性和泛化能力。
  2. 指导模型训练:在模型训练过程中,损失函数的梯度被用于更新模型的参数。通过最小化损失函数,我们可以调整模型的参数,使得模型的预测值逐渐接近真实值

损失函数常见类型:

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)

把这些数据(训练损失、训练准确率、测试准确率)添加到画图的东西里,这样就可以看到随着训练周期的增加,这些指标是怎么变化的。

运行结果:

代码解释:

  1. 数据准备与加载

    • 首先,代码设定了每次从数据集中拿数据的数量(batch_size = 256),然后用这个batch_size去加载Fashion - MNIST数据集,这个数据集就像一堆衣服的图片,被分成了训练集(用来让模型学习怎么分类衣服)和测试集(用来看看模型学得好不好)。
  2. 模型搭建基础

    • 因为这些衣服图片是 28×28 像素的灰度图,把它展开成一条线的话就有 784 个数字(这就是每个图片的特征数量,也就是num_inputs),而这些衣服一共分为 10 个类别(比如 T 恤、裤子之类的,这就是num_outputs)。然后,代码随机初始化了两个重要的东西,一个是W(可以想象成一个大表格,用来调整每个特征对每个类别的影响),另一个是b(可以当成每个类别的一个初始分数),这两个东西都是模型的参数,就像调整收音机的旋钮一样,通过调整它们来让模型更好地工作。
  3. 定义 Softmax 和模型

    • Softmax 部分
      • 定义了softmax函数,这个函数就像是一个打分机器,它把模型计算出来的一些数字(比如对每个类别的初始判断分数)变成每个类别的概率。它的做法是先把这些数字变成指数形式(就像把分数放大了),然后把每个样本(这里的样本就是一张衣服图片)对应的这些指数数字加起来,再用每个指数数字除以这个和,这样就得到了每个类别的概率,这些概率加起来就是 1,就像把 100% 的可能性分配到 10 个类别里。
    • 模型部分
      • 定义了一个叫net的模型,这个模型做的事情就是把输入的图片数据(先把图片拉平成一条线),用前面的Wb进行计算(先做乘法再加上偏置),然后把结果送到softmax函数里,得到这张图片属于每个类别的概率。
  4. 定义损失和准确率计算

    • 损失函数
      • 定义了cross_entropy这个损失函数,它是用来衡量模型预测得有多差的。如果模型预测某个衣服是某个类别的概率很高,但是实际上不是这个类别,那这个损失就会很大。它是通过取预测概率的对数(只取真实类别对应的那个概率)然后再取负得到的。
    • 准确率计算
      • 有一个accuracy函数,它是看模型预测正确的比例是多少。如果模型预测的类别和真实类别一样,那就是预测正确了。对于模型输出的概率结果,如果是多个类别的概率,就看概率最大的那个类别是不是真实类别。还有一个evaluate_accuracy函数,这个函数是计算模型在整个数据集(比如测试集)上预测正确的比例,它会把数据集中的所有数据都过一遍模型,然后统计正确的数量除以总数得到准确率。
  5. 模型训练相关

    • 单个周期训练
      • train_epoch_ch3函数是用来训练模型一次的。在这个过程中,把模型设置成训练模式(如果是那种特殊的模型结构的话),然后对训练集中的每个批次数据进行操作。它会先让模型预测,然后计算损失,根据不同的更新方式(是用PyTorch自带的优化器还是自定义的)来调整模型的Wb这两个参数,同时还会累加这个批次的损失、正确预测的数量和这个批次的样本数量,最后算出这个周期的平均损失和准确率。
    • 更新函数
      • updater函数就是用来更新模型参数的,这里用的是随机梯度下降(sgd)的方法,就是根据损失函数的梯度(可以理解为损失函数变化的方向)来调整Wb,每次调整的幅度由学习率(这里是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函数的主要目的是使用给定的神经网络nettest_iter中的部分数据进行预测,并可视化预测结果。它将真实标签和预测标签组合成标题,与对应的图像一起展示,这样可以直观地查看模型预测的准确性。

 预测结果可视化输出:

 预测结果由两部分组成:真实标签(trues)和预测标签(preds)。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值