Todos los productos
Search
Centro de documentos

OpenSearch:Guía para desarrolladores de modelos de ordenamiento personalizados

Última actualización: Sep 05, 2026

La función de modelo de ordenamiento personalizado permite desplegar en OpenSearch un modelo de ordenamiento de red neuronal profunda (DNN) basado en TensorFlow. Esta guía explica cómo implementar la clase CustomModel, configurar las columnas de funciones y cumplir los requisitos de compatibilidad para el inference en línea.

Cómo funciona

El framework construye un grafo de cómputo de TensorFlow mediante la llamada a la función build, la cual ejecuta en orden los métodos que se muestran a continuación:

  1. build_placeholder()

  2. build_model()

  3. setup_global_step()

  4. reg_loss()

  5. loss_op()

  6. update_op()

  7. training_op()

  8. predictions_op()

  9. mark_output()

  10. metrics_op()

  11. summary_op()

  12. trace_sample_op()

Debe implementar cinco de estos métodos. Los restantes usan la lógica predeterminada del framework; no los sobrescriba.

Requisitos previos

Antes de empezar, asegúrese de contar con:

  • Un entorno de TensorFlow 1.x en funcionamiento

  • Familiaridad con las columnas de funciones de TensorFlow y con el entrenamiento de modelos DNN

  • Acceso a los datos de entrenamiento y al esquema de funciones

Implemente la clase CustomModel

CustomModel debe heredar de BaseModel. El flujo de trabajo de alto nivel es el siguiente:

  1. Defina la clase y llame al constructor de la clase base.

  2. Implemente los cinco métodos obligatorios.

  3. Configure las columnas de funciones.

  4. Asigne las salidas a los atributos obligatorios (self.logits, self.loss, self.reg_loss).

Métodos obligatorios

Implemente exactamente estos cinco métodos; el framework se encarga de todo lo demás:

Método

Qué implementar

build_model()

Defina la arquitectura del modelo (capa de embedding, capas DNN, capa de logits)

update_op()

Recopile las operaciones de actualización de normalización por lotes de tf.GraphKeys.UPDATE_OPS

reg_loss()

Recopile las pérdidas de regularización de tf.GraphKeys.REGULARIZATION_LOSSES

loss_op()

Defina la pérdida de entrenamiento (por ejemplo, entropía cruzada sigmoide + pérdida de regularización)

training_op()

Configure el optimizador y cree self.train_op

Métodos que se deben dejar sin implementar

No implemente build_placeholder, mark_output ni trace_sample_op. Estos métodos dependen de la lógica del framework que garantiza la compatibilidad en línea.

Ejemplo de implementación

El ejemplo que se muestra a continuación presenta una implementación completa de CustomModel que usa funciones de embedding y una DNN de tres capas:

from collections import OrderedDict
import tensorflow as tf
from tensorflow.contrib import layers
from tensorflow.contrib.framework.python.ops import arg_scope
from tensorflow.python.framework import ops
from tensorflow.python.ops import variable_scope

from model_ops.tflog import tflogger as logging
import model_ops.optimizer_ops as myopt
from model.base_model import BaseModel
from model_ops import ops as base_ops
from model_ops import utils

