进阶Task2:《深度学习详解》(Datawhale X 李宏毅苹果书 AI夏令营)

前言 

文章概述《深度学习详解》- 3.3-3.5

书中探讨了梯度下降算法及其改进方法,介绍了动量法、自适应学习率(包括AdaGrad、RMSProp和Adam算法)、以及学习率调度技巧。文章首先讲述了传统梯度下降的局限性,例如容易陷入局部最优解,随后引出了动量法的概念,通过积累历史梯度信息来加速搜索过程并突破局部最优。进一步讨论了自适应学习率方法如何针对不同参数和不同迭代阶段动态调整学习率,以提高优化效率。此外,文中详细分析了学习率调度策略,如学习率衰减和预热技术,旨在解决学习率过大或过小时的训练问题,从而确保模型能够在训练过程中持续稳定地改善。最后,文章对比了分类与回归任务的特点,指出了在处理分类问题时使用独热编码的必要性,强调了选择合适的优化方法对于深度学习模型训练效果的重要性。通过整合多种优化技术和策略,文章提供了深度学习模型高效训练的全面视角。


个人学习笔记以及概念梳理,望对大家有所帮助。


几种常见的自适应学习率算法

涉及的一些术语

术语

解释

动量(Momentum)

在梯度下降过程中加入历史梯度方向信息以加速收敛并避免振荡。

自适应学习率

根据训练过程动态调整学习率的技术。

临界点

梯度接近零的点,可能是局部最小值或鞍点。

局部最小值

损失函数上的点,在其邻域内损失值达到最小。

鞍点

损失函数上的点,某些方向上是局部最大值,其他方向则是局部最小值。

梯度下降

一种优化算法,通过沿着负梯度方向更新参数以最小化损失。

梯度范数

梯度向量的长度,用于衡量梯度的大小。

误差表面

描述模型参数与损失之间关系的多维曲面。

凸误差表面

误差表面的一种形式,其中任意两点间的连线在误差表面上方。

AdaGrad

一种自适应学习率方法,根据每个参数的历史梯度大小调整学习率。

RMSProp

一种自适应学习率方法,使用指数加权平均来估计梯度的标准差。

Adam

一种结合了动量和自适应学习率的优化算法。

学习率调度

动态调整学习率以提高训练效率和性能的技术。

学习率衰减(退火)

逐渐减少学习率的方法。

预热(Warm-up)

训练初期逐渐增加学习率以稳定训练过程。

涉及的一些公式

学习过程遇到的一些问题的理解:

1解释为什么在某些情况下,直接使用类别的数字编号作为标签可能会导致问题,并说明独热编码如何解决这个问题。

首先,直接使用类别的数字编号作为标签可能会导致问题,主要原因在于这种编码方式隐含了一个顺序关系,即数值之间的大小关系被错误地赋予了类别意义。例如,如果类别是“狗”、“猫”和“鸟”,分别用数字1、2、3来表示,模型可能会错误地认为“猫”(2)比“狗”(1)更接近“鸟”(3),即使在实际语义上这三个类别之间并没有这样的顺序关系。

这种问题会导致模型学习到错误的模式,尤其是在使用距离度量(如欧几里得距离或余弦相似性)的算法中更为明显。例如,在神经网络中,如果类别标签被直接用作输出层的激活值,那么这些数值会被视为连续的实数值,从而影响损失函数的计算,进而影响模型的训练结果。

独热编码(One-Hot Encoding)的作用

独热编码是一种常用的解决方案,它可以将每个类别转换为一个独立的维度,并且只在这个维度上有一个值为1,其余均为0。这样,不同类别之间就没有了隐含的顺序关系,每个类别都被表示为一个独立的特征向量。例如,“狗”、“猫”和“鸟”可以分别表示为[1, 0, 0]、[0, 1, 0]和[0, 0, 1]。

独热编码解决了上述问题,因为它确保了类别之间的独立性,不会因为数字编码而引入不必要的结构。这对于分类任务尤为重要,因为它能够帮助模型更好地理解和区分不同的类别。

代码运行

仿 实践(DNN)深度神经网络-图像分类  (详情可见,‍​​​​​​​​‬‌‍​​​‬​​​‍‍⁠‌⁠​‌‌⁠‍​‬‌​​⁠‍‬⁠Task2.3(实践任务):HW3(CNN)卷积神经网络-图像分类 - 飞书云文档 (feishu.cn) )

