从零构建ResNet18模型:Kaggle树叶分类竞赛实战指南

2025年夏季《移动软件开发》实验报告—实验5:第一个 HarmonyOS 应用 1.问题一:使用 DevEco Studio 进行开发工作时,突然出现 “Cannot connect to already running IDE instance” 的错误提示,导致软件无法正常启动。解决方案:首先检查并终止 DevEco Studio 残留进程,再删除 DevEco Studio 的.lock 残留文件。 阅读详情

1. 从零开始:为什么选择Kaggle树叶分类竞赛作为你的第一个实战项目?

如果你刚学完一些深度学习的理论,或者跟着教程敲过几行代码,但心里总感觉不踏实,不知道这些知识怎么用在真实世界里,那你来对地方了。Kaggle的树叶分类竞赛,简直就是为新手量身定做的“毕业设计”。我第一次接触这个比赛时,感觉它完美地卡在了“有挑战性”和“可实现”的甜蜜点上。它不像那些动辄几百万张图片、需要几周训练时间的庞然大物,也不至于简单到用几行代码就能搞定,失去学习的意义。

这个比赛的任务很直观:给你一堆树叶的图片,让你训练一个模型,能准确识别出它是哪种树。听起来是不是很像我们小时候玩的“看图识物”?只不过这次,我们是用神经网络这个“超级大脑”来玩。数据量适中,类别清晰,目标明确,这让我们可以专注于模型构建、训练和优化的核心流程,而不会被复杂的数据清洗、海量存储或者分布式训练这些进阶问题一下子吓退。

更重要的是,这个项目能给你带来一个完整的、闭环的深度学习项目体验。从下载数据、理解数据格式,到亲手搭建一个经典的ResNet模型,再到训练、调试、评估,最后生成提交文件,整个过程你都能自己走一遍。这种成就感,是看十篇教程都比不上的。我当年做完这个项目后,对PyTorch的理解和信心都上了一个大台阶。所以,别犹豫了,跟着我一起,咱们不用任何现成的“黑箱”工具,就从最基础的代码开始,手搓一个ResNet18,把这个比赛拿下。

2. 数据准备:你的第一步不是写代码,而是“看”数据

很多新手朋友一拿到项目就急着打开编辑器写模型,这是个大坑。我踩过,所以告诉你:磨刀不误砍柴工,花半小时好好看看你的数据,能省去后面好几天的调试时间。

首先,我们去Kaggle竞赛页面把数据下载下来。解压后,你会看到几个文件:一个images文件夹,里面装满了树叶图片;一个train.csv文件,这是训练集的标签;一个test.csv文件,这是测试集的信息。我们先打开train.csv看看,它通常长这样:

imagelabel
images/0.jpgquercus_robur
images/1.jpgacer_campestre
......

第一列是图片在images文件夹下的路径,第二列是树叶的类别标签,比如“quercus_robur”(夏栎)。这里有个关键点:标签是英文的物种名。模型最终需要输出的是数字,所以我们必须把这些文本标签转换成数字编码,这个过程叫“标签编码”。同时,我们还得把这个映射关系记下来,因为最后预测出数字后,还得再转换回英文名才能提交。

接下来,我们得看看图片本身。用几行简单的Python脚本,或者直接在文件夹里预览一下,你会发现图片的尺寸、亮度、角度可能都不一样。有的叶子是完整的,有的可能只拍了一部分;有的背景干净,有的背景复杂。这种多样性是好事,也是挑战。好事在于它能让模型更鲁棒,挑战在于我们需要通过数据增强来人工创造更多的多样性,帮助模型学习。

这里我分享一个我自己的小习惯:在写任何数据处理代码前,我会随机挑几十张图片,用PIL或OpenCV打开看看,直观感受一下数据的“质感”。比如,我发现这个数据集里有些图片比较暗,那我在做数据增强时,就可能会有意地加入一些亮度调整。这个步骤看似多余,但它能帮你建立对数据的“直觉”,后面调参时心里更有底。

3. 打造高效数据管道:PyTorch Dataset和DataLoader实战

数据处理是深度学习工程里的重头戏,写得好,训练顺风顺水;写得烂,各种Bug和性能瓶颈能把你搞崩溃。PyTorch提供了Dataset和DataLoader这两个神器,我们来好好用上。

3.1 创建自定义Dataset类

我们要创建两个Dataset类,一个用于训练和验证,一个专门用于最后的测试预测。因为训练集需要图片和标签,而测试集只需要图片。下面这个CustomDataset类是核心:

