《动手学深度学习(PyTorch版)》笔记3.6

注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过。

Chapter3 Linear Neural Networks

3.6 Implementations of Softmax Regression from Scratch

import torch
import torchvision
import time
import matplotlib.pyplot as plt
import numpy as np 
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
from IPython import display

#初始化参数
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
num_inputs=784
#图像有28*28像素,本节将其看作长度为784的向量
num_outputs=10
#softmax回归中输出与类别一样多,数据集有10个类别
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

#定义softmax操作
def softmax(X):
    X_exp=torch.exp(X)
    partition=X_exp.sum(1,keepdim=True)
    return X_exp/partition#结果每行和为1

#定义softmax回归模型
#在将数据传递到模型之前,使用reshape将每个原始图像展开为向量
def net(X):
    return softmax(torch.matmul(X.reshape((-1,W.shape[0])),W)+b)

y = torch.tensor([0, 2])
#有了y,我们知道在第一个样本中第一类是正确的预测;在第二个样本中第三类是正确的预测
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
#2个样本在3个类别上的预测概率
#print(y_hat[[0, 1], y])
#使用y作为y_hat中概率的索引
#我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率,即输出[y[0],y[1]]

#定义交叉熵损失函数
def cross_entropy(y_hat,y):
    return -torch.log(y_hat[range(len(y_hat)),y])

#定义一个用于对多个变量累加的的类
class Accumulator:#@save
    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]
    
#计算分类精度
def accuracy(y_hat,y):#@save
    """计算预测正确的数量"""
    if len(y_hat.shape)>1 and y_hat.shape[1]>1:#如果`y_hat`是矩阵,那么假定第二个维度存储每个类的预测分数
        y_hat=y_hat.argmax(axis=1)
        #使用`argmax`获得每行中最大元素的索引来获得预测类别
    cmp=y_hat.type(y.dtype)==y
    #由于等式运算符"`==`"对数据类型很敏感,因此我们将`y_hat`的数据类型转换为与`y`的数据类型一致。
    return float(cmp.type(y.dtype).sum())

def evaluate_accuracy(net,data_iter):#@save
    """计算在指定数据集上模型的精度"""
    if isinstance(net,torch.nn.Module):
        net.eval()#将模型设为评估模式
    metric=Accumulator(2)#Initializes an Accumulator with two variables: the number of correct predictions and the total number of predictions.
    with torch.no_grad():#disables gradient computation
        for X,y in data_iter:
            metric.add(accuracy(net(X),y),y.numel())#y.numel() returns the total number of elements in y.
    return metric[0]/metric[1]

#if __name__ == '__main__':
    #print(evaluate_accuracy(net,test_iter))
    #由于使用随机权重初始化net模型,因此该模型的精度接近于随机猜测,如在有10个类别情况下的精度接近0.1 

#定义一个在动画中绘制数据的类
class Animator:
    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:#lengend:图例
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        #"plt.subplots()" is called to create a figure (self.fig) and one or more subplots (self.axes).
        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)
        #A lambda function is used to create an anonymous function that is then assigned to the "self.config_axes" attribute. 
        #This is a common pattern in Python, especially when a short, simple function is needed, and there's no intention to reuse it elsewhere in the code.
        # It provides a more compact and inline way to express the behavior.
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):#Adds data points to the plot.
        if not hasattr(y, "__len__"):#If y is not iterable (doesn't have a length), it is converted to a list to ensure it can be processed as a collection of values.
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):#If x is not iterable, it is repeated n times to match the length of y.
            x = [x] * n
        if not self.X:#If "self.X" is not initialized, it is initialized as a list of empty lists, with one list for each element in y.
            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()# clears the current axis to prepare for the new data
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        #configures the axis using specified parameters.
        display.display(self.fig)
        display.clear_output(wait=True)
        
#训练
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    #updater是更新模型参数的常用函数,在后文定义
    """训练模型一个迭代周期"""
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):#checks if the object referred to by the variable net is an instance of the "torch.nn.Module" class
        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()# Clear previously calculated gradients
            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]

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    """训练模型"""
    # 创建一个用于动画绘制的实例
    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#If the condition is False, it raises an AssertionError exception.
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

if __name__ == '__main__':
    lr = 0.1
    num_epochs = 10
    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

#预测
#给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)
def predict_ch3(net, test_iter, n=6):
    """预测标签"""
    # Iterate over the test dataset to get a batch of images and their true labels
    for X, y in test_iter:
        break
    
    # Get the true labels in text format
    trues = d2l.get_fashion_mnist_labels(y)
    # Use the trained model to make predictions on the test batch and convert predictions to text labels
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1)) 
    # Create titles for the images by combining true labels and predicted labels
    titles = [true + '\n' + pred for true, pred in zip(trues, preds)]
    # Display a subset (n) of the images along with their true and predicted labels
    d2l.show_images(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

    
if __name__ == '__main__':
    predict_ch3(net, test_iter)
    
plt.show()#将折线图和预测结果的图像统一显示## 3.6 Implementations of Softmax Regression from Scratch

import torch
import torchvision
import time
import matplotlib.pyplot as plt
import numpy as np 
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
from IPython import display

#初始化参数
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
num_inputs=784
#图像有28*28像素,本节将其看作长度为784的向量
num_outputs=10
#softmax回归中输出与类别一样多,数据集有10个类别
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

#定义softmax操作
def softmax(X):
    X_exp=torch.exp(X)
    partition=X_exp.sum(1,keepdim=True)
    return X_exp/partition#结果每行和为1

#定义softmax回归模型
#在将数据传递到模型之前,使用reshape将每个原始图像展开为向量
def net(X):
    return softmax(torch.matmul(X.reshape((-1,W.shape[0])),W)+b)

y = torch.tensor([0, 2])
#有了y,我们知道在第一个样本中第一类是正确的预测;在第二个样本中第三类是正确的预测
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
#2个样本在3个类别上的预测概率
#print(y_hat[[0, 1], y])
#使用y作为y_hat中概率的索引
#我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率,即输出[y[0],y[1]]

#定义交叉熵损失函数
def cross_entropy(y_hat,y):
    return -torch.log(y_hat[range(len(y_hat)),y])

#定义一个用于对多个变量累加的的类
class Accumulator:#@save
    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]
    
