すべてのプロダクト
Search
ドキュメントセンター

Artificial Intelligence Recommendation:カスタム特徴量オペレーター

最終更新日:Mar 31, 2026

ドメイン固有の特徴量変換を実現するためのプラグインにより、特徴量生成 (FG) フレームワークを拡張します。

構成

{
    "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,
    ...
}

必要に応じてその他の設定項目を追加してください。この JSON 文字列全体がカスタムオペレーターに渡されます。

設定項目

説明

feature_type

この値を custom_feature に設定します。

operator_name

特徴量オペレーターが登録される名前です。実装済みのクラス名と一致させる必要があります。同一の operator を複数の特徴量変換で再利用できます。

operator_lib_file

特徴量オペレーターのダイナミックリンクライブラリファイルの名前です。ファイル名は必ず .so で終わる必要があります。このパラメーターは オフラインタスク では必須であり、オンラインタスクでは任意です。

  • Torch/EasyRecProcessor などのオンラインサービスでは、custom_fg_lib サブディレクトリ内のすべてのダイナミックリンクライブラリファイルをスキャンし、fg.json モデルファイルが配置されているディレクトリ直下の該当サブディレクトリからそれらをメモリに読み込みます。

  • 公式の拡張オペレーターがいくつか提供されています(開発者向けサンプル セクションのリストをご参照ください)。公式オペレーターを指定する場合は、operator_lib_filepyfg/lib/libxxx.so に設定します。

  • オフラインタスクでは、公式ではないダイナミックリンクライブラリファイルを MaxCompute リソースとして、同じ名前でアップロードし、コミットしてください。

expression

入力式です。複数の入力をサポートします。

value_type

特徴量変換の出力型です。基本型のみ指定可能です。stringint32int64floatdouble のいずれかを指定します。

default_value

特徴量のデフォルト値です。文字列として設定してください。コード側で必要な型へ自動変換されます。

separator

複数値の区切り文字です。default_value の分割に使用されます。出力特徴量が多次元である場合、複数の値を持つデフォルト値を設定します。

stub_type

現在の特徴量オペレーターが特徴量変換の中間結果としてのみ使用可能かどうかを示します。この値を true に設定すると、オペレーターは有向非巡回グラフ (DAG) 実行グラフのリーフノードとして使用できなくなります。

is_sequence

特徴量がシーケンス特徴量であるかどうかを指定します。

sequence_length

シーケンスの最大長です。この値を超えると、シーケンスは切り捨てられます。

sequence_delim

シーケンス要素間の区切り文字です。入力が文字列型の場合にのみ設定します。

split_sequence

入力シーケンス特徴量が文字列型の場合、フレームワークがシーケンスに対して分割操作を実行するかどうかを指定します。デフォルト値は true です。

  • 分割操作後、現在の入力フィールドの型は、元々 scalar フィールドであったとしても、std::vector<std::string> になります。

  • 複数の入力フィールドがあり、そのうち一部がシーケンスで他がスカラーである場合、フレームワーク層での分割操作の実行可否を慎重に検討してください。

  • フレームワークレベルの分割操作では CPU の AVX512 命令セットが使用され、通常はより優れたパフォーマンスを発揮します。

value_dimension

出力特徴量のディメンションです。これはオフラインタスクの出力を切り捨てるために使用でき、出力テーブルのスキーマに影響を与えます。特徴量が複数の値を持ち、出力ディメンションが不確定な場合は、この設定項目を省略してください。

  • このパラメーターは任意です。デフォルト値は 0 です。オフラインタスクの出力を切り捨てる際に使用できます。

  • 値が 1is_sequence=false の場合、出力テーブルのスキーマ型は value_type です。離散化操作が設定されている場合、出力型は bigint になります。

  • 値が 1is_sequence=true の場合、出力テーブルのスキーマ型は array<value_type> です。離散化操作が設定されている場合、出力型は array<bigint> になります。

  • 値が 1 でなく is_sequence=false の場合、出力テーブルのスキーマ型は array<value_type> です。離散化操作が設定されている場合、出力型は array<bigint> になります。

  • 値が 1 でなく is_sequence=true の場合、出力テーブルのスキーマ型は array<array<value_type>> です。離散化操作が設定されている場合、出力型は array<array<bigint>> になります。

  • 特殊ケース 1:上記のルールにより出力テーブルのスキーマ型が array<array<int>> となる場合、強制的に array<array<bigint>> に変更されます。

  • 特殊ケース 2:上記のルールにより出力テーブルのスキーマ型が array<array<double>> となる場合、強制的に array<array<float>> に変更されます。

