特徵產生(FeatureGenerator,下文簡稱FG)是一套把原始輸入轉換為模型所需輸入(特徵)的資料變換過程,用來保證離線、線上樣本產生結果的一致性。特徵產生也可以理解為特徵變換,對單個特徵或者多個特徵做變換。我們提供了各種類型的FG來完成各種特徵變換操作。
特徵產生只關注同時需要在離線和線上樣本產生過程中的變換操作。如果某個變換操作只需要作用在離線階段,則不需要定義為FG的操作。 FG模組在推薦系統架構中的位置如下圖所示:
特徵產生過程由一系列特徵變換運算元(下文簡稱為FG運算元)按照設定檔定義的DAG圖的拓撲順序並存執行。
設定檔樣本
features列表配置特徵運算元,每個特徵運算元必須包含feature_name、feature_type兩項,其餘配置項請參見內建特徵運算元。
配置項reserves指定離線任務中透傳出的欄位,這些欄位會原樣輸出,不會做特徵變換。
{
"features": [
{
"feature_name": "goods_id",
"feature_type": "id_feature",
"value_type": "string",
"expression": "item:goods_id",
"default_value": "-1024",
"need_prefix": false
},
{
"feature_name": "color_pair",
"feature_type": "combo_feature",
"value_type": "string",
"expression": ["user:query_color", "item:color"],
"default_value": "",
"need_prefix": false
},
{
"feature_name": "current_price",
"feature_type": "raw_feature",
"value_type": "double",
"expression": "item:current_price",
"default_value": "0",
"need_prefix": false
},
{
"feature_name": "usr_cate1_clk_cnt_1d",
"feature_type": "lookup_feature",
"map": "user:usr_cate1_clk_cnt_1d",
"key": "item:cate1",
"need_discrete": false,
"need_key": false,
"default_value": "0",
"combiner": "max",
"need_prefix": false,
"value_type": "double"
},
{
"feature_name": "recommend_match",
"feature_type": "overlap_feature",
"method": "is_contain",
"query": "user:query_recommend",
"title": "item:recommend",
"default_value": "0"
},
{
"feature_name": "norm_title",
"feature_type": "text_normalizer",
"expression": "item:title",
"max_length": 512,
"parameter": 0,
"remove_space": false,
"is_gbk_input": false,
"is_gbk_output": false
},
{
"feature_name": "title_terms",
"feature_type": "tokenize_feature",
"expression": "feature:norm_title",
"default_value": "",
"vocab_file": "tokenizer.json",
"output_type": "word_id",
"output_delim": ","
},
{
"feature_name": "query_title_match_ratio",
"feature_type": "overlap_feature",
"method": "query_common_ratio",
"query": "user:query_terms",
"title": "feature:title_terms",
"default_value": "0"
},
{
"feature_name": "title_term_match_ratio",
"feature_type": "overlap_feature",
"method": "title_common_ratio",
"query": "user:query_terms",
"title": "feature:title_terms",
"default_value": "0"
},
{
"feature_name": "term_proximity_min_cover",
"feature_type": "overlap_feature",
"method": "proximity_min_cover",
"query": "user:query_terms",
"title": "feature:title_terms",
"default_value": "0"
}
],
"input_alias": {
"non_exist_field1": "exist_field1",
"non_exist_field2": "exist_field2"
},
"reserves": [
"request_id",
"user_id",
"is_click",
"is_pay",
"sample_weight",
"event_unix_time"
]
}特殊配置項input_alias:配置特徵輸入名映射字典,把一個可能不存在的輸入欄位名映射到一個實際存在的欄位名;(input_alias配置從1.0.0版本才開始支援,通常可跳過該用法)
用途1:為較長的欄位名稱設定更簡短的別名
用途2:當一個自訂特徵運算元使用兩個相同的參數,為第二個參數設定別名。
同一個輸入欄位可以在不同特徵間複用,但不能在單一特徵變換內複用;配置input_alias可繞過這一限制。
例如,某自訂特徵運算元有兩個輸入參數都需要同一個欄位
A,此時可以配置A,B兩個輸入,同時配置一個input_alias映射"B": "A"。在真正執行的時候會把自訂特徵運算元的參數從(A,B)替換為(A,A)。
輸入欄位
輸入欄位表示當前輸入來自哪個實體,目前支援以下4種類型:
user:使用者側特徵,包括user profile、user維度統計特徵等。
context:上下文特徵,時間、地點、天氣等隨時變化的特徵。
item:物品側特徵,包括靜態內容特徵、item維度統計特徵等。
feature:表示當前輸入是另一個特徵變換的輸出。
其中,feature輸入欄位比較特殊,通過該輸入欄位配置特徵運算元之間的依賴關係。從整體來看,所有特徵運算元構成一個有向非循環圖(DAG),架構會按照拓撲順序來並存執行這些特徵變換操作。對應的拓撲結構如下:
一般情況下,DAG的中間節點的輸出不會作為FG的輸出。可以用特徵配置項stub_type來改變這一行為。
多實值型別及分隔字元
FG支援複雜類型的輸入,如Array、Map等,與MaxCompute的複雜類型一致。
字串類型的多值特徵可以使用chr(29)分隔字元。
例如v1^]v2^]v3,^]表示多值分隔字元,這是⼀個符號,其ASCII編碼是"\x1D",不是兩個符號。該字元在emacs中的輸⼊⽅式是C-q C-5,在vi中的輸⼊⽅式是C-v C-5。
特徵分箱(離散化)
架構支援如下6種類型的分箱操作:
hash_bucket_size:對特徵變換結果進行hash和模數。
vocab_list:把特徵變換結果轉化為列表的索引。
vocab_dict:把特徵變換結果轉化為字典的值(必須可轉化為int64類型)。
vocab_file: 從檔案讀入vocab_list或vocab_dict。
boundaries:指定分箱邊界,把特徵變換結果轉化為對應的桶號。
num_buckets:直接使用特徵變換結果作為分箱桶號。
hash_bucket_size
對特徵變換結果進行hash和模數,適用於任意類型的特徵值。
結果範圍:[0,
hash_bucket_size)空的特徵分箱結果為
hash(default_value)%hash_bucket_size
{
"hash_bucket_size": 128000,
"default_value": "預設值"
}vocab_list
根據詞彙表分箱,把輸入映射到詞彙表的索引;分箱結果為特徵值對應的vocab_list數組的索引。
vocab_list數組的元素類型需要與value_type的配置相同num_oov_bucket: Non-negative integer, the number of out-of-vocabulary buckets.All out-of-vocabulary inputs will be assigned IDs in the range [vocabulary_size, vocabulary_size+num_oov_buckets) based on a hash of the input value.
A positive num_oov_buckets can not be specified with
default_bucketize_value.
default_bucketize_value: The integer ID value to return for out-of-vocabulary feature values.This can not be specified with a positive
num_oov_buckets.預設值為
vocab_list.size()
{
"vocab_list": [
"",
"<OOV>",
"token1",
"token2",
"token3",
"token4"
],
"num_oov_bucket": 0,
"default_bucketize_value": 1
}vocab_dict
分箱結果為特徵值對應的vocab_dict字典的值,支援不同的特徵值對應到相同的分箱結果。
vocab_dict字典的鍵的類型需要與value_type的配置相同要求
vocab_dict的值必須能夠轉換為int64類型num_oov_bucket: Non-negative integer, the number of out-of-vocabulary buckets.All out-of-vocabulary inputs will be assigned IDs in the range [vocabulary_size, vocabulary_size+num_oov_buckets) based on a hash of the input value.
A positive num_oov_buckets can not be specified with
default_bucketize_value.
default_bucketize_value: The integer ID value to return for out-of-vocabulary feature values.This can not be specified with a positive
num_oov_buckets.預設值為
vocab_dict.size()
{
"vocab_dict": {
"token1": 1,
"token2": 2,
"token3": 3,
"token4": 1
},
"num_oov_bucket": 0,
"default_bucketize_value": 4
}vocab_file
從檔案讀入vocab_list或vocab_dict
{
"vocab_file": "vocab.txt",
"num_oov_bucket": 0,
"default_bucketize_value": 4
}vocab_file: 檔案路徑,檔案內容是詞彙表,每行一個詞彙,支援指定映射值(可選);
支援相對路徑;部署線服務時,需要與
fg.json放置在同一個目錄下只有token時,映射為行號(從0開始);有value時,token-value之間用空白符(空格或Tab)分割;value必須為
int64類型
num_oov_bucket與default_bucketize_value的含義同上文
boundaries
對數值型特徵安裝指定的分箱邊界分箱。 Represents discretized dense input bucketed by boundaries.
boundaries數組的元素類型需要與value_type的配置相同。Buckets include the left boundary, and exclude the right boundary.
Namely, boundaries=[0., 1., 2.] generates buckets (-inf, 0.), [0., 1.), [1., 2.), and [2., +inf).
{
"boundaries": [0.0, 1.0, 2.0],
"default_value": -1
}num_buckets
直接使用特徵變換結果作為分箱桶號,適用於特徵值可以轉換為整數的情況。
結果範圍:[0,
num_buckets)如果特徵值超出配置的範圍,則賦值為
default_bucketize_value。
{
"num_buckets": 128000,
"default_bucketize_value": 127999
}內建特徵運算元
每個特徵運算元的配置方法不同,所有能夠作為DAG葉子節點的特徵運算元都支援配置特徵分箱。
更多資訊,請參見內建特徵運算元。
特徵類型 | 說明 |
id_feature | 類型特徵 |
raw_feature | 數值型特徵 |
expr_feature | 運算式特徵 |
combo_feature | 組合特徵 |
lookup_feature | 字典查詢特徵 |
match_feature | 主從鍵字典查詢特徵 |
overlap_feature | 交疊特徵 |
sequence_feature | 序列特徵 |
text_normalizer | 文本歸一化 |
tokenize_feature | 文本分詞特徵 |
bm25_feature | BM25文本相關性特徵 |
kv_dot_product | kv向量內積 |
str_replace_feature | 字串替換 |
regex_replace_feature | Regex替換 |
slice_feature | 數組切片 |
運算元組合使用
通過配置DAG圖的方式,組合使用各種內建運算元可以發揮強大的特徵變換能力。
案例1:求一個序列前4個元素的平均值
{
"features": [
{
"feature_name": "top_n_prices",
"feature_type": "sequence_raw_feature",
"expression": "user:clk_prices",
"separator": ",",
"sequence_length": 4,
"stub_type": true
},
{
"feature_name": "top_n_avg_price",
"feature_type": "expr_feature",
"expression":"reduce_mean(top_n_prices)",
"default_value": "-1",
"variables":["feature:top_n_prices"]
}
]
}案例2:求一個序列中滿足條件的元素的平均值
{
"features": [
{
"feature_name": "valid_list",
"feature_type": "expr_feature",
"expression":"clk_times < 10",
"variables":["user:clk_times"],
"value_dimension": 5
},
{
"feature_name": "top_n_prices",
"feature_type": "bool_mask_feature",
"expression": ["user:clk_prices", "feature:valid_list"],
"value_type": "float",
"separator": ","
},
{
"feature_name": "top_n_avg_price",
"feature_type": "expr_feature",
"expression":"reduce_mean(top_n_prices)",
"default_value": "-1",
"variables":["feature:top_n_prices"]
}
]
}備忘:上述案例中,clk_prices和clk_times是兩個平行序列。
自訂特徵運算元
自訂特徵運算元能夠以外掛程式的形式被架構動態載入並執行。
更多資訊,請參見自訂特徵運算元。
效能最佳化經驗
FG模組的效能跟FG的配置有很大的關係,總的原則是盡量減少“不必要的”資料(特徵)變換。
如果能在離線、近線階段做好的資料加工、變換就不要放在FG階段(線上服務)中做。
遵循以下注意事項可獲得更佳的效能。
輸入的結構化資料優先使用MaxCompute表的複雜類型(e.g. Map、Array等),而不是STRING類型。減少字串解析的開銷。
線上服務(EasyRec Processor/TorchEasyRec Processor)中使用FeatureStore,並使用特徵資料庫FeatureDB作為線上儲存,以便開啟對複雜類型的支援
lookup_feature 的 map 欄位強烈推薦使用 Map 類型;
sequence_feature、overlap_feature、bm25_feature 強烈推薦使用 Array 類型的輸入;
盡量避免使用 match_feature(不支援複雜類型),使用 lookup_feature 來代替(組合 pkey & skey);
避免資料類型轉換的開銷
raw_feature 的
value_type沒有特殊原因不要設定為 float 以外的類型lookup_feature 的輸入保證 Map<Key, Value> 的 Key 的類型 與查詢欄位(Query)的類型一致
如果需要配置
num_buckets類型的特徵分箱,value_type一定設定為int64如果某列資料的最優類型在不同情景下是不一樣時,考慮添加一個不同類型的副本。
比如,作為
lookup_feature的查詢欄位時需要是BIGINT類型,作為combo_feature的一部分時需要是STRING類型;這種情況下,推薦為該列資料添加一個相應類型的副本,一個BIGINT類型,另一個STRING類型。樣本的SQL代碼如下:
SELECT int_data, int_data as str_data FROM ....
盡量使用特徵依賴(DAG模式)複用能夠複用的部分
全域配置
配置項 | 類型 | 預設值 | 說明 |
USE_CITY_HASH_TO_BUCKETIZE | string | 'false' | 是否使用CityHash作為特徵分箱的hash函數 |
USE_MULTIPLICATIVE_HASH | string | 'false' | 是否使用乘法hash替代特徵hash的取餘操作,建議開啟 |
DISABLE_FG_PRECISION | string | 'true' | 是否禁用浮點型特徵精度約束; 如不禁用,浮點型的特徵FG只保留6位精度 |
DISABLE_STRING_TRIM | string | 'false' | 是否禁用多值字串特徵split後去除前後空格 |
MONITOR_CUSTOM_OP_EVERY_N_SECONDS | string | '0' | 是否需要監控自訂OP的效能,列印效能資料的時間間隔(單位:秒) |
IGNORE_CUSTOM_OP_EXCEPTION | string | 'false' | 是否忽略自訂OP內部拋出的異常 |
注意:上述配置需要在各種執行環境保持一致:離線與線上一致;訓練與推理一致;否則會出現離線上打分不一致的問題。
Hash衝突率
在某資料集上,共計26個不同基數的特徵,每個特徵都設定hash_bucket_size為特徵基數的10倍,測試結果如下:
hash類型 | 特徵基數總和 | 分箱數總和 | hash衝突率 |
std::hash | 882774549 | 840065238 | 4.8381% |
cityhash | 882774549 | 840072446 | 4.8373% |
std+cityhash | 882774549 | 840075948 | 4.8369% |
cityhash+multiplicative | 882774549 | 840072195 | 4.8373% |
std+multiplicative | 882774549 | 840077306 | 4.8367% |
綜上,推薦使用std::hash + MultiplicativeHash 相結合的方式來最佳化模型效果,其中std::hash已預設開啟;MultiplicativeHash因為需要向下相容的原因,預設關閉,需要使用者按照以下方法手動開啟。
另外,CityHash是一種理論上均勻性更好的方法,但在該資料集上並未體現出明顯的優勢,使用者可在自己的資料集上進一步測試。
線上打分服務端配置
通過服務端的環境變數來配置;具體可以通過EasyRec Processor或者TorchEasyRec Processor的服務配置來設定。
{
"processor_envs": [
{
"name": "USE_MULTIPLICATIVE_HASH",
"value": "true"
}
]
}離線作業的配置
在MaxCompute環境執行FG的離線任務,具體可參考在離線任務中使用FG。
具體地,參考如下代碼:
from pyfg100 import run_on_odps
fg_task = run_on_odps.FgTask(...)
fg_task.add_fg_setting('USE_CITY_HASH_TO_BUCKETIZE', 'false')
fg_task.add_fg_setting('USE_MULTIPLICATIVE_HASH', 'true')
fg_task.run(o)使用pyfg API時的配置
在使用pyfg API時,比如,一邊訓練一邊做FG,可以通過如下方法來配置。
import pyfg
pyfg.set_env('USE_MULTIPLICATIVE_HASH', 'true')
pyfg.set_env('USE_CITY_HASH_TO_BUCKETIZE', 'false')