class CustomModel(BaseModel):
    def __init__(self, config, name="CTR"):
        # Always call the parent constructor with this exact signature
        super(CustomModel, self).__init__(config, name)

        # TensorFlow variable collections for tracking DNN layers
        self.collections_dnn_hidden_layer = "{}_dnn_hidden_layer".format(self.name)
        self.collections_dnn_hidden_output = "{}_dnn_hidden_output".format(self.name)

        self.layer_dict = OrderedDict()

        # Configure embedding feature columns for sparse categorical features
        self.embedding_columns = ['feature1', 'feature2']
        for feature_name in self.embedding_columns:
            self.generate_embedding_feature_column(
                feature_name,
                hash_bucket_size=1000,
                dimension=16,
                initializer=tf.zeros_initializer,
                combiner="sum",
                is_share_embedding=False,
                shared_embedding_name=None
            )

        # Uncomment to add real-valued or sparse ID columns:
        # self.real_valued_columns = ['feature3', 'feature4']
        # for feature_name in self.real_valued_columns:
        #     self.generate_real_valued_feature_column(feature_name, dtype="Float", value_dimension=1)
        #
        # self.sparse_id_columns = ['feature5', 'feature6']
        # for feature_name in self.sparse_id_columns:
        #     self.generate_sparse_id_feature_column(feature_name, hash_bucket_size=1000, combiner="sum")

        # Model hyperparameters
        self.embedding_partition_size = 4 * 1024 * 1024
        self.dnn_partition_size = 64 * 1024
        self.dnn_l2_reg = 1e-6
        self.clip_gradients = 5.0
        self.dnn_hidden_units = [1024, 512, 256]

    def build_placeholder(self):
        try:
            self.is_training = tf.get_default_graph().get_tensor_by_name("training:0")
        except KeyError:
            self.is_training = tf.placeholder(tf.bool, name="training")

    def setup_global_step(self):
        global_step = tf.Variable(
            initial_value=0,
            name="global_step",
            trainable=False,
            dtype=tf.int64,
            collections=[tf.GraphKeys.GLOBAL_STEP, tf.GraphKeys.GLOBAL_VARIABLES])
        self.global_step = global_step

    def embedding_layer(self):
        with tf.variable_scope(
            name_or_scope="Embedding_Layer",
            partitioner=base_ops.partitioner(self.config.ps_num, self.embedding_partition_size),
            reuse=tf.AUTO_REUSE
        ) as scope:
            logging.info('ps num: {}, embedding partition size: {} \n scope: {}'.format(
                self.config.ps_num, self.embedding_partition_size, scope))
            # Use input_from_feature_columns to embed sparse features — do not use other embedding functions
            self.layer_dict['dnn'] = layers.input_from_feature_columns(
                self.features,
                self.feature_columns_from_column_names(self.embedding_columns),
                scope=scope
            )

    def dnn_layer(self):
        dnn_layer = [self.layer_dict['dnn']]
        with tf.variable_scope(
            name_or_scope="{}_Score_Network".format(self.name),
            partitioner=base_ops.partitioner(self.config.ps_num, self.dnn_partition_size)
        ):
            self.dnn_net = tf.concat(values=dnn_layer, axis=1)
            with arg_scope(base_ops.model_arg_scope(weight_decay=self.dnn_l2_reg)):
                for layer_id, num_hidden_units in enumerate(self.dnn_hidden_units):
                    with variable_scope.variable_scope("hiddenlayer_{}".format(layer_id)) as dnn_hidden_layer_scope:
                        tf.contrib.layers.apply_regularization(
                            regularizer=tf.contrib.layers.l2_regularizer(float(self.dnn_l2_reg)),
                            weights_list=[self.dnn_net])
                        self.dnn_net = layers.fully_connected(
                            self.dnn_net,
                            num_hidden_units,
                            utils.getActivationFunctionOp("llrelu"),
                            scope=dnn_hidden_layer_scope,
                            variables_collections=[self.collections_dnn_hidden_layer],
                            outputs_collections=[self.collections_dnn_hidden_output],
                            normalizer_fn=layers.batch_norm,
                            normalizer_params={"scale": True, "is_training": self.is_training})

    def logits_layer(self):
        with tf.variable_scope(
            name_or_scope="{}_Logits".format(self.name),
            partitioner=base_ops.partitioner(self.config.ps_num, self.dnn_partition_size)
        ) as dnn_logits_scope:
            with arg_scope(base_ops.model_arg_scope(weight_decay=self.dnn_l2_reg)):
                # Assign raw logits to self.logits — OpenSearch applies sigmoid automatically
                self.logits = layers.linear(
                    self.dnn_net,
                    1,
                    scope=dnn_logits_scope,
                    variables_collections=[self.collections_dnn_hidden_layer],
                    outputs_collections=[self.collections_dnn_hidden_output])

    def build_model(self):
        self.embedding_layer()
        self.dnn_layer()
        self.logits_layer()

    def update_op(self):
        update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
        self.update_ops = [op for op in update_ops if op.name.startswith(self.name)]

    def reg_loss(self):
        reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
        self.reg_losses = [loss for loss in reg_losses if loss.name.startswith(self.name)]
        self.reg_loss = tf.reduce_sum(self.reg_losses)

    def training_op(self):
        if self.config.predict:
            self.train_op = None
            return
        with tf.variable_scope(name_or_scope="Optimize_Layer", reuse=tf.AUTO_REUSE):
            gs = tf.train.get_or_create_global_step()
            logging.info("Global_step: {}, {}".format(self.name, str(gs)))
            logging.info("Model_name: {}, train_op_final_loss: {}".format(self.name, str(self.loss)))

            self.train_op, _, _ = myopt.optimize_loss(
                loss=self.loss,
                global_step=self.global_step,
                learning_rate=0.01,
                optimizer=tf.train.AdamAsyncOptimizer(
                    learning_rate=0.01,
                    beta1=0.9,
                    beta2=0.999,
                    epsilon=1e-8,
                    use_locking=False),
                update_ops=self.update_ops,
                clip_gradients=self.clip_gradients,
                variables=ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES),
                increment_global_step=True,
                summaries=myopt.OPTIMIZER_SUMMARIES)

    def loss_op(self):
        with tf.name_scope("{}_Loss_Op".format(self.name)):
            # Assign total loss (task loss + regularization) to self.loss
            self.loss = tf.reduce_mean(
                tf.nn.sigmoid_cross_entropy_with_logits(
                    logits=self.logits,
                    labels=self.label))
            self.loss = self.loss + self.reg_loss

    def metrics_op(self):
        # Call the parent to let OpenSearch track common system metrics
        super(CustomModel, self).metrics_op()

    def summary_op(self):
        with tf.name_scope("{}_Metrics_Scalar".format(self.name)):
            for key, metric in self.metrics.items():
                tf.summary.scalar(name=key, tensor=metric)

        with tf.name_scope("{}_Layer_Summary".format(self.name)):
            base_ops.add_norm2_summary(self.collections_dnn_hidden_layer)
            base_ops.add_dense_output_summary(self.collections_dnn_hidden_output)
            base_ops.add_weight_summary(self.collections_dnn_hidden_layer)

