📋 任务背景

随着数字化转型的不断深入,企业网络规模不断扩大,各类业务系统、物联网设备以及云服务大量接入网络环境,使得网络流量规模呈指数级增长。在复杂的网络环境中,大量正常业务流量与潜在的恶意行为混合在一起,例如扫描行为、暴力破解、僵尸网络通信以及拒绝服务攻击等。不同攻击行为之间往往存在一定的特征重叠,同时网络环境也会随着时间发生变化。因此,如何从复杂的网络流量数据中准确识别不同类型的安全事件,是网络安全领域中的重要问题。
竞赛聚焦于基于网络安全事件智能分类,参赛者需要利用机器学习或深度学习方法,对网络流量样本进行分析,并准确识别其所属的安全事件类别。竞赛旨在推动数据驱动的网络安全分析技术发展,探索更加高效、智能的网络安全事件检测方法。竞赛数据通过对原始流量数据进行特征提取与匿名化处理,形成网络流量统计特征数据。

任务目标

基于网络流量统计特征数据,构建多分类模型,准确识别12类网络安全事件(class_0 ~ class_11)。

数据规模

数据集 样本数 特征维度
训练集 53,477 50维匿名化流量特征
测试集 19,440 50维匿名化流量特征

评估指标

  • 宏平均F1分数(Macro F1):对所有类别平等对待,要求模型在每个类别上都有良好表现

核心挑战

  • 50维匿名化特征,无法利用领域知识
  • 训练集与测试集存在显著分布偏移(对抗验证AUC≈0.87)
  • 类别不完全平衡(样本数从2,665到5,576不等)

🧠 整体技术路线

本方案采用 “海量特征工程 + 高置信度伪标签 + 多模型集成 + 无泄漏阈值优化” 的四阶段策略。核心思想是:先通过特征工程极大地扩充信息量,再利用伪标签让模型间接学习测试集分布,最后通过多模型融合与无泄漏的阈值优化最大化宏平均F1。

原始特征 (50维)
    ↓ 统计特征 + 无监督特征 + 交互特征
增强特征 (500+维)
    ↓ 互信息特征选择
精选特征 (80维)
    ↓ 第一阶段:5折CV + 极高置信度伪标签
扩充训练集 (原始 + 伪标签)
    ↓ 第二阶段:5种模型融合
OOF + 测试集预测
    ↓ 无泄漏阈值优化(仅用原始训练集OOF)
最终提交

🧬 特征工程:从50维到500+维的蜕变

特征工程是本次方案最核心的驱动力,分为三个层次。

1. 统计特征(7维)

对每行样本计算跨特征的统计量,捕获样本的整体分布特性。

特征 含义
sum_features 所有特征值的总和
mean_features 所有特征值的均值
std_features 所有特征值的标准差
max_features 所有特征值的最大值
min_features 所有特征值的最小值
skew_features 特征间的偏度
kurt_features 特征间的峰度
df_out['sum_features'] = df.sum(axis=1)
df_out['mean_features'] = df.mean(axis=1)
df_out['std_features'] = df.std(axis=1)
df_out['skew_features'] = df.skew(axis=1)
df_out['kurt_features'] = df.kurtosis(axis=1)

2. 无监督特征(20维)

通过聚类和降维方法,从全局视角挖掘数据的内在结构。

  • PCA降维(5维):提取全局主成分,捕获线性结构
  • KMeans聚类距离(15维):将样本映射到15个聚类中心,计算到每个中心的距离作为新特征
pca = PCA(n_components=5, random_state=42)
kmeans = KMeans(n_clusters=15, random_state=42, n_init=10)

3. 高阶交互特征(420维)

基于互信息分析选出的15个最关键特征,两两之间生成四则运算特征:

  • 加法特征 (A + B):捕获协同关系
  • 减法特征 (A - B):捕获相对差异
  • 乘法特征 (A × B):捕获放大效应
  • 除法特征 (A ÷ B):捕获比例关系

组合数=C152×4=105×4=420组合数 = C_{15}^2 \times 4 = 105 \times 4 = 420组合数=C152×4=105×4=420

X[f'{col1}_plus_{col2}'] = X[col1] + X[col2]
X[f'{col1}_minus_{col2}'] = X[col1] - X[col2]
X[f'{col1}_mult_{col2}'] = X[col1] * X[col2]
X[f'{col1}_div_{col2}'] = X[col1] / (X[col2] + 1e-5)

