自訂特徵運算元能夠以外掛程式的形式被架構動態載入並執行。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 | 多值分隔字元,用來split配置的 |
stub_type | 表示當前特徵運算元是否只能作為特徵變換的中間結果,設定為 |
is_sequence | 標記是否是序列特徵。 |
sequence_length | sequence的最大長度,超過該值時會截斷 |
sequence_delim | sequence元素之間的分隔字元,僅在輸入為string類型時才需要設定 |
split_sequence | 當sequence輸入特徵的類型為string時,是否需要架構對sequence進行split操作,預設值為
|
value_dimension | 輸出特徵的維度,可以用來截斷離線任務的輸出結果,影響輸出表的schema;如果是多值特徵且輸出維度不確定,可以不添加該配置。
|
分箱操作 | 支援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: 計算兩個經緯度座標的距離,參數為[lng1_seq, lat1_seq, lng2, lat2],前兩個參數是序列,後兩個參數是標量值。
這是平鋪形式的自訂
Sequence特徵樣本,如果需要嵌套格式的自訂Sequence特徵樣本請參考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 自訂特徵運算元的公用基類
*
* 架構會檢測子類有沒有override批量介面`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 is a json string,
* @return 如果為0,則表示模型載入成功,否則表示模型載入失敗。
*/
virtual int Initialize(const string& feature_config) = 0;
/**
* @brief 特徵變換,輸出為string類型
* @param inputs 表示一條記錄,可以有多個欄位(field)
* @param outputs 特徵變換的輸出
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int ProcessWithStrOutputs(const vector<FieldPtr>& inputs,
vector<string>& outputs) {
throw NotOverriddenException("ProcessWithStrOutputs(FieldPtr)");
}
/**
* @brief 特徵變換,輸出為int32類型
* @param inputs 表示一條記錄,可以有多個欄位(field)
* @param outputs 特徵變換的輸出
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int ProcessWithInt32Outputs(const vector<FieldPtr>& inputs,
vector<int32>& outputs) {
throw NotOverriddenException("ProcessWithInt32Outputs(FieldPtr)");
}
/**
* @brief 特徵變換,輸出為int64類型
* @param inputs 表示一條記錄,可以有多個欄位(field)
* @param outputs 特徵變換的輸出
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int ProcessWithInt64Outputs(const vector<FieldPtr>& inputs,
vector<int64>& outputs) {
throw NotOverriddenException("ProcessWithInt64Outputs(FieldPtr)");
}
/**
* @brief 特徵變換,輸出為float類型
* @param inputs 表示一條記錄,可以有多個欄位(field)
* @param outputs 特徵變換的輸出
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int ProcessWithFloatOutputs(const vector<FieldPtr>& inputs,
vector<float>& outputs) {
throw NotOverriddenException("ProcessWithFloatOutputs(FieldPtr)");
}
/**
* @brief 特徵變換,輸出為double類型
* @param inputs 表示一條記錄,可以有多個欄位(field)
* @param outputs 特徵變換的輸出
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int ProcessWithDoubleOutputs(const vector<FieldPtr>& inputs,
vector<double>& outputs) {
throw NotOverriddenException("ProcessWithDoubleOutputs(FieldPtr)");
}
/**
* @brief 可選,處理多個records的批量介面
*
* @param inputs 輸入column的vector,VariantVector表示一個特徵column
* @param outputs
* 輸出,變換後的特徵;支援輸出複雜類型,可作為其他的特徵變換的輸入
* @return 狀態代碼,如果為0表示執行成功
*/
virtual int BatchProcess(const vector<VariantVector>& inputs,
VariantVector& outputs) {
throw NotOverriddenException("BatchProcess");
}
/**
* @brief 用於顯式聲明子類是否實現了BatchProcess方法
*
* 架構會優先調用此方法來檢測BatchProcess是否被重寫。
* 如果子類實現了BatchProcess,應該override此方法並返回true。
* 預設返回false表示未實現BatchProcess。
*
* 注意:此方法用於避免跨動態庫邊界的異常傳播問題。
* 當自訂運算元(.so檔案)與主程式使用不同的C++ ABI或編譯選項時,
* 通過調用BatchProcess並捕獲異常的方式來檢測可能會失敗。
*
* @return true 表示子類實現了BatchProcess
* @return false 表示子類未實現BatchProcess(預設)
*/
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方法,同時至少實現一個ProcessWith*方法。您的實作類別必須包含一個無參建構函式。
架構會把配置的JSON字串傳遞給
Initialize方法,您自行解析需要的配置項。架構會根據
value_type配置項調用對應的ProcessWith*方法,如果您未實現對應類型的方法,會拋出運行時異常。ProcessWith*方法僅需要處理一條記錄,可以有多個輸入欄位,也可有多維輸出(比如多值特徵)。VariantRecord定義了所有可以被架構處理的特徵field類型。您的代碼需要儘可能支援每種類型,即對每種可能的輸入類型實現相應的特徵變換操作,除非確定某些類型確實不需要用到,這種情況可直接拋異常。
FSMAP是使用
featurestore時需要支援的類型,可大幅提高Processor的效能。
您僅需要實現分箱操作前的特徵變換操作,如果有配置分箱操作,架構會自動執行分箱操作。
您需要使用
REGISTER_PLUGIN宏註冊新開發的特徵OP,否則架構無法使用。REGISTER_PLUGIN("OperatorName", OperatorClass);兩個宏參數根據需要替換,建議保持一致
配置項中的
operator_name就是這裡的"OperatorName",需要保持一致。在實現檔案而不是標頭檔中註冊OP
架構會掃描一個指定目錄下的所有動態庫,並在必要時嘗試載入其中需要用到的特徵運算元。
通過環境變數
FEATURE_OPERATOR_DIR指定動態庫檔案所在的目錄。每個動態庫裡可以包含多個特徵運算元的實現。
批處理介面
BatchProcess支援一次處理一個批量的資料可選介面,如果實現了該介面,則FG架構不會再調用樣本粒度的介面
ProcessWith*;當實現了該介面後,需要同步override
bool HasBatchProcessImpl() const這個函數,並返回true,告訴主程式調用該介面;實現該介面有機會提供更高的效能,比如,user-side的特徵在一次請求中只有1條樣本,對於交叉特徵,可通過
廣播機制避免重複解析user側特徵;當配置了
stub_type=true並且沒有配置分箱操作時,該介面可返回所有合法的類型,比如Map類型;BatchProcess函數返回的VariantVector的具體類型需要根據is_sequence、value_dimension和value_type的值來決定;具體參考上文中value_dimension配置項的描述;批處理介面的樣本 RegexReplace,請下載後查閱。
依賴的三方庫
abseil-cpp(推薦使用與FG架構相同的版本)
自訂運算元依賴的三方庫只能以嵌入原始碼或者靜態連結的方式編譯,不能依賴任何動態連結程式庫(會導致運算元載入失敗)
序列特徵
如果配置項is_sequence設定為true,有以下注意事項:
稀疏特徵序列
當運算元產生的是一個稀疏特徵序列,如歷史訪問過的
item_id的序列,並且序列的每個元素都是單值,此時可以輸出任意類型。當運算元產生的是一個稀疏特徵序列,並且序列的每個元素可能是多值時,只能輸出string類型(value_type必須設為string),多值使用分隔字元
chr(29)隔開。
稠密特徵序列
當運算元產生的是一個稀疏特徵序列,如歷史訪問過的物品的embedding向量,此時需要配置
value_dimension,值為序列的每個元素的維度。序列的元素是標量(scalar)時,
value_dimension設定為1。序列的元素是向量(vector)時,
value_dimension設定為向量的長度。運算元輸出的特徵值數量必須是
value_dimension的整數倍。
自訂運算元列表
運算元名稱 | 運算元功能 | 源碼下載連結 | 二進位包下載連結 |
EditDistance | 編輯距離 | ||
SeqExpr | 序列運算式 | ||
BPETokenize | BPE分詞 | 已包含在內建tokenize_feature中 |
配置項
EditDistance
encoding: 輸入文本的編碼,可選:
utf-8,latin,預設值為latin
開發樣本
下面以計算兩個輸入文本的編輯距離為例,標頭檔為edit_distance.h。
#pragma once
#include "api/base_op.h"
namespace fg {
namespace functor {
class EditDistanceFunctor;
}
using std::string;
using std::vector;
/**
* @brief 編輯距離:輸入兩個字串,輸出是它們的文本編輯距離
*/
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 unsupport text encoding";
assert(false);
return 0;
}
const Encoding TextEncoding() const { return encoding_; }
private:
Encoding encoding_;
std::wstring from_bytes(absl::string_view str) {
std::wstring result;
int i = 0;
int len = (int)str.length();
while (i < len) {
int char_size = 0;
int unicode = 0;
if ((str[i] & 0x80) == 0) {
unicode = str[i];
char_size = 1;
} else if ((str[i] & 0xE0) == 0xC0) {
unicode = str[i] & 0x1F;
char_size = 2;
} else if ((str[i] & 0xF0) == 0xE0) {
unicode = str[i] & 0x0F;
char_size = 3;
} else if ((str[i] & 0xF8) == 0xF0) {
unicode = str[i] & 0x07;
char_size = 4;
} else {
// Invalid UTF-8 sequence
++i;
continue;
}
for (int j = 1; j < char_size; ++j) {
unicode = (unicode << 6) | (str[i + j] & 0x3F);
}
if (unicode <= 0xFFFF) {
result += static_cast<wchar_t>(unicode);
} else {
// Handle surrogate pairs for characters outside the BMP
unicode -= 0x10000;
result += static_cast<wchar_t>((unicode >> 10) + 0xD800);
result += static_cast<wchar_t>((unicode & 0x3FF) + 0xDC00);
}
i += char_size;
}
return result;
}
};
} // namespace functor
// 定義 overloaded 類
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; // 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運算元。
編譯自訂運算元
需要與FG架構保持相同的編譯環境,比如語言標準(C++17)、編譯選項等。推薦使用官方提供的編譯鏡像,可以在build.sh指令碼中查看。
編譯環境鏡像(CentOS7):
mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easyrec/feature_generator:centos7-0.1.1編譯環境鏡像(rockylinux:8,相容CentOS8):
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為基礎鏡像的第二個鏡像(tag:0.1.1)務必注意自訂運算元不可以用動態連結的方式連結三方庫,可以使用靜態連結或者拷貝源碼到專案中的方式編譯。
具體可以查看開發樣本中的CMakeLists.txt檔案。