news 2026/9/30 9:02:29

传统村落图景分类实战:数据集构建、迁移学习与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
传统村落图景分类实战:数据集构建、迁移学习与避坑指南

简介:这份PDF为华南理工大学覃巧华等学者发表于《城市规划》2020年第7期的学术论文《基于卷积神经网络的传统村落图景分类研究》。论文面向规划师、建筑师及城乡建设者,提出一种基于卷积神经网络的自动识别与分类方法,利用自建样本分布均衡的传统村落数据库,提取村落肌理、地形地貌、建筑形态结构、表皮材质等特征,将全国传统村落图景按省份归类,旨在为城乡规划与文化保护工作提供大样本村落现状素材库,也为中国传统村落数字管理信息系统建设及后续研究提供素材与定量支撑。资源包共1个PDF文件,大小5.87MB,包含论文全文、完整图表、中英文摘要及参考文献,可作为深度学习、空间量化与村落遗产保护交叉领域的重要研究参考。目前已有219人学习浏览,适合从事人居环境科学、古村落保护及计算机视觉研究的师生与研究人员快速把握方法框架与实验思路,从而少走弯路。

1. 传统村落图景分类为什么比“给房子贴标签”更难

做传统村落数字化建库的人大概都碰过这个场景:一个县几十个村,摄影师拍了上万张照片,回来要按“民居院落、街巷空间、公共建筑、环境要素”归档。人工标了两天就翻车——上午觉得这个角度该算“整体聚落”,下午又觉得它更像“街巷空间”,标准前后打架。基于卷积神经网络的传统村落图景分类研究,本质就是把这件事从“人工盯着图面猜”变成“让 CNN 自动学图面规律”:输入一张村落照片,模型判断它属于哪一类聚落景观要素,从而支撑后续保护范围划定、风貌评估、破坏识别。适合做文化遗产数字化、乡村规划、GIS 分析的从业者,也适合刚跑完 MNIST 分类、想上手真实图像数据的开发者。这里有个反直觉的结论:图景分类比“给猫狗分类”难得多,难的不是模型,是类别本身的边界会随着光线、季节、拍摄角度变化,同一个村口,夏天是“公共空间”,冬天落满雪就成了“环境要素”。

2. 数据集与类别体系:把村落图景拆成模型能学的“语言”

2.1 类别先定对,模型才不白训

常见做法是按“图景要素”分,而不是按“建筑年代”分。年代是属性,图景是画面内容,CNN 学的是像素规律,你给它“明代的祠堂”和“清代的民居”当标签,它在画面上找不到稳定的区分依据,训练自然发散。回头去看这篇研究标题里“传统村落图景分类”——“图景”两个字就是锚点:你要分的,是画面里呈现的景观类型。

我一般会先做一张类别定义表,把每个类别的图面判读特征写死,人工标的时候拿着它比对,比对着类别定义训练集也省心。以下是一个可以直接用的七类体系:

一级类别图面判读特征典型拍摄对象
整体聚落景观俯瞰/远眺,能看到村域边界与山地关系无人机航拍、山路远眺
民居院落以单体建筑为主,有院墙或屋顶轮廓正立面、院内视角
公共建筑有庙宇、祠堂、戏台等识别性构件歇山顶、匾额、彩绘
街巷空间两侧有墙体或建筑立面,形成线性通道巷道、石板路、弄堂
环境要素自然要素占比高,无明显建筑主体古树、水系、农田、山体
构筑物小型人工设施,不属于建筑主体古桥、水井、寨门、石阶
其他/干扰车辆、标语牌、施工围挡、模糊废片无法归入以上任意类

这里有一个关键决策:一定要留“其他”类。很多第一次上手的人把类别定成互斥的“六类”,结果模型在真实数据上把一张电线杆近景硬塞进“构筑物”,因为他在类别设计阶段没给模型留“弃权”出口。加了“其他”之后,训练时模型能把干扰项吸收掉,推理时低置信度样本也有地方去,后面做人工复核压力小很多。