提供数据集来源

Food-101:101 个食物类别数据集_数据集-飞桨AI Studio星河社区 (baidu.com)

保留以下文件夹:
 ['apple_pie', 'beef_tartare', 'beet_salad', 'cheesecake', 'chicken_wings', 'dumplings', 'fish_and_chips', 'gyoza', 'hamburger', 'hot_dog']

将其中的图片放在一个文件夹中,并每一个类型保留100张图片,共计1000张图片,同时通过对颜色直方图进行处理,获得512个特征后用于后续分类任务。

实现功能

将图片分类为10种:'apple_pie', 'beef_tartare', 'beet_salad', 'cheesecake', 'chicken_wings', 'dumplings', 'fish_and_chips', 'gyoza', 'hamburger', 'hot_dog'。

同时输出每一种分类的精度。

处理过程:

预处理

将文件夹下子目录中对应分类的文件夹中的图片重命名,便于后续分类使用

代码如下

import os

# 指定目录路径
directory = r'.\food-101\images'

# 获取所有子目录的名字
folder_names = [f for f in os.listdir(directory) if os.path.isdir(os.path.join(directory, f))]

# 遍历每个子目录
for folder_name in folder_names:
    # 构建完整的子目录路径
    sub_directory = os.path.join(directory, folder_name)

    # 获取子目录中的所有文件
    files = os.listdir(sub_directory)

    # 对子目录中的 .jpg 文件进行重命名
    for index, file in enumerate(files, start=1):
        if file.endswith('.jpg'):
            # 构建新的文件名
            new_filename = f"{folder_name}-{index}.jpg"

            # 获取当前文件的完整路径
            current_file_path = os.path.join(sub_directory, file)

            # 获取新文件名的完整路径
            new_file_path = os.path.join(sub_directory, new_filename)

            # 重命名文件
            os.rename(current_file_path, new_file_path)

# 输出完成信息
print("所有文件已重命名。")

将重命名完的图片移到新建的picture文件夹中,生成对应的csv文件,展示每类型的数量

import os
import cv2
import pandas as pd
from PIL import Image
import numpy as np

# 指定目录路径
directory = r'.\food-101\picture'
output_csv = 'food_dataset.csv'

# 创建一个空的DataFrame来存储数据
data = pd.DataFrame(columns=['filename', 'class'] + [f'f{i}' for i in range(1, 256)])

