カスタム特徴オペレーターは、フレームワークが動的に読み込んで実行できるプラグインです。特徴生成 (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 | これを |
operator_name | 特徴オペレーターが登録される名前です。この名前は、実装されたクラス名と一致させることを推奨します。同じ |
operator_lib_file | 特徴オペレーターのダイナミックリンクライブラリファイルの名前です。名前は
|
expression | 入力式です。複数の入力がサポートされています。 |
value_type | 特徴変換の出力タイプです。 |
default_value | 特徴のデフォルト値です。これを文字列として構成します。コードはそれを必要なタイプに変換します。 |
separator | 複数の値を区切るためのセパレーターです。構成された |
stub_type | 現在の特徴オペレーターが特徴変換の中間結果としてのみ使用できるかどうかを示します。これを |
is_sequence | 特徴がシーケンス特徴であるかどうかをマークします。 |
sequence_length | シーケンスの最大長です。長さがこの値を超えると、シーケンスは切り捨てられます。 |
sequence_delim | シーケンス要素間のセパレーターです。入力が文字列型の場合にのみ設定します。 |
split_sequence | 入力シーケンス特徴が文字列型の場合、このパラメーターはフレームワークがシーケンスに対して分割操作を実行する必要があるかどうかを指定します。デフォルト値は
|
value_dimension | 出力特徴のディメンションです。これは、オフラインタスクの出力を切り捨てるために使用でき、出力テーブルのスキーマに影響します。特徴に複数の値があり、出力ディメンションが不確かな場合は、この構成を省略できます。
|
離散化操作 | 6 種類の離散化操作がサポートされています。これらの操作を自分で実装する必要はありません。詳細については、「特徴の離散化 (ビニング)」をご参照ください。
|
normalizer | 数値特徴の場合、この構成を追加して、式の値の計算など、変換結果をさらに処理できます。 サポートされているオペレーターと関数については、「組み込み特徴オペレーター」をご参照ください。minmax、zscore、log10、expression の 4 つのフレームワークがサポートされています。構成と計算方法は次のとおりです:
|
placeholder | シーケンス特徴において、シーケンスの各要素が複数の値を持つ場合 (
|
disable_string_view |
|
is_op_thread_safe | 現在の特徴オペレーターがスレッドセーフであるかどうかを示します。スレッドセーフな場合は
|
追加の注意事項:
ユーザー定義の設定項目は、フレームワークが使用する設定項目と同じ名前であってはなりません。
カスタムオペレーターは、フレームワークによって定義された設定項目を読み取って使用できます。ただし、そのセマンティクスを変更しようとすると、未定義の動作が発生します。
外部リソースファイルに依存する設定項目は、
_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_sequence、value_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-8、latin。デフォルト値は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 ファイルをご参照ください。