2.2 采集与清洗:尺寸、去重、损坏检查

类别定完之后是数据采集。真实村落图景项目很难像 ImageNet 那样直接下载,常见做法是三个渠道并走:委托拍摄(可控机位)、地方志与规划文本插图扫描件(历史对比)、公开地理图片平台(补充覆盖)。不管哪条渠道,都要过一遍清洗管线。

最小可用的数量底线:每类至少 300 张原始图,训练集建议每类 500 张以上。少于这个数,迁移学习能勉强跑通,但泛化到另一个村时会明显拉胯。另外注意长尾类别:宗祠、戏台这类公共建筑在多数村落里数量本身就少,拍摄时就要刻意补拍,别等标完才发现只有 40 张。

分辨率基线我卡在最长边 800 像素。低于这个线,木构建筑的斗拱、雀替细节直接被下采样抹掉,模型只能靠轮廓猜;高于这个线,显存吃紧不说,训练收益边际递减。清洗阶段做三件事:感知哈希去重(连拍图只留一张)、剔除画面中超过 30% 是天空的废片(这类图对“环境要素”类别干扰极大)、用 PIL 打开并检查通道数,排除损坏文件。

from PIL import Image import os def check_images(root_dir, min_long_edge=800): bad = [] for dirpath, _, files in os.walk(root_dir): for f in files: if not f.lower().endswith(('.jpg', '.jpeg', '.png')): continue path = os.path.join(dirpath, f) try: img = Image.open(path) w, h = img.size if max(w, h) < min_long_edge: bad.append((path, 'too_small')) # 检查是否损坏:重新编码并比较 img.load() except Exception: bad.append((path, 'corrupt')) return bad bad_files = check_images('data/traditional_village') for path, reason in bad_files[:20]: print(path, reason)

这段脚本遍历原始目录,把低于 800 像素和打不开的图挑出来。逻辑上分了三道:后缀过滤只在常规图片格式内扫描,避免混入 .gif 动图;img.load()是真正触发解码的动作,文件头正常但像素数据损坏的图片在这一步会抛异常;too_small单独标记出来而不是直接删除,因为有些历史扫描件虽然小,但内容稀缺,宁可人工确认后再决定去留。

2.3 按村落划分数据集是底线

这是整条数据管线里最容易被忽略、影响却最大的一个决定:划分训练/验证/测试集时,必须按村落划分,绝不能按图片随机划分。

道理很简单:同一个村落的照片之间,天空色温、墙体材料、屋顶形制高度相似。如果随机划分,同一村的照片会同时出现在训练集和验证集,模型实际上记住了这个村的“村容村貌”,验证集分数虚高。等模型部署到没有参与训练的新村,性能断崖式下跌。我见过最典型的翻车:训练集准确率 98%,验证集 96%,换了一个县的数据直接掉到 71%——这就是数据泄漏后模型被“村景记忆”喂饱了。

正确的做法是先把图片按村落分组,再在村落粒度上做分层抽样。

import random from collections import defaultdict def split_by_village(data_root, train_ratio=0.7, val_ratio=0.15, seed=42): random.seed(seed) # 目录结构:data_root / village_name / class_name / images village_dirs = [d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))] random.shuffle(village_dirs) n = len(village_dirs) n_train = int(n * train_ratio) n_val = int(n * val_ratio) return { 'train': village_dirs[:n_train], 'val': village_dirs[n_train:n_train + n_val], 'test': village_dirs[n_train + n_val:] }

这里 train_ratio 取 0.7、val_ratio 取 0.15,剩下的 15% 村落作为测试集。注意我没有做严格的分层抽样——也就是没有保证每个类别在每个集合里比例一致。实际项目里,如果某个类别只在少数村落出现,按村落划分后可能整个类别掉进测试集,这时候要先看一下类别-村落的交叉分布表,把稀有类别的村落单独抽出来做人工干预,而不是迷信这个脚本。

