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

Keras自定义Sequential子类:重写fit/predict、保存加载及标签编码实现

问题背景

我继承了keras.models.Sequential类,重写了fit()和predict()方法,目的是封装sklearn的LabelEncoder,这样调用fit()和predict()时可以直接传入任意标签组成的y数组,无需先将标签转换为[0,1,...,num_classes-1]范围内的整数。

我的实现代码如下:

import numpy as np

from keras.models import Sequential
from keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder

class SuperSequential(Sequential):

  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    self.encoder = LabelEncoder()

  def fit(self, X: np.ndarray, y: np.ndarray, **kwargs) -> Sequential:
    y_enc = self.encoder.fit_transform(y)
    y_enc = to_categorical(y_enc, len(np.unique(y_enc)))

    return super().fit(X, y_enc)

  def predict(self, X: np.ndarray) -> np.ndarray:
    y_pred = super().predict(X)
    y_pred = np.argmax(y_pred , axis=1)

    return self.label_encoder.inverse_transform(y_pred)

但这个实现存在问题:我想用keras.models.save_model()保存训练好的模型,再通过keras.models.load_model()加载,但加载后的模型总是基类Sequential,不包含我重写的fit()和predict()方法。

更新:后来发现加载时传入custom_objects参数后,加载出来的确实是SuperSequential类型,但LabelEncoder的拟合状态丢失了:

keras.models.load_model("model_path", custom_objects={"SuperSequential": SuperSequential})

另外我注意到Keras有keras.layers.IntegerLookup这类预处理层,似乎能实现我的需求,但不知道如何将其作为Sequential模型的一部分完成标签编码。

我的具体问题:

  1. 能否保存并加载Sequential的子类?如果可以,该怎么做?
  2. 如何用Keras的预处理层(比如keras.layers.IntegerLookup)实现和子类相同的效果?
  3. 由于相关参考资料较少,我不确定当前思路是否合理,有没有更好的方法实现我的需求?

解答

1. 保存并加载Sequential子类的方法

可以实现,但默认模型保存机制不会自动保存自定义属性(比如你的self.encoder),需要手动实现序列化与反序列化逻辑:

步骤1:重写get_config()方法

在SuperSequential类中添加该方法,保存encoder的拟合状态:

def get_config(self):
    config = super().get_config()
    # 保存LabelEncoder的类标签
    config["encoder_classes"] = self.encoder.classes_.tolist()
    return config

步骤2:添加类方法from_config()

从加载的配置中恢复encoder的状态:

@classmethod
def from_config(cls, config):
    # 先恢复父类模型配置
    model = super().from_config(config)
    # 重建LabelEncoder并恢复类标签
    model.encoder = LabelEncoder()
    model.encoder.classes_ = np.array(config["encoder_classes"])
    return model

步骤3:加载模型

现在使用load_model时传入custom_objects,即可完整恢复模型与encoder的状态:

keras.models.load_model("model_path", custom_objects={"SuperSequential": SuperSequential})

注意:如果LabelEncoder还有其他自定义状态,需在get_config中一并保存。

2. 用Keras预处理层实现标签编码

使用IntegerLookup和CategoryEncoding层可以将标签处理逻辑整合到模型中,保存/加载时会自动携带预处理逻辑,无需额外处理:

完整代码示例

import numpy as np
import keras
from keras import layers

# 创建包含标签预处理的模型
def build_model(num_classes, label_vocab):
    # 特征输入分支
    input_features = layers.Input(shape=(10,))  # 假设输入特征为10维
    x = layers.Dense(32, activation='relu')(input_features)
    output = layers.Dense(num_classes, activation='softmax')(x)
    
    # 标签预处理分支(训练时使用)
    input_labels = layers.Input(shape=(), dtype='string')
    # 映射标签为整数索引
    label_lookup = layers.IntegerLookup(output_mode='int')
    label_lookup.adapt(np.array(label_vocab))
    encoded_labels = label_lookup(input_labels)
    # 转换为独热编码
    one_hot_labels = layers.CategoryEncoding(num_tokens=num_classes, output_mode='one_hot')(encoded_labels)
    
    # 训练模型:输入为特征+标签,输出为模型预测
    train_model = keras.Model(inputs=[input_features, input_labels], outputs=output)
    train_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
    
    # 预测模型:输入特征,输出原始标签
    pred_output = label_lookup.inverse(layers.Argmax(axis=1)(output))
    pred_model = keras.Model(inputs=input_features, outputs=pred_output)
    
    return train_model, pred_model

# 使用示例
label_vocab = ["cat", "dog", "bird"]
train_model, pred_model = build_model(3, label_vocab)

# 准备数据
X = np.random.rand(100, 10)
y = np.random.choice(label_vocab, size=100)

# 训练模型
train_model.fit([X, y], epochs=5)

# 预测
predictions = pred_model.predict(X[:5])
print(predictions)

# 保存与加载预测模型
pred_model.save("pred_model.h5")
loaded_pred_model = keras.models.load_model("pred_model.h5")
loaded_predictions = loaded_pred_model.predict(X[:5])
print(loaded_predictions)

说明

  • 该方案将标签编码/解码逻辑完全整合到模型中,保存与加载时自动携带预处理层的状态(如IntegerLookup的类标签)
  • 训练时可直接传入原始标签,模型内部完成编码;预测时直接输出原始标签,无需额外处理

3. 思路合理性与更优方案

你的原始子类化思路是可行的,但Keras预处理层方案更符合框架设计理念:

  • 预处理层可与模型一起保存、部署,避免训练与推理阶段的预处理不一致
  • 可利用Keras的adapt()方法自动拟合标签,无需手动维护LabelEncoder
  • 无额外sklearn依赖,更适合生产环境部署

更轻量的替代方案:
若不需要将预处理层整合到模型中,可单独使用keras.utils.StringLookup处理标签,再保存其状态:

from keras.utils import StringLookup

# 拟合标签
lookup = StringLookup(output_mode='int')
lookup.adapt(y)
# 编码标签并训练模型
y_enc = lookup(y)
model = Sequential([layers.Dense(32, activation='relu'), layers.Dense(3, activation='softmax')])
model.compile(optimizer='adam', loss='categorical_crossentropy')
model.fit(X, keras.utils.to_categorical(y_enc), epochs=5)

# 保存模型与lookup状态
model.save("model.h5")
np.save("lookup_vocab.npy", lookup.get_vocabulary())

# 加载与预测
loaded_model = keras.models.load_model("model.h5")
loaded_lookup = StringLookup(output_mode='int')
loaded_lookup.set_vocabulary(np.load("lookup_vocab.npy"))

y_pred = loaded_model.predict(X)
y_pred = loaded_lookup.inverse(np.argmax(y_pred, axis=1))

该方案比子类化更简洁,避免了子类化带来的序列化问题,同时保持了逻辑清晰。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 20:41:08