Configure las columnas de funciones

Acceda a las funciones mediante self.features y use el nombre de la función como clave. Configure todas las columnas de funciones mediante las operaciones de API proporcionadas por OpenSearch; usar directamente funciones nativas de TensorFlow para las columnas de funciones puede provocar incompatibilidad con el servicio de inference en línea.

Importante

Use contrib.layers.input_from_feature_columns para obtener el embedding de las funciones. Otras funciones de embedding no son compatibles y causarán problemas en el servicio en línea.

Tipos de columna de funciones compatibles

Tipo

Cuándo usar

embedding_column (generate_embedding_feature_column)

Funciones categóricas dispersas que requieren una búsqueda de embedding

real_valued_column (generate_real_valued_feature_column)

Funciones numéricas continuas

sparse_column_with_hash_bucket (generate_sparse_id_feature_column)

Funciones de ID dispersas sin embedding

shared_embedding_columns

Cuando se debe compartir el mismo embedding entre varias funciones

Importante

Una columna de embedding no se puede usar dos veces en el mismo modelo. Si dos funciones deben compartir un embedding, configure esas funciones con is_share_embedding=True y el mismo shared_embedding_name.

Operaciones de API

Use las operaciones de API encapsuladas que se muestran a continuación para configurar las columnas de funciones:

# Embedding column — for sparse categorical features
self.generate_embedding_feature_column(
    feature_name,
    hash_bucket_size,       # Number of hash buckets for the sparse feature
    dimension,              # Embedding dimension
    initializer=tf.zeros_initializer,
    combiner="sum",         # How to combine multi-valued features: "sum", "mean", or "sqrtn"
    is_share_embedding=False,
    shared_embedding_name=None
)

# Real-valued column — for continuous numeric features
self.generate_real_valued_feature_column(
    feature_name,
    dtype="Float",          # "Float" or "Int" only
    value_dimension=1
)

# Sparse ID column — for sparse features without embedding
self.generate_sparse_id_feature_column(
    feature_name,
    hash_bucket_size,
    combiner="sum"
)

# Retrieve configured feature columns by name
self.feature_columns_from_column_names(feature_list)

Especificaciones del modelo

Cumpla todos los requisitos que se indican a continuación para garantizar la compatibilidad con el servicio en línea:

Atributo / método

Requisito

CustomModel.__init__

Llame a super(CustomModel, self).__init__(config, name)

self.logits

Asigne los logits sin procesar (puntuaciones previas a la sigmoide). OpenSearch aplica la función sigmoide automáticamente para convertirlos en la puntuación final de ordenamiento. Para usar una activación diferente, sobrescriba predictions_op.

self.loss

Asigne la pérdida total de entrenamiento

self.reg_loss

Asigne la pérdida de regularización

metrics_op

Llame a super(CustomModel, self).metrics_op() para que OpenSearch pueda registrar las métricas del sistema

Notas de uso

Variables personalizadas fuera del módulo contrib

Si crea variables con tf.Variable o usa funciones fuera del módulo contrib, agregue esas variables a la colección MODEL_VARIABLES. OpenSearch carga los pesos con base en esta colección.

  • Agregue solo variables cuyos pesos deban cargarse en tiempo de servicio.

  • No agregue global_step a esta colección.

from tensorflow.python.framework import ops
from tensorflow.python.ops import variable_scope as vs

self._weights = vs.get_variable(
    _WEIGHTS_VARIABLE_NAME,
    [total_arg_size, output_size],
    dtype=dtype,
    initializer=kernel_initializer,
    collections=[ops.GraphKeys.GLOBAL_VARIABLES, ops.GraphKeys.MODEL_VARIABLES]
)