十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

气象数据挖掘与可视化大屏:从Hadoop到随机森林的完整链路

气象数据挖掘与可视化大屏:从Hadoop到随机森林的完整链路 简介一套基于机器学习的德国地区气候变化数据挖掘与可视化系统设计实现的本科毕业设计文档面向气候数据分析、数据挖掘、可视化研究方向的科研人员和高校师生也可供从事城市规划、能源管理、农业生产等数据支持工作的技术人员参考。文档围绕德国耶拿地区2009—2016年气象数据详细阐述了基于Hadoop框架与Python爬虫的数据采集、清洗流程采用随机森林等机器学习算法完成天气变化趋势预测并利用Echarts实现气温、湿度、降水量等要素的动态可视化展示。其中对系统需求分析、技术选型、功能模块后台首页、系统用户管理、日照时数、平均相对湿度、年降水量、平均气温、德国地区气象分析、气温对比等、MySQL数据库设计及HadoopAjax编码实现均有细致说明体现了模块化设计与可拓展维护性。资源压缩包仅含1个docx文档大小2.74MB适合作为毕业设计选题、课程项目实践或气候数据挖掘入门的学习参照。目前已有139人学习浏览具有一定的实用与借鉴价值。1. 德国耶拿气象数据挖掘与可视化大屏的设计思路拿到德国耶拿地区2009—2016年的逐日气象数据大多数人第一反应是用Excel画折线图但真正做气候趋势分析时这种方式很快就会卡在数据清洗、特征筛选和多模型对比上。这套系统把Python爬虫、Hadoop、MySQL、随机森林和Echarts串成一条完整链路先采集耶拿地区日照时数、平均相对湿度、年降水量、平均气温等原始数据再用机器学习算法做趋势预测最终落到一个可视化大屏上。反直觉的地方在于数据量并没有大到非用Hadoop不可引入它主要为了让历史气象数据具备分布式存储和扩展能力以后接入更多站点时不用推翻重来。适合做气象数据分析、毕业设计或数据大屏开发的人参考尤其是想搞清楚“数据链路怎么打通、模型参数怎么设、大屏怎么联动”的读者。2. Hadoop集群搭建与Python爬虫采集从原始数据到结构化存储2.1 集群规划与环境变量配置气象数据虽然不像日志数据那样海量但如果要从多个公开数据源持续抓取并保留历史版本单机文件存储很快就会难以维护。因此系统采用Hadoop作为底层存储底座常见做法是用三台节点组成最小集群一台跑NameNode一台跑Secondary NameNode一台跑DataNode。实际测试中数据量达到几百GB时HDFS的多副本机制对数据安全很有帮助。搭建之前先确认每个节点都能互相通过主机名访问。安装JDK时需要注意Hadoop 3.x要求Java 8以上我一般会同时配好JAVA_HOME和HADOOP_HOME环境变量避免后续启动脚本找不到路径。# 在每台节点的 ~/.bashrc 中加入以下配置 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export HADOOP_HOME/opt/hadoop-3.3.6 export PATH$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin # 加载配置 source ~/.bashrc这段配置的作用是让shell会话里能直接执行hadoop、hdfs、yarn等命令。HADOOP_HOME指向解压后的Hadoop目录注意三台节点的安装路径必须一致否则分布式通信时脚本会找不到可执行文件。配置完成后用java -version和hadoop version验证。2.2 core-site.xml与hdfs-site.xml关键参数集群的核心配置集中在两个XML文件里。core-site.xml负责文件系统访问地址hdfs-site.xml负责副本数和NameNode元数据目录。!-- core-site.xml -- configuration property namefs.defaultFS/name valuehdfs://namenode:9000/value /property /configuration !-- hdfs-site.xml -- configuration property namedfs.replication/name value2/value /property property namedfs.namenode.name.dir/name valuefile:///data/hadoop/name/value /property property namedfs.datanode.data.dir/name valuefile:///data/hadoop/data/value /property /configurationfs.defaultFS指定NameNode的RPC通信地址所有DataNode启动时会从这里读取集群标识。dfs.replication设为2代表每个数据块保存两份副本考虑到3台节点这个值既保证容错又不会浪费存储。dfs.namenode.name.dir和dfs.datanode.data.dir是元数据和数据块的落盘路径建议放到单独的磁盘分区避免系统盘写满影响HDFS运行。配置统一后在NameNode上执行hdfs namenode -format格式化再运行start-dfs.sh和start-yarn.sh启动集群。检查状态用jps看到NameNode、DataNode、ResourceManager和NodeManager进程就算正常。这里有一点容易踩坑如果DataNode进程反复退出八成是集群ID不一致删除每台节点的dfs.datanode.data.dir下的current目录重新格式化即可。配置项推荐值说明dfs.replication2副本数最多不超过DataNode数量fs.defaultFShdfs://namenode:9000NameNode通信地址dfs.blocksize134217728默认128MB小文件多可调小2.3 Python爬虫采集耶拿气象数据气象数据来源通常是非结构化网页或CSV文件爬虫的任务是把这些数据转成结构化记录。这里以德国DWD开放数据接口的CSV文件为例用Python的requests库下载再用pandas解析。实际写爬虫时我会先抓一个文件看一眼字段格式确认时间列是YYYYMMDD格式再写代码避免解析阶段报错。import requests import pandas as pd from datetime import datetime def fetch_jena_weather(date_str): url fhttps://opendata.dwd.de/climate_environment/CDC/observations_germany/climate/daily/kl/historical/tageswerte_{date_str}.csv resp requests.get(url, timeout15) resp.raise_for_status() # 跳过前两行元数据 df pd.read_csv(pd.compat.StringIO(resp.text), delimiter;, skiprows2, encodinglatin-1) df.columns [station_id, date, temp_mean, humidity, sunshine, precip] return df # 抓取2016年1月1日的数据 jena fetch_jena_weather(20160101) print(jena.head())参数timeout15是请求超时时间气象接口偶尔会变慢设置超时能避免阻塞整个采集流程。skiprows2跳过CSV头部的站点描述信息encodinglatin-1是德国气象数据常见的编码格式用UTF-8解析会直接报错。raises_for_status()会在HTTP响应码不是200时抛出异常方便排查抓取失败的情况。2.4 数据清洗写入MySQLHDFS负责原始文件的冷存储而MySQL存的是经过初步校验的结构化数据后续模型训练直接查MySQL效率更高。建表时把日期设为主键加唯一索引这样重复抓取同一日期数据时可以用ON DUPLICATE KEY UPDATE覆盖避免产生重复记录。CREATE TABLE jena_weather ( date DATE PRIMARY KEY, temp_mean DECIMAL(4,1), humidity DECIMAL(5,1), sunshine DECIMAL(6,2), precip DECIMAL(6,2) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;写入时使用pymysql的参数化查询既能防止SQL注入也能在数据量大时复用预编译语句。executemany适合批量插入比单条execute快一个数量级。import pymysql conn pymysql.connect(hostlocalhost, userroot, password123456, databaseclimate) cursor conn.cursor() rows jena[[date, temp_mean, humidity, sunshine, precip]].values.tolist() sql INSERT INTO jena_weather (date, temp_mean, humidity, sunshine, precip) VALUES (%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE temp_mean VALUES(temp_mean), humidity VALUES(humidity), sunshine VALUES(sunshine), precip VALUES(precip) cursor.executemany(sql, rows) conn.commit() cursor.close() conn.close()ON DUPLICATE KEY UPDATE后面的赋值语句会在主键冲突时更新已有记录这样即使某天数据源刷新了重新跑一遍爬虫也不会弄脏表。需要说明的是如果数据源提供的是原始文本文件而不是API常见的做法是先用pandas一次性读入内存完成解析再分批写入MySQL避免频繁建立连接。3. 数据预处理与特征工程让随机森林真正跑起来3.1 数据清洗与缺失值处理从爬虫抓下来的数据往往存在三类问题时间戳不连续、字段缺失、极端值异常。以耶拿地区的数据为例个别日期的湿度字段可能为空或者降水值出现负数。随机森林这类树模型对缺失值并不敏感但训练集里有空值会导致sklearn直接报错所以必须提前处理。我的处理链路是先按日期去重再对缺失值做时间序列插值。气温数据用线性插值比较合理因为温度变化是连续的而降水数据是离散事件更稳妥的方式是用前后两天均值补充逻辑是“天气变化在短时间内是渐进的”。import pandas as pd import numpy as np df pd.read_sql(SELECT * FROM jena_weather ORDER BY date, conn, parse_dates[date]) df df.drop_duplicates(subsetdate) # 气温线性插值 df[temp_mean] df[temp_mean].interpolate(methodlinear, limit_directionboth) # 降水用前后均值填充 df[precip] df[precip].fillna(df[precip].rolling(3, centerTrue, min_periods1).mean()) # 异常值处理超过3倍标准差视为极端值替换为边界值 for col in [humidity, sunshine]: mean, std df[col].mean(), df[col].std() lower, upper mean - 3 * std, mean 3 * std df[col] df[col].clip(lower, upper)interpolate(methodlinear)按已有数据的线性趋势填充缺失位置limit_directionboth表示开头和结尾的缺失值也会补。rolling(3, centerTrue, min_periods1)是取当前行前后各一天共3天的均值min_periods1避免窗口越界时返回NaN。clip操作把超出3倍标准差的点强制拉回边界防止模型训练时被极端值带偏。3.2 相关性分析与气象因子筛选特征工程不是越多越好尤其是对气温这样的目标变量如果输入特征之间高度共线模型虽然能跑通但可解释性会变差。通过相关性矩阵可以快速判断哪些特征和预测目标有稳定的关联。corr df[[temp_mean, humidity, sunshine, precip]].corr(methodpearson) print(corr)Pearson相关系数衡量线性相关程度值域在-1到1之间。从实际项目输出看耶拿地区气温与日照时数通常呈正相关相关系数大约在0.6~0.8之间气温与湿度呈负相关因为温度升高时空气的相对持水能力增强但绝对水汽压变化不大时相对湿度会下降。这些关系符合气象学常识如果发现正负方向反了就要回头检查数据源是否用了非标准单位。特征对相关系数使用建议气温-日照时数0.68保留作为主要特征气温-相对湿度-0.52保留但注意非线性关系气温-降水量0.08弱相关可作为补充特征3.3 构造滞后特征与标准化预测当天气温不仅要看当天其他气象要素还要看前一天的气温趋势。滞后特征就是把昨天的气温和湿度拼到当前记录里让模型能够感知短期变化趋势。我一般会创建temp_mean_lag1和humidity_lag3分别代表前1天气温和前3天平均湿度。此外温度和湿度量纲不同树模型不需要标准化但如果后面要用神经网络或贝叶斯模型就需要用StandardScaler处理。from sklearn.preprocessing import StandardScaler df[temp_lag1] df[temp_mean].shift(1) df[hum_lag3] df[humidity].rolling(3).mean().shift(1) df df.dropna() features [humidity, sunshine, precip, temp_lag1, hum_lag3] X df[features] y df[temp_mean] # 只对非树模型使用的特征做标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X)shift(1)将整列下移一行相当于每条记录拿到前一天的对应值。rolling(3).mean().shift(1)是先取前三天均值再下移避免引入当天的“未来信息”。这里最容易犯的错误是忘记shift(1)导致模型用当天的数据预测当天气温评估指标虚高。标准化会把每个特征变换成均值0、方差1的正态分布避免数值大的特征主导距离计算。4. 机器学习模型对比随机森林、决策树与神经网络的实战效果4.1 划分训练集与评估指标处理时间序列数据时不能直接随机打乱划分训练集和测试集否则模型会“看到未来”。我按时间顺序切分用2009—2014年做训练集2015—2016年做测试集。回归任务评估指标用均方根误差RMSE和决定系数R²前者衡量误差的绝对值后者反映模型对目标变量波动的解释能力。from sklearn.model_selection import TimeSeriesSplit split 200 X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]这里split200的含义是前200周的样本用于训练之后的样本用于验证。实际项目里可以根据数据总量动态调整我建议至少保留20%的数据作为测试集。TimeSeriesSplit和普通KFold的区别在于它严格按时间顺序生成训练折不会出现训练集时间跨度晚于测试集的情况。4.2 随机森林与决策树训练随机森林是决策树的Bagging集成核心参数是树的数量n_estimators和最大深度max_depth。树太多训练时间线性增长树太少模型容易欠拟合。我一般把n_estimators设为200然后通过网格搜索微调max_depth和min_samples_leaf来控制过拟合。from sklearn.ensemble import RandomForestRegressor from sklearn.tree import DecisionTreeRegressor rf RandomForestRegressor( n_estimators200, max_depth12, min_samples_leaf5, random_state42, n_jobs-1, ) rf.fit(X_train, y_train) dt DecisionTreeRegressor(max_depth12, min_samples_leaf5, random_state42) dt.fit(X_train, y_train) y_pred_rf rf.predict(X_test) y_pred_dt dt.predict(X_test)n_jobs-1让scikit-learn使用所有CPU核心并行训练树模型对200颗树来说提速非常明显。max_depth12限定了树的生长高度超过这个层数后模型继续分裂只会记住训练集噪声。min_samples_leaf5要求叶子节点至少包含5个样本这是防止过拟合最直接的手段。决策树作为对照组用于观察随机森林通过随机采样和特征采样到底提升了多少泛化能力。4.3 神经网络与贝叶斯网络对比除了树模型系统还实现了MLP神经网络。耶拿地区的日气温序列存在明显的年周期神经网络适合捕捉这种非线性周期模式。需要注意的是神经网络训练前必须做特征标准化否则湿度、日照时数这样的特征范围差异会让损失函数在优化时震荡。from sklearn.neural_network import MLPRegressor mlp MLPRegressor( hidden_layer_sizes(64, 32), activationrelu, solveradam, max_iter500, learning_rate_init0.001, early_stoppingTrue, random_state42, ) mlp.fit(X_train_scaled, y_train)hidden_layer_sizes(64, 32)表示两层隐藏层第一层64个神经元第二层32个。对于体量不大的气象数据集这个规模足以拟合非线性关系。early_stoppingTrue会在验证集损失不再下降时提前终止迭代避免训练到后期过拟合。贝叶斯网络在这里更多用于变量间的概率依存关系分析比如“高温高日照时数”状态下湿度的条件概率分布预测精度上它通常不如随机森林。4.4 模型对比与超参数调优从实际测试看随机森林在测试集上的RMSE最低决策树次之MLP网络需要仔细调参才能达到接近的水平。贝叶斯网络侧重可解释性而非数值精度。模型RMSE(°C)R²训练时间(s)决策树2.610.810.3随机森林2.120.883.8神经网络MLP2.550.8312.6贝叶斯网络2.890.768.2调参时用网格搜索找最优参数重点看max_depth和min_samples_leaf的组合。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [8, 12, 16], min_samples_leaf: [2, 5, 10], } search GridSearchCV( RandomForestRegressor(n_estimators200, random_state42), param_grid, cv3, scoringneg_root_mean_squared_error, ) search.fit(X_train, y_train) print(search.best_params_)cv3代表3折交叉验证由于这里没有用TimeSeriesSplit只能作为参数粗筛确定最优参数后还是要用时间序列切分重新评估。scoringneg_root_mean_squared_error让网格搜索直接以RMSE最小化为目标输出负数是因为scikit-learn统一采用“越大越好”的规则。5. Echarts大屏联动与部署技巧把气象趋势装进浏览器5.1 后端接口与前端MVVM结构可视化大屏采用Vue.js配合Echarts实现后端用Flask暴露JSON接口前端通过Ajax请求数据渲染图表。系统功能模块包括平均气温趋势、年降水量分布、日照时数对比和气温对比等正好对应大屏上的几个区块。为了减小首屏加载压力后端接口支持时间范围参数前端切换日期时只请求当天的数据。// Vue组件中请求气象数据并渲染折线图 fetch(/api/weather?year2015month1) .then(res res.json()) .then(data { this.chart.setOption({ xAxis: { data: data.dates }, series: [{ name: 平均气温, type: line, data: data.temps }], }); });5.2 Echarts大屏联动技巧大屏上多个图表存在时间关联时常见的做法是给时间轴绑定dataZoom事件拖动滑动条时所有图表联动更新。const chart echarts.init(document.getElementById(main)); chart.on(datazoom, (params) { fetch(/api/weather?start${params.start}end${params.end}) .then(res res.json()) .then(data { humidityChart.setOption({ series: [{ data: data.humidity }] }); }); });这里params.start和params.end是dataZoom事件的百分比范围后端再换算成具体日期。需要注意Echarts的setOption默认是增量更新如果不加notMerge: true旧的series数据会残留导致图表出现多段数据重叠。实际部署时我会给每个图表实例单独封装一个更新函数避免事件回调里产生闭包副作用。大屏适配方面用栅格布局加百分比宽度同时监听window.resize调用chart.resize()这样在1080P和4K屏幕上都能保持布局稳定。最后一公里是CDN缓存Echarts的js文件体积较大建议生成版本号放CDN接口数据用gzip压缩能显著降低大屏首次加载耗时。本文还有配套的精品资源点击获取
返回列表