import pandas as pd
from PIL import Image
from torch.utils.data import Dataset
from sklearn.preprocessing import LabelEncoder

class CustomDataset(Dataset):
    def __init__(self, csv_file, img_dir, transform=None):
        """
        Args:
            csv_file (string): CSV文件路径(如'train.csv')。
            img_dir (string): 图片文件夹的根目录路径。
            transform (callable, optional): 可选的数据增强/转换函数。
        """
        self.data = pd.read_csv(csv_file)
        self.img_dir = img_dir
        self.transform = transform
        
        # 关键步骤:标签编码
        self.label_encoder = LabelEncoder()
        # 将文本标签(如‘quercus_robur’)转换为数字(0,1,2...)
        self.data['label'] = self.label_encoder.fit_transform(self.data['label'])
        # 保存这个编码器,预测时要用它把数字变回文字
        self.classes = self.label_encoder.classes_
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        # 构造完整的图片路径
        img_name = self.data.iloc[idx, 0] # 假设第一列是‘image’
        img_path = os.path.join(self.img_dir, img_name)
        # 读取图片
        image = Image.open(img_path).convert('RGB') # 确保是三通道
        label = self.data.iloc[idx, 1] # 假设第二列是‘label’,此时已是数字
        
        if self.transform:
            image = self.transform(image)
            
        return image, label

这里有几个容易踩坑的细节:

  1. 路径拼接:一定要用os.path.join,而不是字符串相加,这样在不同操作系统(Windows/macOS/Linux)上都能工作。
  2. 图片模式:用.convert('RGB')强制转换,能避免一些灰度图或带透明通道(RGBA)的图片引发维度错误。
  3. 标签编码器保存:self.label_encoder和self.classes一定要作为属性保存下来!这是很多新手会忘的。等你训练完模型,对测试集进行预测,得到的是数字0,1,2...,你必须用同一个编码器的inverse_transform方法,才能把这些数字变回‘quercus_robur’这样的提交格式。

3.2 设计强大的数据增强策略

数据增强是防止模型过拟合、提升泛化能力的廉价且有效的方法。对于图像任务,我们可以做很多操作。下面这个transform组合是我经过多次实验后,觉得对这个树叶分类任务比较有效的:

from torchvision import transforms

train_transform = transforms.Compose([
    # 随机缩放裁剪:模拟拍摄时距离和角度的变化
    transforms.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3./4., 4./3.)),
    # 随机水平翻转:叶子可能朝左也可能朝右
    transforms.RandomHorizontalFlip(p=0.5),
    # 随机旋转:叶子在自然界中角度是任意的
    transforms.RandomRotation(degrees=15),
    # 颜色抖动:模拟光照、季节变化
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    # 转换为Tensor,并归一化
    transforms.ToTensor(),
    # 使用ImageNet的均值和标准差进行归一化(这是通用做法,因为很多预训练模型用这个)
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

对于验证集和测试集,我们不应该使用这些带有“Random”的增强,否则每次评估的结果都会波动。我们只需要进行确定性的 resize、中心裁剪和归一化:

