news 2026/9/28 11:05:19

卫星云图识别:从CSV数据到PyTorch CNN全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
卫星云图识别:从CSV数据到PyTorch CNN全流程实战

简介:面向计算机视觉课程大作业与毕业设计场景,这份资料包围绕卫星云层图像的理解与识别任务,提供了完整可运行的 Python 源码、文档说明、报告 PPT 与实验报告。项目代码经测试可正常执行,覆盖数据预处理、模型训练与结果分析等环节,适合计科、人工智能、通信工程等专业学生在课程设计、项目初期立项或答辩演示中参考,也便于在已有代码基础上进行二次扩展功能。包内共 140 个文件,以 70 个 Python 源码文件为主,附有编译生成的 pyc、Shell 脚本、CSV 数据表、YAML 配置文件、PNG 结果图以及 README、实验报告 PDF 和 PPTX 演示文稿,整体约 62.3 MB。文件组织方式清晰,便于按代码、数据、配置和说明文档分模块查阅,能节省整理资料的时间。目前已有 239 人学习下载。从内容预览看,内含训练与测试的 CSV 数据、多组模型测试结果、实验报告 PDF 和结果图片,读者可直接获取项目全貌、复现识别流程,并参考其文档与答辩材料完善自己的大作业或毕设展示。

1. 卫星云图识别大作业:从CSV到可跑的CNN全流程

拿到“计算机视觉大作业:卫星云层图像的理解与识别”这套资源时,绝大多数人的第一反应是找现成的图片文件夹,结果打开train_700.ver0.csv才发现数据全在文本表格里。这不是数据坏了,而是小规模课程数据集常用的保存方式:700张图,每张图按像素拉平成一行,标签放在第一列。整套资源包含训练集、四个测试CSV、Python源码、实验报告PDF和汇报PPT,目标是让一个没有完整跑过深度学习流程的人,在一天内把卫星云图分类从数据加载做到测试评估。它适合正在做计算机视觉课设、毕设初期演示,或者想用一份规范实验报告模板的同学。接下来的章节,我按自己拆这套项目时的顺序来讲:先处理数据,再选模型,然后训练、评估,最后是几个必须避开的坑。

2. 先把数据集吃透:CSV图像读取、还原与划分的三个实操点

2.1 为什么用CSV装图像

卫星云图大作业选CSV而不是图片文件夹,我一开始也疑惑。后来拆完发现,CSV对小数据集有实打实的好处。第一是版本管理方便,700个样本的像素改动在git diff里一目了然,这对课设答辩时讲“我改过什么”很有价值;第二是pandas直接能读,做类别分布统计、像素均值方差分析不用另外写IO逻辑;第三是省去了图片路径在不同操作系统上的兼容问题,Windows和Linux下不会出现路径分隔符翻车。当然它的边界很明显,CSV按文本存像素,一张512×512的图拉平就是26万列,文件会迅速膨胀,所以这种格式只适合几百到几千张图的小样本,超出这个量级还是老老实实用TFRecord或者按文件夹组织图片的ImageFolder方案。

2.2 读取CSV并还原成图像张量

常见的做法是先读进来看看shape,再决定怎么reshape。我一般用pandas读入,打印前几行确认标签列:

import pandas as pd import numpy as np df = pd.read_csv('train_700.ver0.csv') print('shape:', df.shape) print(df.head(2))

第一次打印shape时要注意两个数字:行数是样本数700,列数减1就是单张图像的像素总数。如果列数是4097,说明每张图4096个像素,开方正是64,那就是单通道64×64;如果不能整除,就得考虑是不是RGB三通道。这一步判断决定了后面的reshape参数,值得花30秒确认。

接下来把标签和像素拆开。这里有个细节:读入像素要用float32,不要用int,因为CSV里有些值可能带小数,用int会直接截断,云层灰度细节会丢得干干净净。

labels = df.iloc[:, 0].values.astype(np.int64) pixels = df.iloc[:, 1:].values.astype(np.float32) img_size = (64, 64)

然后写一个标准Dataset类,把一行像素还原成图像张量:

import torch from torch.utils.data import Dataset class CloudDataset(Dataset): def __init__(self, pixels, labels, img_size=(64, 64)): self.pixels = pixels self.labels = labels self.img_size = img_size def __len__(self): return len(self.labels) def __getitem__(self, idx): img = self.pixels[idx].reshape(1, *self.img_size) img = torch.from_numpy(img) / 255.0 label = torch.tensor(self.labels[idx], dtype=torch.long) return img, label