🎯 特征选择

面对500+维的庞大数据集,使用**互信息(Mutual Information)**评估每个特征与标签的依赖关系,精选Top80特征入模。

选择互信息的优势

  • 能捕获非线性依赖关系
  • 不依赖模型假设
  • 比皮尔逊相关系数更适合复杂匿名数据
selector = SelectKBest(mutual_info_classif, k=80)
X_selected = selector.fit_transform(X_scaled, y_encoded)

🏗️ 模型训练与优化策略

LightGBM 参数配置

lgb_params = {
    'objective': 'multiclass',
    'num_class': 12,
    'metric': 'multi_logloss',
    'boosting_type': 'gbdt',
    'num_leaves': 63,
    'max_depth': 8,
    'learning_rate': 0.05,
    'min_child_samples': 50,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'reg_alpha': 0.15,
    'reg_lambda': 0.15,
    'is_unbalance': True
}

第一阶段:高置信度伪标签

采用5折交叉验证对测试集进行预测,取平均概率。仅选择预测概率 > 0.99的样本作为“高置信度伪标签”加入训练集。

CONFIDENCE_THRESHOLD = 0.99
max_probs = np.max(test_preds_stage1, axis=1)
pseudo_indices = np.where(max_probs > CONFIDENCE_THRESHOLD)[0]

设计考量

  • 极高阈值(0.99):确保伪标签的准确性,防止噪声
  • 领域自适应:让模型间接学习测试集分布,缓解分布偏移

第二阶段:5-Seed Model Blending

使用5个不同随机种子(42, 77, 888, 2026, 9999)训练模型,将预测结果平均融合。

seeds = [42, 77, 888, 2026, 9999]
blended_test_preds = sum(各种子预测) / len(seeds)

集成优势

  • 不同种子 → 不同采样顺序 → 不同树结构
  • 平均多个模型预测 → 降低方差,提升泛化性

⚖️ 无泄漏阈值优化(关键优化点)

问题识别

如果将伪标签数据也用于阈值优化,会导致严重的分数虚高(本地分数虚高,线上无效)。

解决方案

阈值优化过程严格隔离,仅使用原始真实标签训练集的OOF(Out-of-Fold)预测进行搜索

def optimize_thresholds(y_true, y_pred_proba):
    def f1_opt(weights):
        weighted_preds = y_pred_proba * weights
        pred_labels = np.argmax(weighted_preds, axis=1)
        return -f1_score(y_true, pred_labels, average='macro')
    
    init_weights = [1.0] * 12
    res = minimize(f1_opt, init_weights, method='Nelder-Mead')
    return np.abs(res.x)

# 严格截取:只用原始训练集部分
pure_train_oof = blended_oof_preds[:ORIGINAL_TRAIN_LEN]
best_weights = optimize_thresholds(y_encoded, pure_train_oof)

优化后,每个类别的预测概率乘以最优权重,再取argmax作为最终标签。


📊 实验结果

本地交叉验证性能

类别 Precision Recall F1-score
class_0 0.87 0.91 0.89
class_1 0.98 0.94 0.96
class_2 0.77 0.80 0.78
class_3 0.83 0.73 0.78
class_4 0.92 0.96 0.94
class_5 0.97 0.99 0.98
class_6 0.98 0.98 0.98
class_7 0.94 0.93 0.94
class_8 0.95 0.99 0.97
class_9 0.95 0.92 0.93
class_10 0.94 0.94 0.94
class_11 1.00 0.98 0.99
  • 宏平均F1:0.92
  • 加权平均F1:0.93

线上得分

方案 线上 Macro F1
最终方案 0.71703

完整的python代码

import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import RobustScaler, LabelEncoder
from sklearn.feature_selection import SelectKBest, mutual_info_classif
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.metrics import f1_score
from scipy.optimize import minimize
import warnings

warnings.filterwarnings('ignore')

print("========== 1-4. 数据加载与神级特征重组 ==========")
train = pd.read_csv('train_data.csv')
test = pd.read_csv('test_data.csv')

feature_cols = [col for col in train.columns if col not in ['id', 'label']]
X = train[feature_cols].astype(float)
y = train['label']
X_test = test[feature_cols].astype(float)

le = LabelEncoder()
y_encoded = le.fit_transform(y)


