You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决多列LabelEncoder保存后加载编码结果不一致问题

问题描述

我编写了一个预处理函数,使用LabelEncoder处理DataFrame中的多列分类变量(categorical_cols),将每列对应的LabelEncoder存入字典后通过pickle保存为label_encoders.pkl。但加载后在测试集(测试数据取自原数据集)中使用fit_transform处理时,编码结果与原处理结果不一致。

保存代码

# LabelEncoder Save Side
for column in categorical_cols:
        label_encoder = LabelEncoder()
        taken_df[column] = label_encoder.fit_transform(taken_df[column])
        label_encoders[column] = label_encoder
        
with open('label_encoders.pkl', 'wb') as file:
    pickle.dump(label_encoders, file)

加载代码

# LabelEncoder Load Side
categorical_cols = ['from_city', 'to_city',"vehicle_type","trailer_type"]
    
with open('label_encoders.pkl', 'rb') as file:
     label_encoders = pickle.load(file)

for column in categorical_cols:
     test_df[column] = label_encoders[column].fit_transform(test_df[column])

我还尝试过用单个LabelEncoder处理所有列后保存,同样无效。现提出两个问题:

  1. 是否可以通过字典方式保存多列的LabelEncoder,还是需要为每列单独保存pickle文件?
  2. 如何解决当前编码结果不一致的问题?

完整预处理函数

def preprocessed_data(taken_df):
    
    used_cols = [....]
    taken_df = taken_df[used_cols]
    taken_df["weight"] = taken_df["weight"].str.replace(",",".")
    taken_df["weight"] = taken_df["weight"].astype(float)
    taken_df.dropna(inplace=True)
    
    # 处理日期时间列
    taken_df["offer_date"] = pd.to_datetime(taken_df["offer_date"]).dt.tz_localize(None).dt.tz_localize("UTC")
    taken_df["cargo_load_date"] = pd.to_datetime(taken_df["cargo_load_date"]).dt.tz_localize(None).dt.tz_localize("UTC")
    taken_df["cargo_delivery_date"] = pd.to_datetime(taken_df["cargo_delivery_date"]).dt.tz_localize(None).dt.tz_localize("UTC")
    taken_df["vehicle_assignment_date"] = pd.to_datetime(taken_df["vehicle_assignment_date"]).dt.tz_localize(None).dt.tz_localize("UTC")
    
     
    vehicle_types = {
        "(?i).*(Tir|Tır).*":"TIR",
        "(?i).*(Kamyon)":"Kamyon"
    }
    
    taken_df.loc[:,
    "vehicle_type"] = taken_df.loc[:,
    "vehicle_type"].replace(vehicle_types,regex=True)
      
    # 提取分类列
    categorical_cols = ['from_city', 'to_city',"vehicle_type","trailer_type"]
    
    label_encoders = {}
    
    for column in categorical_cols:
        label_encoder = LabelEncoder()
        taken_df[column] = label_encoder.fit_transform(taken_df[column])
        label_encoders[column] = label_encoder
        
    with open('label_encoders.pkl', 'wb') as file:
        pickle.dump(label_encoders, file)

    # 权重系数
    weight_factor = 0.6
    delivery_time_factor = 0.4
    offer_date_factor = 0.2
    
    # 将报价日期转为UNIX时间戳
    taken_df['offer_date'] = pd.to_datetime(taken_df['offer_date'])
    epoch = dt.datetime(1970, 1, 1, tzinfo=pytz.UTC)
    taken_df['unix_offer_date'] = (taken_df['offer_date'] - epoch).dt.total_seconds()
    
    # 将交付日期转为UNIX时间戳
    taken_df['cargo_delivery_date'] = pd.to_datetime(taken_df['cargo_delivery_date'])
    taken_df['unix_delivery_time'] = (taken_df['cargo_delivery_date'] - epoch).dt.total_seconds()
    
    # 用MinMaxScaler做归一化
    scaler = MinMaxScaler()
    
    # 归一化重量列
    taken_df['normalized_weight'] = scaler.fit_transform(taken_df['weight'].values.reshape(-1, 1))
    
    # 归一化UNIX时间戳
    taken_df['normalized_offer_date'] = scaler.fit_transform(taken_df['unix_offer_date'].values.reshape(-1, 1))
    taken_df['normalized_delivery_time'] = scaler.fit_transform(taken_df['unix_delivery_time'].values.reshape(-1, 1))
    
    with open('scaler.pkl', 'wb') as f:
        pickle.dump(scaler, f)
    
    # 计算优先级得分
    taken_df['priority_score'] = (weight_factor * taken_df['normalized_weight']) + (offer_date_factor * taken_df['normalized_offer_date']) + (delivery_time_factor * taken_df['normalized_delivery_time'])
    
    return taken_df

尝试过的无效方式

encoder = LabelEncoder()
for col in categorical_cols:
    taken_df[col] = encoder.fit_transform(taken_df[col])
    
with open('encoder.pkl', 'wb') as f:
    pickle.dump(encoder, f)

问题解答

1. 能否用字典保存多列LabelEncoder?

完全可以用字典方式保存多列对应的LabelEncoder,这是合理且高效的做法,不需要为每列单独保存pickle文件。字典可以清晰映射每列与其对应的编码器,加载和使用都很便捷。

2. 编码结果不一致的解决方法

问题核心在于测试集处理时误用了fit_transform:

原因分析

  • 训练集处理时,fit_transform()会让编码器拟合当前列的分类值,建立映射关系(比如"Istanbul"→0,"Ankara"→1),并完成转换。
  • 测试集处理时如果再调用fit_transform(),编码器会重新拟合测试集的分类值,生成新的映射(比如测试集只有"Ankara"时,它会被编码为0),这就导致和训练集的编码规则不一致。

修正后的加载代码

测试集处理时只需调用transform()方法,直接复用训练时拟合好的映射规则:

# LabelEncoder Load Side 修正版
categorical_cols = ['from_city', 'to_city',"vehicle_type","trailer_type"]
    
with open('label_encoders.pkl', 'rb') as file:
     label_encoders = pickle.load(file)

for column in categorical_cols:
     # 仅使用transform,禁止用fit_transform
     test_df[column] = label_encoders[column].transform(test_df[column])

额外提示

  • 单个LabelEncoder处理多列的方式本身就不合理:LabelEncoder是为单一特征设计的,每次fit()都会覆盖之前的映射关系,保存的编码器只会记住最后一列的规则,处理其他列必然出错,这种方式应该直接弃用。
  • 你的归一化流程也存在相同问题:训练集上用同一个MinMaxScaler对多列做fit_transform(),但保存的scaler只记住了最后一列的缩放规则,测试集处理时会出错。建议为每个需要归一化的列单独保存scaler,或者用ColumnTransformer统一管理预处理流程。

内容的提问来源于stack exchange,提问作者Zyxnon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 10:53:19