这段代码的逻辑说明:reshape成(1, 64, 64)是PyTorch的NCHW约定,单通道要放最前面;除以255把像素归一到[0, 1]区间,让反向传播更稳定。为什么不在Dataset里做标准化而要放到后面单独做?因为标准化用的均值和标准差只能从训练集算,在Dataset里直接套用全局数据,极容易污染验证和测试数据。如果CSV列数不是4096,把img_size改成对应分辨率即可。这一节里我踩过的坑是把图像尺寸写死,换数据集时报维度不匹配,所以建议在类外面定义img_size并打印验证。

2.3 训练集与验证集划分:标准化必须在划分之后

数据加载类写完后不要急着训练,先做train/val划分。我一般用sklearn的train_test_split,并且一定要开stratify,保证云层每个类别在训练集和验证集里的比例一致:

from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val = train_test_split( pixels, labels, test_size=0.2, stratify=labels, random_state=42) mean = X_train.mean() std = X_train.std() X_train = (X_train - mean) / (std + 1e-8) X_val = (X_val - mean) / (std + 1e-8) train_dataset = CloudDataset(X_train, y_train) val_dataset = CloudDataset(X_val, y_val)

这里有两个关键点:第一,mean和std只能用X_train算,哪怕X_val的信息就摆在手边也不能合起来算,否则验证集的分布信息渗进特征标准化环节,属于典型的数据泄露,后面评估结果会虚高;第二,标准差分母加1e-8是为了防止像素值全相同导致除零。划分完后打印一下每个类别的样本数量,确认没有哪个类只剩个位数的样本,不然训练阶段模型会直接忽略它。至于random_state,我习惯固定成42,这样多次复现结果完全一致,报告里写“随机种子42”也显得严谨,别人照着跑能拿到同一份数字。

3. 训练一个能用的CNN:模型结构、训练参数与日志记录

3.1 CNN为什么适合卫星云图

卫星云图的识别本质是纹理分类:云层的厚度、边界、卷云和积云在局部区域内有明显的图案差异。CNN通过卷积核在整张图上滑动提取局部纹理,再用池化逐步聚合,天然具备平移不变性,云在画面偏左还是偏右不影响分类结果。如果换成全连接网络,64×64的图输入层就有4096个节点,第一层全连接的权重就是百万级,在700张训练图上是灾难。所以在这个任务里,两层卷积加两层全连接是性价比很高的结构:卷积负责提取特征,全连接负责做最终分类。自己搭而不是一上来就上ResNet,是因为课设场景里老师往往要求核心模型可以讲清楚前向流程,而且这么大的数据量用深层网络反而是负优化。

3.2 从零搭一个轻量CNN

import torch.nn as nn import torch.nn.functional as F class CloudCNN(nn.Module): def __init__(self, num_classes=2): super().__init__() self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2) self.fc1 = nn.Linear(32 * 16 * 16, 64) self.fc2 = nn.Linear(64, num_classes) self.dropout = nn.Dropout(0.3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) # 64x64 -> 32x32 x = self.pool(F.relu(self.conv2(x))) # 32x32 -> 16x16 x = x.view(x.size(0), -1) x = self.dropout(F.relu(self.fc1(x))) return self.fc2(x)

逻辑说明:conv1从1通道变成16通道,padding=1保持尺寸不变,经过第一层池化变成32×32;conv2扩到32通道,再池化成16×16,所以全连接层的输入维度是32×16×16=8192。加Dropout是为后面过拟合留的缓冲:小数据集上模型记忆训练数据太容易,0.3的丢弃比例通常够用。注释里标出了每一步的空间尺寸变化,这是答辩时老师必问的东西——如果讲不清特征图怎么从64变到16,报告会扣分。建议第一轮跑通后打印model摘要,看每层输出形状是不是和预期一致,再进完整训练。

3.3 训练参数怎么定

训练参数我用下表给出推荐值,这样报告里可以直接引用:

参数推荐值说明
batch_size32700张图太小,64会让梯度震荡变小
epochs30~50小数据集上过50轮后验证loss通常开始回升
optimizerAdam自适应学习率,省去手动调整的麻烦
lr1e-3Adam下1e-3是稳妥起点,跑不稳再降到3e-4
schedulerCosineAnnealingLR让学习率在训练后期平滑衰减
lossCrossEntropyLossPyTorch里该损失自带Softmax

