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

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

最終更新日:Jan 30, 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 などのオンラインサービスは、fg.json モデルファイルが配置されているディレクトリの custom_fg_lib サブディレクトリにあるすべてのダイナミックリンクライブラリファイルをスキャンし、メモリに読み込みます。

  • いくつかの公式拡張オペレーターが提供されています (開発者向けの例セクションのリストを参照)。公式オペレーターを指定するには、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 で終わる必要があります。

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

構成例

{
    "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 の例です。ネスト形式のカスタム Sequence feature の例については、「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*>;

// 特徴テーブルの列を表します。
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 カスタム特徴オペレーターの共通基底クラスです。
 *
 * フレームワークは、サブクラスが BatchProcess バッチインターフェイスメソッドをオーバーライドしているかどうかを確認します。
 * 実装されている場合、フレームワークはこのメソッドを呼び出して特徴変換を完了します。
 * 実装されていない場合、フレームワークは value_type 構成に基づいて ProcessWith* メソッドを選択します。
 * 指定したタイプに対応するインターフェイスを実装してください。
 */
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";
      }
      // 有効なポインターを返すためにメンバーにキャッシュします。
      cached_ = "unimplemented method called: " + msg_;
      return cached_.c_str();
    }

   private:
    std::string msg_;
    mutable std::string cached_;
  };

  virtual ~IFeatureOP() = default;

  /**
   * @brief 初期化メソッドです。
   * @param feature_config は JSON 文字列です。
   * @return 0 はモデルのロードが成功したことを示します。それ以外の場合、ロードは失敗します。
   */
  virtual int Initialize(const string& feature_config) = 0;

  /**
   * @brief 文字列出力による特徴変換です。
   * @param inputs 複数のフィールドを含むことができるレコードです。
   * @param outputs 変換された特徴出力です。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
                                    vector<string>& outputs) {
    throw NotOverriddenException("ProcessWithStrOutputs(FieldPtr)");
  }

  /**
   * @brief int32 出力による特徴変換です。
   * @param inputs 複数のフィールドを含むことができるレコードです。
   * @param outputs 変換された特徴出力です。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
                                      vector<int32>& outputs) {
    throw NotOverriddenException("ProcessWithInt32Outputs(FieldPtr)");
  }

  /**
   * @brief int64 出力による特徴変換です。
   * @param inputs 複数のフィールドを含むことができるレコードです。
   * @param outputs 変換された特徴出力です。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
                                      vector<int64>& outputs) {
    throw NotOverriddenException("ProcessWithInt64Outputs(FieldPtr)");
  }

  /**
   * @brief float 出力による特徴変換です。
   * @param inputs 複数のフィールドを含むことができるレコードです。
   * @param outputs 変換された特徴出力です。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
                                      vector<float>& outputs) {
    throw NotOverriddenException("ProcessWithFloatOutputs(FieldPtr)");
  }

  /**
   * @brief double 出力による特徴変換です。
   * @param inputs 複数のフィールドを含むことができるレコードです。
   * @param outputs 変換された特徴出力です。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
                                       vector<double>& outputs) {
    throw NotOverriddenException("ProcessWithDoubleOutputs(FieldPtr)");
  }

  /**
   * @brief 複数のレコードを処理するためのオプションのバッチインターフェイスです。
   *
   * @param inputs 入力列のベクターです。VariantVector は特徴列を表します。
   * @param outputs
   * 変換された特徴です。複雑な型がサポートされており、他の特徴変換の入力として機能できます。
   * @return ステータスコード。0 は成功を示します。
   */
  virtual int BatchProcess(const vector<VariantVector>& inputs,
                           VariantVector& outputs) {
    throw NotOverriddenException("BatchProcess");
  }

  /**
   * @brief サブクラスが BatchProcess を実装しているかどうかを明示的に宣言します。
   *
   * フレームワークは、まずこのメソッドを呼び出して BatchProcess がオーバーライドされているかどうかを確認します。
   * サブクラスが BatchProcess を実装している場合は、このメソッドをオーバーライドして true を返します。
   * デフォルトでは false を返し、BatchProcess が実装されていないことを示します。
   *
   * 注意: これにより、ダイナミックライブラリの境界を越えた例外伝播の問題を回避できます。
   * カスタムオペレーター (.so ファイル) とメインプログラムが異なる C++ ABI またはコンパイルオプションを使用している場合、
   * BatchProcess の呼び出しと例外キャッチによる検出が失敗する可能性があります。
   *
   * @return サブクラスが BatchProcess を実装している場合は true。
   * @return サブクラスが BatchProcess を実装していない場合は false (デフォルト)。
   */
  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 メソッドに渡します。その後、必要な設定項目を解析できます。

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

    • ProcessWith* メソッドは単一のレコードを処理します。複数の入力フィールドと、多値特徴などの多次元出力を持つことができます。

    • VariantRecord は、フレームワークが処理できるすべての特徴フィールドタイプを定義します。

    • コードは、考えられる各入力タイプに対応する特徴変換操作を実装することで、できるだけ多くのタイプをサポートする必要があります。特定のタイプが不要であることが確実な場合は、直接例外をスローできます。

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

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

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

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

    • 設定項目の operator_name の値は「OperatorName」でなければなりません。

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

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

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

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

  • BatchProcess インターフェイスは、データのバッチ処理をサポートします。

    • これはオプションのインターフェイスです。このインターフェイスを実装すると、FG フレームワークは単一サンプルの ProcessWith* インターフェイスを呼び出さなくなります。

    • このインターフェイスを実装する場合は、bool HasBatchProcessImpl() const もオーバーライドして true を返し、メインプログラムに通知します。

    • このインターフェイスを実装すると、より高いパフォーマンスが得られます。たとえば、クロス特徴を処理する場合、broadcast メカニズムを使用して、ユーザー側の特徴の繰り返し解析を回避できます。これらの特徴は、リクエスト内のすべてのアイテムで一貫しているためです。

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

    • BatchProcess が返す VariantVector の具体的なタイプは、is_sequencevalue_dimension、および value_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 を計算する方法を示しています。ヘッダーファイルは edit_distance.h です。

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

namespace fg {
namespace functor {
  class EditDistanceFunctor;
}

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


/**
 * @brief 編集距離: 2 つの文字列を入力し、そのテキスト編集距離を出力します。
 */
class EditDistance : public IFeatureOP {
 public:
  int Initialize(const string& feature_config) override;

  /// @return 状態コード。0 は実行成功を示します。
  int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
                            vector<string>& outputs) override;

  /// @return 状態コード。0 は実行成功を示します。
  int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
                              vector<int32>& outputs) override;

  /// @return 状態コード。0 は実行成功を示します。
  int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
                              vector<int64>& outputs) override;

  /// @return 状態コード。0 は実行成功を示します。
  int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
                              vector<float>& outputs) override;

  /// @return 状態コード。0 は実行成功を示します。
  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>  // 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 {
        // 無効な UTF-8 シーケンス
        ++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 {
        // 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

// オーバーロードされたクラスを定義します。
template <class... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};
// クラスのテンプレート引数推論ガイド (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;  // 無効な入力
  }

  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 オペレーターをコンパイルおよび生成します。

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

FG フレームワークと同じコンパイル環境 (言語標準 (C++17) とコンパイルオプションを含む) を使用する必要があります。公式のコンパイライメージを使用することを推奨します。イメージの詳細は 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 ファイルをご参照ください。