ARTICLE DETAIL

资讯详情

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

颜色与浓度辨识:多元线性回归与层次分析建模实战

颜色与浓度辨识:多元线性回归与层次分析建模实战 1. 从一道数学建模题说起颜色与浓度之间到底藏着什么关系第一次拿到“颜色与物质浓度辨识”这个题目的时候我脑子里第一反应是高中化学的比色法——用眼睛看颜色深浅来判断浓度。但真要把这件事做成一个能跑出结果的数学模型光靠肉眼显然不够。题目给了一组不同浓度溶液对应的颜色读数要求建立颜色与浓度之间的定量关系进而对未知浓度的溶液进行预测。这类问题在数学建模竞赛里属于典型的数据拟合与回归分析题型核心关键词就是颜色、物质浓度、多元线性回归、层次分析、相关性分析。说白了这道题要解决的核心问题是颜色是一个三维甚至多维的信息比如RGB三个通道或者色调、饱和度、亮度而浓度是一个标量。怎么把多维的颜色信息映射到一个浓度值上这中间需要做相关性分析来筛选有效变量需要回归模型来建立映射关系可能还需要层次分析来对多个模型或多种颜色指标做综合评价。适合谁来参考这篇笔记正在准备数学建模竞赛的同学、需要做数据拟合与预测的初学者以及任何对“从颜色反推浓度”这个思路感兴趣的人。我在做这道题的过程中踩了不少坑也总结了一些比较实用的方法。下面把我整个分析和建模的过程拆开来讲尽量把每一步为什么这么做、怎么做、做完之后怎么验证都说清楚。2. 数据到手先别急着建模颜色指标的预处理与相关性筛选2.1 原始数据的结构长什么样题目给出的数据通常是这样组织的每一种物质比如高锰酸钾、硫酸铜等在不同浓度下用颜色传感器或者图像采集设备读取颜色值。颜色值可能是RGB三通道也可能是HSV、Lab等色彩空间的数值。每一行是一条样本包含浓度和对应的颜色读数。我拿到数据后的第一件事是确认几件事样本量有多少浓度梯度是怎么分布的颜色读数的量纲和范围是什么有没有缺失值或异常值这些看起来是废话但我确实见过有人拿到数据直接扔进回归模型结果因为某个通道的数值范围是0-255而另一个是0-1导致回归系数完全没法解释。2.2 为什么不能把所有颜色通道一股脑塞进模型很多人第一反应是把R、G、B三个通道全部作为自变量浓度作为因变量直接跑一个多元线性回归。这样做不是不行但有几个问题。第一RGB三个通道之间往往存在高度相关性。比如某种溶液浓度增加时R和G可能同时下降这两个变量携带的信息是冗余的。把高度相关的变量同时放进回归模型会导致多重共线性回归系数估计不稳定甚至出现符号与实际相反的情况。第二不同颜色通道对浓度的敏感程度不一样。有的通道随浓度变化很明显有的几乎不变。把不敏感的通道放进去只会增加噪声。所以在建模之前必须先做相关性分析。我通常会用Pearson相关系数和Spearman秩相关系数各算一遍。Pearson衡量的是线性相关Spearman衡量的是单调相关。如果某个通道与浓度的Spearman相关系数很高但Pearson一般说明它们之间可能是非线性单调关系这时候可以考虑对变量做变换。2.3 用Python快速做相关性筛选的实操我一般用Python的pandas和scipy来做这一步代码不复杂import pandas as pd from scipy.stats import pearsonr, spearmanr df pd.read_csv(color_concentration.csv) color_cols [R, G, B, H, S, V] # 根据实际列名调整 for col in color_cols: p_r, p_val pearsonr(df[col], df[concentration]) s_r, s_val spearmanr(df[col], df[concentration]) print(f{col}: Pearson{p_r:.4f} (p{p_val:.4f}), Spearman{s_r:.4f} (p{s_val:.4f}))跑完之后我会把相关系数绝对值低于某个阈值比如0.3的通道先排除掉。但这里有个经验不要机械地只看相关系数。有时候两个通道单独看相关性都不算特别高但它们的比值或者差值跟浓度关系很好。比如R/B的比值在某些比色实验中就比单独的R或B更稳定。所以除了原始通道我还会构造一些组合特征比如R-G、R/G、G/B等一起做相关性筛选。2.4 异常值处理别让一个坏点毁掉整个模型颜色数据特别容易受光照条件、设备噪声影响出现异常值是常有的事。我一般用两种方法结合来判断异常值一是箱线图的IQR规则二是基于马氏距离的多维异常检测。对于确认的异常值如果数量很少比如不到5%直接删除如果较多需要考虑是不是数据采集环节出了问题不能随便删。注意删除异常值之前一定要记录删了哪些点、为什么删。建模论文里如果被问到数据预处理这些记录就是你的依据。3. 多元线性回归不是万能钥匙模型选择与变量变换的取舍3.1 什么时候用多元线性回归什么时候该换思路多元线性回归的形式是 y β₀ β₁x₁ β₂x₂ ... βₙxₙ ε其中y是浓度x是颜色指标。它的优点是解释性强每个回归系数直接反映了对应颜色通道对浓度的贡献。但它有一个隐含假设颜色和浓度之间是线性关系。实际数据里颜色随浓度的变化往往不是线性的。比如浓度很低时颜色变化很快浓度高了之后颜色趋于饱和变化变慢。这种关系用线性回归拟合残差图会呈现明显的弯曲模式。我的做法是先跑一个线性回归看残差。如果残差随机分布说明线性假设可以接受如果残差呈现U型或倒U型就需要考虑加入二次项或者对变量做对数变换。3.2 逐步回归让数据自己告诉你该留哪些变量变量筛选我常用逐步回归stepwise regression。它的思路是从一个空模型开始每次加入一个对模型改进最大的变量同时检查已有变量是否因为新变量的加入而变得不显著如果显著就保留不显著就剔除。这个过程反复进行直到没有变量可以加入也没有变量需要剔除。在Python里可以用statsmodels配合自己写循环来实现也可以用sklearn的RFE递归特征消除。我倾向于用statsmodels因为它的summary输出包含了每个系数的p值和置信区间方便判断显著性。import statsmodels.api as sm X df[[R, G, B, R_over_B]] # 候选变量 X sm.add_constant(X) y df[concentration] model sm.OLS(y, X).fit() print(model.summary())看summary的时候重点关注几个东西R²和调整R²判断拟合优度、F检验的p值判断模型整体显著性、各系数的p值判断单个变量显著性、以及条件数Condition Number判断多重共线性严重程度。条件数超过30就要警惕共线性问题了。3.3 变量变换的几种常用手段如果线性回归效果不理想我会尝试以下几种变换对数变换对浓度取对数适用于浓度跨度大、低浓度区域变化剧烈的情况。多项式项加入R²、G²等二次项捕捉非线性关系。交互项加入R×G等交互项捕捉通道之间的协同效应。比值特征用R/G、G/B等比值代替原始通道减少光照强度变化的影响。每做一次变换都要重新看残差图和调整R²。不要一次性把所有变换都加上去那样容易过拟合。我的原则是每次只加一个变换看效果有没有显著提升有就保留没有就撤掉。3.4 模型验证别只看R²R²高不代表模型好。我见过有人把多项式次数加到很高R²接近1但一做交叉验证就露馅了。所以建模之后一定要做交叉验证。对于样本量不大的情况用留一法交叉验证LOOCV样本量大的话用K折交叉验证K通常取5或10。from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score model LinearRegression() scores cross_val_score(model, X, y, cv5, scoringneg_mean_squared_error) rmse_scores np.sqrt(-scores) print(f交叉验证RMSE: {rmse_scores.mean():.4f} ± {rmse_scores.std():.4f})交叉验证的RMSE才是模型泛化能力的真实反映。如果交叉验证RMSE远大于训练集RMSE说明模型过拟合了需要简化。4. 层次分析法的介入多模型或多指标的综合评价4.1 为什么这道题会用到层次分析层次分析法AHP在这道题里通常出现在两个场景一是当你有多个候选模型比如线性回归、多项式回归、支持向量回归等需要综合多个评价指标来选一个最优的二是当颜色指标本身需要综合评价时比如同时考虑色调、饱和度、亮度对浓度判断的贡献权重。AHP的核心思想是把复杂决策问题分解成目标层、准则层和方案层通过构造判断矩阵来计算各因素的权重。它的优势在于能把定性判断和定量计算结合起来。4.2 构造判断矩阵的实操要点假设我要在三个模型之间做选择准则层有三个指标拟合优度R²、交叉验证RMSE、模型复杂度。我需要构造一个3×3的判断矩阵表示这三个准则两两之间的重要程度。准则R²CV-RMSE复杂度R²11/23CV-RMSE214复杂度1/31/41矩阵中aᵢⱼ表示准则i相对于准则j的重要性。比如CV-RMSE相对于R²是2说明我认为泛化能力比拟合优度重要一倍。这个判断矩阵需要满足一致性即aᵢⱼ × aⱼₖ ≈ aᵢₖ。实际构造时很难完全一致所以需要计算一致性比率CRCR 0.1才认为一致性可以接受。4.3 权重计算与一致性检验权重计算通常用特征值法求判断矩阵的最大特征值对应的特征向量归一化后就是权重。一致性检验用CI (λmax - n) / (n - 1)然后CR CI / RI其中RI是随机一致性指标查表可得。import numpy as np A np.array([[1, 1/2, 3], [2, 1, 4], [1/3, 1/4, 1]]) eigvals, eigvecs np.linalg.eig(A) max_idx np.argmax(eigvals.real) lambda_max eigvals.real[max_idx] w eigvecs[:, max_idx].real w w / w.sum() print(f权重: {w}) print(f最大特征值: {lambda_max:.4f}) n A.shape[0] CI (lambda_max - n) / (n - 1) RI 0.58 # n3时查表 CR CI / RI print(fCR {CR:.4f})CR小于0.1就通过。如果没通过需要回去调整判断矩阵通常是检查哪两个准则之间的比较明显不合理。4.4 AHP的打分与模型排序有了权重之后对每个模型在每个准则下打分比如R²最高的打1分最低的打0分中间线性插值然后加权求和得到综合得分。得分最高的模型就是推荐模型。这里有个经验AHP的权重对最终结果影响很大所以判断矩阵的构造不能太随意。我一般会参考领域知识或者文献中常用的权重分配而不是完全凭感觉。如果时间允许可以请几个同学分别构造判断矩阵然后取几何平均这样能减少个人偏见。5. 从颜色到浓度的完整预测流程代码实现与踩坑记录5.1 完整流程的代码骨架把前面的步骤串起来整个建模流程大概是这样的import pandas as pd import numpy as np from scipy.stats import pearsonr, spearmanr import statsmodels.api as sm from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score from sklearn.preprocessing import PolynomialFeatures # 1. 读数据 df pd.read_csv(color_concentration.csv) # 2. 相关性筛选 color_cols [R, G, B] selected [] for col in color_cols: s_r, _ spearmanr(df[col], df[concentration]) if abs(s_r) 0.3: selected.append(col) print(f筛选后的变量: {selected}) # 3. 构造组合特征 df[R_over_B] df[R] / df[B] df[R_minus_G] df[R] - df[G] selected [R_over_B, R_minus_G] # 4. 多元线性回归 X sm.add_constant(df[selected]) y df[concentration] model sm.OLS(y, X).fit() print(model.summary()) # 5. 交叉验证 lr LinearRegression() scores cross_val_score(lr, df[selected], y, cv5, scoringneg_mean_squared_error) print(fCV-RMSE: {np.sqrt(-scores).mean():.4f}) # 6. 如果线性效果不好尝试多项式 poly PolynomialFeatures(degree2, include_biasFalse) X_poly poly.fit_transform(df[selected]) X_poly sm.add_constant(X_poly) model_poly sm.OLS(y, X_poly).fit() print(f多项式模型调整R²: {model_poly.rsquared_adj:.4f})5.2 踩坑记录一量纲不一致导致回归系数无法解释我第一次跑回归的时候直接把RGB值0-255和浓度0-1放进去结果回归系数小得离谱而且符号跟预期相反。后来才意识到RGB的数值范围远大于浓度导致系数被压缩了。解决办法是对所有变量做标准化z-score标准化这样回归系数就代表了自变量每变化一个标准差因变量变化多少个标准差解释起来直观得多。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(df[selected]) X_scaled sm.add_constant(X_scaled) model_scaled sm.OLS(y, X_scaled).fit() print(model_scaled.summary())5.3 踩坑记录二多重共线性导致系数符号反转有一次我发现R的系数是正的但实际数据里R随浓度增加而减小按理说系数应该是负的。查了半天才发现是R和G高度相关相关系数0.95导致共线性。解决办法是删掉其中一个或者用PCA降维。我选择了删掉G因为R和浓度的相关性更高。判断共线性的另一个方法是看VIF方差膨胀因子。VIF大于10就说明共线性严重。statsmodels里可以用variance_inflation_factor来计算。from statsmodels.stats.outliers_influence import variance_inflation_factor vif_data pd.DataFrame() vif_data[feature] X.columns vif_data[VIF] [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] print(vif_data)5.4 踩坑记录三交叉验证的随机性导致结果不稳定用K折交叉验证的时候如果不对数据进行打乱可能会因为数据本身的顺序比如按浓度排序导致每一折的分布不均匀CV结果波动很大。解决办法是在交叉验证之前先打乱数据或者用StratifiedKFold按浓度分层抽样。from sklearn.model_selection import KFold import numpy as np kf KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(lr, X, y, cvkf, scoringneg_mean_squared_error)设置random_state是为了结果可复现这在写论文的时候很重要。6. 模型评估与结果呈现让论文里的图表说话6.1 残差图最直观的模型诊断工具残差图是判断模型是否合适的最简单方法。横轴是预测值纵轴是残差。如果残差随机分布在0附近没有明显的模式说明模型拟合得不错。如果残差呈现喇叭形异方差说明需要对因变量做变换。如果残差呈现弯曲说明有非线性关系没被捕捉到。我一般会用matplotlib画残差图同时画一条y0的参考线import matplotlib.pyplot as plt pred model.predict(X) residuals y - pred plt.figure(figsize(8, 5)) plt.scatter(pred, residuals, alpha0.6) plt.axhline(y0, colorr, linestyle--) plt.xlabel(Predicted Concentration) plt.ylabel(Residuals) plt.title(Residual Plot) plt.show()6.2 预测值与真实值的对比图另一个常用的图是预测值 vs 真实值的散点图加上一条yx的对角线。点越靠近对角线说明预测越准。plt.figure(figsize(6, 6)) plt.scatter(y, pred, alpha0.6) plt.plot([y.min(), y.max()], [y.min(), y.max()], r--) plt.xlabel(True Concentration) plt.ylabel(Predicted Concentration) plt.title(Predicted vs True) plt.show()6.3 模型对比表格让审阅者一眼看懂如果做了多个模型最好用一个表格对比它们的各项指标模型调整R²CV-RMSE变量数是否通过残差检验线性回归全部变量0.850.126否线性回归逐步筛选0.890.093是多项式回归二次0.930.079是对数线性回归0.910.083是这样的表格在论文里非常加分因为它清晰地展示了你的建模过程和模型选择依据。6.4 关于颜色样式的一个小技巧画图的时候颜色选择其实挺有讲究的。如果是多组数据对比尽量用色盲友好的配色方案比如matplotlib的viridis或cividis色图。避免用红绿对比因为红绿色盲的人很难区分。另外打印成黑白的时候不同组要用不同的线型或标记来区分不能只靠颜色。plt.style.use(seaborn-v0_8-whitegrid) colors plt.cm.viridis(np.linspace(0, 1, 5))7. 这道题还能怎么扩展几个值得尝试的方向7.1 用非线性模型替代线性回归如果数据量足够可以尝试支持向量回归SVR、随机森林回归或高斯过程回归。这些模型能自动捕捉非线性关系通常预测精度更高。但代价是解释性变差而且需要调参。我的建议是先用线性模型建立基线再用非线性模型看能提升多少。如果提升不明显就没必要为了那一点点精度牺牲解释性。7.2 把颜色识别做成实时系统如果这道题是从图像中读取颜色那可以进一步做成一个实时预测系统用摄像头采集溶液图像提取颜色特征输入模型实时输出浓度估计值。这就涉及到OpenCV的颜色识别和边缘检测了。不过要注意实时系统的难点在于光照条件的稳定性需要做白平衡校正或者用参考色卡做归一化。7.3 多物质同时辨识如果题目扩展到多种物质混合的情况颜色信息会更加复杂可能需要用光谱数据而不是简单的RGB。这时候问题就从回归变成了分类回归的组合先判断是哪种物质再预测浓度。层次分析法在这个场景下可以用来综合多个分类器的输出。7.4 不确定性量化回归模型给出的是点估计但实际应用中我们往往需要知道预测的置信区间。可以用bootstrap方法或者贝叶斯回归来量化不确定性。这样在给出浓度预测值的同时还能给出一个置信区间实用性更强。我在做这道题的过程中最大的体会是数学建模不是把方法堆上去就行每一步都要有理由。为什么选这个变量、为什么用这个模型、为什么做这个变换这些“为什么”比“做了什么”更重要。另外数据预处理和模型诊断花的时间往往比建模本身还多但这些都是值得的因为垃圾进垃圾出数据没处理好再花哨的模型也救不回来。
返回列表