val_test_transform = transforms.Compose([
    transforms.Resize(256), # 先将短边缩放到256
    transforms.CenterCrop(224), # 再从中心裁剪出224x224
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

3.3 用DataLoader组装数据流水线

有了Dataset和transform,我们就可以用DataLoader把它们组装成可以迭代的数据流了。DataLoader会帮我们处理批处理(batching)、打乱数据(shuffling)和多进程数据加载,极大地简化了我们的工作。

from torch.utils.data import DataLoader

# 假设你的数据路径
train_csv = './classify-leaves/train.csv'
img_dir = './classify-leaves/images'

# 创建训练集和验证集Dataset
full_dataset = CustomDataset(train_csv, img_dir, transform=train_transform)
# 手动划分训练集和验证集,比如7:3
train_size = int(0.7 * len(full_dataset))
val_size = len(full_dataset) - train_size
train_dataset, val_dataset = torch.utils.data.random_split(full_dataset, [train_size, val_size])
# 注意:验证集应该用val_test_transform,这里为了演示,假设我们重新创建
val_dataset.dataset.transform = val_test_transform # 修改子数据集的transform

# 创建DataLoader
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2, pin_memory=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2, pin_memory=True)

参数解释:

  • batch_size=32:一次训练喂入32张图片。太小了训练慢、不稳定,太大了可能内存放不下。32或64是常见的起点。
  • shuffle=True:每个epoch(完整遍历一遍数据)开始时打乱数据顺序,防止模型学到数据的顺序信息。
  • num_workers=2:用2个子进程来加载数据,可以加速数据准备。根据你的CPU核心数调整。
  • pin_memory=True:如果使用GPU,这个设置可以将数据锁在主机内存中,加速向GPU的传输。

4. 亲手搭建ResNet18:理解残差连接的精髓

现在来到最激动人心的部分:从零开始构建ResNet18。你可能听说过ResNet很深,解决了梯度消失问题,但它的核心思想其实非常直观——捷径连接。

想象一下你在学一道复杂的数学题。如果直接从头推导,很容易在中间某步卡住。但如果参考答案给了你一个关键的中间公式(一条“捷径”),你就能绕过复杂的推导,直接利用这个结果继续前进。ResNet的残差块就是给网络提供了这样的“捷径”。

4.1 构建基础的残差块

残差块是ResNet的基石。它的目标不是让网络直接学习一个复杂的映射 H(x),而是学习残差 F(x) = H(x) - x。这样,原本的映射就变成了 H(x) = F(x) + x。这个“+ x”就是那条捷径。

import torch
import torch.nn as nn
import torch.nn.functional as F

class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResidualBlock, self).__init__()
        # 主路径:两个卷积层
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        # 捷径路径:如果输入输出维度不一致(通道数不同或空间尺寸不同),就需要一个1x1卷积来调整
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )
    
    def forward(self, x):
        # 主路径 forward
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out)) # 注意:这里先不加ReLU!
        # 加上捷径
        out += self.shortcut(x)
        # 最后再加ReLU激活
        out = F.relu(out)
        return out

关键点剖析:

  1. bias=False:因为后面紧跟着批归一化(BatchNorm)层,它自带可学习的偏移参数,所以卷积层可以省去bias,减少参数且不影响性能。
  2. shortcut:这是残差连接的核心。只有当输入和输出的维度(通道数和尺寸)完全匹配时,才能直接相加(x)。如果不匹配(比如下采样时stride=2,或者通道数变化),就需要用一个1x1的卷积(配合BatchNorm)来将x投影到正确的维度。这个1x1卷积就像一座桥,让两条路径得以汇合。
  3. ReLU的位置:注意在主路径中,第一个卷积后有ReLU,但第二个卷积后没有立即加ReLU。这是原论文的设计,残差相加之后再加最后一个ReLU。这个顺序很重要。

4.2 组装完整的ResNet18模型

ResNet18就是由一堆这样的残差块堆叠起来的。名字里的“18”大概是指有18层带参数的层(卷积层和全连接层)。它的结构是:开头一个卷积和池化层,然后4个“阶段”(stage),每个阶段由若干个残差块组成,最后是一个全局平均池化和全连接层。

class ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes=176): # 树叶分类有176类
        super(ResNet, self).__init__()
        self.in_channels = 64 # 初始通道数
        
        # 入口层:快速降低图片尺寸,提取初步特征
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        
        # 四个阶段(stage)
        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)  # 输出尺寸:56x56
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) # 输出尺寸:28x28
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) # 输出尺寸:14x14
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) # 输出尺寸:7x7
        
        # 分类头
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 自适应平均池化到1x1
        self.fc = nn.Linear(512, num_classes)
        
    def _make_layer(self, block, out_channels, num_blocks, stride):
        """构建一个由多个残差块组成的阶段。"""
        layers = []
        # 第一个块可能需要下采样(stride可能为2)
        layers.append(block(self.in_channels, out_channels, stride))
        self.in_channels = out_channels
        # 后续的块,输入输出通道数一致,stride为1
        for _ in range(1, num_blocks):
            layers.append(block(out_channels, out_channels, stride=1))
        return nn.Sequential(*layers)
    
    def forward(self, x):
        # 入口层
        x = F.relu(self.bn1(self.conv1(x))) # 输出尺寸减半
        x = self.maxpool(x) # 输出尺寸再减半
        
        # 四个阶段
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        
        # 分类头
        x = self.avgpool(x) # [batch, 512, 1, 1]
        x = torch.flatten(x, 1) # [batch, 512]
        x = self.fc(x) # [batch, num_classes]
        return x

如何实例化一个ResNet18? ResNet18的配置是每个阶段有2个残差块,即 num_blocks = [2, 2, 2, 2]。

