2. 数据导入与工程化预处理

在MATLAB机器学习工程中,数据导入与预处理往往占据整个项目60%以上的工作量。模型再先进,如果输入的数据存在缺失、异常或类别失衡,训练结果也会严重偏离预期。本节围绕“多源导入—质量诊断—清洗变换—函数封装”这条主线,讲解如何把零散的数据处理动作沉淀为可复用的工程资产。

一、多源数据导入:选对函数,保留元信息

MATLAB针对不同数据源提供了差异化的导入接口,选择依据不仅是文件格式,还包括数据规模、列类型混合程度以及是否需要保留表头语义。

  • CSV/文本文件:小规模、列类型混杂时用 readtable,它返回 table 类型,自动识别表头并保留变量名;纯数值大矩阵用 readmatrix 更快;需要精细控制分隔符、缺失标记时用 readtable 的 'Delimiter'、'TreatAsMissing' 参数。
  • Excel文件:readtable('data.xlsx','Sheet','Sheet1','Range','A1:F200') 可指定工作表与单元格区域,避免把说明行、汇总行误读为数据。
  • 数据库:通过Database Toolbox建立连接后,用 sqlread 或 fetch 执行查询,把结果直接读入table。连接对象应使用 close 及时释放,避免连接池耗尽。
  • MAT文件:load('exp.mat') 会把变量注入工作区,工程中更推荐 S = load('exp.mat'); T = S.T; 这种显式结构体接收方式,防止变量名冲突。

导入后第一件事不是急着建模,而是做一次“数据体检”:用 summary(T) 查看每列类型、缺失数量与取值范围,用 size、class、head(T) 确认维度与类型是否符合预期。这一步能提前暴露“数值列被读成字符串”“日期列被读成文本”等常见陷阱。

二、缺失值:先判断机制,再选择策略

缺失值处理不能一律删除或一律填充。工程上先区分三种机制:完全随机缺失(MCAR)、随机缺失(MAR)和非随机缺失(MNAR)。判断依据是缺失是否与其他变量相关。例如传感器掉线导致的缺失往往与设备状态相关,属于MNAR,简单均值填充会引入偏差。

常用策略及适用场景:

  • 删除:缺失比例低于5%且为MCAR时,rmmissing(T) 最直接;但要注意删除行会改变类别分布。
  • 统计量填充:数值列用中位数(抗异常值),类别列用众数。fillmissing(T,'Constant',0) 或 fillmissing(T,'previous') 适合时间序列的前向填充。
  • 模型填充:用 fitlm 或 fitcknn 基于其他完整列预测缺失值,适合缺失比例中等且变量间相关性强的场景。

无论采用哪种方式,都建议新增一列 isMissing_xxx 标记原始缺失位置,保留“缺失本身可能是信息”的可能性。

三、异常值:检测与处置要分开决策

异常值检测常用三类方法:基于统计的Z-score(|z|>3)、基于分位数的IQR法则(超出Q1-1.5IQR或Q3+1.5IQR)、基于密度的 isoutlier 函数。MATLAB的 isoutlier(A,'quartiles') 一行即可返回逻辑索引。

检测到异常后,处置方式取决于业务含义:如果是录入错误,应修正或置为缺失;如果是真实极端事件(如故障峰值),应保留并考虑单独建模。切忌无差别用均值替换,那会抹掉真实信号。工程中常用“缩尾处理”(Winsorize):把超出阈值的值截断到边界,既保留样本量又降低极端值影响。

多源数据导入与工程化预处理流水线 CSV / 文本 readtable Excel readtable + Range 数据库 sqlread / fetch MAT 文件 load → struct 统一 table summary 体检 质量诊断 缺失 / 异常 类别分布 缺失值处理 rmmissing / fillmissing / 模型填充 异常值处理 isoutlier / IQR / 缩尾 类别不平衡 重采样 / 加权 / SMOTE 可复用预处理函数 preprocessData(T, cfg) → [Tclean, report] 干净数据集 + 处理报告 供训练 / 验证 / 复现使用 关键工程原则 • 导入后先 summary 体检,确认类型与缺失分布 • 缺失机制决定策略,不盲目删除或均值填充 • 异常值检测与处置分开决策,保留真实极端事件 • 类别不平衡优先用加权,重采样注意验证集独立 • 所有处理步骤封装为函数,配置与数据分离

