1. 项目背景与准备工作街景字符识别是计算机视觉领域一个非常经典的任务主要应用于自动驾驶、地图标注等场景。阿里云天池平台提供了这样一个实战项目让我们可以从零开始构建一个完整的识别系统。作为刚接触这个领域的新手我刚开始也是一头雾水但跟着这个教程一步步做下来发现其实并没有想象中那么难。首先需要准备好开发环境。我推荐使用Python 3.7版本并安装最新版的PyTorch框架。这里有个小技巧如果你有NVIDIA显卡一定要记得安装对应版本的CUDA工具包这能让训练速度提升好几倍。我刚开始就犯了个错误没装CUDA就直接跑模型结果训练一个epoch要半小时装了之后只要5分钟。安装依赖包时要注意版本兼容性。建议创建一个新的conda环境然后安装以下核心包pip install torch torchvision pillow numpy pandas数据集可以从天池比赛页面下载解压后会得到两个重要文件包含图片的mchar_train文件夹和标注文件mchar_train.json。这里有个细节需要注意图片文件名和json中的标注是一一对应的所以处理时一定要保持顺序一致。我第一次跑代码时就因为没注意这个导致标签和图片对不上训练完全没效果。2. 数据读取与预处理实战数据处理是整个项目中最关键的环节之一。我刚开始觉得这部分很枯燥但后来发现数据处理的好坏直接决定了模型最终的表现。我们的数据集比较特殊每个图片可能包含1-5个数字字符而且长度不固定这给建模带来了挑战。2.1 自定义Dataset类PyTorch的Dataset类让我们可以灵活地定义数据加载方式。在这个项目中我们需要处理三个关键点图片读取后要转换为RGB格式对图片进行多种增强变换处理不定长的标签信息这里有个很巧妙的处理方式用数字10表示空字符并将所有标签统一为5位长度。比如数字19会被转换为[1,9,10,10,10]。这样就把变长识别问题转化为了固定长度的分类问题。class SVHNDataset(Dataset): def __init__(self, img_path, img_label, transformNone): self.img_path img_path self.img_label img_label self.transform transform def __getitem__(self, index): img Image.open(self.img_path[index]).convert(RGB) if self.transform is not None: img self.transform(img) lbl np.array(self.img_label[index], dtypenp.int) lbl list(lbl) (5 - len(lbl)) * [10] return img, torch.from_numpy(np.array(lbl[:5]))2.2 数据增强技巧数据增强是提升模型泛化能力的关键。我们使用了以下几种增强方式随机裁剪模拟不同拍摄角度颜色抖动适应不同光照条件随机旋转增强对倾斜字符的识别能力需要注意的是验证集不应该使用随机变换只需要简单的resize和归一化train_transform transforms.Compose([ transforms.Resize((64, 128)), transforms.RandomCrop((60, 120)), transforms.ColorJitter(0.3, 0.3, 0.2), transforms.RandomRotation(10), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((60, 120)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])3. 模型构建与优化3.1 网络结构设计我们基于ResNet18进行改造这是一个非常实用的技巧使用预训练模型能大大提升小数据集上的表现。原生的ResNet18是为ImageNet设计的我们需要做两处关键修改移除最后的全连接层添加5个新的全连接层每个对应一位数字的分类class SVHN_Model1(nn.Module): def __init__(self): super(SVHN_Model1, self).__init__() model_conv models.resnet18(weightstorchvision.models.ResNet18_Weights.IMAGENET1K_V1) model_conv.avgpool nn.AdaptiveAvgPool2d(1) model_conv nn.Sequential(*list(model_conv.children())[:-1]) self.cnn model_conv self.fc1 nn.Linear(512, 11) # 0-9加10(空白) self.fc2 nn.Linear(512, 11) self.fc3 nn.Linear(512, 11) self.fc4 nn.Linear(512, 11) self.fc5 nn.Linear(512, 11)3.2 损失函数与优化器由于我们有5个分类任务需要将5个损失相加作为总损失。这里使用交叉熵损失它非常适合分类问题。优化器选择Adam学习率设为0.001是个不错的起点criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), 0.001)在实际训练中我发现学习率的设置非常关键。一开始用0.01导致训练不稳定改为0.001后收敛就好多了。还可以考虑使用学习率调度器在训练后期逐步降低学习率。4. 训练过程与技巧4.1 训练循环实现训练过程需要特别注意几个细节每次迭代前要清零梯度合理设置batch size根据显存大小调整定期在验证集上评估模型def train(train_loader, model, criterion, optimizer, epoch): model.train() train_loss [] for i, (input, target) in enumerate(train_loader): if use_cuda: input, target input.cuda(), target.cuda() c0, c1, c2, c3, c4 model(input) loss criterion(c0, target[:, 0]) \ criterion(c1, target[:, 1]) \ criterion(c2, target[:, 2]) \ criterion(c3, target[:, 3]) \ criterion(c4, target[:, 4]) optimizer.zero_grad() loss.backward() optimizer.step() train_loss.append(loss.item()) return np.mean(train_loss)4.2 验证与模型选择验证阶段要设置model.eval()并且使用torch.no_grad()来节省内存。我建议每训练1-2个epoch就验证一次并保存表现最好的模型if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), ./model.pt)这里有个经验教训不要只看准确率也要关注训练损失和验证损失的对比。如果训练损失持续下降但验证损失开始上升很可能出现了过拟合。5. 结果分析与改进方向5.1 基准模型表现经过10个epoch的训练基准模型在验证集上能达到约55%的准确率。这个结果看似不高但对于一个baseline来说已经不错了。实际提交到天池平台后可能会发现线上分数只有0.3左右这通常是因为训练epoch过多导致过拟合验证集和测试集分布不一致数据预处理方式有差异5.2 后续优化建议根据我的经验可以从以下几个方向进一步提升模型表现数据层面增加更多数据增强方式对样本进行统计分析处理类别不平衡问题尝试不同的图像尺寸和长宽比模型层面尝试更大的预训练模型如ResNet50添加注意力机制使用更复杂的解码策略训练技巧使用学习率warmup尝试不同的优化器如RAdam加入标签平滑正则化在实际项目中我建议先确保baseline模型能稳定运行再逐步尝试这些优化方法。每次只改变一个变量这样才能准确评估每种改进的效果。
【阿里云天池】实战:从零构建街景字符识别Baseline模型
发布时间:2026/6/11 19:59:06
1. 项目背景与准备工作街景字符识别是计算机视觉领域一个非常经典的任务主要应用于自动驾驶、地图标注等场景。阿里云天池平台提供了这样一个实战项目让我们可以从零开始构建一个完整的识别系统。作为刚接触这个领域的新手我刚开始也是一头雾水但跟着这个教程一步步做下来发现其实并没有想象中那么难。首先需要准备好开发环境。我推荐使用Python 3.7版本并安装最新版的PyTorch框架。这里有个小技巧如果你有NVIDIA显卡一定要记得安装对应版本的CUDA工具包这能让训练速度提升好几倍。我刚开始就犯了个错误没装CUDA就直接跑模型结果训练一个epoch要半小时装了之后只要5分钟。安装依赖包时要注意版本兼容性。建议创建一个新的conda环境然后安装以下核心包pip install torch torchvision pillow numpy pandas数据集可以从天池比赛页面下载解压后会得到两个重要文件包含图片的mchar_train文件夹和标注文件mchar_train.json。这里有个细节需要注意图片文件名和json中的标注是一一对应的所以处理时一定要保持顺序一致。我第一次跑代码时就因为没注意这个导致标签和图片对不上训练完全没效果。2. 数据读取与预处理实战数据处理是整个项目中最关键的环节之一。我刚开始觉得这部分很枯燥但后来发现数据处理的好坏直接决定了模型最终的表现。我们的数据集比较特殊每个图片可能包含1-5个数字字符而且长度不固定这给建模带来了挑战。2.1 自定义Dataset类PyTorch的Dataset类让我们可以灵活地定义数据加载方式。在这个项目中我们需要处理三个关键点图片读取后要转换为RGB格式对图片进行多种增强变换处理不定长的标签信息这里有个很巧妙的处理方式用数字10表示空字符并将所有标签统一为5位长度。比如数字19会被转换为[1,9,10,10,10]。这样就把变长识别问题转化为了固定长度的分类问题。class SVHNDataset(Dataset): def __init__(self, img_path, img_label, transformNone): self.img_path img_path self.img_label img_label self.transform transform def __getitem__(self, index): img Image.open(self.img_path[index]).convert(RGB) if self.transform is not None: img self.transform(img) lbl np.array(self.img_label[index], dtypenp.int) lbl list(lbl) (5 - len(lbl)) * [10] return img, torch.from_numpy(np.array(lbl[:5]))2.2 数据增强技巧数据增强是提升模型泛化能力的关键。我们使用了以下几种增强方式随机裁剪模拟不同拍摄角度颜色抖动适应不同光照条件随机旋转增强对倾斜字符的识别能力需要注意的是验证集不应该使用随机变换只需要简单的resize和归一化train_transform transforms.Compose([ transforms.Resize((64, 128)), transforms.RandomCrop((60, 120)), transforms.ColorJitter(0.3, 0.3, 0.2), transforms.RandomRotation(10), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((60, 120)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])3. 模型构建与优化3.1 网络结构设计我们基于ResNet18进行改造这是一个非常实用的技巧使用预训练模型能大大提升小数据集上的表现。原生的ResNet18是为ImageNet设计的我们需要做两处关键修改移除最后的全连接层添加5个新的全连接层每个对应一位数字的分类class SVHN_Model1(nn.Module): def __init__(self): super(SVHN_Model1, self).__init__() model_conv models.resnet18(weightstorchvision.models.ResNet18_Weights.IMAGENET1K_V1) model_conv.avgpool nn.AdaptiveAvgPool2d(1) model_conv nn.Sequential(*list(model_conv.children())[:-1]) self.cnn model_conv self.fc1 nn.Linear(512, 11) # 0-9加10(空白) self.fc2 nn.Linear(512, 11) self.fc3 nn.Linear(512, 11) self.fc4 nn.Linear(512, 11) self.fc5 nn.Linear(512, 11)3.2 损失函数与优化器由于我们有5个分类任务需要将5个损失相加作为总损失。这里使用交叉熵损失它非常适合分类问题。优化器选择Adam学习率设为0.001是个不错的起点criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), 0.001)在实际训练中我发现学习率的设置非常关键。一开始用0.01导致训练不稳定改为0.001后收敛就好多了。还可以考虑使用学习率调度器在训练后期逐步降低学习率。4. 训练过程与技巧4.1 训练循环实现训练过程需要特别注意几个细节每次迭代前要清零梯度合理设置batch size根据显存大小调整定期在验证集上评估模型def train(train_loader, model, criterion, optimizer, epoch): model.train() train_loss [] for i, (input, target) in enumerate(train_loader): if use_cuda: input, target input.cuda(), target.cuda() c0, c1, c2, c3, c4 model(input) loss criterion(c0, target[:, 0]) \ criterion(c1, target[:, 1]) \ criterion(c2, target[:, 2]) \ criterion(c3, target[:, 3]) \ criterion(c4, target[:, 4]) optimizer.zero_grad() loss.backward() optimizer.step() train_loss.append(loss.item()) return np.mean(train_loss)4.2 验证与模型选择验证阶段要设置model.eval()并且使用torch.no_grad()来节省内存。我建议每训练1-2个epoch就验证一次并保存表现最好的模型if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), ./model.pt)这里有个经验教训不要只看准确率也要关注训练损失和验证损失的对比。如果训练损失持续下降但验证损失开始上升很可能出现了过拟合。5. 结果分析与改进方向5.1 基准模型表现经过10个epoch的训练基准模型在验证集上能达到约55%的准确率。这个结果看似不高但对于一个baseline来说已经不错了。实际提交到天池平台后可能会发现线上分数只有0.3左右这通常是因为训练epoch过多导致过拟合验证集和测试集分布不一致数据预处理方式有差异5.2 后续优化建议根据我的经验可以从以下几个方向进一步提升模型表现数据层面增加更多数据增强方式对样本进行统计分析处理类别不平衡问题尝试不同的图像尺寸和长宽比模型层面尝试更大的预训练模型如ResNet50添加注意力机制使用更复杂的解码策略训练技巧使用学习率warmup尝试不同的优化器如RAdam加入标签平滑正则化在实际项目中我建议先确保baseline模型能稳定运行再逐步尝试这些优化方法。每次只改变一个变量这样才能准确评估每种改进的效果。