El framework de generación de funciones (FG) admite operadores de funciones personalizados como plugins de biblioteca compartida. Utilícelos cuando los operadores integrados no cubran su lógica de transformación específica del dominio; por ejemplo, la distancia de edición entre dos campos de texto, la distancia esférica a partir de coordenadas GPS o cualquier fórmula numérica aplicada elemento a elemento en una secuencia.
Funcionamiento
Implemente la interfaz C++
IFeatureOPy registre el operador conREGISTER_PLUGIN.Compile la implementación en una biblioteca compartida (
.so).Haga referencia al operador en
fg.json; para ello, establezcafeature_typecomocustom_featureyoperator_namecon el nombre registrado.Despliegue el archivo
.so: los servicios en línea lo detectan automáticamente desde el directoriocustom_fg_lib, mientras que las tareas sin conexión requieren cargarlo como recurso de MaxCompute.
Requisitos previos
Antes de empezar, asegúrese de contar con lo siguiente:
Un entorno de compilación compatible con C++17 que utilice la imagen oficial del compilador (consulte Compilar un operador personalizado)
El paquete de dependencias de la API: fg-api.tar.gz, que contiene todos los archivos de encabezado obligatorios
Conocimiento del framework FG y del funcionamiento de la configuración de
fg.json
Implementar un operador
Ejemplo mínimo
Todo operador personalizado sigue el mismo patrón: heredar de IFeatureOP, implementar Initialize y al menos un método ProcessWith*, además de registrarse con REGISTER_PLUGIN.
A continuación se muestra un operador mínimo que genera un entero constante:
#include "api/base_op.h"
namespace fg {
class ConstantOp : public IFeatureOP {
public:
int Initialize(const string& feature_config) override {
// Parse JSON config if needed; return 0 on success.
return 0;
}
int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
vector<int32>& outputs) override {
outputs.push_back(42);
return 0; // 0 = success
}
};
} // namespace fg
REGISTER_PLUGIN("ConstantOp", ConstantOp);
Requisitos clave:
Incluya un constructor sin parámetros (el constructor predeterminado funciona en este caso).
El método
Initializerecibe la entrada completa defg.jsoncomo una cadena JSON. Analice sintácticamente los elementos de configuración que necesite.Devuelva
0en todos los métodos para indicar éxito; un valor distinto de cero indica error.Llame a
REGISTER_PLUGINen el archivo de implementación, no en el encabezado.
Elegir entre ProcessWith* y BatchProcess
Hay dos interfaces de procesamiento disponibles. Elija según sus requisitos de throughput y complejidad:
|
** |
** |
|
|
Granularidad |
Un registro a la vez |
Un lote de registros |
|
Tipos de salida |
Solo tipos escalares básicos ( |
Cualquier tipo válido de |
|
Uso recomendado |
Transformaciones sencillas con bajo costo de implementación |
Se requiere alto throughput; funciones del lado del usuario con una muestra por solicitud (utilice el mecanismo de difusión para evitar análisis sintácticos redundantes) |
|
Obligatorio |
Al menos un método que coincida con |
Sobrescribir |
Cuando se implementa BatchProcess y HasBatchProcessImpl() devuelve true, el framework omite todos los métodos ProcessWith*.
Interfaz C++ completa
#pragma once
#ifndef FEATURE_GENERATOR_PLUGIN_BASE_H
#define FEATURE_GENERATOR_PLUGIN_BASE_H
#include <absl/container/flat_hash_map.h>
#include <absl/strings/string_view.h>
#include <absl/types/optional.h>
#include <stdexcept>
#include <utility>
#include <vector>
#include "fsmap.h"
#include "integral_types.h"
namespace fg {
using absl::optional;
using std::string;
using std::vector;
template <typename T>
using List = std::vector<T>;
template <typename K, typename V>
using Map = absl::flat_hash_map<K, V>;
template <typename K, typename V>
using MapArray = std::vector<std::pair<K, V>>;
using Matrix = std::vector<std::vector<float>>;
using MatrixL = std::vector<std::vector<int64>>;
using MatrixS = std::vector<std::vector<string>>;
template <typename K, typename V>
using FSMap = featurestore::type::fs_map<K, V>;
using FieldPtr = absl::variant<
const optional<string>*, const optional<int32>*, const optional<int64>*,
const optional<float>*, const optional<double>*,
const optional<absl::string_view>*,
const List<string>*, const List<int32>*, const List<int64>*,
const List<float>*, const List<double>*, const List<absl::string_view>*,
const Map<string, string>*, const Map<string, int32>*,
const Map<string, int64>*, const Map<string, float>*,
const Map<string, double>*, const Map<string, absl::string_view>*,
const Map<absl::string_view, absl::string_view>*,
const Map<absl::string_view, int32>*, const Map<absl::string_view, int64>*,
const Map<absl::string_view, float>*, const Map<absl::string_view, double>*,
const Map<absl::string_view, string>*,
const Map<int32, string>*, const Map<int32, int32>*,
const Map<int32, int64>*, const Map<int32, float>*,
const Map<int32, double>*, const Map<int32, absl::string_view>*,
const Map<int64, string>*, const Map<int64, float>*,
const Map<int64, double>*, const Map<int64, int32>*,
const Map<int64, int64>*, const Map<int64, absl::string_view>*,
const FSMap<absl::string_view, absl::string_view>*,
const FSMap<absl::string_view, int32>*,
const FSMap<absl::string_view, int64>*,
const FSMap<absl::string_view, float>*,
const FSMap<absl::string_view, double>*,
const FSMap<int32, int32>*, const FSMap<int32, int64>*,
const FSMap<int32, float>*, const FSMap<int32, double>*,
const FSMap<int32, absl::string_view>*,
const FSMap<int64, float>*, const FSMap<int64, double>*,
const FSMap<int64, int32>*, const FSMap<int64, int64>*,
const FSMap<int64, absl::string_view>*,
const MapArray<string, string>*, const MapArray<string, int32>*,
const MapArray<string, int64>*, const MapArray<string, float>*,
const MapArray<string, double>*,
const MapArray<int32, string>*, const MapArray<int32, float>*,
const MapArray<int32, double>*, const MapArray<int32, int32>*,
const MapArray<int32, int64>*,
const MapArray<int64, string>*, const MapArray<int64, float>*,
const MapArray<int64, double>*, const MapArray<int64, int32>*,
const MapArray<int64, int64>*, const Matrix*, const MatrixL*,
const MatrixS*>;
// Represents a COLUMN of the feature table.
using VariantVector = absl::variant<
vector<optional<string>>, vector<optional<int32>>, vector<optional<int64>>,
vector<optional<float>>, vector<optional<double>>,
vector<optional<absl::string_view>>,
vector<List<string>>, vector<List<int32>>, vector<List<int64>>,
vector<List<float>>, vector<List<double>>, vector<List<absl::string_view>>,
vector<Map<string, string>>, vector<Map<string, int32>>,
vector<Map<string, int64>>, vector<Map<string, float>>,
vector<Map<string, double>>, vector<Map<string, absl::string_view>>,
vector<Map<absl::string_view, absl::string_view>>,
vector<Map<absl::string_view, int32>>,
vector<Map<absl::string_view, int64>>,
vector<Map<absl::string_view, float>>,
vector<Map<absl::string_view, double>>,
vector<Map<int32, string>>, vector<Map<int32, int32>>,
vector<Map<int32, int64>>, vector<Map<int32, float>>,
vector<Map<int32, double>>, vector<Map<int32, absl::string_view>>,
vector<Map<int64, string>>, vector<Map<int64, float>>,
vector<Map<int64, double>>, vector<Map<int64, int32>>,
vector<Map<int64, int64>>, vector<Map<int64, absl::string_view>>,
vector<FSMap<absl::string_view, absl::string_view>>,
vector<FSMap<absl::string_view, int32>>,
vector<FSMap<absl::string_view, int64>>,
vector<FSMap<absl::string_view, float>>,
vector<FSMap<absl::string_view, double>>,
vector<FSMap<int32, int32>>, vector<FSMap<int32, int64>>,
vector<FSMap<int32, float>>, vector<FSMap<int32, double>>,
vector<FSMap<int32, absl::string_view>>,
vector<FSMap<int64, float>>, vector<FSMap<int64, double>>,
vector<FSMap<int64, int32>>, vector<FSMap<int64, int64>>,
vector<FSMap<int64, absl::string_view>>,
vector<MapArray<string, string>>, vector<MapArray<string, int32>>,
vector<MapArray<string, int64>>, vector<MapArray<string, float>>,
vector<MapArray<string, double>>,
vector<MapArray<int32, string>>, vector<MapArray<int32, float>>,
vector<MapArray<int32, double>>, vector<MapArray<int32, int32>>,
vector<MapArray<int32, int64>>,
vector<MapArray<int64, string>>, vector<MapArray<int64, float>>,
vector<MapArray<int64, double>>, vector<MapArray<int64, int32>>,
vector<MapArray<int64, int64>>, vector<Matrix>, vector<MatrixL>,
vector<MatrixS>>;
/**
* @brief The public base class for custom feature operators.
*
* The framework checks if a subclass overrides the `BatchProcess` method. If it is overridden,
* the framework calls this method to perform the feature transformation.
* Otherwise, the framework selects one of the `ProcessWith*` methods based on the `value_type` configuration.
* Implement the method that corresponds to the required output type.
*/
class IFeatureOP {
public:
class NotOverriddenException : public std::exception {
public:
explicit NotOverriddenException(std::string msg) : msg_(std::move(msg)) {}
const char* what() const noexcept override {
if (msg_.empty()) {
return "unimplemented method called";
}
// Cache the message to a member variable to ensure that the returned pointer remains valid.
cached_ = "unimplemented method called: " + msg_;
return cached_.c_str();
}
private:
std::string msg_;
mutable std::string cached_;
};
virtual ~IFeatureOP() = default;
/**
* @brief Initialization method.
* @param feature_config The full fg.json entry for this feature, as a JSON string.
* @return 0 on success; non-zero indicates failure.
*/
virtual int Initialize(const string& feature_config) = 0;
/**
* @brief Processes one record and outputs string values.
* @param inputs A record that can contain multiple fields.
* @param outputs The outputs of the feature transformation.
* @return 0 on success.
*/
virtual int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
vector<string>& outputs) {
throw NotOverriddenException("ProcessWithStrOutputs(FieldPtr)");
}
/**
* @brief Processes one record and outputs int32 values.
*/
virtual int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
vector<int32>& outputs) {
throw NotOverriddenException("ProcessWithInt32Outputs(FieldPtr)");
}
/**
* @brief Processes one record and outputs int64 values.
*/
virtual int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
vector<int64>& outputs) {
throw NotOverriddenException("ProcessWithInt64Outputs(FieldPtr)");
}
/**
* @brief Processes one record and outputs float values.
*/
virtual int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
vector<float>& outputs) {
throw NotOverriddenException("ProcessWithFloatOutputs(FieldPtr)");
}
/**
* @brief Processes one record and outputs double values.
*/
virtual int ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
vector<double>& outputs) {
throw NotOverriddenException("ProcessWithDoubleOutputs(FieldPtr)");
}
/**
* @brief Optional batch interface that processes one batch of records.
*
* @param inputs A vector of input columns; each `VariantVector` represents one feature column.
* @param outputs The transformed features. Supports complex output types usable as inputs for downstream operators.
* @return 0 on success.
*/
virtual int BatchProcess(const vector<VariantVector>& inputs,
VariantVector& outputs) {
throw NotOverriddenException("BatchProcess");
}
/**
* @brief Declares whether the subclass implements BatchProcess.
*
* Override this to return `true` if you implement `BatchProcess`.
* This avoids exception propagation issues across dynamic library boundaries
* when the .so and the main program use different C++ ABIs.
*
* @return true if BatchProcess is implemented; false otherwise (default).
*/
virtual bool HasBatchProcessImpl() const { return false; }
};
using CreateOperatorFunc = IFeatureOP* (*)();
inline FieldPtr GetFieldPtr(const VariantVector& input, size_t i) {
return absl::visit(
[&](const auto& vec) -> FieldPtr {
if (i >= vec.size()) {
throw std::out_of_range("GetFieldPtr: index " + std::to_string(i) +
" out of range [0, " +
std::to_string(vec.size()) + ")");
}
return &vec.at(i);
},
input);
}
} // namespace fg
#if defined(__GNUC__)
#define PLUGIN_API_HIDDEN \
__attribute__((visibility("hidden"))) __attribute__((used))
#define PLUGIN_API_EXPORT \
__attribute__((visibility("default"))) __attribute__((used))
#else
#define PLUGIN_API_HIDDEN
#define PLUGIN_API_EXPORT
#endif
std::vector<std::string>& getLocalNames();
std::vector<std::pair<std::string, void*>>& getLocalRegs();
#define REGISTER_PLUGIN(OpName, OpClass) \
extern "C" PLUGIN_API_EXPORT fg::IFeatureOP* create##OpClass() { \
return new fg::OpClass(); \
} \
namespace { \
struct _Reg_##OpClass { \
_Reg_##OpClass() { \
getLocalNames().push_back(OpName); \
getLocalRegs().emplace_back(OpName, (void*)&create##OpClass); \
} \
}; \
static _Reg_##OpClass _dummy_##OpClass __attribute__((used)); \
}
#endif // FEATURE_GENERATOR_PLUGIN_BASE_H
Notas de implementación
Tipos de entrada: Implemente los métodos
ProcessWith*para todos los tipos de entrada que desee admitir. Para los tipos no compatibles, lance una excepción directamente.VariantRecorddefine todos los tipos de campos de función que el framework puede procesar. Los tiposFSMapson necesarios para la integración con Feature Store, ya que mejoran significativamente el rendimiento del procesador en línea.Discretización: Implemente únicamente la lógica de transformación previa a la discretización. Si se configura
hash_bucket_size,vocab_list,boundariesu otra operación de discretización, el framework la gestiona automáticamente.Seguridad de hilos: De forma predeterminada (
is_op_thread_safe=true), el operador debe carecer de estado o usar exclusivamente variablesthread_local. Establezcais_op_thread_safe=falsepara que el framework cree una réplica del objeto por hilo; esto simplifica la implementación, pero consume más memoria.Entradas
string_view: Los servicios en línea (EasyRecProcessor, TorchEasyRec Processor) pasan las funciones de cadena del lado del ítem comoabsl::string_viewpara mayor eficiencia. Si su operador no es compatible constring_view, establezcadisable_string_view=trueen la configuración para que el framework las convierta astringantes de llamar a su operador. Tenga en cuenta que esto reduce el rendimiento.Elementos de configuración personalizados: Puede usar cualquier nombre de clave en la entrada JSON, ya que el framework pasa la cadena JSON completa a
Initialize. No reutilice nombres de clave reservados por el framework (comofeature_type,operator_name,value_type). Las claves que hagan referencia a archivos externos deben terminar con_filepara que el framework pueda sincronizarlas en tareas sin conexión.Directorio de operadores: Utilice la variable de entorno
FEATURE_OPERATOR_DIRpara especificar el directorio donde se encuentran los archivos de biblioteca de vínculo dinámico. Cada biblioteca de vínculo dinámico puede contener implementaciones de varios operadores de funciones.Tipo de retorno de
BatchProcess: El tipo deVariantVectorque devuelveBatchProcessdepende de los valores deis_sequence,value_dimensionyvalue_type. Para obtener más información, consulte el esquema de la tabla de salida en Referencia de configuración. Cuando se configurastub_type=truey no se establece ninguna operación de agrupamiento,BatchProcesspuede devolver cualquier tipo válido, comoMap.
Referencia de configuración
Estructura de entrada de fg.json
{
"feature_name": "my_custom_fg_op",
"feature_type": "custom_feature",
"operator_name": "EditDistance",
"operator_lib_file": "libedit_distance.so",
"expression": [
"user:query",
"item:title"
],
"value_type": "string",
"separator": ",",
"default_value": "-1",
"value_dimension": 1,
"normalizer": "method=expression,expr=x>16?16:x",
"num_buckets": 10000,
"stub_type": false,
"is_sequence": false,
"is_op_thread_safe": true
}
Agregue cualquier elemento de configuración adicional que requiera su operador. La entrada JSON completa se pasa a Initialize.
Parámetros de configuración
|
Parámetro |
Obligatorio |
Descripción |
|
|
Sí |
Establézcalo como |
|
|
Sí |
Nombre utilizado en |
|
|
Sin conexión: obligatorio; En línea: opcional |
Nombre del archivo |
|
|
Sí |
Campos de entrada. Admite múltiples entradas. |
|
|
Sí |
Tipo de salida. Uno de los siguientes: |
|
|
Sí |
Valor predeterminado como cadena. El framework lo convierte al tipo definido en |
|
|
Cuando la salida es multidimensional |
Divide |
|
|
No |
Si es |
|
|
No |
Indica si la salida es una función de secuencia. Valor predeterminado: |
|
|
Cuando |
Longitud máxima de la secuencia. Las secuencias más largas se truncan. |
|
|
Cuando la entrada es una secuencia de tipo cadena |
Separador entre los elementos de la secuencia. |
|
|
No |
Para secuencias de entrada de tipo cadena, indica si el framework divide la cadena antes de pasarla al operador. Valor predeterminado: |
|
|
No |
Dimensión de la función de salida. Valor predeterminado: |
|
|
No |
Normalización posterior a la transformación para funciones numéricas. Consulte Frameworks de normalización. |
|
|
Cuando |
Rellena las posiciones vacías en un elemento de secuencia con múltiples valores. Valor predeterminado para flotantes: |
|
|
No |
Convierte las entradas de tipo |
|
|
No |
Indica si el operador es seguro para hilos. Valor predeterminado: |
Esquema de la tabla de salida
Las configuraciones de value_dimension y is_sequence determinan el tipo de esquema de la tabla de salida en tareas sin conexión:
|
** |
** |
Tipo de esquema |
Con discretización |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Casos especiales: array<array<int>> se fuerza a array<array<bigint>>; array<array<double>> se fuerza a array<array<float>>.
Frameworks de normalización
Para funciones numéricas, agregue un normalizer para procesar aún más el resultado de la transformación:
|
Framework |
Configuración de ejemplo |
Fórmula |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Cualquier expresión; la variable |
Para consultar las funciones compatibles en expression, consulte Operadores de funciones integrados.
Operaciones de discretización
Hay seis tipos de discretización disponibles sin necesidad de implementación adicional:
|
Tipo |
Descripción |
|
|
Operación de hash y módulo sobre el resultado de la transformación. |
|
|
Convierte el resultado en un índice dentro de una lista. |
|
|
Convierte el resultado en un valor dentro de un diccionario. El valor debe ser convertible a |
|
|
Carga una |
|
|
Convierte el resultado en un número de cubeta según los límites de intervalo especificados. |
|
|
Utiliza el resultado directamente como número de cubeta. |
Para obtener más información, consulte Feature discretization (binning).
Ejemplos de configuración
Función de diferencia horaria de secuencia
{
"feature_name": "time_diff_seq",
"feature_type": "custom_feature",
"operator_name": "SeqExpr",
"expression": ["user:cur_time", "user:clk_time_seq"],
"formula": "cur_time - clk_time_seq",
"default_value": "0",
"value_type": "int32",
"is_sequence": true,
"num_buckets": 1000,
"is_op_thread_safe": false
}
Distancia esférica con normalización
{
"feature_name": "spherical_distance",
"feature_type": "custom_feature",
"operator_name": "SeqExpr",
"expression": ["item:click_id_lng", "item:click_id_lat", "user:j_lng", "user:j_lat"],
"formula": "spherical_distance",
"default_value": "0",
"value_type": "double",
"is_sequence": true,
"is_op_thread_safe": true,
"value_dimension": 1,
"normalizer": "method=expression,expr=sqrt(x)"
}
Ambos ejemplos utilizan el operador SeqExpr. El campo formula es un elemento de configuración específico de SeqExpr que se transmite mediante Initialize.
spherical_distance: Calcula la distancia entre dos pares de coordenadas de latitud y longitud. Los parámetros son[lng1_seq, lat1_seq, lng2, lat2]: los dos primeros son secuencias y los dos últimos son escalares.
Estos ejemplos muestran el formato plano para funciones de secuencia personalizadas. Para ver un ejemplo en formato anidado, consulte sequence_feature.
Funciones de secuencia
Cuando is_sequence=true, los requisitos de salida varían según si la función es dispersa o densa:
Secuencias dispersas
Un solo valor por elemento: genere cualquier tipo.
Múltiples valores por elemento: genere solo
string. Establezcavalue_typecomostringy utilicechr(29)como separador entre los valores dentro de un mismo elemento.
Secuencias densas
Establezca
value_dimensioncon la dimensión de cada elemento.Elementos escalares:
value_dimension=1.Elementos vectoriales:
value_dimension=longitud del vector.El número total de valores de salida debe ser un múltiplo entero de
value_dimension.
Ejemplo para desarrolladores
El siguiente ejemplo implementa un operador de distancia de edición que calcula la distancia de Levenshtein entre dos entradas de texto.
Archivo de encabezado (edit_distance.h):
#pragma once
#include "api/base_op.h"
namespace fg {
namespace functor {
class EditDistanceFunctor;
}
using std::string;
using std::vector;
/**
* @brief EditDistance: takes two strings, outputs their edit distance.
*/
class EditDistance : public IFeatureOP {
public:
int Initialize(const string& feature_config) override;
/// @return 0 on success.
int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
vector<string>& outputs) override;
/// @return 0 on success.
int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
vector<int32>& outputs) override;
/// @return 0 on success.
int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
vector<int64>& outputs) override;
/// @return 0 on success.
int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
vector<float>& outputs) override;
/// @return 0 on success.
int ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
vector<double>& outputs) override;
private:
string feature_name_;
std::unique_ptr<functor::EditDistanceFunctor> functor_p_;
};
} // end of namespace fg
Archivo de implementación (edit_distance.cc):
#include "edit_distance.h"
#include <absl/strings/ascii.h>
#include <absl/strings/str_join.h>
#include <nlohmann/json.hpp>
#include <numeric> // std::iota
#include <stdexcept>
#include "api/log.h"
namespace fg {
using absl::optional;
namespace functor {
template <class T>
int edit_distance(const T& s1, const T& s2) {
int l1 = s1.size();
int l2 = s2.size();
if (l1 * l2 == 0) {
return l1 + l2;
}
vector<int> prev(l2 + 1);
vector<int> curr(l2 + 1);
std::iota(prev.begin(), prev.end(), 0);
for (int i = 0; i <= l1; ++i) {
curr[0] = i;
for (int j = 1; j <= l2; ++j) {
int d = prev[j - 1];
if (s1[i - 1] == s2[j - 1]) {
curr[j] = d;
} else {
int d2 = std::min(prev[j], curr[j - 1]);
curr[j] = 1 + std::min(d, d2);
}
}
prev.swap(curr);
}
return prev[l2];
}
enum class Encoding : unsigned int { Latin = 0, UTF8 = 1 };
class EditDistanceFunctor {
public:
EditDistanceFunctor(const string& encoding) {
string enc = absl::AsciiStrToLower(encoding);
if (enc == "utf-8" || enc == "utf8") {
encoding_ = Encoding::UTF8;
} else {
encoding_ = Encoding::Latin;
}
}
int operator()(absl::string_view s1, absl::string_view s2) {
if (encoding_ == Encoding::Latin) {
return edit_distance(s1, s2);
}
if (encoding_ == Encoding::UTF8) {
return edit_distance(from_bytes(s1), from_bytes(s2));
}
LOG(ERROR) << "EditDistanceFunctor found unsupported text encoding";
assert(false);
return 0;
}
const Encoding TextEncoding() const { return encoding_; }
private:
Encoding encoding_;
std::wstring from_bytes(absl::string_view str) {
std::wstring result;
int i = 0;
int len = (int)str.length();
while (i < len) {
int char_size = 0;
int unicode = 0;
if ((str[i] & 0x80) == 0) {
unicode = str[i];
char_size = 1;
} else if ((str[i] & 0xE0) == 0xC0) {
unicode = str[i] & 0x1F;
char_size = 2;
} else if ((str[i] & 0xF0) == 0xE0) {
unicode = str[i] & 0x0F;
char_size = 3;
} else if ((str[i] & 0xF8) == 0xF0) {
unicode = str[i] & 0x07;
char_size = 4;
} else {
// Invalid UTF-8 sequence
++i;
continue;
}
for (int j = 1; j < char_size; ++j) {
unicode = (unicode << 6) | (str[i + j] & 0x3F);
}
if (unicode <= 0xFFFF) {
result += static_cast<wchar_t>(unicode);
} else {
// Handle surrogate pairs for characters outside the BMP
unicode -= 0x10000;
result += static_cast<wchar_t>((unicode >> 10) + 0xD800);
result += static_cast<wchar_t>((unicode & 0x3FF) + 0xDC00);
}
i += char_size;
}
return result;
}
};
} // namespace functor
// Overloaded helper for absl::visit (C++17).
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
int EditDistance::Initialize(const string& feature_config) {
nlohmann::json cfg;
try {
cfg = nlohmann::json::parse(feature_config);
} catch (nlohmann::json::parse_error& ex) {
LOG(ERROR) << "parse error at byte " << ex.byte;
LOG(ERROR) << "config: " << feature_config;
throw std::runtime_error("parse EditDistance config failed");
}
feature_name_ = cfg.at("feature_name");
string encoding = cfg.value("encoding", "latin");
functor_p_ = std::make_unique<functor::EditDistanceFunctor>(encoding);
functor::Encoding enc = functor_p_->TextEncoding();
encoding = (enc == functor::Encoding::UTF8) ? "UTF-8" : "Latin";
LOG(INFO) << "feature <" << feature_name_ << "> with text encoding: " << encoding;
return 0;
}
int EditDistance::ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
vector<int32>& outputs) {
outputs.clear();
if (inputs.size() < 2) {
outputs.push_back(0);
return -1; // invalid inputs
}
int d = absl::visit(
overloaded{
[this](const optional<string>* s1, const optional<string>* s2) {
absl::string_view empty_view;
return functor_p_->operator()(*s1 ? **s1 : empty_view, *s2 ? **s2 : empty_view);
},
[this](const optional<absl::string_view>* s1,
const optional<absl::string_view>* s2) {
absl::string_view empty_view;
return functor_p_->operator()(*s1 ? **s1 : empty_view, *s2 ? **s2 : empty_view);
},
[this](const optional<absl::string_view>* s1,
const optional<string>* s2) {
absl::string_view empty_view;
return functor_p_->operator()(*s1 ? **s1 : empty_view, *s2 ? **s2 : empty_view);
},
[this](const optional<string>* s1,
const optional<absl::string_view>* s2) {
absl::string_view empty_view;
return functor_p_->operator()(*s1 ? **s1 : empty_view, *s2 ? **s2 : empty_view);
},
[this](const List<string>* s1, const List<string>* s2) {
string str1 = absl::StrJoin(*s1, "");
string str2 = absl::StrJoin(*s2, "");
return functor_p_->operator()(str1, str2);
},
[this](const List<absl::string_view>* s1,
const List<absl::string_view>* s2) {
string str1 = absl::StrJoin(*s1, "");
string str2 = absl::StrJoin(*s2, "");
return functor_p_->operator()(str1, str2);
},
[this](const auto* x, const auto* y) {
ERROR_EXIT(feature_name_,
"unsupported input type: ", typeid(*x).name(), " vs ",
typeid(*y).name());
return 0;
}},
inputs.at(0), inputs.at(1));
outputs.push_back(d);
return 0;
}
// int32 results are reused for int64, float, and double outputs.
int EditDistance::ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
vector<int64>& outputs) {
vector<int32> distances;
int status = ProcessWithInt32Outputs(inputs, distances);
if (0 != status) return status;
outputs.assign(distances.begin(), distances.end());
return 0;
}
int EditDistance::ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
vector<float>& outputs) {
vector<int32> distances;
int status = ProcessWithInt32Outputs(inputs, distances);
if (0 != status) return status;
outputs.assign(distances.begin(), distances.end());
return 0;
}
int EditDistance::ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
vector<double>& outputs) {
vector<int32> distances;
int status = ProcessWithInt32Outputs(inputs, distances);
if (0 != status) return status;
outputs.assign(distances.begin(), distances.end());
return 0;
}
int EditDistance::ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
vector<string>& outputs) {
vector<int32> distances;
int status = ProcessWithInt32Outputs(inputs, distances);
if (0 != status) return status;
outputs.reserve(distances.size());
std::transform(distances.begin(), distances.end(),
std::back_inserter(outputs),
[](int32& x) { return std::to_string(x); });
return 0;
}
} // end of namespace fg
REGISTER_PLUGIN("EditDistance", EditDistance);
Descargue el código fuente de la tabla en Operadores disponibles y ejecute el script build.sh para compilar el operador.
Compilar un operador personalizado
Utilice el mismo entorno de compilación que el framework FG: C++17 y la imagen oficial del compilador. Los detalles de la imagen se encuentran en el script build.sh incluido con cada ejemplo.
|
Imagen |
SO base |
Notas |
|
|
CentOS 7 |
ABI C++11 predeterminada (no habilitada) |
|
|
Rocky Linux 8 (compatible con CentOS 8) |
Utilice esta imagen si necesita la nueva ABI C++11 ( |
Dependencias de terceros: Integre todas las dependencias como código fuente o utilice vínculos estáticos. El vínculo dinámico a bibliotecas de terceros provoca que el operador no se cargue durante la ejecución.
Dependencia obligatoria:
abseil-cpp: utilice la misma versión que el framework FG.
Para obtener detalles sobre la configuración de CMake, consulte el archivo CMakeLists.txt en cada ejemplo para desarrolladores.
Operadores disponibles
A continuación se presentan los operadores disponibles como código fuente y binarios precompilados:
|
Operador |
Descripción |
Código fuente |
Binario |
|
EditDistance |
Distancia de edición entre dos entradas de texto |
||
|
SeqExpr |
Evaluación de expresiones de secuencia |
||
|
BPETokenize |
Tokenización mediante codificación por pares de bytes (BPE) |
Incluido en tokenize_feature integrado |
Configuración de EditDistance
|
Parámetro |
Opciones |
Valor predeterminado |
|
|
|
|
Para consultar un ejemplo de BatchProcess, descargue y revise RegexReplace.
Pasos siguientes
Built-in feature operators — operadores disponibles y sintaxis de la expresión
expr_featureUse FG in offline tasks — cómo desplegar operadores personalizados para tareas sin conexión
Feature Generation overview and configuration — esquema completo de
fg.jsony referencia de discretización