简介:这套MATLAB归一化工具集面向数据分析和机器学习初学者,用于解决数据尺度不统一导致模型训练缓慢、精度下降的问题,提供一套完整的预处理方案。压缩包内共4个文件,均为MATLAB脚本(.m),整体大小仅1KB,轻量却覆盖多种主流归一化算法。脚本分别实现了最大最小归一化(缩放至[0,1])、Z-score标准化(零均值单位方差)、自定义区间缩放(可映射到任意新范围)以及带异常值处理的数据标准化;用户可直接读取数据运行,也可将函数嵌入更大的分析流程。通过对比这些脚本的输出,还能直观理解不同归一化方法对数据分布和后续模型效果的影响。对于KNN、SVM等距离敏感算法,最大最小归一化往往更合适;对于PCA、SVD等涉及线性代数运算的场景,Z-score标准化则是首选,可按需灵活选用,有效避免特征量纲带来的干扰。已有217人学习下载,适合需要快速完成特征缩放、提升建模效果的开发者参考。
1. 为什么 MATLAB 里的 normalization 有四种解法
网上下载的 normalization 工具包解压出来大多是零散的 .m 文件:有的是手写 min-max 映射,有的是对 zscore 的封装,还有的只是包了一层内置函数。真正让使用者头疼的不是缺代码,而是同一个名字在 MATLAB 里对应完全不同的技术线:线性代数里的范数归一化、统计里的特征缩放、图像处理里的灰度拉伸,以及深度学习里的批归一化层。公式不同,适用数据形状不同,混用的典型后果是代码不报错、结果全错。这篇文章把常见做法、参数设置和验证方法沿四条线讲清楚,适合正在做数据预处理、信号特征提取或用 Deep Learning Toolbox 搭网络的人,看完全文能直接照着自己数据改。
2. 向量与矩阵归一化:norm、vecnorm、normalize 的边界
2.1 先回答:你要归一化的是长度还是分布
范数归一化和统计归一化在 MATLAB 里对应两套完全不同的函数家族。范数归一化的目标是让向量变成单位长度,数学上写作 x / ||x||₂,它处理的是一行一行的样本,把每一行当成空间里的一个点,方向比幅值重要;统计归一化处理的是一列一列的维度,让每个特征落到指定的均值、方差或值域。开始写代码之前先用一句话判断自己在哪一边:如果矩阵的行可以被解释成点、后续要算余弦相似度或欧氏距离,走范数路线;如果每一列是物理意义不同的测量量(比如温度、压力、转速),走 z-score 或 min-max 路线。两边混用的最典型症状是:归一化之后距离倒挂,原本相似的两个样本反而被拉远。
2.2 norm 的三种调用与一个常见误解
norm函数本身在向量和矩阵上的行为不一样:norm(v)返回向量的 2-范数,而norm(A)对矩阵返回的是最大奇异值,不是逐列范数。从 rar 包里拿到的旧脚本里经常看到用 for 循环逐行调用norm,数据量一上来就慢得明显。R2017b 之后 MATLAB 提供了vecnorm,专门解决「对每一行/每一列批量求范数」的需求;R2018a 之后又有normalize,把求范数和做除法合并成一步。
| 函数 | 引入版本 | 返回内容 | 典型用法 |
|---|---|---|---|
norm(A) | 早期版本 | 标量:向量为 2-范数,矩阵为最大奇异值 | 判断矩阵病态程度、验证算法收敛 |
vecnorm(A,p,dim) | R2017b | 沿 dim 方向逐条计算的 p-范数数组 | 批量归一化矩阵的行或列 |
normalize(A,dim,method) | R2018a | 与 A 同尺寸的归一化结果 | 一步完成范数、z-score、区间缩放 |
2.3 对特征矩阵按行做单位长度归一化的最小代码
假设有一个 5×3 的特征矩阵,每行是一个样本:
% 特征矩阵 X:每一行是一个样本,每一列是一个维度 X = [1 2 3; 4 5 6; 7 8 9; 0 1 2; 3 1 4]; % 方式一:vecnorm 先算范数,再手动做广播相除 n = vecnorm(X, 2, 2); % 第二个参数 2 表示欧氏范数,第三个参数 2 表示沿行方向 Xn1 = X ./ n; % 每行缩放到单位长度 % 方式二:normalize 一步完成,默认就是 2-范数 Xn2 = normalize(X, 2, 'norm');vecnorm 的第二个参数是范数阶数,传 1 得到 L1 范数(各分量绝对值之和),传 2 得到欧氏长度;第三个参数 1 表示沿列方向计算,2 表示沿行方向计算,这一步最容易写反。normalize 的维度参数含义一致,normalize(X, 1, 'norm')会按列归一化,得到的是每一列长度为一的结果,语义完全不同。需要 L1 归一化时写法是normalize(X, 2, 'norm', 1),注意 p 值要放在最后一个位置。
2.4 零范数行与平方溢出的两个坑
实际数据里几乎必然出现全零行,除零之后产生 NaN,后续任何统计量都会被污染。常见做法是给范数加一个极小值保护:
% 保护零范数行:零行保持全零,非零行缩放量几乎不变 X = [0 0 0; 1 1 1; 2 2 2]; n = vecnorm(X, 2, 2); Xn = X ./ max(n, 1e-12);max(n, 1e-12)只影响范数小于 1e-12 的行,相当于把除零换成除以一个接近零的常数,结果仍然趋近于零,不会产生 NaN。另一个坑是大数值平方溢出:对x = [1e8, 1e8 + 1]这类数据直接算平方和,double 的精度会被吃掉,归一化后两个分量几乎无法区分。常见做法是先减去整行均值再做范数归一化,让数据落在零附近,然后再按需平移回去。需要说明的是,范数归一化解决不了单位不一致的问题,厘米和千克放在一起算欧氏距离,数值大的单位天然主导结果,这种情况应该直接改用第三章的 z-score 或 min-max。
3. 特征归一化实战:zscore、mapminmax 与 normalize 的三种写法
3.1 三种方法适用场景对照
特征归一化是数据预处理里最容易被「哪个顺手用哪个」决定的一步。三种方法输出范围不同,工具箱依赖不同,风险点也完全不同。
| 方法 | 输出含义 | 适用场景 | 工具箱依赖 | 主要风险 |
|---|---|---|---|---|
| min-max(range) | 映射到指定区间,如 [0,1] | 数据有明确物理边界、分布较均匀 | 基础 MATLAB | 异常值会把正常数据压缩到很窄的区间 |
| zscore | 均值 0、标准差 1 | 分布大致对称、后续接线性模型或距离计算 | Statistics and Machine Learning Toolbox | 严重偏态数据标准化后仍不对称 |
| mapminmax | 默认映射到 [-1,1] | 神经网络输入的传统预处理 | Deep Learning Toolbox | 按行处理,和多数人的按列习惯相反 |
选型逻辑并不复杂:先看数据有没有已知边界,再看有没有异常值。边界明确的物理量(角度 0 到 360、灰度 0 到 255)用 min-max;特征来自传感器且偶发尖峰,用 zscore 比 min-max 稳,因为均值对方差的影响远小于极值对 min-max 的影响;要喂给神经网络且想保留稀疏性,通常会落在 [-1,1] 区间,这正是 mapminmax 的历史位置。
3.2 三种写法:同一份数据,可复现代码
rng(0); % 固定随机种子,保证结果可复现 data = randn(200, 4) .* [3 5 1 8] + [10 0 -2 100]; % 写法一:zscore 按列标准化,返回归一化结果、均值、标准差 [zs, mu, sigma] = zscore(data); % 写法二:mapminmax 注意按行处理,因此需要先转置 [mp_norm, ps] = mapminmax(data'); % ps 结构体保存原始边界与目标边界 % 写法三:normalize 指定目标区间 [0,1] [rng_norm, C, S] = normalize(data, 1, 'range', [0 1]);zscore 的第一个输出与输入同尺寸,mu 和 sigma 是 1×4 的向量,分别对应每列的均值和标准差,默认分母是 N-1。mapminmax 的返回值里,ps.xmin、ps.xmax 记录原始数据每行的最小最大值,ps.ymin、ps.ymax 是目标边界,这个结构体后面做逆向还原要用。normalize 的 C 和 S 含义随方法变化:'range'时 C 是每列最小值,S 是每列的 max 减 min;'zscore'时 C 是均值,S 是标准差。三种写法选一种统一项目风格即可,混用会让后来接手的人反复确认到底落在哪个分布上。
3.3 训练集和测试集必须用同一套统计量
这是特征归一化里最容易造成模型失真的地方,也是数据泄漏最常见的一种形式。正确的顺序是:只在训练集上估计均值、标准差或上下界,然后把同一组参数套到验证集和测试集上。如果测试集单独重算 min-max,等于让模型提前看到了测试集的范围信息,离线指标会虚高,上线后真实分布一变立刻现形。
rng(1); Xtr = randn(500, 8); Xte = randn(200, 8) + 2; % 模拟线上数据分布漂移 % 训练集上估计参数,并归一化 [Xtr_n, C, S] = normalize(Xtr, 1, 'zscore'); % 测试集复用同一组 Center/Scale,禁止重新计算 Xte_n = normalize(Xte, 1, 'center', C, 'scale', S);normalize 支持把前一步输出的 C 和 S 直接当作'center'和'scale'的实参传入,这是最不容易出错的写法。手写代码时很多人会犯的错是测试集直接调用zscore(Xte),新数据被自己的均值和方差标准化,训练和推理的标准就不一致了。
3.4 三个必查的边界条件
数据里有 NaN 或 Inf 时,所有归一化方法都会把缺失值传导到结果里,不同 MATLAB 版本对normalize的缺失值处理策略不完全一致,建议在归一化之前自己先清洗。常数列是第二个高频问题:某列最大值等于最小值时,min-max 直接产生 0/0,zscore 产生 0/0,结果整列变成 NaN。normalize对常数列的表现是这一列全部变成 NaN,但不会报错,所以必须主动检测:
% 检测常数列:range 计算每列 max - min bad_cols = find(range(data, 1) == 0); if ~isempty(bad_cols) error('第 %d 列为常数,无法归一化', bad_cols); end第三个问题是整型数据直接参与运算,uint8 类型在做减法时会发生截断,必须先转 double。高维特征里如果异常值造成 min-max 结果几乎全部挤在 0.98 附近,可以改看分位数,或者直接换用normalize(data, 1, 'medianiqr'),它用中位数和四分位距做中心化缩放,对长尾异常值的抵抗能力明显更好。
4. 图像与深度学习任务里的归一化:从 im2double 到批归一化层
4.1 图像灰度归一化的三种做法别混用
图像处理里的归一化和特征归一化是两码事,前者处理的是像素灰度。用imread读进来的图通常是 uint8,范围 0 到 255,直接送进网络或矩阵运算会因为数据类型产生各种问题,第一步转成 double 是共识,但转的方式差别很大:
img = imread('coins.png'); % uint8,范围 0-255 im1 = im2double(img); % 固定除以 255,属于类型转换 im2 = mat2gray(img); % 按实际 min/max 拉伸到 [0,1] im3 = (double(img) - min(img(:))) ./ (max(img(:)) - min(img(:))); % im2 的手写等价im2double 做的事情是固定的线性缩放:uint8 除以 255,uint16 除以 65535,且 double 输入原样返回。mat2gray 是动态范围归一化,把当前这一幅图的最小值映射到 0、最大值映射到 1,两张明暗差异很大的图会被拉到同一个对比度水平。选择依据是:光照条件固定、需要保留绝对亮度信息时用 im2double;光照变化明显、希望突出相对对比度时用 mat2gray。还有一类做法是全局 z-score,即用整幅图像的均值和标准差做标准化,这通常出现在预训练模型的输入要求里,注意它是全局统计量而不是逐像素的。
4.2 Deep Learning Toolbox 里的归一化配置
深度学习里归一化至少出现在三个位置:输入层、网络中间层、损失函数之前的输出处理。输入层的标准化可以在网络定义时直接声明,featureInputLayer和sequenceInputLayer都支持'Normalization'这个参数,设成'zscore'之后网络会在训练过程中自己维护均值方差:
layers = [ featureInputLayer(10, 'Normalization', 'zscore') fullyConnectedLayer(32) batchNormalizationLayer reluLayer ];featureInputLayer 的 'zscore' 统计分析来自训练数据,这一点和上一节的「测试集复用同一套统计量」原则一致。放在全连接层后面的batchNormalizationLayer行为更微妙:训练阶段使用当前 mini-batch 的均值方差做归一化,推理阶段改用手头维护的滑动平均统计量,相当于把归一化参数的学习和预测分开处理。这个差异带来的实际影响是 batch size 太小时,训练和推理的分布不一致会明显放大,常见的处理方式是 batch size 不要低于 32,或者改用layerNormalizationLayer,后者按特征维做归一化,不依赖 batch 内样本数量。中间层归一化的顺序固定是线性层之后、激活函数之前,顺序写反会削弱归一化效果,这是搭网络时最容易抄错的一行。
4.3 判断 rar 包里的函数到底做的是哪种归一化
拿到一份 normalization 工具包,先别急着调用,打开源码搜几个关键词就能定位实现方式,判断之后才能决定是否替换成内置函数。出现mapminmax(说明是旧神经网络工具箱风格,处理对象是行方向;出现zscore或手写的(x - mean(x)) ./ std(x),是统计标准化,按列处理;出现sqrt(sum(x.^2, 2))这类写法,是范数归一化,按行处理;出现im2double或mat2gray,是图像灰度的处理代码,和特征归一化根本不在一个语境里。替换成内置normalize时,先确认原函数按哪个维度运算,normalize的维度参数和原脚本的行列习惯不一致是替换后最容易翻车的地方。
5. 归一化做完怎么验证和还原:一套可复制的检查脚本
5.1 用输出统计量验证归一化是否生效
归一化做完别直接往下跑,先用一段小脚本验证结果落到了预期的空间。下面这个函数输入归一化后的矩阵和方法名,返回布尔值并打印实际统计量:
function ok = verify_normalization(Xn, method) switch method case 'minmax' lo = min(Xn, [], 1); hi = max(Xn, [], 1); ok = all(lo >= -1e-12) && all(hi <= 1 + 1e-12); fprintf('范围 [%.4f, %.4f]\n', min(lo), max(hi)); case 'zscore' m = mean(Xn, 1); s = std(Xn, 0, 1); ok = all(abs(m) < 1e-10) && all(abs(s - 1) < 1e-8); fprintf('均值 max=%.2e,标准差范围 [%.6f, %.6f]\n', max(abs(m)), min(s), max(s)); case 'l2row' nn = vecnorm(Xn, 2, 2); ok = all(abs(nn - 1) < 1e-10); fprintf('行范数 max=%.6e\n', max(abs(nn - 1))); otherwise error('未知方法:%s', method); end end验证 zscore 时要注意std的权重参数:std(Xn, 0, 1)的第二个参数 0 表示除以 N-1,和 zscore 内部默认一致,写 1 则会得到总体标准差,数值上会有微小差异。范数归一化的验证边界可以放宽到 1e-10,因为平方根和除法会引入浮点误差,卡得太死反而误报。
5.2 还原与参数持久化
需要把归一化后的数据还原回原始尺度时,两条路对应前面两种实现。对normalize的结果,因为格式是 Xn = (X - C) ./ S,还原就是反向乘加:
Xback = Xn .* S + C; % 对应 zscore 或 range,C、S 来自训练集对mapminmax则直接用它的内置逆向模式:
Xback2 = mapminmax('reverse', mp_norm, ps); % ps 里存有原始边界最后一条建议是把 C、S 或 ps 结构体连同训练好的模型一起保存成 .mat 文件,线上推理时用这套参数归一化新样本。手写归一化代码时常见的问题是每次启动重新计算统计量,一旦新数据混入异常值,线上结果就会和离线评测产生系统性偏差,保存参数是最省事的规避办法。
本文还有配套的精品资源,点击获取