损失函数这块有个初学者常走的弯路:在模型forward里先过一遍Softmax,又在CrossEntropyLoss里过一次,两个Softmax叠在一起反而让梯度变小。CrossEntropyLoss已经把Softmax和交叉熵合并了,所以模型的最后一层就输出原始logits,不要额外加激活函数。

3.4 训练循环与日志记录

训练循环我习惯封装成一个函数,把每个epoch的loss和acc存下来:

def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0.0, 0, 0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() out = model(imgs) loss = criterion(out, labels) loss.backward() optimizer.step() total_loss += loss.item() * imgs.size(0) correct += (out.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total

这个函数的逻辑是:每批数据先清零梯度,前向得到out,和目标标签算交叉熵loss,反向传播后更新参数。计算正确率时用argmax(1)取每个样本得分最高的类别,再跟labels比较。用out.argmax(1)而不是torch.max(out, 1)的原因是前者直接返回索引,代码意图清晰。调用时每轮验证集也跑一遍,把结果追加到日志列表:

train_log, val_log = [], [] best_acc = 0.0 for epoch in range(30): tr_loss, tr_acc = train_one_epoch(model, train_loader, optimizer, criterion, device) va_loss, va_acc = evaluate(model, val_loader, criterion, device) train_log.append((tr_loss, tr_acc)) val_log.append((va_loss, va_acc)) if va_acc > best_acc: best_acc = va_acc torch.save(model.state_dict(), 'best_model.pth') print(f'epoch {epoch:02d} | train {tr_loss:.4f}/{tr_acc:.4f} | val {va_loss:.4f}/{va_acc:.4f}')

设备管理这里注意,在一开始就把device写成torch.device('cuda' if torch.cuda.is_available() else 'cpu'),千万别在脚本里到处写死cuda,否则在无GPU的机器上整个脚本直接崩掉。保存模型时只存state_dict,不存整个model对象,这样换机器加载方便,也不会因为类定义位置不同而反序列化失败。

4. 测试集评估与实验报告:四个测试文件怎么用、指标怎么看

4.1 四个测试CSV的关系

资源里同时出现了model_test_1.csv、model_test_3.csv、model_test_4.csv和test.csv,第一次看到时我也愣了一下。看名字,前三个更可能是不同实验阶段(第1轮、第3轮、第4轮)留出的测试分片,test.csv是最终统一的测试集。怎么验证这个判断?简单办法是分别读进来,看行数和列数是否一致,如果数据来源相同,特征分布应当接近。我倾向于把它们当四份独立测试集分别跑预测,再对比结果;如果评分环节只要求一个数字,就以test.csv为准,其他三个作为调试参考。这样做还有一个额外的好处:答辩时能拿“多测试集交叉验证”当作加分项,说明你考虑了模型的稳定性。

4.2 预测与评估指标

加载训练好的权重,在测试CSV上做预测:

def predict_csv(model, csv_path, mean, std, img_size=(64, 64), device='cpu'): df = pd.read_csv(csv_path) pixels = df.iloc[:, 1:].values.astype(np.float32) pixels = (pixels - mean) / (std + 1e-8) imgs = torch.from_numpy(pixels).view(-1, 1, *img_size) model.eval() with torch.no_grad(): preds = model(imgs.to(device)).argmax(1).cpu().numpy() return preds

注意这里标准化用了训练集算好的mean和std,原因和验证集一样:不能拿测试集自己的均值来标准化,否则等价于把测试集的分布信息提前暴露给模型。跑完后打印预测结果的类别分布,如果某一类占比异常高,说明模型预测有偏,要回到混淆矩阵排查。

评估不能只看准确率。卫星云图类别常有不均衡,某一类占80%时,全猜这一类的准确率也有80%,看起来很漂亮但模型没有泛化能力。我一般会打印混淆矩阵和F1分数:

from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_val, val_preds, digits=3)) print(confusion_matrix(y_val, val_preds))

classification_report会按类别给出precision、recall、f1-score,哪个类别拖后腿一目了然。判断模型是否可用时我的标准是:多数类准确率高不算达标,少数类的recall必须也有实际值,否则答辩时老师挑一个样本追问就能难住你。

