如果训练集和测试集分别独立编码,会导致编码不一致的问题,严重影响模型评估的准确性。
问题演示
import pandas as pd from sklearn.preprocessing import LabelEncoder # 假设训练集和测试集 train_df = pd.DataFrame({'label': ['猫', '狗', '猫', '鸟']}) test_df = pd.DataFrame({'label': ['狗', '鸟', '猫']}) # ❌ 错误做法:分别编码 le_train = LabelEncoder() train_encoded = le_train.fit_transform(train_df['label']) # 编码结果: 猫→0, 狗→1, 鸟→2 le_test = LabelEncoder() test_encoded = le_test.fit_transform(test_df['label']) # 编码结果: 狗→0, 鸟→1, 猫→2 ⚠️ 编码不一致! print("训练集编码:", dict(zip(train_df['label'], train_encoded))) # {'猫': 0, '狗': 1, '猫': 0, '鸟': 2} print("测试集编码:", dict(zip(test_df['label'], test_encoded))) # {'狗': 0, '鸟': 1, '猫': 2} ❌ 同一个'狗'在训练集是1,测试集是0正确的解决方案
方案1:在训练集上 fit,然后 transform 测试集(推荐)
from sklearn.preprocessing import LabelEncoder # 1. 只在训练集上训练编码器 le = LabelEncoder() y_train_encoded = le.fit_transform(train_df['label']) # 2. 用同一个编码器转换测试集 y_test_encoded = le.transform(test_df['label']) # ✅ 编码一致 print("训练集编码:", dict(zip(train_df['label'], y_train_encoded))) # {'猫': 0, '狗': 1, '猫': 0, '鸟': 2} print("测试集编码:", dict(zip(test_df['label'], y_test_encoded))) # {'狗': 1, '鸟': 2, '猫': 0} ✅ 编码一致方案2:合并编码后再拆分(适合小数据集)
# 合并所有标签 all_labels = pd.concat([train_df['label'], test_df['label']]) # 统一编码 le = LabelEncoder() le.fit(all_labels) # 分别转换 train_encoded = le.transform(train_df['label']) test_encoded = le.transform(test_df['label'])方案3:使用 OrdinalEncoder 处理新类别(处理未知类别)
如果测试集可能出现训练集没有的类别:
from sklearn.preprocessing import OrdinalEncoder import numpy as np # 处理为二维数组 train_labels = train_df[['label']] test_labels = test_df[['label']] # handle_unknown='use_encoded_value' 处理未知类别 oe = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1) y_train_encoded = oe.fit_transform(train_labels) # 测试集如果有新类别,会被编码为 -1 y_test_encoded = oe.transform(test_labels)方案4:自定义编码映射(最可控)
# 手动建立映射字典 unique_labels = train_df['label'].unique() label_to_num = {label: idx for idx, label in enumerate(unique_labels)} train_encoded = train_df['label'].map(label_to_num) test_encoded = test_df['label'].map(label_to_num) # 处理测试集中的未知类别 test_encoded = test_encoded.fillna(-1).astype(int) print("编码映射:", label_to_num) # {'猫': 0, '狗': 1, '鸟': 2}完整示例:集成到你的代码
根据你之前的代码,修改建议:
import pandas as pd from sklearn.preprocessing import LabelEncoder def encode_target_consistent(train_df, test_df, target_col): """ 对训练集和测试集的目标列进行一致的编码 """ # 检查目标列是否存在 if target_col not in train_df.columns or target_col not in test_df.columns: raise ValueError(f"目标列 '{target_col}' 在数据集中不存在") # 检查数据类型 y_train = train_df[target_col] y_test = test_df[target_col] if y_train.dtype == 'object' or y_train.dtype.name == 'category': print(f"🔤 目标变量 '{target_col}' 需要编码...") # 创建编码器 le = LabelEncoder() # 在训练集上 fit y_train_encoded = le.fit_transform(y_train) print(f" 训练集类别: {le.classes_.tolist()}") print(f" 编码映射: {dict(zip(le.classes_, range(len(le.classes_))))}") # 用同一个编码器转换测试集 try: y_test_encoded = le.transform(y_test) except ValueError as e: # 测试集出现新类别 print(f"⚠️ 测试集包含训练集未见的类别: {e}") # 处理方式1:将这些样本标记为-1 # 或者方式2:用众数填充 # 建议在数据预处理阶段就处理这个问题 # 使用 OrdinalEncoder 作为备选 from sklearn.preprocessing import OrdinalEncoder oe = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1) y_train_encoded = oe.fit_transform(y_train.values.reshape(-1, 1)).flatten() y_test_encoded = oe.transform(y_test.values.reshape(-1, 1)).flatten() print(f" 测试集新类别被编码为 -1") # 保存编码器以便后续解码 return y_train_encoded, y_test_encoded, le else: print(f"✅ 目标变量 '{target_col}' 已是数值类型,无需编码") return y_train.values, y_test.values, None # 使用示例 # 假设你已经拆分好了训练集和测试集 # train_df, test_df = train_test_split(df, test_size=0.2, random_state=42) target = 'label' y_train, y_test, encoder = encode_target_consistent(train_df, test_df, target) # 现在 y_train 和 y_test 的编码是一致的 print(f"训练集编码样本: {y_train[:5]}") print(f"测试集编码样本: {y_test[:5]}") # 如果需要解码预测结果(可选) def decode_predictions(predictions, encoder): """将预测的数值标签解码为原始标签""" if encoder is not None: return encoder.inverse_transform(predictions) return predictions # 例如:模型预测后解码 # y_pred = model.predict(X_test) # y_pred_original = decode_predictions(y_pred, encoder)关键原则
永远不要在测试集上
fit,只在训练集上fit,然后在测试集上transform
这不仅是编码的原则,也是整个机器学习流程的基本原则:测试集应该模拟未知数据,不应该泄露任何信息给模型。