3. 用迁移学习跑通最小可运行模型:从 MNIST 范式到村落图景

3.1 为什么不从零训练 CNN,而是用预训练模型微调

纯从零训练一个 CNN 来做村落图景分类,理论上是可行的,但在数据量只有几千张的项目里几乎必然过拟合。卷积神经网络原理上靠学习大量样本里的统计规律来更新卷积核,几千张图对 5 层以上的随机初始化网络来说,信息量远远不够。常见做法是加载在 ImageNet 上预训练好的 ResNet 权重,把最后的全连接层换成目标类别的分类头,再做微调。

这套迁移学习方案有三个理由:预训练模型的前几层已经学到了边缘、纹理、形状这些通用特征,村落图景里的瓦片纹理、墙体轮廓正好用得上;微调只更新全连接层和最后几个残差块的参数,需要的数据量小很多;收敛快,同样的 batch size 和学习率,从零训练可能要 100 轮才动,微调 20 轮就能看到结果。

如果跑过“mnist 手写数字分类 pytorch”这套经典流程,你会发现结构完全一致:数据加载、模型定义、训练循环、评估,区别只在数据加载方式从 MNIST 变成了 ImageFolder,模型从几层全连接换成了 ResNet。这个熟悉感能让调试成本大幅下降。

3.2 ResNet 迁移学习的最小训练脚本

下面给出一个可以直接跑的最小脚本。前提是数据目录已经按“train/类别名/图片”和“val/类别名/图片”组织好。

import torch import torch.nn as nn from torchvision import datasets, transforms, models from torch.utils.data import DataLoader transform_train = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder('data/split/train', transform=transform_train) val_ds = datasets.ImageFolder('data/split/val', transform=transform_val) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True)

Resize 到 256 然后随机裁剪到 224,是 ImageNet 分类的标准做法。RandomCrop 等于在训练时给模型提供了平移不变的约束,比直接 Resize 到 224 的效果明显更稳。Normalize 用的均值方差是 ImageNet 数据集的统计值,迁移学习场景下必须沿用预训练模型的归一化参数,不能自己重新算,否则预训练权重相当于被重新“翻译”了一遍。batch_size 32 在 224 分辨率下约占 8GB 显存,如果你的卡不够,先降到 16。

model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) num_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(num_features, 512), nn.ReLU(inplace=True), nn.Linear(512, len(train_ds.classes)) )

这里选 ResNet18 而不是 ResNet50,是因为村落图景分类的类别粒度并不需要特别深的网络。ResNet50 在 ImageNet 上有优势,但在几千张村落图上,收益主要来自预训练而非深度,18 层跑一轮的时间几乎只有 50 的一半,迭代调参的效率高得多。分类头改成两层线性层并夹一个 Dropout,目的不是增加容量,而是防止全连接层直接记住训练集里那些“某个村的特定角度”。len(train_ds.classes)自动从目录结构读取类别数,比硬编码 7 更不容易出错。

criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4) for epoch in range(30): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() 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_ds) print(f'Epoch {epoch+1:02d} loss: {epoch_loss:.4f}')

这个循环没有任何学习率调整,是最朴素的训练方式。AdamW 的 weight_decay 设为 1e-4,对微调场景是安全起点;学习率 1e-4 比常见 ImageNet 训练经验低一个数量级,因为预训练权重已经很接近最优区域,过大的学习率会破坏前几层已经学好的纹理特征。如果你发现 loss 在前三轮不降反升,大概率是学习率偏高,直接调到 3e-5 再试。

3.3 第一次训练后要看哪几个数

一轮完整训练跑完后,不要急着看准确率,先看三个东西:训练损失和验证损失的差距、按类别的召回率、以及错误样本的具体分布。