# 遍历指定目录下的所有文件
for filename in os.listdir(directory):
    if filename.endswith('.jpg') or filename.endswith('.png'):  # 假设图片文件是.jpg或.png格式
        filepath = os.path.join(directory, filename)
        try:
            # 使用PIL读取图像
            img_pil = Image.open(filepath)

            # 转换为NumPy数组
            img_array = np.array(img_pil)

            # 提取类名
            class_name = filename.split('-')[0]

            # 计算颜色直方图
            hist = cv2.calcHist([img_array], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
            hist = cv2.normalize(hist, hist).flatten()

            # 将数据添加到DataFrame
            row_data = {'filename': filename, 'class': class_name}
            row_data.update(dict(zip([f'f{i}' for i in range(1, len(hist) + 1)], hist)))
            data = data.append(row_data, ignore_index=True)

        except Exception as e:
            print(f"Error processing {filepath}: {str(e)}")

# 保存数据到CSV文件
data.to_csv(output_csv, index=False)

运行结果,数据集情况

开始正式的分类任务

简单地构建一个分类器。通过一系列卷积层、批归一化层、激活函数和池化层构建卷积神经网络(CNN),用于提取图像特征

import os
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix

# 指定目录路径
directory = r'.\food-101\picture'

# 定义标签映射
label_to_index = {
    'apple_pie': 0,
    'beef_tartare': 1,
    'beet_salad': 2,
    'cheesecake': 3,
    'chicken_wings': 4,
    'dumplings': 5,
    'fish_and_chips': 6,
    'gyoza': 7,
    'hamburger': 8,
    'hot_dog': 9,
}

index_to_label = {v: k for k, v in label_to_index.items()}


# 数据预处理
class FoodImageDataset(Dataset):
    def __init__(self, directory, transform=None):
        self.directory = directory
        self.transform = transform
        self.image_filenames = []
        self.labels = []

        for filename in os.listdir(directory):
            if filename.endswith('.jpg') or filename.endswith('.png'):
                self.image_filenames.append(filename)
                label = filename.split('-')[0]
                self.labels.append(label_to_index[label])

    def __len__(self):
        return len(self.image_filenames)

    def __getitem__(self, idx):
        img_path = os.path.join(self.directory, self.image_filenames[idx])
        image = Image.open(img_path)
        label = self.labels[idx]

        if self.transform:
            image = self.transform(image)

        return image, label


# 定义数据转换
transform = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 创建数据集
dataset = FoodImageDataset(directory, transform=transform)

# 分割数据集
train_size = int(0.8 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)


# 定义模型
class Classifier(nn.Module):
    """
    定义一个图像分类器类,继承自PyTorch的nn.Module。
    该分类器包含卷积层和全连接层,用于对图像进行分类。
    """

    def __init__(self):
        """
        初始化函数,构建卷积神经网络的结构。
        包含一系列的卷积层、批归一化层、激活函数和池化层。
        """
        super(Classifier, self).__init__()
        # 定义卷积神经网络的序列结构
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # 输入通道3,输出通道64,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(64),  # 批归一化,作用于64个通道
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2, 2, 0),  # 最大池化,池化窗口大小2,步长2,填充0

            nn.Conv2d(64, 128, 3, 1, 1),  # 输入通道64,输出通道128,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(128),  # 批归一化,作用于128个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # 最大池化,池化窗口大小2,步长2,填充0

            nn.Conv2d(128, 256, 3, 1, 1),  # 输入通道128,输出通道256,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(256),  # 批归一化,作用于256个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # 最大池化,池化窗口大小2,步长2,填充0

            nn.Conv2d(256, 512, 3, 1, 1),  # 输入通道256,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),  # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # 最大池化,池化窗口大小2,步长2,填充0

            nn.Conv2d(512, 512, 3, 1, 1),  # 输入通道512,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),  # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # 最大池化,池化窗口大小2,步长2,填充0
        )
        # 定义全连接神经网络的序列结构
        self.fc = nn.Sequential(
            nn.Linear(512 * 4 * 4, 1024),  # 输入大小512*4*4,输出大小1024
            nn.ReLU(),
            nn.Linear(1024, 512),  # 输入大小1024,输出大小512
            nn.ReLU(),
            nn.Linear(512, 11)  # 输入大小512,输出大小11,最终输出11个类别的概率
        )

    def forward(self, x):
        """
        前向传播函数,对输入进行处理。

        参数:
        x -- 输入的图像数据,形状为(batch_size, 3, 128, 128)

        返回:
        输出的分类结果,形状为(batch_size, 11)
        """
        out = self.cnn(x)  # 通过卷积神经网络处理输入
        out = out.view(out.size()[0], -1)  # 展平输出,以适配全连接层的输入要求
        return self.fc(out)  # 通过全连接神经网络得到最终输出

# 实例化模型
model = Classifier()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item() * images.size(0)
    epoch_loss = running_loss / len(train_dataset)
    print(f"Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss:.4f}")

# 评估模型
model.eval()
y_true = []
y_pred = []

with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        y_true.extend(labels.cpu().numpy())
        y_pred.extend(predicted.cpu().numpy())

# 计算性能指标
print("Classification Report:")
print(classification_report(y_true, y_pred, target_names=list(index_to_label.values())))
print("Confusion Matrix:")
print(confusion_matrix(y_true, y_pred))

# 输出每个类别的精度和召回率
report = classification_report(y_true, y_pred, output_dict=True)
for label, metrics in report.items():
    if label in index_to_label:
        print(f"{index_to_label[int(label)]}: Precision={metrics['precision']:.2f}, Recall={metrics['recall']:.2f}")

运行后结果

可见,整体效果不是很好,精度较低,大部分分类连50%都达不到,其中最高的一个分类才0.8。

优化

对此,就以下几个方面进行了优化

数据增强

对训练集使用了随机裁剪 RandomResizedCrop 和水平翻转 RandomHorizontalFlip,这有助于增加模型的泛化能力。

模型架构

使用预训练的 ResNet18 模型替换原有自定义 CNN 架构。

