4. 特征工程(下):基于IC曲线的特征提取

好了,咱们接着聊特征工程的下半场。

上一章我们讲了基础特征怎么抓,这一章要动真格的了——IC曲线。说白了,就是容量增量曲线。这东西在电池寿命预测里,地位相当于心电图在心脏科的地位。我个人习惯,拿到一批电池数据,第一件事就是画IC曲线看看。

4.1 为什么IC曲线这么香?

你想想看,电池充放电的电压平台变化,其实藏着很多信息。但直接看电压-容量曲线,很多细节被淹没了。IC曲线相当于一个「放大镜」,把电压平台上的微小变化给凸显出来。

我在项目中遇到过一件事:两批电池,外观一样,容量曲线几乎重合。但IC曲线一画,峰值位置差了0.02V。后来拆解发现,是正极材料批次差异。嗯,从那以后我再也不敢只看容量曲线了。

核心思想:IC曲线 = dQ/dV,即容量对电压的微分。峰值对应相变过程,峰高对应反应速率,峰面积对应容量贡献。

4.2 峰值特征提取:位置、高度、面积

提取IC曲线特征,我一般分三步走:

  1. 平滑处理——原始dQ/dV噪声大,先滤波
  2. 峰值检测——找局部极大值
  3. 特征量化——记录位置、高度、半峰宽、面积

代码实现其实不复杂,我贴一段常用的:

import numpy as np
from scipy.signal import find_peaks, savgol_filter

def extract_ic_features(voltage, capacity):
    """
    从IC曲线提取峰值特征
    voltage: 电压序列 (V)
    capacity: 容量序列 (Ah)
    """
    # 1. 计算dQ/dV
    dq = np.diff(capacity)
    dv = np.diff(voltage)
    ic = dq / dv
    
    # 2. 平滑处理 - Savitzky-Golay滤波器
    ic_smooth = savgol_filter(ic, window_length=11, polyorder=3)
    
    # 3. 峰值检测
    peaks, properties = find_peaks(
        ic_smooth, 
        prominence=0.1,   # 峰显著度
        width=5,          # 最小峰宽
        distance=10       # 最小峰间距
    )
    
    # 4. 提取特征
    features = {}
    for i, peak_idx in enumerate(peaks):
        features[f'peak_{i}_position'] = voltage[peak_idx]
        features[f'peak_{i}_height'] = ic_smooth[peak_idx]
        # 半峰宽
        left = properties['left_ips'][i]
        right = properties['right_ips'][i]
        features[f'peak_{i}_width'] = voltage[right] - voltage[left]
        # 峰面积(近似梯形积分)
        area = np.trapz(
            ic_smooth[left:right], 
            voltage[left:right]
        )
        features[f'peak_{i}_area'] = area
    
    return features

避坑指南:我曾经吃过一个亏——直接用原始数据算dQ/dV,结果噪声把峰值全淹了。后来养成了习惯:先平滑,再求导。顺序不能反。

4.3 特征相关性分析与筛选

特征多了不是好事。你想想看,如果两个特征高度相关,它们其实在说同一件事。我见过有人一口气提取了50多个IC特征,结果模型过拟合得一塌糊涂。

我的做法是:

  • 计算相关系数矩阵——皮尔逊或斯皮尔曼
  • 设定阈值——相关系数 > 0.9 的,只保留一个
  • 结合业务理解——有些特征虽然相关,但物理意义不同,要斟酌

举个例子:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# 假设features_df是特征数据框
corr_matrix = features_df.corr(method='spearman')

# 找出高相关对
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
    for j in range(i+1, len(corr_matrix.columns)):
        if abs(corr_matrix.iloc[i, j]) > 0.9:
            high_corr_pairs.append(
                (corr_matrix.columns[i], 
                 corr_matrix.columns[j],
                 corr_matrix.iloc[i, j])
            )

print(f"发现 {len(high_corr_pairs)} 对高相关特征")

注意:相关性高不代表冗余。比如峰值位置和峰面积,虽然相关,但一个反映热力学,一个反映动力学。我一般会保留,除非模型表现明显变差。

4.4 特征归一化与标准化

这一步很多人会忽略,但实际影响很大。为什么?

你想想看,峰值位置是3.5V左右,峰面积可能是0.5,高度可能是2.0。量纲不同,直接扔进模型,大数值的特征会主导学习过程。

我常用的两种方法:

方法 公式 适用场景
Min-Max归一化 (x - min) / (max - min) 特征有明确边界,如电压范围
Z-score标准化 (x - mean) / std 特征分布近似正态,无边界约束

我个人习惯:IC峰值位置用Min-Max,因为电压范围固定(比如3.0V-4.2V)。峰高和峰面积用Z-score,因为它们受电池容量衰减影响,分布会漂移。

from sklearn.preprocessing import MinMaxScaler, StandardScaler

# 峰值位置 - 归一化
pos_scaler = MinMaxScaler(feature_range=(0, 1))
peak_pos_norm = pos_scaler.fit_transform(peak_pos_features)

# 峰高/面积 - 标准化
height_scaler = StandardScaler()
peak_height_std = height_scaler.fit_transform(peak_height_features)

小技巧:标准化时,记得用训练集的均值和标准差去变换测试集。我见过有人把整个数据集一起标准化,结果造成数据泄露,模型评估虚高。

4.5 多维特征融合策略

单一IC特征不够用,怎么办?融合。

我常用的融合策略有三种:

  • 串联融合——直接把IC特征和电压特征、温度特征拼起来。简单粗暴,但特征维度会膨胀。
  • 加权融合——根据特征重要性给权重。比如用随机森林的特征重要性排序,前10个特征权重高一些。
  • 降维融合——用PCA或自编码器把高维特征压缩。适合特征数量超过样本数量的情况。

举个例子,串联融合的代码:

import numpy as np

# IC特征 (N_samples, N_ic_features)
ic_features = extract_all_ic_features(data)

# 基础特征 (N_samples, N_base_features)
base_features = extract_base_features(data)

# 串联融合
fused_features = np.hstack([ic_features, base_features])

print(f"融合后特征维度: {fused_features.shape[1]}")

不过我要提醒一句:不是特征越多越好。我做过一个实验,IC特征从10个增加到20个,模型精度只提升了0.5%,但训练时间翻了一倍。性价比不高。

4.6 本章知识体系

为了让你更直观地理解,我画了一张流程图:

IC曲线特征工程流程 原始充放电数据 dQ/dV 计算 + 平滑滤波 峰值位置 | 峰高 | 峰宽 | 峰面积 相关性分析 → 特征筛选 → 归一化/标准化 多维特征融合 → 模型输入 特征工程流水线

这张图把整个流程串起来了。从原始数据到最终模型输入,每一步都有讲究。我个人觉得,特征工程占整个项目60%的工作量,模型反而是相对简单的部分。

总结一下:IC曲线特征提取的核心是峰值的位置、高度和面积。特征筛选要兼顾统计相关性和物理意义。归一化标准化不能省,融合策略要根据数据量灵活选择。

好了,这一章就到这里。下一章我们聊聊特征工程里另一个重要话题——时间序列特征的构造方法。

专注资料整理