训练损失收敛、验证损失不降,是过拟合的典型信号;两个都在降但速度极慢,是学习率太低或者分类头设计有问题;训练损失降了验证损失反弹,是正则化强度不够。按类别看召回率这一步,能直接把“哪类图景学不会”暴露出来——村落项目里几乎永远是“公共建筑”和“构筑物”这两类最低,前者数量少,后者定义与“环境要素”重叠。

错误样本分布不要只看数值,要把每张验证图的预测结果导出,按“真实类别-预测类别”的格子抽样看原图。这一步会花大约半小时,但你能直接看到模型是把“带石凳的巷口”误判成“公共空间”,还是把“寨门特写”误判成“民居院落”——这个判断决定了下一步是调数据还是调类别边界。

4. 训练配置与调参:把收敛速度提上去、把翻车概率压下来

4.1 优化器选择:AdamW 快速跑通,SGD 精调收尾

第一次跑通用 AdamW 是效率最高的选择。它的自适应学习率机制对初始学习率不敏感,1e-4 大致能覆盖多数情况,省去了 SGD 需要反复试探初始学习率的痛苦。但 AdamW 有个先天问题:后期收敛不干净,最终精度通常比 SGD 配动量低一个百分点左右。所以在粗调到 85% 以上的验证准确率之后,我习惯把优化器换成 SGD,momentum 0.9、学习率降到 1e-3,再跑 20 轮精调。

优化器初始学习率weight_decay适用阶段典型行为
AdamW1e-41e-4快速跑通基线前 5 轮 loss 快速下降,后期平台期
SGD + Momentum1e-35e-4基线之上精调前期慢热,后期稳步逼近最优
SGD 无动量1e-21e-4从零训练村落数据集上不推荐,收敛极慢

注意 SGD 在迁移学习里的学习率和从零训练完全不同。从零训练时 1e-2 起步很正常,但迁移学习下预训练特征已经被激活,1e-3 已经是偏高的起点,调不好甚至会看到 loss 直接冲高。

4.2 warmup 与余弦退火:让预训练权重平稳交接

预训练权重在第一步反向传播时会产生一个不小的梯度冲击,直接全量更新容易让权重偏离原来的特征空间。常见做法是先用一个小的学习率热身几个轮次,再切到正式的学习率计划。我一般用 5 轮 warmup + 余弦退火到初始学习率十分之一。

from torch.optim.lr_scheduler import CosineAnnealingLR scheduler = CosineAnnealingLR(optimizer, T_max=30, eta_min=1e-5) # 手动实现 warmup:前 5 轮线性上升到 1e-4 warmup_epochs = 5 for epoch in range(8): if epoch < warmup_epochs: lr = 1e-5 + (1e-4 - 1e-5) * (epoch + 1) / warmup_epochs for g in optimizer.param_groups: g['lr'] = lr else: scheduler.step()

这段代码里 warmup 部分没有用到 scheduler,而是手动调整每个参数组的学习率。原因是 CosineAnnealingLR 在T_max=30的设定下第一轮就会把学习率压得很低,warmup 逻辑会和它打架。我实际项目里是先跑一个只有 warmup 的脚本确认前 5 轮 loss 稳定下降,再把 scheduler 挂上去。eta_min 设为 1e-5 是经验值,微调阶段最终学习率衰减到初始的十分之一就够了,衰减到太低会让最后几轮几乎学不动。

4.3 针对村落实景的数据增强:别把天空当学习对象

通用数据增强在村落图景上有个细节差异:水平翻转完全安全,但垂直翻转和 180 度旋转绝对不能开。村落图景是有语义上下之分的——天空在上、地面在下,你垂直翻转之后模型学到的是“天空在下方”的奇怪分布,推理时一到真实场景就会混乱。

我一般额外加两个增强:RandomResizedCrop 和 ColorJitter。RandomResizedCrop 模拟不同拍摄距离——无人机俯瞰、街巷平视、山腰远眺,尺度多样对泛化很重要;ColorJitter 模拟季节光线差异——同一个村落在夏天和冬天的色温差别很大,这个增强让模型学会忽略绝对颜色、关注形状和纹理。