# 基础统计
def add_stat_features(df):
    df_out = df.copy()
    df_out['sum_features'] = df.sum(axis=1)
    df_out['mean_features'] = df.mean(axis=1)
    df_out['std_features'] = df.std(axis=1)
    df_out['max_features'] = df.max(axis=1)
    df_out['min_features'] = df.min(axis=1)
    df_out['skew_features'] = df.skew(axis=1)
    df_out['kurt_features'] = df.kurtosis(axis=1)
    return df_out


X = add_stat_features(X)
X_test = add_stat_features(X_test)

# 无监督特征
scaler_unsup = RobustScaler()
X_scaled_unsup = scaler_unsup.fit_transform(X)
X_test_scaled_unsup = scaler_unsup.transform(X_test)

pca = PCA(n_components=5, random_state=42)
pca_train = pca.fit_transform(X_scaled_unsup)
pca_test = pca.transform(X_test_scaled_unsup)

for i in range(5):
    X[f'pca_{i}'] = pca_train[:, i]
    X_test[f'pca_{i}'] = pca_test[:, i]

kmeans = KMeans(n_clusters=15, random_state=42, n_init=10)
kmeans.fit(X_scaled_unsup)

kmeans_dist_train = kmeans.transform(X_scaled_unsup)
kmeans_dist_test = kmeans.transform(X_test_scaled_unsup)

for i in range(15):
    X[f'kmeans_dist_{i}'] = kmeans_dist_train[:, i]
    X_test[f'kmeans_dist_{i}'] = kmeans_dist_test[:, i]

# 精英交互
top15_cols = ['behavior_template_activity', 'behavior_template_control', 'control_signal_intensity',
              'protocol_variation_level', 'direction_gap_dispersion', 'direction_rate_gap', 'behavior_template_time',
              'behavior_template_volume', 'protocol_mix_entropy', 'payload_irregularity', 'behavior_compactness_score',
              'traffic_irregularity', 'pattern_concentration', 'payload_unit_dispersion', 'volume_irregularity']

for i in range(len(top15_cols)):
    for j in range(i + 1, len(top15_cols)):
        col1, col2 = top15_cols[i], top15_cols[j]
        X[f'{col1}_plus_{col2}'] = X[col1] + X[col2]
        X[f'{col1}_minus_{col2}'] = X[col1] - X[col2]
        X[f'{col1}_mult_{col2}'] = X[col1] * X[col2]
        X[f'{col1}_div_{col2}'] = X[col1] / (X[col2] + 1e-5)

        X_test[f'{col1}_plus_{col2}'] = X_test[col1] + X_test[col2]
        X_test[f'{col1}_minus_{col2}'] = X_test[col1] - X_test[col2]
        X_test[f'{col1}_mult_{col2}'] = X_test[col1] * X_test[col2]
        X_test[f'{col1}_div_{col2}'] = X_test[col1] / (X_test[col2] + 1e-5)

print("========== 5. 特征缩放与互信息优选 ==========")
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)
X_test_scaled = scaler.transform(X_test)

selector = SelectKBest(mutual_info_classif, k=80)
X_selected = selector.fit_transform(X_scaled, y_encoded)
X_test_selected = selector.transform(X_test_scaled)
print(f"最终入模特征数量: {X_selected.shape[1]}")

# 【修复点1】:退回稳定高效的对数损失评估,保持 0.05 的安全学习率,适度微调树参数
lgb_params = {
    'objective': 'multiclass', 'num_class': 12, 'metric': 'multi_logloss',
    'boosting_type': 'gbdt', 'is_unbalance': True, 'verbosity': -1,
    'n_estimators': 2500, 'learning_rate': 0.05,
    'num_leaves': 63, 'max_depth': 8, 'min_child_samples': 50,
    'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0.15,
    'reg_lambda': 0.15, 'min_split_gain': 0.01
}

n_folds = 5

print("\n========== 阶段一:提取高置信度伪标签 ==========")
skf_base = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
test_preds_stage1 = np.zeros((len(X_test_selected), 12))

base_params = lgb_params.copy()
base_params['random_state'] = 42

for train_idx, valid_idx in skf_base.split(X_selected, y_encoded):
    X_tr, X_val = X_selected[train_idx], X_selected[valid_idx]
    y_tr, y_val = y_encoded[train_idx], y_encoded[valid_idx]

    train_data = lgb.Dataset(X_tr, label=y_tr)
    valid_data = lgb.Dataset(X_val, label=y_val, reference=train_data)

    model = lgb.train(
        base_params, train_data, valid_sets=[train_data, valid_data],
        callbacks=[lgb.early_stopping(100, verbose=False)]
    )
    test_preds_stage1 += model.predict(X_test_selected, num_iteration=model.best_iteration) / n_folds