指标关注点场景
Accuracy总体正确比例类别均衡时参考
Precision预测为某类的可信度误报成本高的场景
Recall某类能不能找全不均衡数据必看
F1两者的调和平均综合排名用

4.3 实验报告的组织

资源里的实验报告PDF是一个现成的写作模板,我拆下来觉得它的结构值得复用。这类课设报告的套路是:实验目的与数据集介绍、预处理流程、模型设计、实验参数、结果分析与对比。最关键的是不能只贴训练好的最终结果,得有中间对照,比如把只用两层卷积和三层卷积都跑一遍,在报告里放两个准确率,再解释为什么在这个数据量下浅层更稳。报告里的图表无非是训练曲线、混淆矩阵、几张预测结果的样例可视化,用matplotlib就能出,重点是每张图下面必须有结论性说明,而不是只写“如图所示”。这份报告PDF正好是个结构参考,比从零排版省事得多。

5. 卫星云图识别避坑指南:五个翻车现场的排查记录

这一章把拆这套资源时实际遇到的坑按“现象→原因→解决”整理出来,每一条都是先有现象再有结论,可以按编号快速对照。

5.1 数据泄露:验证集准确率虚高的真相

现象:训练集、验证集准确率都到0.98,换到test.csv只有0.7,差距大得不正常。原因:数据标准化时手滑用了全部数据的mean/std,验证集和测试集的分布信息已经参与计算,模型在验证集上被“剧透”了。解决:严格按划分后的数据分别计算,先train_test_split,再取X_train.mean()和X_train.std()。从那以后我每次写预处理都在代码里加注释,标注mean和std的来源,防止回头自己都忘了数据是怎么算的。

5.2 类别不均衡让准确率失真

现象:训练日志显示准确率一直上升,但查看混淆矩阵发现有一整类几乎没预测对。原因:云层数据里某类占绝大多数,模型把所有样本都推向多数类就能拿到高准确率,少数类的梯度信号被淹没。解决:打印每个类的样本数,如果差距超过10倍,给DataLoader加WeightedRandomSampler,让少数类样本在每轮中被抽到的概率提高;效果不理想时再给损失函数加class_weight作为补充。

5.3 小数据集上的过拟合

现象:训练loss降到0.05,验证loss反而从0.3涨到0.8,训练曲线后期完全背离。原因:两层卷积加两层全连接对700张图来说容量已经偏大,epoch跑到后期模型开始背训练样本而不是学泛化特征。解决:把Dropout从0.3提到0.5,减少conv1的通道数(比如从16降到8),同时监控验证loss,连续5轮不降就early stop,保存历史最优权重而不是最后一轮权重。

5.4 设备写死导致换机器直接崩

现象:在自己笔记本上跑得好好的脚本,拿到有GPU的机器上反而报错;或者反过来,GPU环境下写的代码在CPU机器上直接RuntimeError。原因:代码里出现了imgs.cuda()这类写死GPU的调用,环境一换就失效。解决:所有张量和模型统一走device = torch.device('cuda' if torch.cuda.is_available() else 'cpu'),数据加载后执行.to(device)。习惯上我在脚本最开头集中定义device,后面一律引用变量,不要再出现device字样以外的硬编码。

5.5 CSV像素值精度丢失与噪声

现象:训练loss下降奇慢,把预测错误的图片画出来发现全是颗粒状噪点,云层纹理完全看不清。原因:读取像素时用了int类型,CSV里的小数被截断;有些列本身存在缺失值,直接读成了0。解决:读入用astype(np.float32),再画一张图检查像素分布是否连续,直方图集中在几个孤立峰值就说明精度丢了。缺失值先看df.isna().sum(),把带有缺失值的行删掉或按列均值填充,别让0值混进正常像素。

6. 迁移学习与可视化验证:不做重复实验的进阶做法

6.1 把ResNet18接在云图数据上

如果老师允许用预训练权重,最省事的进阶路线是用torchvision自带的ResNet18做迁移学习。卫星云图和ImageNet的纹理结构不完全一样,但底层边缘、颜色过渡这些特征是可以复用的。常见做法是冻结backbone,只替换最后的全连接层:

import torchvision.models as models model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) for param in model.parameters(): param.requires_grad = False model.fc = nn.Linear(512, num_classes)