transform_train = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.5, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.4, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

scale 范围取(0.5, 1.0)而不是 ImageNet 常用的(0.08, 1.0),是因为村落图景分类里目标占比通常比较大——你要分类的对象是整幅图景,而不是图中某个小物件。scale 下限太低会把一片瓦当裁成主体,反而产生噪声样本。这里的直觉是:ImageNet 分类要识别图中占比很小的物体,所以裁剪范围很大;图景分类要保留整体关系,所以裁剪范围收窄。

4.4 类别不均衡的三级处理方案

村落实拍图天然不均衡:民居院落占了六成,宗祠戏台只有几个百分点。这种情况直接训练,模型会变成“无脑预测多数类”的懒汉。我按严重程度分三级处理。

最轻的情况(少数类占比 20% 以上),只用WeightedRandomSampler给少数类加权就能解决。中等情况(少数类占比 5%-10%),在加权采样基础上把损失函数换成带 class weight 的 CrossEntropyLoss。极端情况(少量类和其它类数量差一个数量级),加权采样会让模型反复看同一批少数类图片,直接过拟合到“背下这几张”,这时候要靠更强的数据增强和类别合并。

from torch.utils.data import WeightedRandomSampler class_counts = train_ds.targets weights = [1.0 / class_counts.count(c) for c in class_counts] sampler = WeightedRandomSampler(weights, num_samples=len(weights), replacement=True) train_loader = DataLoader(train_ds, batch_size=32, sampler=sampler)

这段代码按每个样本的“所属类别总数的倒数”来加权,出现次数少的类别,它的样本被抽中的概率反而高。replacement=True表示可以有放回地重复抽取同一样本,这是加权采样的精髓——但注意重复抽样的次数不宜过多,不然模型会记住那张图而不是那个类别。建议开启加权后训练轮次减半,给模型更少的机会去记忆单张图片。

5. 传统村落图景分类的常见问题与避坑清单

5.1 公共建筑被“淹”在多数类里

现象:训练了 20 轮后,整体准确率看着有 86%,但打开分类报告,宗祠、戏台的召回率只有 12%,几乎全部被预测成“民居院落”。

原因:数据集里民居院落占 60% 以上,交叉熵损失在多数类上累计的梯度远大于少数类,模型学会了“全预测成民居”这个最省事的策略。损失函数本身并不惩罚这种偷懒行为,它只在意总损失最小化。

解决:按 4.4 节的方案加上WeightedRandomSampler,同时把少数类做额外增强。注意一个常见误区:光加权不增强,少数类样本数量没有增加,模型翻来覆去就看见那几十张图,训练集损失降到很低但验证集照样稀烂。加权和增强要同时上。

5.2 “街巷空间”和“公共空间”边界互相渗透

现象:混淆矩阵里,街巷空间有将近三分之一的误判流向公共空间。抽查原图发现,被误判的大多是画面里有石凳、树荫、聊天人群的巷口——这些图按人的直觉确实两种都算。

原因:类别定义本身重叠。街巷空间是线性通道,公共空间是节点广场,但真实村落里巷口放大就是半广场,过渡带没有清晰的像素级边界。模型在低维特征空间里找不到一个稳定分割两类图景的超平面。

解决:两条路。一条是合并类别,把“街巷空间”和“公共空间”并为“街巷与公共空间”,这是最果断的解法,模型精度立刻回升;另一条是做层级分类,第一层先分“建筑”“街巷”“自然”,第二层再细分。实践中我倾向于先合并,等数据量超过每类 1000 张再拆回细粒度。

5.3 过拟合到“记住村落”而不是“学会类别”

现象:训练集准确率 99%,验证集只有 73%;然后按村子重新划分数据集,验证集进一步掉到 64%。

原因:回到 2.3 节的那句话——随机划分导致同一村落的图片同时进入训练集和验证集。模型记住了瓦片颜色、墙体材料这些村域特征,而验证集里正好又是那几个村,给了一个虚假的高分。