调整最后一层以适应 10 类分类任务。

优化器和学习率调度

使用 Adam 优化器。

使用 ReduceLROnPlateau 学习率调度器,当验证集上的性能不再改善时降低学习率。

训练和验证

训练轮数增加到 20 轮。

保存验证集上表现最好的模型。

import os
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, models
from PIL import Image
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix

# 指定目录路径
directory = r'.\food-101\picture'

# 定义标签映射
label_to_index = {
    'apple_pie': 0,
    'beef_tartare': 1,
    'beet_salad': 2,
    'cheesecake': 3,
    'chicken_wings': 4,
    'dumplings': 5,
    'fish_and_chips': 6,
    'gyoza': 7,
    'hamburger': 8,
    'hot_dog': 9,
}

index_to_label = {v: k for k, v in label_to_index.items()}


# 数据预处理
class FoodImageDataset(Dataset):
    def __init__(self, directory, transform=None):
        self.directory = directory
        self.transform = transform
        self.image_filenames = []
        self.labels = []

        for filename in os.listdir(directory):
            if filename.endswith('.jpg') or filename.endswith('.png'):
                self.image_filenames.append(filename)
                label = filename.split('-')[0]
                self.labels.append(label_to_index[label])

    def __len__(self):
        return len(self.image_filenames)

    def __getitem__(self, idx):
        img_path = os.path.join(self.directory, self.image_filenames[idx])
        image = Image.open(img_path)
        label = self.labels[idx]

        if self.transform:
            image = self.transform(image)

        return image, label