#计算分类精度
def accuracy(y_hat,y):#@save
    """计算预测正确的数量"""
    if len(y_hat.shape)>1 and y_hat.shape[1]>1:#如果`y_hat`是矩阵,那么假定第二个维度存储每个类的预测分数
        y_hat=y_hat.argmax(axis=1)
        #使用`argmax`获得每行中最大元素的索引来获得预测类别
    cmp=y_hat.type(y.dtype)==y
    #由于等式运算符"`==`"对数据类型很敏感,因此我们将`y_hat`的数据类型转换为与`y`的数据类型一致。
    return float(cmp.type(y.dtype).sum())

def evaluate_accuracy(net,data_iter):#@save
    """计算在指定数据集上模型的精度"""
    if isinstance(net,torch.nn.Module):
        net.eval()#将模型设为评估模式
    metric=Accumulator(2)#Initializes an Accumulator with two variables: the number of correct predictions and the total number of predictions.
    with torch.no_grad():#disables gradient computation
        for X,y in data_iter:
            metric.add(accuracy(net(X),y),y.numel())#y.numel() returns the total number of elements in y.
    return metric[0]/metric[1]

#if __name__ == '__main__':
    #print(evaluate_accuracy(net,test_iter))
    #由于使用随机权重初始化net模型,因此该模型的精度接近于随机猜测,如在有10个类别情况下的精度接近0.1 

#定义一个在动画中绘制数据的类
class Animator:
    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:#lengend:图例
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        #"plt.subplots()" is called to create a figure (self.fig) and one or more subplots (self.axes).
        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)
        #A lambda function is used to create an anonymous function that is then assigned to the "self.config_axes" attribute. 
        #This is a common pattern in Python, especially when a short, simple function is needed, and there's no intention to reuse it elsewhere in the code.
        # It provides a more compact and inline way to express the behavior.
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):#Adds data points to the plot.
        if not hasattr(y, "__len__"):#If y is not iterable (doesn't have a length), it is converted to a list to ensure it can be processed as a collection of values.
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):#If x is not iterable, it is repeated n times to match the length of y.
            x = [x] * n
        if not self.X:#If "self.X" is not initialized, it is initialized as a list of empty lists, with one list for each element in y.
            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()# clears the current axis to prepare for the new data
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        #configures the axis using specified parameters.
        display.display(self.fig)
        display.clear_output(wait=True)
        
#训练
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    #updater是更新模型参数的常用函数,在后文定义
    """训练模型一个迭代周期"""
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):#checks if the object referred to by the variable net is an instance of the "torch.nn.Module" class
        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()# Clear previously calculated gradients
            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]

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    """训练模型"""
    # 创建一个用于动画绘制的实例
    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#If the condition is False, it raises an AssertionError exception.
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

if __name__ == '__main__':
    lr = 0.1
    num_epochs = 10
    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

#预测
#给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)
def predict_ch3(net, test_iter, n=6):
    """预测标签"""
    # Iterate over the test dataset to get a batch of images and their true labels
    for X, y in test_iter:
        break
    
    # Get the true labels in text format
    trues = d2l.get_fashion_mnist_labels(y)
    # Use the trained model to make predictions on the test batch and convert predictions to text labels
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1)) 
    # Create titles for the images by combining true labels and predicted labels
    titles = [true + '\n' + pred for true, pred in zip(trues, preds)]
    # Display a subset (n) of the images along with their true and predicted labels
    d2l.show_images(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

    
if __name__ == '__main__':
    predict_ch3(net, test_iter)
    
plt.show()#将折线图和预测结果的图像统一显示
相关推荐
1101 1101几秒前
STM32-笔记14-排队控制系统
笔记·stm32·嵌入式硬件
volcanical7 分钟前
Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena
人工智能·自然语言处理·机器翻译
大知闲闲哟8 分钟前
深度学习J6周 ResNeXt-50实战解析
人工智能·深度学习
执着的小火车25 分钟前
02-18.python入门基础一基础算法
数据结构·python·算法·排序算法
梦茹^_^26 分钟前
排序算法(系列)
数据结构·python·算法·排序算法·希尔排序·基数排序·计数排序和桶排序
远离UE427 分钟前
UE5 渲染管线 学习笔记
笔记·学习·ue5
码上好玩41 分钟前
vscode写python,遇到问题:ModuleNotFoundError: No module named ‘pillow‘(已解决 避坑)
vscode·python·pillow
Dcy_ASK42 分钟前
认识Python语言
开发语言·python
静静AI学堂1 小时前
Yolo11改策略:卷积改进|SAC,提升模型对小目标和遮挡目标的检测性能|即插即用
人工智能·深度学习·目标跟踪