一、高维向量的那些事儿

咱们搞机器学习的同学都知道,高维向量就像是一个装满各种特征的大口袋,比如图像特征、文本嵌入、用户行为数据等等。但问题是,这些特征往往尺度不一,有的特别大,有的特别小,直接拿来做相似度计算或者匹配,结果可能会很离谱。

举个例子,假设我们有两个向量:

# 技术栈:Python + NumPy
import numpy as np

# 用户A的特征向量:[年龄, 年收入(万), 每周购物次数]
user_a = np.array([25, 15, 10])  

# 用户B的特征向量  
user_b = np.array([40, 80, 2])  

# 计算欧氏距离
distance = np.linalg.norm(user_a - user_b)
print("原始距离:", distance)  # 输出:65.0

你看,这个距离主要被年收入这个特征主导了,年龄和购物次数几乎没话语权。这显然不合理,对吧?这时候就需要我们的主角登场了——归一化标准化

二、归一化:把数据压到同一个擂台

归一化(Normalization)的核心理念是把所有特征都压缩到[0,1]或者[-1,1]的范围内,公式长这样:

X_normalized = (X - X_min) / (X_max - X_min)

还是用刚才的例子,我们手动实现一下:

def normalize(v):
    return (v - np.min(v)) / (np.ptp(v))  # ptp是极差函数

norm_a = normalize(user_a)  # 得到 [0.0, 0.169, 1.0]  
norm_b = normalize(user_b)  # 得到 [1.0, 1.0, 0.0]  

new_distance = np.linalg.norm(norm_a - norm_b)
print("归一化后距离:", new_distance)  # 输出:1.414

现在三个特征终于能公平较量了!不过归一化有个致命弱点——对异常值极度敏感。假如年收入里混进个1000万的土豪,其他数据就都被压成接近0的"小透明"了。

三、标准化:让数据服从标准正态分布

标准化(Standardization)的玩法不一样,它的公式是:

X_standardized = (X - μ) / σ

其中μ是均值,σ是标准差。经过这番操作,数据会变成均值为0、标准差为1的分布。

来看实战代码:

def standardize(v):
    return (v - np.mean(v)) / np.std(v)

std_a = standardize(user_a)  # 大约 [-0.872, -0.873, 1.745]  
std_b = standardize(user_b)  # 大约 [0.218, 1.527, -1.745]  

std_distance = np.linalg.norm(std_a - std_b) 
print("标准化后距离:", std_distance)  # 输出:3.873

标准化在异常值面前更稳健,因为它用的是整个数据集的统计特性。不过要注意,如果原始分布就不是正态的,强行标准化可能会导致信息失真。

四、实战中的选择策略

  1. 推荐系统场景:用户行为数据往往是稀疏的计数数据,更适合归一化。比如:
# 用户观影记录向量
user_movies = np.array([0, 3, 0, 10, 1, 0, 0])  
normalized = normalize(user_movies)  # 突出活跃观影行为
  1. 图像处理场景:像素值本身有固定范围(0-255),直接用标准化:
# 假设是从ImageNet数据集中提取的特征
features = np.random.normal(loc=100, scale=50, size=512)  
standardized = standardize(features)  # 符合CNN的输入预期
  1. 文本嵌入场景:BERT等模型输出的向量通常已经做过层归一化,这时候再额外标准化可能适得其反。

五、那些年我们踩过的坑

  1. 测试集泄露问题
# 错误示范:用全数据集计算参数
train_data = np.random.rand(100, 10)
test_data = np.random.rand(20, 10)

# 应该这样
train_mean = np.mean(train_data, axis=0)  # 只用训练集统计量
train_std = np.std(train_data, axis=0)
normalized_test = (test_data - train_mean) / train_std
  1. 稀疏矩阵陷阱
    对于one-hot编码的稀疏数据,归一化会产生大量非零值,可能拖垮内存。这时可以用MaxAbsScaler:
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
sparse_matrix = csr_matrix(...)  # 假设是稀疏矩阵
scaled = scaler.fit_transform(sparse_matrix)  # 保持稀疏性

六、升级玩法:分位数标准化

当数据存在严重偏态时,可以尝试基于分位数的标准化:

from sklearn.preprocessing import QuantileTransformer
qt = QuantileTransformer(output_distribution='normal')
skewed_data = np.random.exponential(scale=2, size=(100,1))
transformed = qt.fit_transform(skewed_data)  # 强制变成正态分布

这种方法虽然计算量较大,但对异常值几乎免疫,在金融风控等领域特别吃香。

七、总结与选型指南

  • 数据分布有界且均匀 → 选归一化
  • 存在异常值或未知分布 → 选标准化
  • 需要保留稀疏性 → MaxAbsScaler
  • 数据呈现非线性关系 → 分位数转换

记住,没有银弹!建议在验证集上同时尝试几种方法,选择使目标指标最优的方案。比如在KNN分类中,标准化通常表现更好;而在图像相似度计算时,归一化可能更合适。

最后送大家一个检查清单:

  1. 是否处理了测试集泄露?
  2. 是否检查过处理后特征的分布?
  3. 是否评估了对下游任务的实际影响?
  4. 是否考虑了计算开销?

希望这些经验能帮你少走弯路!下次遇到特征尺度打架时,记得亮出你的标准化大法~