相信大家看了NLP方向的题目之后都被出题人的善良耐心认真所折服😀🎉🎉 接下来,欢迎大家跟我一起走进CV的世界😋👍


写在前面的话

"随着自然语言处理技术的飞速发展,计算机视觉领域迎来了新的变革。🌀 大语言模型的出现,不仅推动了机器理解人类语言的能力,也为计算机视觉系统提供了新的视角。👀 这些模型能够将图像内容与语言描述紧密结合,使得机器能够以前所未有的方式 “看到” 并 “讲述” 视觉故事。📖 这种跨学科的融合,为图像识别、场景理解以及视觉问答等领域开辟了新的可能性,预示着人工智能技术的下一个前沿。✨

(上面那段话是KIMI老师帮我写的)

数据集(此处是本题用会到的全部数据集):

大家需要自行将数据集划分为训练、测试以及验证集。

由于数据集大小限制,大家的测试集与验证集每一个类别仅包含5张图片即可

📝注意事项

在本题中出题人并未给大家推荐任何学习资料或网站,希望大家可以熟练运用B站、Google、CSDN、知乎、AI工具(GPT、Kimi、智谱清言···)等多种信息检索渠道来对知识点进行学习

想要成为扑克高手吗

想要成为扑克高手吗?回想我初学扑克牌时的情景,我发现我常常苦恼于Card种类繁杂,无法记住所有的Card,而在我学习了CV知识后,便心生一计☝️🤓,准备做一个识别各种Card的模型,当遇到有不认识的Card时,只需要将图片扔给模型,就能知道这张Card是什么。

Part 01:前置知识

本题主要是帮助大家打牢机器学习的基础,为后续题目的完成做铺垫~

🛠️ 环境、基础相关

🧠 ML基础相关

提交要求

请以 Markdown 格式编写笔记,并将笔记导出为PDF格式提交

Part 02 数据预处理与数据增强

Little Tips:在炼丹过程中,数据预处理的效果可能直接决定了模型的准确度。我们从最简单的数据预处理方法开始,逐步探索对图像数据进行处理的方法。

👀 任务列表

在使用了数据增强的训练过程中,可以打印出每一个epoch之后(甚至在每10个batch之后打印都行,因为我就是这么干的),模型在训练集上的准确率与验证集上的准确率,并对二者的变化趋势进行观察。

提交要求

请以 Markdown 格式编写笔记,并将笔记导出为PDF格式提交

Part 03 看看👀效果

在本题中,我为大家会提供预先训练好的参数稍大的模型及其权重参数。

大家要做的就是参考并学习出题人给出的代码(代码在出题人的环境下恰好能跑,所以想偷懒的出题人便直接原封不动上传了),同时根据报错信息或直接对代码进行debug,找出代码不能在各位的电脑上运行的原因,并对bug进行修复,使其在你们自己的电脑上运行起来。

代码

import torch
import cv2
import pandas as pd
from torchvision import transforms
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
from PIL import Image
from torch.utils.data import DataLoader
from torch import nn
import torch.optim as optim
import torch.nn.init as init
import torch.nn.functional as F
import os
from tqdm import tqdm

transform_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),  # 将图像转换为Tensor
    '''
    在这里添加数据增强操作···
    '''
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 归一化
])
testset = datasets.ImageFolder(root='./test', transform=transform_test)
test_loader = DataLoader(testset, batch_size=32, shuffle=False)

