从Linux内核日志到故障预警:我是如何用PyTorch搞定天池智能运维赛题的

第一次打开那个包含490469行日志的CSV文件时,密密麻麻的布尔值列让我头皮发麻。这是天池平台智能运维竞赛提供的服务器内存故障数据,28列中24列都是代表不同故障模板的布尔标记。作为参加过三届Kaggle的老兵,我意识到这次挑战的独特之处——它不像结构化数据预测那样有明确的特征工程路径,更像是在一堆杂乱无章的服务器"呼救信号"中寻找规律。

1. 数据迷雾中的第一束光

原始数据中的memory_sample_kernel_log_*.csv文件记录着服务器内核抛出的各种错误信息,每个布尔列对应一个预定义的错误模板。但棘手的是,组织方明确说明这些模板不一定都与DRAM故障相关。我的第一个决策点出现了:是直接使用所有24个布尔特征,还是先做特征筛选?

关键数据预处理步骤:

def etl(path, agg_time):
    data = pd.read_csv(os.path.join(PARENT_FOLDER, path))
    data['collect_time'] = pd.to_datetime(data['collect_time']).dt.ceil(agg_time)
    return data.groupby(['serial_number','collect_time'],as_index=False).agg('sum')

这个5分钟聚合函数成了我的突破口。将原始日志按服务器序列号和时间窗口聚合后,True/False被转换为计数,突然让数据有了时序特征的味道。但真正的转折点出现在分析标签分布时——正样本占比不足0.5%,典型的类别不平衡问题。

处理样本不平衡时,下采样多数类往往比过采样少数类更不容易导致过拟合

我最终选择保留全部布尔特征,原因有二:一是初赛时间有限;二是发现某些冷门模板(如3_hwerr_mce_l)在故障发生前出现频率会突然升高。这个直觉后来被证明是对的——在A榜取得了前3%的成绩。

2. 特征工程的蝴蝶效应

在实验室通宵的第三个晚上,我发现了原始方案的重大缺陷:直接对布尔值求和会丢失模板出现的先后顺序信息。于是诞生了第二版特征工程方案:

  1. 滑动窗口统计:计算每个5分钟窗口内各模板的累计出现次数
  2. 变化率特征:记录相邻时间窗口间各模板计数的变化幅度
  3. 组合特征:对常同时出现的模板组合(如1_hwerr_e和2_hwerr_c)创建交叉特征
# 变化率特征示例
def add_delta_features(df):
    for col in df.columns[2:-3]:  # 排除非模板列
        df[f'{col}_delta'] = df.groupby('serial_number')[col].diff().fillna(0)
    return df

这个改进让模型AUC提升了0.15,但也带来了新问题——特征维度爆炸到72维。为解决这个问题,我不得不重构数据管道:

处理阶段原始方案优化方案
特征提取24维布尔和72维动态特征
特征选择保留全部基于互信息筛选
归一化无RobustScaler

3. 模型选择的艺术与科学

面对处理后的数据,我测试了三种架构:

  1. 基准MLP:简单的三层全连接网络
  2. LSTM:捕捉时序依赖关系
  3. 集成模型:MLP+LightGBM组合

最终选择MLP并非因为它最优,而是考虑到:

  • 比赛对推理速度有严格要求
  • 其他模型提升有限(<2%)但复杂度大增
  • MLP更容易解释故障触发逻辑
class DRAMPredictor(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, 64)
        self.bn1 = nn.BatchNorm1d(64)
        self.fc2 = nn.Linear(64, 32)
        self.dropout = nn.Dropout(0.3)
        self.out = nn.Linear(32, 2)
        
    def forward(self, x):
        x = F.relu(self.bn1(self.fc1(x)))
        x = self.dropout(F.relu(self.fc2(x)))
        return self.out(x)

这个看似简单的网络包含几个关键设计:

  • BatchNorm层加速收敛
  • Dropout防止过拟合
  • 输出层不做softmax(与CrossEntropyLoss配合)

4. 训练过程中的血泪教训

最初直接训练时,模型完全学不到有效模式。通过分析发现三个致命问题:

问题排查清单:

  • 学习率过高(>0.01)导致损失震荡
  • 没有早停机制导致过拟合
  • 批量归一化层在验证集泄露

解决方案是引入渐进式学习率预热:

optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4)
scheduler = torch.optim.lr_scheduler.OneCycleLR(
    optimizer, 
    max_lr=1e-3,
    steps_per_epoch=len(train_loader),
    epochs=100
)

另一个突破是采用加权损失函数处理类别不平衡:

pos_weight = torch.tensor([10.0])  # 正样本权重
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)

在最终提交的模型中,这些技巧组合使用使得F1分数从0.31提升到0.68。但比赛结束后复盘时,我发现如果加入更多业务逻辑(如特定厂商服务器的故障模式差异),效果还能再提升5-8%。这让我深刻意识到:在工业场景中,领域知识有时比算法技巧更重要。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