離散化操作

追加の実装なしで使用可能な離散化操作は 6 種類あります。詳細については、「特徴量の離散化(ビニング)」をご参照ください。

  • hash_bucket_size:特徴量変換結果に対するハッシュおよび剰余演算。

  • vocab_list:特徴量変換結果をリストのインデックスに変換。

  • vocab_dict:特徴量変換結果を辞書の値に変換。値は int64 型へ変換可能である必要があります。

  • vocab_file:ファイルから vocab_list または vocab_dict を読み込み。

  • boundaries:ビニングの境界を指定し、特徴量変換結果を対応するバケット番号に変換。

  • num_buckets:特徴量変換結果を直接ビニングバケット番号として使用。

normalizer

数値特徴量の場合、この設定を追加して変換結果をさらに処理します(例:式の値計算)。

サポートされるオペレーターおよび関数については、「組み込み特徴量オペレーター」をご参照ください。サポートされる正規化手法は minmax、zscore、log10、expression の 4 種類です。設定および計算方法は以下のとおりです。

  • log10

    設定例:method=log10,threshold=1e-10,default=-10

    計算式:x = x > threshold ? log10(x) : default;

  • zscore

    設定例:method=zscore,mean=0.0,standard_deviation=10.0

    計算式:x = (x - mean) / standard_deviation

  • minmax

    設定例:method=minmax,min=2.1,max=2.2

    計算式:x = (x - min) / (max - min)

  • expression

    設定例:method=expression,expr=sign(x)

    計算式:任意の関数または式を設定できます。変数名は固定で x であり、これは式の入力を表します。

placeholder

シーケンス特徴量において、各シーケンス要素が複数の値を持つ場合(value_dimension != 1)、空き位置を埋め、特殊な値でディメンションを補完するために使用します。

  • 浮動小数点数のデフォルト値は NaN です。整数のデフォルト値は対応する型の最小値です。

  • FG フレームワークはこれらの特殊なプレースホルダー値をフィルター処理します。スパース特徴量に対して離散化操作が設定されている場合、ジャギーな特徴量値が出力されます。密な特徴量に対して離散化操作が実行されていない場合、プレースホルダー値は特徴量のデフォルト値(default_value)に置き換えられます。

disable_string_view

string_view 型の特徴量値の使用を無効にするかどうかを指定します。デフォルト値は false です。

  • FG が EasyRecProcessorTorchEasyRec Processor などのモデル推論サービスに統合されると、アイテム側の文字列型の特徴は、パフォーマンス向上のために FG への入力として string_view 型に変換されます。

  • オペレーター開発者の利便性を考慮し、この設定スイッチを有効化できます。FG フレームワークは string_view 型の特徴量値を string 型に変換し、カスタムオペレーターに渡します。

  • 注意:Map 型データにおいて、キーまたは値が string_view 型である場合、変換できません。開発者は依然として string_view 型を処理する必要があります。

  • 注意:この設定スイッチを有効化すると、パフォーマンスが低下します。

is_op_thread_safe

現在の特徴量オペレーターがスレッドセーフであるかどうかを指定します。スレッドセーフである場合は true、そうでない場合は false を設定します。

  • デフォルト値は true です。つまり、オペレーター開発者はオペレーターがスレッドセーフであることを保証する必要があります。オペレーターはステートレスであるか、thread_local 変数のみを持つ必要があります。

    • [推奨] ネイティブでスレッドセーフなオペレーターを提供します。

  • このパラメーターを false に設定した場合、フレームワークは各スレッドごとにオブジェクトのレプリカを作成します。

    • この方法は、ネイティブでスレッドセーフなオペレーターと比較して、より多くのメモリを消費します。