def ResNet18(num_classes=176):
    return ResNet(ResidualBlock, [2, 2, 2, 2], num_classes=num_classes)

# 创建模型
model = ResNet18(num_classes=176)
print(model) # 可以打印看看网络结构

亲手敲完这些代码,你对ResNet的理解会比单纯调用torchvision.models.resnet18()深刻得多。你会明白每一层在做什么,维度是如何变化的,残差连接具体加在了哪里。这种“造轮子”的经历对于打牢基础至关重要。

5. 模型训练与调优:让模型真正“学”起来

模型搭好了,数据管道也建好了,接下来就是让它们运转起来。训练一个深度学习模型就像教小孩认字,需要耐心、合适的方法,还要不断检查他学得怎么样。

5.1 编写训练和验证循环

训练循环的核心步骤是:前向传播 -> 计算损失 -> 反向传播 -> 更新参数。验证循环则去掉反向传播和参数更新。

def train_one_epoch(model, train_loader, criterion, optimizer, device, epoch):
    model.train() # 切换到训练模式(影响Dropout、BatchNorm等层)
    running_loss = 0.0
    correct = 0
    total = 0
    
    # 使用tqdm添加进度条,训练时看着更直观
    from tqdm import tqdm
    pbar = tqdm(train_loader, desc=f'Epoch {epoch+1} [Train]')
    
    for images, labels in pbar:
        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)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        
        # 更新进度条描述
        pbar.set_postfix({'Loss': loss.item(), 'Acc': correct/total})
    
    epoch_loss = running_loss / total
    epoch_acc = correct / total
    return epoch_loss, epoch_acc

def validate(model, val_loader, criterion, device):
    model.eval() # 切换到评估模式
    running_loss = 0.0
    correct = 0
    total = 0
    
    with torch.no_grad(): # 关闭梯度计算,节省内存和计算
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)
            
            running_loss += loss.item() * images.size(0)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    epoch_loss = running_loss / total
    epoch_acc = correct / total
    return epoch_loss, epoch_acc

5.2 选择损失函数和优化器

对于多分类任务,交叉熵损失(CrossEntropyLoss) 是标准选择。它结合了Softmax和负对数似然,非常适合。

优化器我推荐使用 Adam,它对学习率不那么敏感,通常能给出一个不错的起点。但也可以尝试 SGD with Momentum,它在调优得当后可能达到更好的最终精度。

import torch.optim as optim

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = ResNet18(num_classes=176).to(device)

criterion = nn.CrossEntropyLoss() # 损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # 优化器,初始学习率0.001
# 也可以试试SGD: optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)

# 学习率调度器:在训练过程中动态降低学习率,有助于模型后期收敛
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1) # 每30个epoch,学习率乘0.1

5.3 开始训练并监控过程

现在,我们把所有部分组合起来,进行多轮(epoch)训练。同时,我们要记录训练和验证的损失、准确率,以便后续分析。

num_epochs = 100
history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}

for epoch in range(num_epochs):
    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device, epoch)
    val_loss, val_acc = validate(model, val_loader, criterion, device)
    
    # 记录历史
    history['train_loss'].append(train_loss)
    history['train_acc'].append(train_acc)
    history['val_loss'].append(val_loss)
    history['val_acc'].append(val_acc)
    
    # 打印信息
    print(f'Epoch {epoch+1:03d}/{num_epochs} | '
          f'Train Loss: {train_loss:.4f} Acc: {train_acc:.4f} | '
          f'Val Loss: {val_loss:.4f} Acc: {val_acc:.4f} | '
          f'LR: {scheduler.get_last_lr()[0]:.6f}')
    
    # 更新学习率
    scheduler.step()
    
    # (可选)保存验证集上最好的模型
    # if val_acc > best_acc:
    #     best_acc = val_acc
    #     torch.save(model.state_dict(), 'best_model.pth')

训练过程中,你要密切关注两个指标:损失(Loss) 和 准确率(Accuracy)。理想情况下,训练损失和验证损失都应该稳步下降,训练准确率和验证准确率都应该稳步上升。如果出现训练准确率很高但验证准确率上不去(过拟合),或者两者都很低(欠拟合),就需要调整策略了。

6. 模型评估与预测:检验成果并生成提交文件

训练完成后,我们不仅要看最后的准确率数字,还要可视化训练过程,并对测试集进行预测,生成Kaggle要求的提交文件。

