softmax回归的从零开始实现

一、创建数据集

从Fashion-MNIST数据集中引入创建数据,并设置数据迭代器的批量大小为256

1
2
3
import torch
from IPython import display
from d2l import torch as d2l
1
2
3
4
5
6
#batch_size=256,表明随机读取256张图片
batch_size = 256
 
# 返回训练集和测试集的迭代器
# load_data_fashion_mnist函数是在图像分类数据集中定义的一个函数,可以返回batch_size大小的训练数据集和测试数据集
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

  

二、初始化模型参数

1、原始数据集中的每个样本都是28*28的图像

2、在本书中,我们将展平每个图像,把它们看作长度为784的向量

3、在softmax回归中,我们输出与类别一样多。(因为我们的数据集有10个类别,所以网络输出的维度为10)

4、权重将构成一个 784×10 的矩阵,偏置将构成一个 1×10 的行向量。与线性回归一样,我们将使用正态分布初始化我们的权重 W,偏置初始化为0

1
2
3
4
5
6
7
8
9
10
11
12
num_inputs = 784 # 样本的长和宽都是28,将其展平为空间向量,长度变为784
num_outputs = 10 # 数据集类别,也是输出数
 
 
#size=(num_inputs, num_outputs);行数等于输入的个数,列数等于输出的个数
#requires_grad=True表明要计算梯度
#权重
#在这里W被定义为一个784*10的矩阵
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
 
#偏离
b = torch.zeros(num_outputs, requires_grad=True)

  

三、定义softmax操作

1
2
3
4
5
6
7
8
9
10
11
12
# 定义softmax()函数
 
def softmax(X):
    #torch.exp()对每个元素做指数计算
    X_exp = torch.exp(X)
     
    #对矩阵的每一行求和,重新生成新的矩阵
    partition = X_exp.sum(1, keepdim=True)
     
     
    # X_exp / partition:每一行代表一个样本,行中的每个数据代表在该类别的概率
    return X_exp / partition  # 这里应用了广播机制

可以发现,每个元素都变成了一个非负数。此外,依据概率原理,每行总和为1。

如下,是个小测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建一个随机的均值为0,方差为1的两行五列的矩阵
X = torch.normal(0, 1, (2, 5))
 
#对矩阵进行softmax计算
X_prob = softmax(X)
 
X_prob, X_prob.sum(1)
#通过输出可以发现在softmax之后,每个元素都是正的,且行之和为1
 
#输出结果
 
(tensor([[0.0474, 0.0313, 0.8646, 0.0317, 0.0250],
         [0.1736, 0.4575, 0.2832, 0.0618, 0.0239]]),
 tensor([1.0000, 1.0000]))

  

四、定义模型

1、利用之前实现的softmax操作,可以实现softmax回归模型

2、在将数据传递到模型之前,需要使用reshape函数将每张原始图像展平为向量

1
2
3
4
5
6
def net(X):
    #matmul是矩阵乘法
    #W.shape[0]=784
     
    # W是一个784*10的矩阵,W.shape就是[784,10]的列表,可以通过W.shape[0]来访问784
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

  

五、定义损失函数

1
2
3
4
5
6
7
8
9
10
11
12
y = torch.tensor([0, 2])
 
# y_hat为预测值,此次是对两个样本做预测值
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
 
# 对第零个样本,拿出y0的数据=0.1;对第一个样本,拿出y1的书籍,y1=2,就是拿出第三个数据=0.5
# 第一个参数[0,1]表示样本号,第二个参数y表示在第一个参数确定的样本中取数的序号
y_hat[[0, 1], y]
 
#输出结果
 
tensor([0.1000, 0.5000])

上诉可以帮助我们理解“交叉”的含义。接下来,我们只需要添加一行代码实现损失函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 给定我的y_hat预测和真实标号y
def cross_entropy(y_hat, y):
    #对于每一行range(len(y_hat)):生成一个从零开始一直到len(y_hat)的向量
     
    return -torch.log(y_hat[range(len(y_hat)), y])
 
print(cross_entropy(y_hat, y))
-torch.log(y_hat[[0, 1], y])
#输出:2.3026样本零的损失,0.6931样本1的损失
 
 
#输出结果
 
tensor([2.3026, 0.6931])
tensor([2.3026, 0.6931])

  

六、分类准确度

1、分类准确率即正确预测数量与总预测数量之比。

2、y_hat 是矩阵,假定第二个维度存储每个类的预测分数。