class Tiny_cnn(nn.Module):
    def __init__(self, num_classes=53):
        super(Tiny_cnn, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size = 8 , stride=4, padding=2),  
            
            nn.ReLU(inplace=True),
            nn.BatchNorm2d(64),
            nn.MaxPool2d(kernel_size=2, stride=2), 
            nn.Conv2d(64, 192, kernel_size=5, padding=2), 
            nn.ReLU(inplace=True),
            nn.BatchNorm2d(192),
            nn.MaxPool2d(kernel_size=2, stride=2),  
            nn.Conv2d(192, 384, kernel_size=3, padding=1), 
            nn.ReLU(inplace=True),
            nn.BatchNorm2d(384),
            nn.Conv2d(384, 256, kernel_size=2, padding=2),  
            nn.ReLU(inplace=True),
            nn.BatchNorm2d(256),
            nn.MaxPool2d(kernel_size=3, stride=2)  
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 8 * 8, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 1024),
            nn.ReLU(inplace=True),
            nn.Linear(1024, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        # print(x.shape)
        x = torch.flatten(x, 1)
        # print(x.shape)
        x = self.classifier(x)
        # print(x.shape)
        return x

net = Tiny_cnn()  # 替换为你的模型

# 定义模型保存路径
PATH = 'tiny_cnn.pt'
# net.to('cuda')

state_dict = torch.load(PATH, map_location='cuda')

if os.path.exists(PATH):
    print("加载模型...")
    model = torch.load(PATH, map_location='cuda')
    from collections import OrderedDict
    new_state_dict = OrderedDict()

    
    print(state_dict.keys())

    for k, v in model.state_dict().items():
        name = k[7:] # remove `module.`
        new_state_dict[name] = v
    # load params
    net.load_state_dict(new_state_dict)
    # net.load_state_dict(model.state_dict())

    net.to('cuda')
    # 使用 nn.DataParallel 包装模型以在多个 GPU 上训练
    if torch.cuda.device_count() >= 1: # 注意,这里改为>=1
        print(f"使用 {torch.cuda.device_count()} 个 GPU 进行训练")
        net = nn.DataParallel(net)

         # 评估模式
        net.eval()

        # 测试过程
        correct = 0
        total = 0
        with torch.no_grad():  # 禁用梯度计算
            test_iterator = tqdm(test_loader, desc="Testing")
            for images, labels in test_iterator:
                images, labels = images.to('cuda'), labels.to('cuda')
                outputs = net(images)
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum().item()
                
                # 更新进度条
                test_iterator.set_postfix(accuracy=(correct / total))

        print(f'Accuracy of the network on the test images: {100 * correct / total:.2f}%')
else:
    print("模型文件不存在")

注意,以上代码的运行要求为:GPU数量≥1。若只有CPU环境,还请大家自行修改代码以适配环境(其实就是改几处小地方~)

需要自行下载的文件

注意,models压缩包在此处存在的原因是出题人在保存权重参数时弄出的小bug🤐。大家需要将models文件夹解压后放在与上面的运行代码相同的父文件夹中~~

提交要求

Part 04 动手搭建

Obviously,上一题中出题人给出的网络架构有些复杂。让大家在自己的GPU甚至CPU(不过我们明确要求笔记本上有独立显卡条件的一定要安装cuda版本的torch)上训练是不太现实的。

因此在本题中,我们引导大家根据下面列出的模板自行构建一个神经网络(其中网络的深度、CNN与Linear的层数、卷积核大小、通道数等参数的设置各位均可自由发挥,以下模板仅供参考)

训练流程,数据集加载等就不赘述了。各位可以根据上一Part中的代码及逆行修改。

class MicroCNN(nn.Module):
    def __init__(self, num_classes=53):
        super(MicroCNN, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1),  # 输出尺寸: (16, 224, 224)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),  # 输出尺寸: (16, 112, 112)
            nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1),  # 输出尺寸: (32, 112, 112)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)  # 输出尺寸: (32, 56, 56)
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(32 * 56 * 56, 256),  # 减少到256个神经元
            nn.ReLU(inplace=True),
            nn.Linear(256, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

大家可以将epoch设置为50以内(如果大家的GPU算力比较足的话可以适当开高一些),并且看看效果😀

提交要求

Part 05 🤬知识蒸馏!!!

如果出题人在做自己出的题时没出错的话,那么现在各位应该和出题人一样对Part 04中自己搭建的模型充满了大大的问号🧐

“为什么我的准确率这么低啊啊啊?!!😡”

事实上,大家很有可能遇到了老生常谈的欠拟合问题。也就是说我们自行搭建的模型参数量太少,难以学习到数据集中53种类别的Card的详细信息,导致分类不准确。

📚 知识点

接下来,让我们进入正题:

👩🏫 实践一下叭

以下是知识蒸馏的核心训练部分框架代码:

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
from models import alexnet, MicroCNN  # 假设你已经在models.py中定义了这两个模型
import os
import logging
from tqdm import tqdm

# 配置日志
logging.basicConfig(filename='distill.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 定义图像转换
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),  # 将图像转换为Tensor
    '''
    数据增强操作···
    '''
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 归一化
])

transform_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),  # 将图像转换为Tensor
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 归一化
])

dataset = datasets.ImageFolder(root='./train', transform=transform)
train_loader = DataLoader(dataset, batch_size=128, shuffle=True)

testset = datasets.ImageFolder(root='./test', transform=transform_test)
test_loader = DataLoader(testset, batch_size=128, shuffle=False)

# 初始化模型
teacher_model = alexnet()
student_model = MicroCNN()

teacher_model.to('cuda')
# net.to('cuda')
PATH = 'tiny_cnn.pt'
if os.path.exists(PATH):
    logging.info("加载模型...")
    model = torch.load(PATH, map_location='cuda')
    from collections import OrderedDict
    new_state_dict = OrderedDict()
    for k, v in model.state_dict() .items():
        name = k[7:] # remove `module.`
        new_state_dict[name] = v