6.1 可视化训练曲线

画图能直观地告诉我们模型学得怎么样。

import matplotlib.pyplot as plt

def plot_training_history(history):
    epochs = range(1, len(history['train_loss']) + 1)
    
    plt.figure(figsize=(12, 4))
    
    # 绘制损失曲线
    plt.subplot(1, 2, 1)
    plt.plot(epochs, history['train_loss'], 'b-', label='Training Loss')
    plt.plot(epochs, history['val_loss'], 'r-', label='Validation Loss')
    plt.title('Training and Validation Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    plt.grid(True)
    
    # 绘制准确率曲线
    plt.subplot(1, 2, 2)
    plt.plot(epochs, history['train_acc'], 'b-', label='Training Accuracy')
    plt.plot(epochs, history['val_acc'], 'r-', label='Validation Accuracy')
    plt.title('Training and Validation Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.grid(True)
    
    plt.tight_layout()
    plt.show()

plot_training_history(history)

通过曲线,你可以判断:

  • 是否过拟合:训练损失持续下降,但验证损失在某个点后开始上升。这说明模型只记住了训练数据,没学会泛化。
  • 是否欠拟合:训练和验证的损失/准确率都很差,且早早就停滞不前。这说明模型能力不足或训练不够。
  • 学习率是否合适:如果损失曲线剧烈震荡,可能是学习率太高;如果下降极其缓慢,可能是学习率太低。

6.2 对测试集进行预测

我们之前为测试集创建了一个PredictionDataset(只返回图片,没有标签)。现在用训练好的模型给它做预测。

def predict_test_set(model, test_loader, device):
    model.eval()
    all_preds = []
    all_ids = [] # 如果需要保存图片ID的话
    
    with torch.no_grad():
        for batch in test_loader:
            # 注意:测试Dataset只返回image,不返回label
            images = batch.to(device)
            outputs = model(images)
            _, preds = torch.max(outputs, 1)
            all_preds.extend(preds.cpu().numpy()) # 转移到CPU并转成numpy
    
    return all_preds

# 创建测试集DataLoader (batch_size可以大一些,比如64)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=2)
predictions = predict_test_set(model, test_loader, device)

6.3 生成提交文件

预测得到的是数字标签(0,1,2...),我们需要用之前保存在CustomDataset里的label_encoder,把它们变回原始的英文物种名。

# 假设我们有一个train_dataset对象,里面保存了label_encoder
# 在实际代码中,你可能需要将这个编码器保存下来,或者从训练数据中重新拟合一个
label_encoder = train_dataset.label_encoder

# 将数字预测转换回文本标签
predicted_labels = label_encoder.inverse_transform(predictions)

# 读取测试集CSV,获取图片文件名
test_df = pd.read_csv('./classify-leaves/test.csv')
submission_df = pd.DataFrame({
    'image': test_df['image'], # 图片文件名列
    'label': predicted_labels  # 预测的标签列
})

# 保存为CSV文件
submission_df.to_csv('my_submission.csv', index=False)
print("提交文件已保存为 'my_submission.csv',快去Kaggle提交吧!")

把这个my_submission.csv文件上传到Kaggle竞赛的提交页面,就能看到你的模型在隐藏测试集上的分数和排名了。第一次提交看到分数时,那种感觉真的很棒。

7. 性能提升与进阶技巧:从能跑到跑得好

如果你的第一个模型成绩不太理想,别灰心,这太正常了。深度学习很大程度上是一个“炼丹”过程,需要不断调试。这里分享几个我试过且有效的提升技巧。

1. 使用预训练模型进行迁移学习 这是我们最厉害的“外挂”。与其从头训练ResNet18,不如使用在ImageNet上预训练好的模型。ImageNet有上千万张图片,模型已经学会了识别边缘、纹理、形状等通用特征,我们只需要针对树叶分类这个特定任务微调(Fine-tune)最后几层即可。

import torchvision.models as models

def get_pretrained_resnet18(num_classes):
    # 加载预训练模型,pretrained=True会自动下载权重
    model_ft = models.resnet18(pretrained=True)
    
    # 冻结所有卷积层的参数(不更新)
    # for param in model_ft.parameters():
    #     param.requires_grad = False
    
    # 更常见的做法:只冻结前面的层,微调后面的层
    # 例如,冻结除了最后一层(fc)之外的所有层
    # for name, param in model_ft.named_parameters():
    #     if 'fc' not in name:
    #         param.requires_grad = False
    
    # 替换最后的全连接层,以适应我们的分类数(176类)
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Linear(num_ftrs, num_classes)
    
    return model_ft

使用预训练模型通常能让你在更少的epoch内获得更高的准确率。记得要用和预训练时相同的图像归一化参数(就是之前用的ImageNet的mean和std)。

2. 尝试更深的网络或集成学习 ResNet18只是起点。你可以轻松换成ResNet34, ResNet50,甚至更复杂的ResNeXt、EfficientNet等。在Kaggle上,为了冲击更高的排名,模型集成是常见策略。简单来说,就是训练多个不同的模型(比如ResNet18, ResNet50, DenseNet),然后对它们的预测结果进行投票或平均,往往能获得比单个模型更好的效果。

3. 更精细的数据增强与处理 除了我们之前用的,还可以尝试:

  • CutMix 或 MixUp:将两张图片混合在一起生成新样本,能显著提升模型鲁棒性。
  • AutoAugment 或 RandAugment:自动搜索或随机选择增强策略组合。
  • 针对树叶的特点,可以尝试模拟不同季节的颜色变化、叶片残缺等。

4. 调整超参数

  • 学习率:这是最重要的超参数。可以尝试学习率预热(Warmup)、余弦退火(Cosine Annealing)等更先进的调度策略。
  • 批大小:增大批大小可能使训练更稳定,但需要调整学习率(通常线性缩放)。
  • 优化器:试试AdamW(Adam的改进版,权重衰减处理更正确)或者带动量的SGD。
  • 权重衰减:一种正则化,防止过拟合。Adam优化器中的weight_decay参数就是它。

5. 使用交叉验证 我们之前只是简单地将训练集按7:3划分。更严谨的做法是使用K折交叉验证,用K-1折训练,1折验证,循环K次,最后取平均。这样得到的模型性能评估更可靠,模型也更稳健。

这个过程可能会反复很多次,记录下你每次修改的参数和结果(可以用Excel或专门的工具如Weights & Biases)。慢慢地,你就能找到最适合这个数据集的“配方”。记住,提升模型性能没有银弹,需要耐心地实验、分析和迭代。当你看到自己的排名一点点往上爬的时候,所有的努力都是值得的。

树叶分类竞赛——kaggle 本文利用手搓的resnet18对树叶图片进行分类课程是李沐大神的动手学深度学习。 阅读详情

相关推荐

Kaggle树叶分类竞赛实战:从零开始手搓ResNet18的完整流程(附避坑指南)

本文详细介绍了如何从零开始构建ResNet18模型并应用于Kaggle树叶分类竞赛。通过数据预处理、模型实现、训练调优及竞赛技巧的全流程解析,帮助读者掌握计算机视觉实战技能,特别针对176类细粒度树叶分类任务提供了避坑指南和优化策略。

weixin_29046611的博客 206

移动开发概述

移动开发是指为智能手机、平板等移动设备开发软件应用的过程。随着移动互联网的普及,移动应用开发已成为技术行业的一个重要分支。常见的移动开发平台包括iOS、Android,以及跨平台开发框架。移动开发是一个快速发展的领域,随着5G、AR/VR、AI等技术的应用,未来的移动应用将变得更加智能化和多元化。无论是从事Android、iOS开发,还是跨平台开发,理解平台的特性,掌握开发工具与框架,是成为一名优秀移动开发工程师的基础。希望以上内容能帮助你更好地入门或进阶移动开发。

ajie1117的博客 2375

脑肿瘤分类的非主流路径:物理建模+可解释注意力+小样本原型学习

医学影像AI的核心挑战在于临床落地的可靠性与可信度,而非单纯追求准确率指标。脑肿瘤分类任务需直面设备差异大、标注样本少、决策过程不可追溯等现实瓶颈。本文聚焦MRI物理信号建模,将像素值映射为T1弛豫时间(ms)这一可解释生物物理量,结合病理共识引导的注意力机制与动态原型记忆库,构建端到端可部署、可验证、可演进的技术闭环。方法不依赖ImageNet预训练,显著提升在0.5T低场强设备、县级医院小样本(<25例)及多中心跨设备场景下的鲁棒性与医生采纳率,为医疗AI从‘能跑通’迈向‘敢用、愿用、常用’提供工程化范

weixin_30733003的博客 438

移动软件开发(1)

夏季学期《移动软件开发》

weixin_51014226的博客 589

智能移动应用开发完整指南

本文还有配套的精品资源,点击获取 简介:智能移动开发涉及利用技术和工具创建功能丰富的移动应用,需要掌握编程语言、开发环境、UI设计、响应式布局、网络编程、数据库管理、推送通知、地图集成、性能优化、版本控制、自动化测试、发布流程、安全性、跨平台开发等知识点。本指南涵盖了这些核心知识点,并提供实践任务,帮助开发者掌握移动应用开发的实际应用,成为一名出色的移动应用开发者。 ...

weixin_36464343的博客 1782

移动软件开发项目参考

这是我在移动软件开发课程上写下的一个项目,项目的内容是模仿一个日记软件——“素记”来写一个简易的日记软件。毕竟是学生作品,做的功能和性能一般般,所以仅仅只是一个参考。里面包含了源代码和一个功能说明文档,大家如果有兴趣可以看看。

详解移动App开发的几种方式和各自详细的优缺点,以及如何选择App开发模式,最全总结,一次讲清

移动App开发的几种方式 原生App,即NativeApp WebApp HybridApp,(完全依赖原生App中的WebView组件,本质上只是对WebView组件做更多的扩展,为它提供更多的api,由原生做主导) 跨平台开发( 由web做主导,编译转成原生app的Objective-C或Java代码) 其他类型(各大小程序/快应用等等) 01-移动App开发-NativeApp介绍和特点 定义:传统的原生App开发模式,有iOS和Android两大系统,需要各自语言开发各自App。 优点:性能和

codingWeb的博客 5122

移动终端应用程序开发各章代码安卓

移动终端应用程序开发各章代码 1,2章为概述无代码 第3章《第一个Android程序》 第4章《Android生命周期》 第5章《Android用户界面》 第6章《组件通信与广播消息》 第7章《后台服务》 第8章《数据存储与访问》 第9章《位置服务与地图应用》 第10章《Widget组件开发》 第11章 《Android NDK开发》 第12章 《综合示例设计与开发》

基于Resnet-34的树叶分类(李沐深度学习基础竞赛)

本文介绍了一个基于ResNet-34的树叶分类模型训练与测试流程。数据集来自Kaggle竞赛,包含18000张训练集和8000张测试集图片,涵盖176种树叶类别。模型采用ResNet-34架构,加载ImageNet预训练权重,并通过修改全连接层适配多分类任务。训练过程中,使用交叉熵损失函数和Adam优化器,结合学习率动态调整策略,训练30个epoch后,模型在验证集上的准确率达到92.9%。测试阶段,通过自定义数据集类加载测试数据,并应用与验证集一致的预处理流程,最终将预测结果保存为CSV文件。总结指出,R

vizio.blog.youkuaiyun.com 1579

CTF密码学奇技淫巧:当Base64遇到ROT13与Atbash

本文深入解析CTF竞赛中Base64、ROT13与Atbash的复合编码技术,提供Python实现与CyberChef可视化处理方案,帮助选手掌握密码学题目的解题技巧。通过NewStarCTF2025实战案例,对比脚本化与可视化工具的优劣,提升Web安全竞赛中的编码处理能力。

weixin_29001683的博客 509

数字IC设计——功耗分析

功耗是数字IC芯片的一个重要指标

知识,既贵,又便宜。 9371

基于kaggle树叶分类竞赛讲解pytorch模型训练流程

参考文献,小土堆李沐李沐我讲的很笼统,建议大家看看这几个视频。

2501_91531642的博客 866

【技术实践指南】Python解析FactSet Revere供应链数据:构建企业级断裂与重构监测指标

本文提供了一份企业级Python实践指南,详细解析如何利用FactSet Revere供应链数据库,构建供应链断裂与重构监测指标。内容涵盖数据清洗、关系对筛选、时间序列处理及核心指标(断裂、恢复、转移)的工程化实现,旨在帮助数据工程师与分析师搭建自动化、可解释的供应链风险监控系统。

weixin_29051811的博客 936

18.Kaggle竞赛--使用ResNet-50网络进行树叶分类

18.Kaggle竞赛--使用ResNet-50网络进行树叶分类

m0_71819746的博客 808

(学习笔记)机器人自主导航从零开始第四步———Rviz、Gazebo、Meshlab的安装

一、Rviz的安装 Rviz是一个

qq_59475883的博客 5057

【心得】Back trader避坑指南(一)——关于安装过程的问题

我认为一开始比较容易卡住的地方,主要是安装上面的问题。我给大家整理的这个版本是我能跑通的版本,还有一些好用的神器,手把手教你怎么装!

weixin_38347757的博客 1303

【沐神课程 - 动手学深度学习】实战二详解之 Kaggle比赛:分类树叶

参考与前言 课程链接:跟李沐学AI的个人空间_哔哩哔哩_bilibili 课程主页:https://zh-v2.d2l.ai/chapter_introduction/index.html 相关代码参考:https://github.com/SHENZHENYI/Classify-Leaves-Kaggle-MuLi-d2l-course pytorch官方相关教程参考:https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html

这里是张聪明,正在学习中,请稍后 4866

Python实战:打造学生信息管理系统

通过本教程,我们构建了一个简单的学生信息管理系统,实现了基本的添加、删除、修改和查询功能。当然,这个系统还有很多可以改进和扩展的地方,比如添加用户界面、增加数据持久化功能、支持批量操作等。简介:本文将介绍如何使用Python编写一个功能齐全的学生信息管理系统,包括添加、删除、修改和查询学生信息的基本操作。学生信息管理系统是学校日常管理中不可或缺的一部分,它能够帮助管理人员快速、准确地管理学生信息,提高工作效率。为了实现上述功能,我们可以设计一个包含学生信息的字典,并使用函数来封装各个操作。

铭渊老黄 1492

【学习笔记】PyTorch(2)—Kaggle竞赛树叶分类

Kaggle竞赛树叶分类

m0_48438520的博客 3223

基于双向GRU LSTM DNN水电站水文数据的每日径流量预测 完整代码+数据 可直接运行

基于双向GRU LSTM DNN水电站水文数据的每日径流量预测 完整代码+数据 可直接运行

1903

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

AI绘画挑战量化建模:从图像检测到版权风险分析的数学竞赛实战

在计算机视觉和机器学习领域,图像分类与特征提取是基础且核心的技术。其原理在于通过算法自动学习或人工设计图像的特征表示,并基于这些特征进行模式识别与决策。这项技术的价值在于能够自动化处理海量图像数据,实现高效、准确的识别与分类,广泛应用于内容审核、版权保护、信息安全等场景。支持向量机(SVM)作为一种经典的分类算法,通过寻找最优超平面实现数据分割,在解决高维、非线性问题上表现出色。OpenCV作为强大的计算机视觉库,提供了丰富的图像处理和特征提取工具,是工程实践中的重要基石。本文聚焦于如何将这些技术应用于量化

weixin_33827590的博客 342

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

农业视觉识别:从果园真实约束出发的AI落地实践

农业视觉识别是计算机视觉在非结构化物理场景中的典型应用,其核心在于理解光照变化、目标遮挡、尺度不均等真实环境约束,并将农业先验知识(如果梗定位、成熟度判据)编码为模型结构或后处理规则。区别于通用图像分类,它强调几何定位精度、实时性与机械执行耦合性,技术价值体现在提升采摘机器人鲁棒性与任务成功率。典型应用场景包括智慧果园、果蔬分拣线与农业无人机巡检。本文聚焦水果采摘机器人视觉系统,深入解析数据采集陷阱、双路径模型设计、树莓派轻量化部署及执行导向验证方法。

weixin_30412167的博客 318

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

果园视觉识别的四大物理瓶颈与数学建模解法

图像识别在农业场景中远非标准分类任务,其本质是复杂物理环境下的感知建模问题。需从光照动态范围、非刚性遮挡、果实表面光学散射(BRDF)、传感器-执行器耦合误差等底层物理机制出发,理解视觉失效的确定性规律与随机性扰动。技术价值在于将黑箱深度学习转化为可解释、可验证、可部署的数学结构——如光照自适应白平衡模型、遮挡透射率τ函数、视角-成熟度联合查找表、定位误差椭圆建模等。典型应用场景涵盖智能采摘机器人、边缘端嵌入式视觉系统及农业AI落地工程。本文聚焦真实果园环境下的视觉鲁棒性建模与轻量化实现路径。

weixin_30781107的博客 473

《移动软件开发》

安卓开发APP首页

qq_47171671的博客 265
上一篇: 深入解析BigDecimal的toString()与toPlainString():科学计数法的触发条件与实际应用
下一篇: Unity AssetBundle打包与解包实战:从脚本编写到资源加载
lambda
博客等级 码龄23年 49粉丝 1303原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值