这段代码逻辑:weights=models.ResNet18_Weights.DEFAULT会加载ImageNet预训练权重;冻结全部参数后,只有最后接入的Linear层可训练,前面多层学到的通用特征不需要再调整。这里需要注意resnet18默认输入是3通道,而CSV里只有单通道灰度。两种处理方式:一是把单通道复制三次变成3通道,二是修改backbone第一个卷积层的输入通道为1并加载权重时删掉对应参数。第一种稳妥,第二种省内存但要额外处理权重名,新手建议直接用第一种。

6.2 用曲线和样例图验证结论

训练完成后,把train_log里的loss和验证准确率画出来,两条曲线一起看。训练损失还在下降但验证曲线开始抬升,是过拟合的明确信号;损失曲线出现锯齿状波动,说明学习率偏大,把lr降到3e-4重跑一轮。再把混淆矩阵画成热力图,哪些类互相搞混一目了然,比如卷云和薄云经常混在一起,可以在报告里写成“两类特征相近,需要增加纹理方向的增强”。最后随机挑几张测试图片,把预测标签和置信度标在图上,这是答辩时最直观的展示素材。

从那以后,我每次拿到这类课设源码,都先做一个单样本前向确认输入维度和数据范围,画一张样本图,再跑完整训练循环,节省下来的排错时间比这点步骤多得多。这套资源从数据到报告模板都是现成的,下载后对照README把文件放到位,按我上面的顺序跑一遍就能出完整结果。希望帮到你。

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

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

别只盯着被黑!图解步骤搞定WordPress点赞打赏,3招防挂马

别只盯着被黑!图解步骤搞定WordPress点赞打赏,3招防挂马 昨晚凌晨三点,手机突然弹出一条报警短信:“您的网站检测到异常外链”。我揉着惺忪睡眼登上服务器后台,瞬间头皮发麻。首页底部赫然挂满了赌博网站的链接,后台多出几个陌生的管理员账号。那一刻,那种 网站被黑挂马不知道怎么办…

作者头像 李华
网站建设 2026/9/28 11:04:34

2026最新青海网站建设有哪些坑?告别模板丑站实战指南

2026最新青海网站建设有哪些坑?告别模板丑站实战指南 还在为青海本地企业官网找不回来访用户发愁?看着那些千篇一律、配色刺眼、加载慢如蜗牛的模板网站,是不是觉得既丢面子又伤转化?很多老板在搜索“青海网站建设有哪些”靠谱方案时,最头疼的就是:怎么避开那些低价陷阱,搞出一个既专业又留得住人的网站。…

作者头像 李华
网站建设 2026/9/28 11:04:30

建站用wordpress防黑指南与源码下载避坑

建站用wordpress防黑指南与源码下载避坑 昨晚后台突然收到监控报警,网站首页被植入了博彩广告代码,打开浏览器一片乱码。很多老板第一反应是慌,不知道网站被黑挂马怎么办,甚至不敢重启服务器怕数据丢失。别急,这种“被黑”大多是因为你在 源码下载…

作者头像 李华
网站建设 2026/9/28 11:04:20

网页设计职位新手入门指南,3个步骤搞定域名服务器部署

网页设计职位新手入门指南,3个步骤搞定域名服务器部署 改个需求建站公司拖一周,这种经历谁摊上谁头疼。很多老板找外包建站,合同签得漂亮,真到上线后想改个文案、换个图,对方要么装死,要么说排期满了,这一拖就是好几天,生意都耽误了。其实,如果你懂点基础技术,自己手里攥着服务器和域名的控制权,很多小改动自己…

作者头像 李华
网站建设 2026/9/28 11:03:53

搞定wordpressnginx乱码,从零搭建网站不再头疼

搞定wordpressnginx乱码,从零搭建网站不再头疼 网站做好了没人访问,是不是特别糟心?很多老板以为只要把代码写完、域名绑上,流量就会滚滚而来。现实往往很骨感,打开网页发现中文全变成“???”或者一堆方框,客户看一眼就关掉,哪来的转化率?…

作者头像 李华
网站建设 2026/9/28 11:03:52

3步搞定wordpress如何导出数据库,亲测免费工具最稳

3步搞定wordpress如何导出数据库,亲测免费工具最稳 自己不会代码想做网站,最怕的就是数据丢在服务器里拿不出来。很多人卡在“wordpress如何导出数据库”这一步,明明后台有按钮,点下去却报错,或者导出来的文件打不开。别慌,这行干得久的人都知道, 免费工具…

作者头像 李华