その他の注意事項:

  • ユーザー定義の設定項目の名前は、フレームワークの設定項目と重複してはなりません。

    • カスタムオペレーターは、フレームワークによって定義された設定項目を読み取り、使用できます。ただし、そのセマンティクスを変更しようとすると、未定義の動作を引き起こします。

  • 外部リソースファイルに依存する設定項目は、必ず _file で終わる必要があります。

    • このマーカーは、オフラインタスク用のリソースファイルの同期に使用されます。

構成例

{
    "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
},
{
    "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)"
}
  • formula:式です。サポートされる式の詳細については、「expr_feature」をご参照ください。

    • spherical_distance:2 つの緯度経度座標間の距離を計算します。パラメーターは [lng1_seq, lat1_seq, lng2, lat2] です。最初の 2 つのパラメーターはシーケンス、最後の 2 つはスカラー値です。

    これは、タイル形式のカスタム シーケンス特徴量 の例です。ネスト形式のカスタム シーケンス特徴量 の例については、「sequence_feature」をご参照ください。

C++ インターフェイス

#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 to execute based on the `value_type` configuration.
 * You must 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 The initialization method.
   * @param feature_config is a json string,
   * @return Returns 0 if the model is loaded successfully. Otherwise, it indicates that the model failed to load.
   */
  virtual int Initialize(const string& feature_config) = 0;

  /**
   * @brief Performs feature transformation and outputs the results as the string type.
   * @param inputs A record that can contain multiple fields.
   * @param outputs The outputs of the feature transformation.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
                                    vector<string>& outputs) {
    throw NotOverriddenException("ProcessWithStrOutputs(FieldPtr)");
  }

  /**
   * @brief Performs feature transformation and outputs the results as the int32 type.
   * @param inputs A record that can contain multiple fields.
   * @param outputs The outputs of the feature transformation.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
                                      vector<int32>& outputs) {
    throw NotOverriddenException("ProcessWithInt32Outputs(FieldPtr)");
  }

  /**
   * @brief Performs feature transformation and outputs the results as the int64 type.
   * @param inputs A record that can contain multiple fields.
   * @param outputs The outputs of the feature transformation.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
                                      vector<int64>& outputs) {
    throw NotOverriddenException("ProcessWithInt64Outputs(FieldPtr)");
  }

  /**
   * @brief Performs feature transformation and outputs the results as the float type.
   * @param inputs A record that can contain multiple fields.
   * @param outputs The outputs of the feature transformation.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
                                      vector<float>& outputs) {
    throw NotOverriddenException("ProcessWithFloatOutputs(FieldPtr)");
  }

  /**
   * @brief Performs feature transformation and outputs the results as the double type.
   * @param inputs A record that can contain multiple fields.
   * @param outputs The outputs of the feature transformation.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
                                       vector<double>& outputs) {
    throw NotOverriddenException("ProcessWithDoubleOutputs(FieldPtr)");
  }

  /**
   * @brief An optional batch interface for processing multiple records.
   *
   * @param inputs A vector of input columns. `VariantVector` represents a feature column.
   * @param outputs
   * The transformed features. This method supports complex output types that can be used as inputs for other feature transformations.
   * @return A status code. A value of 0 indicates successful execution.
   */
  virtual int BatchProcess(const vector<VariantVector>& inputs,
                           VariantVector& outputs) {
    throw NotOverriddenException("BatchProcess");
  }

  /**
   * @brief Explicitly declares whether the subclass implements the BatchProcess method.
   *
   * The framework preferentially calls this method to check if `BatchProcess` has been overridden.
   * If your subclass implements `BatchProcess`, you must override this method to return true.
   * By default, it returns false to indicate that `BatchProcess` is not implemented.
   *
   * Note: This method is used to avoid exception propagation issues across dynamic library boundaries.
   * When a custom operator (a .so file) and the main program use different C++ ABIs or compilation options,
   * attempting to detect the implementation by calling `BatchProcess` and catching an exception may fail.
   *
   * @return true if the subclass implements BatchProcess.
   * @return false if the subclass does not implement BatchProcess (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

開発者ガイド

  • API 依存関係ファイル fg-api.tar.gz をダウンロードします。このファイルには必要なヘッダーファイルが含まれています。

  • IFeatureOP 基底クラスを継承し、Initialize メソッドを実装し、少なくとも 1 つの ProcessWith* メソッドを実装します。

  • 実装クラスには、引数なしのコンストラクターを含める必要があります。

  • フレームワークは JSON 構成文字列を Initialize メソッドに渡します。この文字列から必要な構成項目を解析します。

  • フレームワークは ProcessWith* メソッドを、value_type 構成項目に基づいて呼び出します。対応する型のメソッドが実装されていない場合、ランタイム例外がスローされます。

    • ProcessWith* メソッドは単一のレコードを処理します。複数の入力フィールドおよび多次元出力(例:複数値特徴量)をサポートします。

    • VariantRecord は、フレームワークが処理可能なすべての特徴量フィールド型を定義します。

    • コードでは、可能な限り多くの型をサポートするよう、各可能な入力型に対応する特徴量変換操作を実装する必要があります。不要な型については、例外を直接スローします。

    • featurestore を使用する場合、FSMAP 型をサポートする必要があります。これにより、プロセッサのパフォーマンスを大幅に向上させることができます。

  • 離散化操作の前の特徴量変換操作のみを実装してください。離散化操作が構成されている場合、フレームワークが自動的に実行します。

  • 新しい特徴量オペレーターを登録するには、REGISTER_PLUGIN マクロを使用します。これを実行しないと、フレームワークはオペレーターを使用できません。

    • REGISTER_PLUGIN("OperatorName", OperatorClass):2 つのマクロパラメーターを必要に応じて置き換えます。両方のパラメーターに同じ名前を使用します。

    • 構成項目の operator_name の値は 'OperatorName' でなければなりません。

    • オペレーターはヘッダーファイルではなく、実装ファイルに登録します。

  • フレームワークは指定されたディレクトリ内のすべてのダイナミックリンクライブラリをスキャンし、必要に応じて要求された特徴量オペレーターを読み込もうとします。

    • ダイナミックリンクライブラリファイルの配置先ディレクトリを指定するには、FEATURE_OPERATOR_DIR 環境変数を使用します。

    • 各ダイナミックリンクライブラリには、複数の特徴量オペレーターの実装を含めることができます。

  • BatchProcess インターフェイスはバッチ処理に使用され、一度に 1 バッチのデータを処理します。

    • このインターフェイスは任意です。実装した場合、FG フレームワークは ProcessWith* などのサンプル単位のインターフェイスを呼び出さなくなります。

    • このインターフェイスを実装した後は、bool HasBatchProcessImpl() const 関数をオーバーライドし、true を返すことで、メインプログラムがこのインターフェイスを使用するよう指示します。

    • このインターフェイスを実装すると、パフォーマンスが向上します。たとえば、ユーザーサイド特徴量が 1 リクエストあたり 1 サンプルのみを含む場合、クロス特徴量のためのユーザーサイド特徴量を繰り返し解析するのを回避するために、broadcast メカニズム を使用できます。

    • stub_type=true が構成されており、ビニング操作が設定されていない場合、このインターフェイスは Map などの有効な型を返すことができます。

    • VariantVector の型は、BatchProcess 関数が返すものであり、is_sequencevalue_dimensionvalue_type の値によって異なります。詳細については、「value_dimension 構成項目」の説明をご参照ください。

    • バッチ処理インターフェイスの例については、RegexReplace をダウンロードして確認してください。

  • サードパーティ依存関係

    • abseil-cpp(FG フレームワークと同じバージョンを使用してください)

    • カスタムオペレーターが依存するサードパーティライブラリは、ソースコードを埋め込むか静的リンクを使用してコンパイルする必要があります。ダイナミックリンクライブラリに依存しないでください。そうしないと、オペレーターの読み込みに失敗する可能性があります。

シーケンス特徴量

is_sequence 構成項目を true に設定した場合、以下の点に注意してください。

  • スパース特徴量シーケンス

    • オペレーターが以前にアクセスした item_id のようなスパース特徴量シーケンスを生成し、各シーケンス要素が単一の値である場合、任意の型を出力できます。

    • オペレーターが各要素に複数の値を持つスパース特徴量シーケンスを生成する場合、文字列型のみを出力します。value_type を string に設定し、複数の値を区切るには区切り文字として chr(29) を使用します。

  • 密な特徴量シーケンス

    • オペレーターが履歴的にアクセスしたアイテムの埋め込みベクトルのようなスパース特徴量シーケンスを生成する場合、value_dimension をシーケンス各要素のディメンションに設定します。

    • シーケンス要素がスカラーである場合、value_dimension を 1 に設定します。

    • シーケンス要素がベクターである場合、value_dimension をベクターの長さに設定します。

    • オペレーターが出力する特徴量値の数は、value_dimension の整数倍である必要があります。

カスタムオペレーター一覧

オペレーター名

オペレーター機能

ソースコードダウンロードリンク

バイナリパッケージダウンロードリンク

EditDistance

編集距離

ダウンロードリンク

クリックしてダウンロード

SeqExpr

シーケンス式

ダウンロードリンク

クリックしてダウンロード

BPETokenize

BPE トークン化

ダウンロードリンク

組み込みの tokenize_feature に含まれています。

構成項目

  • EditDistance

    • encoding:入力テキストのエンコーディング。選択肢:utf-8latin。デフォルト値は latin です。

開発者向けサンプル

以下の例では、2 つの入力テキスト間の 編集距離 を計算する方法を示します。ヘッダーファイルは edit_distance.h です。

#pragma once
#include "api/base_op.h"

namespace fg {
namespace functor {
  class EditDistanceFunctor;
}

using std::string;
using std::vector;


/**
 * @brief Edit distance: Inputs two strings and outputs their text edit distance.
 */