# load params
    teacher_model.load_state_dict(new_state_dict)
    # net.load_state_dict(model.state_dict())

    teacher_model.to('cuda')
    # 使用 nn.DataParallel 包装模型以在多个 GPU 上训练
    if torch.cuda.device_count() > 1:
        logging.info(f"使用 {torch.cuda.device_count()} 个 GPU 进行训练")
        teacher_model = nn.DataParallel(teacher_model)
else:
    logging.info("模型文件不存在,从头开始训练。")
    student_model.to('cuda')
    # 使用 nn.DataParallel 包装模型以在多个 GPU 上训练
    if torch.cuda.device_count() > 1:
        logging.info(f"使用 {torch.cuda.device_count()} 个 GPU 进行训练")
        teacher_model = nn.DataParallel(teacher_model)




student_model.to('cuda')
# net.to('cuda')
PATH = 'micro_cnn.pt'


if os.path.exists(PATH):
    logging.info("加载模型...")
    model = torch.load(PATH, map_location='cuda')
    from collections import OrderedDict
    new_state_dict = OrderedDict()
    for k, v in model.items():
        name = k[7:] # remove `module.`
        new_state_dict[name] = v
# load params
    student_model.load_state_dict(new_state_dict)
    # net.load_state_dict(model.state_dict())

    student_model.to('cuda')
    # 使用 nn.DataParallel 包装模型以在多个 GPU 上训练
    if torch.cuda.device_count() > 1:
        logging.info(f"使用 {torch.cuda.device_count()} 个 GPU 进行训练")
        student_model = nn.DataParallel(student_model)
else:
    logging.info("模型文件不存在,从头开始训练。")
    student_model.to('cuda')
    # 使用 nn.DataParallel 包装模型以在多个 GPU 上训练
    if torch.cuda.device_count() > 1:
        logging.info(f"使用 {torch.cuda.device_count()} 个 GPU 进行训练")
        student_model = nn.DataParallel(student_model)

# 冻结教师模型的参数
for param in teacher_model.parameters():
    param.requires_grad = False

teacher_model.to('cuda')
student_model.to('cuda')

# 蒸馏温度参数
temperature = 3.0

# 损失函数
def distillation_loss(output_student, output_teacher, temperature):
    # soft_output_student = nn.functional.softmax(output_student / temperature, dim=1)
    soft_output_student = nn.functional.softmax(output_student , dim=1)

    soft_output_teacher = nn.functional.softmax(output_teacher / temperature, dim=1)
    return nn.functional.kl_div(soft_output_student, soft_output_teacher, reduction='batchmean')

# 优化器
optimizer = optim.SGD(student_model.parameters(), lr=0.0005, momentum=0.9, weight_decay=0.005, nesterov=True)

# 训练过程
def train(model, loader, optimizer, device):
    model.train()
    total_loss = 0
    train_total = 0
    train_correct = 0
    for i, (inputs, labels) in tqdm(enumerate(loader), total=len(loader), desc='Train'):  # 正确调用tqdm
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()

        outputs_teacher = teacher_model(inputs)
        outputs_student = model(inputs)
        _, predicted = torch.max(outputs_student.data, 1)
        train_total += labels.size(0)
        train_correct += (predicted == labels).sum().item() # 计算测试集准确率
        

        loss = distillation_loss(outputs_student, outputs_teacher, temperature)
        loss.backward()
        optimizer.step()

        total_loss += loss.item()
    train_accuracy = 100 * train_correct / train_total
    logging.info(f'[{epoch + 1}] Train Acc: {train_accuracy:.2f}%')

    return total_loss / len(loader)

请各位同学根据核心框架,并借助上两个Part中的代码,搭建一个完整的知识蒸馏过程。

要求:

提交要求


或许知识蒸馏后的结果也有些差强人意,接下来就需要开动你们聪明的🧠仔细思考思考🤔如何将我们在前面的4个Part中所学的知识充分利用起来,使得大家在Part 04中自行构建的模型的准确率尽可能高呢😵


Next Level!!!🤯🤯🤯

不用猜都知道知道大家很快就将前五个Part速通了,于是善解人意的出题人贴心地为大家设计了两个next levels✊✊✊

Ⅰ NEXT LEVEL

交互与部署(云文档链接,大家直接点击即可)

Ⅱ NEXT NEXT LEVEL

NLP&CV附加题--探索Transformer模型底层(云文档链接,大家直接点击即可)