四、类别不平衡:从数据层与算法层双管齐下

类别不平衡在故障诊断、欺诈检测中极为常见。假设正类样本占2%,若直接训练,模型会倾向于全部预测为负类,准确率看似98%却毫无价值。处理思路分两层:

数据层:对少数类过采样(randsample 复制或SMOTE插值),对多数类欠采样。过采样易过拟合,欠采样会丢失信息,工程中常组合使用。注意:重采样只能在训练集上进行,验证集和测试集必须保持原始分布,否则评估结果失真。

算法层:使用代价敏感学习,在 fitcsvm、fitctree 等函数中通过 'ClassNames' 与 'Prior' 参数调整先验,或使用 'Weights' 给少数类更高权重。评价指标也应从准确率切换到召回率、F1或AUC。

五、封装可复用预处理函数

把上述步骤写成函数,是工程化的关键。推荐签名如下:

function [Tclean, report] = preprocessData(T, cfg)
% preprocessData 统一预处理入口
% 输入:
%   T   - 原始 table
%   cfg - 配置结构体,字段包括:
%         .missingMethod  'median'|'mode'|'drop'|'model'
%         .outlierMethod  'quartiles'|'zscore'|'none'
%         .outlierAction  'clip'|'remove'|'mark'
%         .balanceMethod  'none'|'oversample'|'weight'
%         .targetCol      目标列名
% 输出:
%   Tclean - 清洗后的 table
%   report - 处理报告(各列缺失数、异常数、类别分布变化)

    report = struct();
    % 1. 缺失值处理
    switch cfg.missingMethod
        case 'drop'
            T = rmmissing(T);
        case 'median'
            T = fillmissing(T, 'constant', ...
                median(T{:,:}, 'omitnan'));
        case 'mode'
            for v = 1:width(T)
                if iscategorical(T{:,v}) || iscell(T{:,v})
                    T{:,v} = fillmissing(T{:,v}, 'mode');
                end
            end
    end
    % 2. 异常值处理
    if ~strcmp(cfg.outlierMethod, 'none')
        for v = 1:width(T)
            if isnumeric(T{:,v})
                idx = isoutlier(T{:,v}, cfg.outlierMethod);
                if strcmp(cfg.outlierAction, 'clip')
                    q = quantile(T{:,v}, [0.25 0.75]);
                    iqr = q(2) - q(1);
                    T{:,v} = min(max(T{:,v}, q(1)-1.5*iqr), q(2)+1.5*iqr);
                elseif strcmp(cfg.outlierAction, 'remove')
                    T(idx, :) = [];
                end
            end
        end
    end
    % 3. 类别不平衡处理(仅训练集调用)
    if strcmp(cfg.balanceMethod, 'oversample')
        [X, y] = splitData(T, cfg.targetCol);
        X = oversample(X, y);
        T = [array2table(X), array2table(y)];
    end
    Tclean = T;
end

调用时只需传入配置结构体,例如:

cfg = struct('missingMethod','median', ...
             'outlierMethod','quartiles', ...
             'outlierAction','clip', ...
             'balanceMethod','weight', ...
             'targetCol','faultType');
[Tclean, report] = preprocessData(rawTable, cfg);
建议把 cfg 保存为独立的 .mat 或 .json 文件,与代码分离。这样同一套预处理逻辑可以在训练、验证、推理阶段完全一致地复用,避免“训练时做了标准化、推理时忘了”这类低级错误。
重采样操作必须严格限定在训练集内部。若在划分训练/测试集之前就做SMOTE,少数类样本的合成信息会“泄漏”到测试集,导致评估指标虚高。正确顺序是:先划分,再对训练集重采样。

最后,report 结构体应记录每一步处理前后的样本量、缺失数、异常数、类别分布,作为实验日志的一部分。当模型效果异常时,回看这份报告往往能快速定位是数据问题还是模型问题。把预处理做成可配置、可追溯、可复用的函数,才是MATLAB机器学习工程从“脚本”走向“平台”的第一步。