ドメイン固有の特徴量変換を実現するためのプラグインにより、特徴量生成 (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で終わる必要があります。-
このマーカーは、オフラインタスク用のリソースファイルの同期に使用されます。
-
構成例
{
"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_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.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 ファイルをご参照ください。