解决:先修正数据划分为按村落划分,再检查另一件事——类别是否与村落高度相关。如果“宗祠”只在某两个村里出现,数据划分后它们全去了训练集,验证集的宗祠数是 0,这个验证集本身就没有意义了。碰上这种,唯一可靠的办法是补充拍摄数据,至少在三个以上村落采到同一类别。

5.4 类激活图高亮区域变成一片噪声

现象:用 GradCAM 可视化把“整体聚落景观”类别的热力图叠在原图上,高亮区域落在了天空和远山上,而不是村落建筑群。模型分类是对的,但解释性图完全不可信。

原因:ResNet18 最后一层特征图的空间分辨率是 7×7,对于一张 224×224 的输入图,每个特征点对应原图 32×32 像素的区域。村落图景中建筑聚落的边界正好落在这个尺度内,高亮区域被网格化打散;再加上 ImageNet 预训练模型对天空纹理本身有强响应,天空就占据了热力图的注意力。

解决:先用 torchvision 的feature_map_visualization手段,检查网络的 layer4 输出响应最大的通道是不是确实对应建筑纹理。如果确认模型行为正常,换用 GradCAM 实现时指定目标层为 layer4 的最后一个残差块,而不是整个 layer4——前者分辨率更高、定位更细。最实在的办法是把输入分辨率提到 384,保持 batch size 减半,特征图分辨率升到 12×12,可视化质量立刻上一个台阶。

5.5 验证集上一张图反复触发同一种错误

现象:多次实验里,某张验证图永远被预测成同一个错误类别,换了模型结构也不变。

原因:这张图在图面特征上确实更接近错误类别。我遇到过一张傍晚拍的村口牌坊,光线暗到牌坊细节全糊,图面只剩一个剪影轮廓,模型学到的“构筑物剪影”和“民居屋顶剪影”在低光下没有区别,这是数据问题而不完全是模型问题。

解决:先看原图和特征空间映射,确认图本身质量。质量差就标记为“质量不足”,从测试集剔除,但保留在补充训练集里——这类低光图对模型鲁棒性是有价值的。不要为了“数据集完整”硬留一张人看着都费劲的图在测试集里,那只会拉低指标并掩盖模型在正常图上的真实水平。

6. 验证与落地:分类结果怎么变成村落保护可用的评估结论

6.1 混淆矩阵的三种读法,不能只看对角线

训练结束后打印混淆矩阵这一步别省。对角线数值代表每类召回率,这是最直观的读法;但更有价值的是看“行”和“列”——行代表这个类别被错分去了哪里,列代表哪些类别被错分进了它。行方向上的集中错分,指向类别边界问题;列方向上的均匀错分,指向该类别特征本身不突出。我通常要求自己在这张图上停留十分钟,把最密集的三个非对角格子对应的原图抽查一遍再决定下一步。

import seaborn as sns import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix cm = confusion_matrix(val_labels, val_preds, labels=train_ds.classes) plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=train_ds.classes, yticklabels=train_ds.classes) plt.xlabel('Predicted') plt.ylabel('True') plt.tight_layout() plt.savefig('village_cm.png', dpi=150)

这段代码用 seaborn 把混淆矩阵画成热力图。labels=train_ds.classes确保矩阵行列顺序和类别列表一致,否则看图时对不上号。对于村落图景分类这种图与图之间边界重叠明显的数据集,我还会额外打印归一化版本的混淆矩阵(每行除以真值总数),因为原始计数容易掩盖比例关系——某类总数多,它的错分数看着大,归一化之后才能公平对比。

6.2 从模型指标到保护决策:置信度阈值与人工复核队列

准确率只是中间产物,真正要交付的是对村落保护有意义的结论。我一般把模型输出组织成三份材料:类别比例统计、置信度分布、以及低置信度图集。

