
简介《基于卷积神经网络的传统村落图景分类研究》是一篇面向城乡规划师、建筑师及文化遗产数字化研究者的学术论文针对海量传统村落图像人工分类耗时、主观性强的问题提出基于卷积神经网络的自动识别与归类方法。论文基于自建样本均衡的村落数据库从村落肌理、地形地貌、建筑形态结构、表皮材质等维度提取特征按省份对村落图景进行分类并探讨深度学习、空间量化在村落数字化管理中的价值。资源为一份PDF全文压缩包共1个文件约5.87MB包含论文摘要、关键词、正文、图表、参考文献及基金项目信息可直接阅读与引用。目前已有219人学习浏览。通过该PDF可系统掌握传统村落图景分类的技术路线、模型搭建思路与实验分析为村落图像识别研究、数字管理信息系统建设提供方法参照和数据支撑也可作为深度学习在建筑遗产领域应用的教学参考资料。1. 卷积神经网络在传统村落图景分类里的定位传统村落保护的前置工作是把村落现状做数字化记录一线采集回来的照片动辄上万张靠人工按门类归档既慢又不稳定。把图景分类这件事交给卷积神经网络CNN目标是用一个端到端的模型把输入图片归入既定的村落场景类别比如民居、街巷、祠堂、水系、农田肌理等。这个任务和公开图像分类比赛不一样类别之间视觉差异小样本量有限不同村落的建筑风格还会互相干扰所以真正决定项目成败的往往不是模型选得多新而是数据怎么标、训练怎么调、误判怎么查。这篇文章顺着「数据集构建 → 模型选型 → 训练调参 → 验证纠错」这条线把这类场景分类项目里最常踩的坑和可以直接抄走的做法讲清楚适合正在做图像识别落地、手里数据不够多又想用 CNN 出结果的工程师。2. 传统村落图景数据集的构建与 CNN 输入规范2.1 图景类别体系怎么定分类研究的第一步不是选模型而是把类别边界定清楚。传统村落的图景通常从三个维度划分空间属性建筑、街巷、场地、功能属性居住、祭祀、生产、要素构成水系、农田、植被。实际项目里最常见的问题是类别互相重叠「街巷」和「建筑立面」在一张照片里经常同时出现标注员会按自己的理解二选一导致同一批照片的标签漂移模型学到的决策边界也跟着漂。我一般会先定一条互斥规则每张图只归一个主类别主类别由画面中占比最大的景别决定并在标注规范里写死。比如建筑立面占画面一半以上就归为建筑否则按场景归为街巷或开放空间。这个规则看起来简单但能显著降低标注噪声。另一个注意点是类别数量控制在 8 到 12 个之间少于 8 个模型学不到足够区分度多于 12 个在小样本情形下每个类别的正样本数量会稀薄到没法训练。下表是一个可直接参考的类别体系每类对应一个数据目录训练时按目录名读取标签类别编号目录名图景定义示例特征0building建筑单体与立面立面占画面 50% 以上1street街巷空间两侧有墙体、路面连续2square公共广场与晒场开敞地面、无连续墙体3water水系与桥梁水面、桥体、驳岸4farmland农田与梯田田块纹理、作物行距5forest山林背景植被覆盖占比高6courtyard庭院与天井四面围合、铺地明显7other其他以上均不满足这个表在项目启动时就固定下来不要中途改定义否则回归测试没法做。每一类收集时优先保证覆盖不同天气、时段和拍摄角度而不是单纯堆单类数量因为颜色和光照多样性对 CNN 泛化能力的影响比数量更大。2.2 图像尺寸、归一化与数据集划分CNN 输入需要固定张量形状。传统村落图景的原始照片多数来自无人机或单反边长在 4000 像素左右直接喂进网络既没必要也没有足够显存。常见做法是统一缩放到 224×224 或 256×256这个尺寸是 ResNet、VGG 等结构的默认输入也是显存占用和空间细节之间的折中。缩放时注意保持宽高比避免直接拉伸导致建筑线条变形。我习惯先按短边缩放再居中裁剪from PIL import Image import torchvision.transforms as T def load_village_image(path, size256): img Image.open(path).convert(RGB) # 短边缩放保持宽高比避免建筑线条被拉伸 scale size / min(img.size) new_size (int(img.size[0] * scale), int(img.size[1] * scale)) img img.resize(new_size, Image.BILINEAR) # 中心裁剪到目标尺寸 transform T.Compose([ T.CenterCrop(size), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) return transform(img)上面的均值方差用的是 ImageNet 统计值迁移学习场景下沿用这个值即可如果你从头训练模型需要先用训练集重新统计。数据划分建议按村落分组做分层抽样而不是对全体图片随机切分。原因在于同一村落的不同照片高度相似随机切分会让模型在测试集上的表现虚高。按村落划分成 70% 训练、15% 验证、15% 测试并保证测试集中的村落不出现在训练集这样评估出来的准确率才接近真实落地时的水平。2.3 为什么图景分类选 CNN 而不是全连接网络搜「图像处理为啥用 cnn 不用前馈神经网络」的人本质是在问参数效率和归纳偏置的差异。一张 224×224 的彩色图像展开后有十五万个维度全连接网络第一层就要在所有这些维度之间做全连接计算参数数量以亿计几千张训练图必然过拟合。CNN 通过局部连接和权值共享把参数降下来同一卷积核滑动扫过整张图等于内置了「特征出现在什么位置不影响类别判断」的先验。图像里相邻像素的相关性远高于远距离像素这正好匹配卷积核的局部感受野。村落图景中的墙体纹理、屋顶坡度、田块的条带状图案、水面的反光特性都是典型的局部强相关特征。CNN 先由低层卷积提取边缘和颜色块再逐层组合成高阶的全局结构而全连接网络把每个像素孤立对待等于放弃了这个天然的多层级结构。所以在这个任务上选 CNN 不是跟风是数据本身的特性决定的。3. 从 LeNet-5 到 ResNet村落图景分类的模型选型与训练3.1 用 LeNet-5 风格网络跑通最小基线先跑通再优化。CNN 的入门结构 LeNet-5 诞生于 1998 年但它那套「卷积提特征、池化降采样、全连接分类」的骨架仍然是现在所有分类网络的原型。用它的变体做基线的好处是训练快、显存小、便于验证数据链路和训练循环是否正确。针对村落图景我做了轻量改造加了 BatchNorm 和 Dropout 来稳定收敛import torch.nn as nn class VillageBaselineCNN(nn.Module): def __init__(self, num_classes8): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size5, padding2), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size5, padding2), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): return self.classifier(self.features(x))这段结构里最关键的是最后的 AdaptiveAvgPool2d。传统 LeNet-5 用固定尺寸的全连接层接卷积输出输入尺寸一变就报错全局平均池化先把特征图压成 1×1后面的全连接层输入维度就与输入图像尺寸解耦换任意尺寸输入都不需要改网络定义。Dropout 放在分类头之前是防止小样本下分类层过拟合的必要手段。这个基线在 8 类、每类约 500 张图的数据上Top-1 准确率通常能到 60% 到 70%作为起点足够后续迭代的空间也清楚。3.2 切到 ResNet18 迁移学习的收益基线验证链路没问题后下一步换预训练主干。ResNet 的残差连接解决的是网络加深后的梯度消失和退化问题。在村落图景这种样本量小的领域从头训练深层网络几乎必然过拟合更稳妥的做法是用 ImageNet 预训练权重做迁移学习把前几十层当作通用的边缘、纹理、颜色提取器只把最后的全连接层换成自己的类别数import torch.nn as nn import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes8)迁移学习这里有一个策略分歧点冻结主干只训分类头还是全量微调。冻结主干适合每类样本只有几十张的情况此时梯度只更新最后一层收敛快且不容易过拟合每类样本超过 200 张时建议全量微调但把主干学习率设为分类头的十分之一比如分类头 1e-3、主干 1e-4。迁移学习在小数据集上的收益非常明显相同训练轮数下准确率通常比基线高 10 个百分点以上。选 ResNet18 而不是更深的 ResNet50理由是数据量。五十层网络的参数量是十八层的近三倍在几千张图的情况下多余容量只会放大过拟合。除非你的类别数超过二十类且每类样本过千否则 ResNet18 是性价比最高的起点。如果后续需要处理无人机航拍的视频帧序列再考虑换成 3D 卷积神经网络对时间维度建模那是后话静态图景分类阶段不需要。3.3 训练超参数与损失函数选择用一张表把这组超参数固定下来每次实验只改动其中一个变量才能判断某个改进是否真的有效超参数推荐取值说明优化器AdamW权重衰减处理得比 Adam 干净初始学习率3e-4微调/ 1e-3从头训练过大冲掉预训练特征学习率策略CosineAnnealingLR 到 1e-6比阶梯下降更平滑batch size32 或 64取决于显存从 32 起步训练轮数30 到 60配合早停patience5权重衰减1e-4抑制全连接层过拟合训练循环建议每个 epoch 记录训练损失、验证损失和验证准确率验证损失连续五个 epoch 不下降就恢复最优权重并停止。早停时机很关键村落图景数据量小验证损失往往在某个 epoch 后迅速反弹这是过拟合开始的信号不是继续训练能解决的。损失函数默认用交叉熵类别不均衡时配合权重调整具体在第 4 章展开。4. 小样本与类不均衡下的村落图景分类优化4.1 数据增强的合理范围村落图景照片有很强的场景属性拍摄时间、天气、器材都会引入分布偏移但这些偏移不改变类别本质。数据增强的作用就是让模型不依赖这些偏移来做判断。常用组合包括随机水平翻转、随机旋转、色彩抖动和随机裁剪参数要克制train_transform T.Compose([ T.RandomResizedCrop(224, scale(0.6, 1.0), ratio(0.8, 1.2)), T.RandomHorizontalFlip(p0.5), T.ColorJitter(brightness0.2, contrast0.2, saturation0.1), T.RandomRotation(degrees8), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意两个容易做过头的地方。RandomRotation 的角度不要超过 10 度传统村落建筑的竖直边界是强语义特征旋转太狠会让模型把「横着的墙」也当成正常样本反而丢掉真实世界的几何约束。ColorJitter 的 saturation 调到 0.1 就够夯土墙和青瓦的颜色本身就是分类线索过度改变色相等于人为抹掉有效特征。RandomResizedCrop 的 scale 下限设在 0.6低于这个值裁剪区域太小模型会看到局部纹理而非整体场景结构对「农田」和「草地」这类靠大尺度纹理区分的类别会造成灾难性影响。4.2 类别权重与 Focal Loss 处理样本不均采集村落图景时街巷和建筑的样本容易大量获得而祠堂、碉楼这类公共建筑样本稀少。直接用交叉熵损失模型会把多数类准确率刷得很高。最轻量的做法是在交叉熵里按样本数反比设置类别权重import torch import torch.nn as nn samples_per_class torch.tensor([850, 760, 210, 180, 320, 150, 90, 480]) total samples_per_class.sum().float() weights total / (len(samples_per_class) * samples_per_class.float()) criterion nn.CrossEntropyLoss(weightweights)权重公式是总样本数除以类别数与每类样本数的乘积结果就是样本越少的类别权重越大但整体权重均值保持在 1 附近不会过度放大噪声样本的贡献。如果加类别权重后少数类还是学不好再考虑 Focal Loss。Focal Loss 在交叉熵上乘一个调制因子让模型把注意力集中在置信度低的难样本上适用于少数类内部还有难易差异的情况。但对噪声大的数据集要小心Focal Loss 会放大脏标签的作用这时回归类别权重更安全。4.3 用混淆矩阵而不是准确率判断改进在类不均衡的数据上准确率是欺骗性最强的指标。80% 的准确率完全可能来自「把全部图片猜成建筑」这种无意义策略。每次实验结束都应该输出归一化混淆矩阵from sklearn.metrics import confusion_matrix, classification_report import numpy as np def evaluate(model, loader, device, class_names): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in loader: preds model(images.to(device)).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) cm_norm cm.astype(np.float32) / cm.sum(axis1, keepdimsTrue) print(classification_report(all_labels, all_preds, target_namesclass_names, digits3)) return cm_norm看混淆矩阵时重点看对角线之外的哪一格最大那说明两个类别在视觉上确实难以区分。村落图景里最常见的混淆对是「街巷」和「建筑立面」因为窄巷子两侧的墙面占比高模型很难判断到底该归哪类其次是「农田」和「草地」绿色色块在缩放后纹理细节丢失。发现混淆对之后优先检查是不是类别定义边界模糊而不是继续调模型结构。这个检查顺序能省下大量无效调参时间。5. 用 Grad-CAM 定位模型的误判来源最后一个实用技巧是用 Grad-CAM 可视化模型决策时的关注区域。传统村落图景分类里模型可能因为背景里的电线杆或临时停放的车辆做判断这在准确率上看不出来但部署到新村落时会导致批量误判。Grad-CAM 通过计算类别得分对最后一层卷积特征图的梯度得到每个空间位置的贡献权重叠到原图上就能看见模型到底在看哪里def grad_cam(model, input_tensor, target_class): model.eval() conv_out None def hook_fn(module, input, output): nonlocal conv_out conv_out output # 挂到最后一个卷积块的输出 last_conv model.layer4[-1].conv2 handle last_conv.register_forward_hook(hook_fn) logits model(input_tensor) score logits[0, target_class] model.zero_grad() score.backward(retain_graphTrue) handle.remove() grads last_conv.weight.grad # 演示用实际应取 conv_out 的梯度 # 正确做法在 hook 中对 conv_out 做 retain_grad() # backward 后读 conv_out.grad再按通道求均值 weights grads.mean(dim(2, 3), keepdimTrue) cam (weights * conv_out).sum(dim1, keepdimTrue) cam torch.relu(cam) cam cam / cam.max() return cam.squeeze().detach().cpu().numpy()注意这段代码为了展示结构做了简化实际取梯度时要对特征图而不是卷积核权重求梯度在 forward_hook 里对 conv_out 调用 retain_grad()backward 之后再读 conv_out.grad按通道做全局平均池化得到每个通道的权重。这个细节写错的话热力图会是错的而且不容易察觉。拿验证集里每个类别的错分样本逐张做 Grad-CAM叠加在原图上观察高亮区域。如果模型分类「祠堂」时高亮集中在屋檐轮廓说明学到了正确线索如果高亮点零散分布在天空和背景树木上说明模型在靠背景色偏做判断这类样本通常带有相同的拍摄背景倾向修复办法不是加数据而是按背景多样性重新平衡取样。这类项目最后还有一件值得做的事把验证集的预测错误按类别和村落分组汇总输出成 CSV交给复核人员。好处是错误类型可以追溯是标注问题、类别定义问题还是模型容量问题在表格里一眼能分清楚。等数据规模扩大到村落视频逐帧分类时同样的分析流程可以直接迁移把帧序列交给 3D 卷积网络或加上时序建模的 Transformer 分支Grad-CAM 这一层的特征可视化依旧是定位问题的核心手段。本文还有配套的精品资源点击获取