class EditDistance : public IFeatureOP {
 public:
  int Initialize(const string& feature_config) override;

  /// @return A status code. 0 indicates successful execution.
  int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
                            vector<string>& outputs) override;

  /// @return A status code. 0 indicates successful execution.
  int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
                              vector<int32>& outputs) override;

  /// @return A status code. 0 indicates successful execution.
  int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
                              vector<int64>& outputs) override;

  /// @return A status code. 0 indicates successful execution.
  int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
                              vector<float>& outputs) override;

  /// @return A status code. 0 indicates successful execution.
  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

実装ファイルは edit_distance.cc です。

#include "edit_distance.h"

#include <absl/strings/ascii.h>
#include <absl/strings/str_join.h>

#include <nlohmann/json.hpp>
#include <numeric>  // Includes 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

// Defines the overloaded class.
template <class... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};
// Class template argument deduction guide (C++17).
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;
}

int EditDistance::ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
                                          vector<int64>& outputs) {
  vector<int32> distances;
  int status = ProcessWithInt32Outputs(inputs, distances);
  if (0 != status) {
    return status;
  }
  outputs.clear();
  outputs.insert(outputs.end(), 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.clear();
  outputs.insert(outputs.end(), 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.clear();
  outputs.insert(outputs.end(), 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.clear();
  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);

上記の表からソースコードをダウンロードし、build.sh スクリプトを実行して、FG オペレーターをコンパイルおよび生成します。

カスタムオペレーターのコンパイル

言語標準(C++17)およびコンパイルオプションを含む、FG フレームワークと同じコンパイル環境を使用します。公式のコンパイライメージを使用してください。 イメージの詳細は build.sh スクリプトに記載されています。

  • コンパイラ環境イメージ(CentOS 7):mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easyrec/feature_generator:centos7-0.1.1

  • コンパイラ環境イメージ(rockylinux:8、CentOS 8 と互換):mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easyrec/feature_generator:0.1.1

  • デフォルトでは C++11 ABI は使用されません。新しい ABI を使用する場合は、_GLIBCXX_USE_CXX11_ABI=1 を設定します。この場合、rockylinux:8 をベースとする 2 番目のイメージ(タグ:0.1.1)のみを使用してください。

  • カスタムオペレーターがサードパーティライブラリのダイナミックリンクを使用しないことを保証してください。静的リンクまたはプロジェクト内へのソースコードのコピーを使用してコンパイルします。

詳細については、開発者向けサンプルの CMakeLists.txt ファイルをご参照ください。