CONFIDENCE_THRESHOLD = 0.99
max_probs = np.max(test_preds_stage1, axis=1)
pseudo_indices = np.where(max_probs > CONFIDENCE_THRESHOLD)[0]

pseudo_X = X_test_selected[pseudo_indices]
pseudo_y = np.argmax(test_preds_stage1[pseudo_indices], axis=1)
print(f"成功提取 {len(pseudo_indices)} 个高置信度测试样本")

# 记录原始训练集的长度,用于后续修复阈值寻优
ORIGINAL_TRAIN_LEN = len(X_selected)

X_combined = np.vstack((X_selected, pseudo_X))
y_combined = np.concatenate((y_encoded, pseudo_y))

print("\n========== 阶段二:合并数据后的 5 种子模型融合训练 ==========")
# 使用 5 个种子
seeds = [42, 77, 888, 2026, 9999]
blended_test_preds = np.zeros((len(X_test_selected), 12))
blended_oof_preds = np.zeros((len(X_combined), 12))

for i, seed in enumerate(seeds):
    print(f"\n>>> 启动 Seed {seed} ({i + 1}/{len(seeds)}) <<<")

    current_params = lgb_params.copy()
    current_params['random_state'] = seed

    skf_combined = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=seed)
    current_seed_test_preds = np.zeros((len(X_test_selected), 12))
    current_seed_oof = np.zeros((len(X_combined), 12))

    for fold_idx, (train_idx, valid_idx) in enumerate(skf_combined.split(X_combined, y_combined)):
        X_tr, X_val = X_combined[train_idx], X_combined[valid_idx]
        y_tr, y_val = y_combined[train_idx], y_combined[valid_idx]

        train_data = lgb.Dataset(X_tr, label=y_tr)
        valid_data = lgb.Dataset(X_val, label=y_val, reference=train_data)

        model = lgb.train(
            current_params, train_data, valid_sets=[train_data, valid_data],
            callbacks=[lgb.early_stopping(100, verbose=False)]
        )

        current_seed_oof[valid_idx] = model.predict(X_val, num_iteration=model.best_iteration)
        current_seed_test_preds += model.predict(X_test_selected, num_iteration=model.best_iteration) / n_folds

    blended_oof_preds += current_seed_oof / len(seeds)
    blended_test_preds += current_seed_test_preds / len(seeds)

print("\n========== 阶段三:修复泄漏的 Macro F1 阈值寻优 ==========")

def optimize_thresholds(y_true, y_pred_proba):
    def f1_opt(weights):
        weights = np.abs(weights)
        weighted_preds = y_pred_proba * weights
        pred_labels = np.argmax(weighted_preds, axis=1)
        return -f1_score(y_true, pred_labels, average='macro')

    init_weights = [1.0] * 12
    res = minimize(f1_opt, init_weights, method='Nelder-Mead', options={'maxiter': 400})
    return np.abs(res.x)


print("正在计算最优类别切分阈值 (仅使用原始真实数据以防泄漏)...")
pure_train_oof = blended_oof_preds[:ORIGINAL_TRAIN_LEN]
pure_train_y = y_combined[:ORIGINAL_TRAIN_LEN]

best_weights = optimize_thresholds(pure_train_y, pure_train_oof)

final_test_preds_weighted = blended_test_preds * best_weights
final_labels = np.argmax(final_test_preds_weighted, axis=1)
final_categories = le.inverse_transform(final_labels)

submission = pd.DataFrame({
    'id': test['id'],
    'label': final_categories
})

submission.to_csv('submission.csv', index=False, encoding='utf-8')
print("提交文件已生成 'submission.csv'。")

💡 关键经验总结

  1. 特征工程是核心:匿名化数据上,统计+无监督+交互的三层特征体系是提分关键。
  2. 伪标签需要极高阈值:0.99确保质量,避免噪声污染。
  3. 多模型融合简单有效:5-Seed Blending比复杂Stacking更稳定。
  4. 阈值优化必须隔离只用原始训练集OOF进行搜索,是防止分数虚高、确保线上有效的关键。
  5. 对抗分布偏移需综合手段:特征增强 + 伪标签 + 多模型集成三管齐下。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