类别比例统计直接反映风貌构成——如果一个村的数据里“公共建筑”占比异常低、而“民居院落”占比异常高,结合实地踏勘记录,可能说明宗祠戏台正在衰败消失,这个信号比人工统计快得多。置信度分布的价值在于筛选“争议图”:设定一个 0.6 的阈值,低于阈值的图自动进入人工复核队列。真实项目里这套“模型初筛 + 人工复核”的流程,比纯人工标注快三四倍,比纯自动预测可信得多——常见做法是把复核工作交给对村落熟悉的地方规划人员,他们对“这是戏台还是民居”的判断比模型准。

6.3 向新村落推实时景分类时,先跑回归测试

模型做出来不是终点,老乡拍一张照片传上来,要在手机上立刻知道这是不是破坏性建设,这才是这个方向的真实价值。往新场景推之前,我会做一次回归测试:把开发阶段没见过的两三个村的图集过一遍模型,对比每个类别的召回率是否有结构性掉点。如果某类掉了超过 10 个点,说明新村的这一类图景与训练集差异太大,需要补充微调数据。

在扩展类别的时候,拿原有的七类模型当特征提取器,冻结前面所有层,只在新类别数据上重新训练分类头,这样做的好处是旧类别不会出现灾难性遗忘——一个已经部署过一轮的分类系统,在扩展类别时不破坏已有能力,这点比一味追求新类别精度更重要。我现在每个项目落地前都会先打印类别分布表,确认少数类比例和村落覆盖数都达标才动手跑模型——这个习惯救过我好几次,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:00:41

前端工程师转型AI Agent开发的零废弃技能路径

1. 这不是“转行”&#xff0c;是前端工程师的自然进化路径最近三个月&#xff0c;我陆续和17位明确想“从前端转向AI Agent开发”的朋友做过深度交流。他们中&#xff0c;有工作3年的Vue中级开发者&#xff0c;有带团队的React技术负责人&#xff0c;也有刚毕业两年、在中小厂…

作者头像 李华
网站建设 2026/9/30 8:59:55

二叉树遍历底层原理与递归改迭代:彻底解决空指针和栈溢出

上个月团队做 Java 基础面试&#xff0c;连续几个候选人卡在同一个问题上&#xff1a;写一个二叉树的前序遍历。代码是背下来了&#xff0c;但一问到递归改迭代、为什么中间节点先出栈、极端情况会不会爆栈&#xff0c;就讲不清楚了。这事让我很有感触。二叉树遍历是一道典型的…

作者头像 李华
网站建设 2026/9/30 8:58:10

apt-get install 默认安装路径与FHS文件系统规范解析

1. 项目概述&#xff1a;搞懂 apt-get install 的默认安装路径&#xff0c;不是查文档&#xff0c;是看系统怎么“落子” 你执行 sudo apt-get install openssh-server &#xff0c;敲完回车&#xff0c;服务就跑起来了&#xff1b;你又试了 sudo apt-get install fcitx fci…

作者头像 李华
网站建设 2026/9/30 8:57:49

CPU不忙为何延迟高?Linux On-CPU/Off-CPU性能分析

线上有个服务&#xff0c;CPU 使用率长期只有 12%&#xff0c;但 P99 延迟从 80ms 悄悄涨到了 1.2s&#xff0c;运维看监控面板一脸茫然——CPU 不忙、内存不涨、磁盘 IO 也不高&#xff0c;那这一秒多到底耗在哪了。这类问题我在 Linux 性能分析里遇到过很多次&#xff0c;答案…

作者头像 李华
网站建设 2026/9/30 8:57:23

SpringBoot接口日期格式化:注解、全局配置与自定义反序列化器

搞了几年 SpringBoot 接口开发&#xff0c;我最大的感受是&#xff1a;真正让前后端在联调阶段反复拉扯的&#xff0c;往往不是什么高深的技术难题&#xff0c;而是接口日期格式化。前端同学问“为什么返回的 createTime 是一串数字”&#xff0c;后端同学反问“前端传的 2024-…

作者头像 李华