3、使用 argmax 获得每行中最大元素的索引来获得预测类别

4、将预测类别与真实 y 元素进行比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 计算分类准确率,正确预测数量与总预测数量之比
# y_hat是预测值,y是真实值
 
# 计算预测正确的样本数
def accuracy(y_hat, y):  #@save
    """计算预测正确的数量。"""
    # len是查看矩阵的行数
    # y_hat.shape[1]就是去列数,y_hat.
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        #每一行中元素最大的那个下标存在y_hat里面
        #最大的元素就可以算为预测分类的类别
        y_hat = y_hat.argmax(axis=1)
         
    #将y_hat转换为y的数据类型然后作比较
    #使用cmp函数存储bool类型
    cmp = y_hat.type(y.dtype) == y
     
    #将cmp转化为y的数据类型再求和——得到找出来预测正确的类别数
    return float(cmp.type(y.dtype).sum())

5、按照之前定义的变量y_hat和y,我们预测准确率(第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。第二个样本的预测类别是2(该行的最大元素为0.5,索引为 2),这与实际标签2一致)

1
2
3
4
5
6
#除以整个y的长度(样本数),就是预测正确的概率
accuracy(y_hat, y) / len(y)
 
#输出结果
 
0.5

 6、泛化,对于任意数据迭代器data_iter 可访问的数据集,我们可以评估在任意模型 net 的准确率。

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
# 评估任意模型的准确率
 
def evaluate_accuracy(net, data_iter):  #@save
    """计算在指定数据集上模型的精度。"""
     
     
    # isinstance():判断一个对象是否是一个已知的类型
    # 判断输入的net模型是否是torch.nn.Module类型
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式(不用计算梯度)
         
    metric = Accumulator(2)  # 存储正确预测数、预测总数
     
    # 每次从迭代器中拿出一个x和y
    for X, y in data_iter:
         
        # 1、net(X):X放在net模型中进行softmax操作
        # 2、accuracy(net(X), y):再计算所有预算正确的样本数
        # numel()函数:返回数组中元素的个数,在此可以求得样本数
        metric.add(accuracy(net(X), y), y.numel())
         
    #metric[0]:分类正确的样本数,metric[1]:总的样本数
    return metric[0] / metric[1]
 
evaluate_accuracy(net, test_iter)
 
#输出结果
0.1001

  

七、训练

1、首先,我们定义一个函数来训练一个迭代周期

2、注意,updater是更新模型参数的常用函数,接受批量大小作为参数

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
def train_epoch_ch3(net, train_iter, loss, updater):  #@save<br>
    """训练模型一个迭代周期(定义见第3章)。"""<br>
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):
        net.train()#告诉pytorch我要计算梯度
         
    # 训练损失总和、训练准确度总和、样本数
    # Accumulator 是一个实用程序类,用于对多个变量进行累加
    #在此创建了一个长度为三的迭代器,用于累加信息
    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.backward() #计算梯度
            updater.step()#自更新
             
            metric.add(
                float(l) * len(y), accuracy(y_hat, y),
                y.size().numel())
        else:
            # 使用定制的优化器和损失函数
            # 如果是自我实现的话,l出来就是向量,我们先做求和,再求梯度
            l.sum().backward()
            updater(X.shape[0])
            metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练准确率
    # metric[0]就是损失样本数目;metric[1]是训练正确的样本数;metric[2]是总的样本数
    return metric[0] / metric[2], metric[1] / metric[2]

3、实现一个训练函数,它会在train_iter 访问到的训练数据集上训练一个模型net

4、该训练函数将会运行多个迭代周期(由num_epochs指定)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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'])
     
     
    # num_epochs:训练次数
    for epoch in range(num_epochs):
        #train_epoch_ch3:训练模型,返回准确度和错误度
        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

5、作为一个从零开始的实现,我们使用 小批量随机梯度下降来优化模型的损失函数,设置学习率为0.1

1
2
3
4
lr = 0.1
 
def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

6、现在,训练模型10个迭代周期。请注意,迭代周期(num_epochs)和学习率(lr)都是可调节的超参数。通过更改它们的值,我们可以提高模型的分类准确率。

1
2
3
#开始训练
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

  

八、预测

给定一系列图像,比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)

1
2
3
4
5
6
7
8
9
10
11
12
def predict_ch3(net, test_iter, n=6):  #@save
    """预测标签(定义见第3章)。"""
    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)
posted @   小秦同学在上学  阅读(1285)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示