如何解决多列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处理所有列后保存,同样无效。现提出两个问题:
- 是否可以通过字典方式保存多列的
LabelEncoder,还是需要为每列单独保存pickle文件? - 如何解决当前编码结果不一致的问题?
完整预处理函数
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
相关产品推荐
相关产品推荐