# 定义数据转换
transform_train = transforms.Compose([
    transforms.RandomResizedCrop(128),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

transform_test = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 创建数据集
dataset = FoodImageDataset(directory, transform=transform_train)

# 分割数据集
train_size = int(0.8 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

# 使用预训练的ResNet模型
model = models.resnet18(pretrained=True)
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 学习率调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

# 训练设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 训练模型
num_epochs = 20
best_acc = 0.0
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item() * images.size(0)
    epoch_loss = running_loss / len(train_dataset)
    scheduler.step(epoch_loss)

    # 在验证集上评估
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    acc = correct / total
    print(f"Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss:.4f}, Val Acc: {acc:.4f}")
    if acc > best_acc:
        best_acc = acc
        torch.save(model.state_dict(), 'best_model.pth')

# 加载最佳模型
model.load_state_dict(torch.load('best_model.pth'))

# 评估模型
y_true = []
y_pred = []

model.eval()
with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        y_true.extend(labels.cpu().numpy())
        y_pred.extend(predicted.cpu().numpy())

# 计算性能指标
print("Classification Report:")
print(classification_report(y_true, y_pred, target_names=list(index_to_label.values())))
print("Confusion Matrix:")
print(confusion_matrix(y_true, y_pred))

# 输出每个类别的精度和召回率
report = classification_report(y_true, y_pred, output_dict=True)
for label, metrics in report.items():
    if label in index_to_label:
        print(f"{index_to_label[int(label)]}: Precision={metrics['precision']:.2f}, Recall={metrics['recall']:.2f}")

分类效果有所提升  (输出会在后面展示)

验证

这里就不再进行提升性能了,我们来验证一下独热编码对分类效果的影响

修改对应代码如下

import os
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, models
from PIL import Image
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix

# 指定目录路径
directory = r'C:\Users\Zivy\Desktop\深度学习\t1\food-101\picture'

# 定义标签映射
# 使用独热编码表示类别标签
num_classes = 10  # 总共的类别数量
label_to_index = {
    'apple_pie': 0,
    'beef_tartare': 1,
    'beet_salad': 2,
    'cheesecake': 3,
    'chicken_wings': 4,
    'dumplings': 5,
    'fish_and_chips': 6,
    'gyoza': 7,
    'hamburger': 8,
    'hot_dog': 9,
}

index_to_label = {v: k for k, v in label_to_index.items()}


# 数据预处理
class FoodImageDataset(Dataset):
    def __init__(self, directory, transform=None):
        self.directory = directory
        self.transform = transform
        self.image_filenames = []
        self.labels = []

        for filename in os.listdir(directory):
            if filename.endswith('.jpg') or filename.endswith('.png'):
                self.image_filenames.append(filename)
                label = filename.split('-')[0]
                self.labels.append(label_to_index[label])

    def __len__(self):
        return len(self.image_filenames)

    def __getitem__(self, idx):
        img_path = os.path.join(self.directory, self.image_filenames[idx])
        image = Image.open(img_path)
        label = self.labels[idx]

        if self.transform:
            image = self.transform(image)

        # 将整数标签转换为独热编码
        label = F.one_hot(torch.tensor(label), num_classes=num_classes).float()

        return image, label


# 定义数据转换
transform_train = transforms.Compose([
    transforms.RandomResizedCrop(128),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

transform_test = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 创建数据集
dataset = FoodImageDataset(directory, transform=transform_train)

# 分割数据集
train_size = int(0.8 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

# 使用预训练的ResNet模型
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 学习率调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

# 训练设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 训练模型
num_epochs = 20
best_acc = 0.0
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item() * images.size(0)
    epoch_loss = running_loss / len(train_dataset)
    scheduler.step(epoch_loss)

    # 在验证集上评估
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)

            # 将独热编码转换回整数标签
            true_labels = torch.argmax(labels, dim=1)

            total += labels.size(0)
            correct += (predicted == true_labels).sum().item()
    acc = correct / total
    print(f"Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss:.4f}, Val Acc: {acc:.4f}")
    if acc > best_acc:
        best_acc = acc
        torch.save(model.state_dict(), 'best_model.pth')

# 加载最佳模型
model.load_state_dict(torch.load('best_model.pth'))

# 评估模型
y_true = []
y_pred = []

model.eval()
with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)

        # 将独热编码转换回整数标签
        true_labels = torch.argmax(labels, dim=1)

        y_true.extend(true_labels.cpu().numpy())
        y_pred.extend(predicted.cpu().numpy())

# 计算性能指标
print("Classification Report:")
print(classification_report(y_true, y_pred, target_names=list(index_to_label.values())))
print("Confusion Matrix:")
print(confusion_matrix(y_true, y_pred))

# 输出每个类别的精度和召回率
report = classification_report(y_true, y_pred, output_dict=True)
for label, metrics in report.items():
    if label in index_to_label:
        print(f"{index_to_label[int(label)]}: Precision={metrics['precision']:.2f}, Recall={metrics['recall']:.2f}")
进行比对

直接使用类别的数字编号

使用独热编码

相应的混淆矩阵

相应的混淆矩阵

分析:

使用独热编码后,模型的整体准确率提高了,某些类别的精确度、召回率和 F1 分数也有改善,比如 beef_tartare 和 chicken_wings。同时,混淆矩阵显示,使用独热编码后,某些类别的交叉预测减少或增加,例如 beef_tartare 和 chicken_wings 的正确预测数量增加。因此,使用独热编码可能有助于提高模型的准确性和类别识别能力。

不过,这个结论仅基于给定数据的初步观察,实际效果还需考虑更多因素,如数据集大小、特征选择和模型架构等。

补充资料

相关具体细节可见:
入门(选修):《深度学习详解》(Datawhale X 李宏毅苹果书 AI夏令营)-CSDN博客


小结(实验)

初始模型与结果
  • 初始模型:构建了一个简单的卷积神经网络(CNN)模型。
  • 结果:模型性能不佳,多数分类准确率低于50%,最佳分类准确率为0.8。
模型优化
  • 数据增强:使用随机裁剪和水平翻转增加模型泛化能力。
  • 模型架构:采用预训练的ResNet18模型,并调整最后一层以适应10类分类任务。
  • 优化器和学习率调度:使用Adam优化器,并在验证集性能停滞时降低学习率。
  • 训练策略:增加训练轮数至20轮,并保存表现最佳的模型。
结果分析
  • 优化后性能:分类效果显著提升。
  • 独热编码对比:直接使用数字编号作为标签可能导致模型学习到错误的模式;独热编码解决了这一问题,提高了模型的准确率和类别识别能力,特别是对于某些特定类别(如beef_tartare和chicken_wings)。
可得
  • 使用独热编码相比直接使用数字编号作为标签能有所提高模型性能。
  • 模型优化策略(如数据增强、模型架构选择和学习率调度)对于提高分类效果至关重要。
  • 总体而言,经过优化的模型在多个指标上表现更好,尤其是对于特定类别的识别。

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值