機(jī)器學(xué)習(xí)特征處理詳解與 tensorflow feature_column 接口實(shí)戰(zhàn)

機(jī)器學(xué)習(xí)特征處理詳解與 tensorflow feature_column 接口實(shí)戰(zhàn)
書接上文,在 模型手把手系列 的 前兩篇 文章 中,我們 已經(jīng) 詳細(xì)介紹 了 ?python、spark 和 java 生成TFrecord 和 六種方法構(gòu)建讀入batch樣本 ,按照 常規(guī) 機(jī)器學(xué)習(xí)模型 pipline 的 流程 來說,我們應(yīng)該在 使用 dataset 構(gòu)建好的 batch 數(shù)據(jù)上開始 分別 對(duì) 讀入的各個(gè)特征進(jìn)行處理 例如 特征數(shù)值化、取embeding 等操作,然后 輸入模型 的 過程 了,那么 本文 就從 這里開始 吧~
因?yàn)楸鞠盗?開發(fā)的模型 主要 使用的是 tensorflow ,而 tensorflow 官方 有著 自己實(shí)現(xiàn)的特征處理接口 feature_column ,非常好用 且 業(yè)界使用 的 非常廣泛,這里 強(qiáng)烈 安利 下~ ?。本文 這里 不欲對(duì) feature_column 接口的 參數(shù) 做解釋 ,而是 更側(cè)重于 在 每一步得到 的 數(shù)據(jù)形式 做一些 說明,方便 我們 靈活對(duì) 數(shù)據(jù) 輸入 進(jìn)行定制 和 debug .
feature_column 接口 本來是 Google 為了 適配 tensorflow estimator 這個(gè) 模型訓(xùn)練 的 高階接口 使用 的,但它 既然 能 方便處理特征 ,并且 特征處理 殊途同歸 ,當(dāng)然 我們也 可以將 feature_column 接口 配合 tensorflow keras 開發(fā)模型使用, 親測(cè) 也 非常好用 哦。這里要 重點(diǎn)推薦 一下 estimator 接口,使用 estimator 開發(fā)的 單機(jī)版模型可以直接適配分布式 模型 訓(xùn)練,代碼 無需怎么 改動(dòng),非常強(qiáng)大?。?! 在本系列 后期 我們 也會(huì)寫 幾篇關(guān)于 使用 estimator 搭建 模型的文章,感興趣 得 同學(xué)可以 關(guān)注下 后續(xù)的 文章 哦 ~
閑言少敘,下面 就讓 我們 開始 本文的 機(jī)器學(xué)習(xí) 特征處理方法 介紹吧~
(1) 特征處理基礎(chǔ)說明
在 深入淺出理解word2vec模型 (理論與源碼分析) 文章中,我們說過 自從 2013年 embeding 誕生 以來 就被 業(yè)界的 深度學(xué)習(xí)模型 進(jìn)行了 深入而廣泛 的 應(yīng)用,特別是在 高維稀疏的sparse ID 類特征 應(yīng)用 特別 廣泛。從 常規(guī)意義 上來 說,對(duì)于 推薦系統(tǒng)或廣告算法 系統(tǒng),百分之80 的 特征 均是以 高維稀疏 的 ID類 特征 的形式 出現(xiàn)的。所以 我們 開發(fā) DNN 模型的時(shí)候,對(duì)于 高維稀疏 的 ID類特征 甚至是 用戶歷史 行為序列 特征,我們 總是 會(huì) 先以某種 方式 去 取得 該 ID 的 embeding , 然后 進(jìn)行 加減乘或則拼接、求attention 等 花樣的 騷操作。
在 企業(yè)級(jí)機(jī)器學(xué)習(xí) Pipline - 特征feature處理 - part 1 文章中,我們 列出了 搜廣推算法 中 經(jīng)常用到 的 一些特征的 設(shè)計(jì)方法 包括: 交叉特征、序列特征、實(shí)時(shí)特征 等,錯(cuò)過 以前文章 的 同學(xué),可以 戳進(jìn)去 看看 哦 。從 上文中 我們 知道了 有哪些特征 可以用,但是 在 模型設(shè)計(jì) 中 真正的把 ?數(shù)據(jù) 以合適的格式 讀進(jìn)去 適配 模型設(shè)計(jì) 的需要,則是 有著 道與術(shù) 的 鴻溝。而 其實(shí) 在 模型開發(fā) 中,我們 大多數(shù) 時(shí)間 均是 花在了 模型 的 數(shù)據(jù)處理 上。
一般意義 上來說,對(duì)于 浮點(diǎn)數(shù) 特征,我們 可以用 一些方式 (例如 log / xgboost ) 進(jìn)行 分桶 離散化 然后求 embeding 扔進(jìn) 模型 里,或則 直接 讀入 浮點(diǎn)數(shù) 將 它和 其它特征的 embeding 拼接后 傳入網(wǎng)絡(luò) 等。 但是 目前的 實(shí)驗(yàn) 來看,統(tǒng)計(jì)類的浮點(diǎn)數(shù)特征直接扔進(jìn)模型效果提升總是不太明顯。
而在 實(shí)際操作 中,對(duì)于 一些類別類型 的 category特征 ,我們 通常會(huì) 構(gòu)建特有的 embeding matrix, 當(dāng)然 我們 也可以 多個(gè)特征 共用一個(gè) embeding matrix, 就像 后文 要介紹的 tf.feature_column.shared_embeddings
。我們 可以將 用戶 剛下載的 Appid 和 用戶 最近3天打開過 的 appid 用一個(gè) embeding 去訓(xùn)練 也是 一種 不錯(cuò)的選擇 ,其可以 有效緩解 因 數(shù)據(jù)稀疏 導(dǎo)致的 訓(xùn)練不充分 的問題。
更細(xì)致 的說,要想 得到 某 ID 的 embeding, 我們通常 需要 根據(jù)索引 (ids)去 embeding matrix 中 查找(look_up) ,這里 我們 就 需要 先有這個(gè) id,一般 這個(gè)id是數(shù)值 int型 的,而 我們 常規(guī)使用 的 高維稀疏 特征 大多 是 字符串 類型的sparse ID , 所以 常規(guī)情況 下我們 需要 去對(duì) 每一列特征 對(duì)應(yīng)的 去維護(hù) 一個(gè)索引id ,id 和 該 特征 的 取值unique 個(gè)數(shù) 一一對(duì)應(yīng),一般 這個(gè) 過程 又稱為 特征取值ID化 。我們 取出 該特征 對(duì)應(yīng)的ID 對(duì)應(yīng)的 embeding , 將 高維sparse特征轉(zhuǎn)化為低維的embedding ,則可以 進(jìn)行 模型 數(shù)據(jù) 的 語(yǔ)義計(jì)算 了。
如 上文 所說 ,在 特征取值ID化 這一步,如果 我們的 模型 中 特征的取值個(gè)數(shù) 和 特征種類個(gè)數(shù) 非常多 的話,我們 就需要 每天 去對(duì) 每列特征 的 舊的ID集合上 加上 新增的 特征取值 并 重新構(gòu)建索引 輸入模型 中 訓(xùn)練,多個(gè)特征 均 需要 如此,對(duì)于 搜廣推模型 上 動(dòng)則上億 的 稀疏特征 來說,可以想象 這是一個(gè) 非常復(fù)雜 且 難以 持久維護(hù) 的 工程,而 早期的 很多大廠 的 dnn 模型均是如此處理的,可怕 ~
好在 tensorflow 中提供了 feature_column 接口,它可以 支持 將 每個(gè) 特征hash 后 快速得到 一定數(shù)值的 索引id , 該特征 空間大小 可以 自行定制。既然是 hash ,肯定 避免不了沖突,這里 我們 不在展開,自己根據(jù) 業(yè)務(wù) 調(diào)整 hash空間大小 即可。 類似的 特征ID化 工具,我知道 百度 的 paddelpaddle 深度學(xué)習(xí)框架 ?中 也有 類似的 hash函數(shù) 的 設(shè)計(jì)。
既然是說 feture_column ,我們就不得不祭出這張圖了。
從 上圖中 我們 可以知道,feature column 處理特征 可以分為 Categorical Column 和 Dense Column 兩大類,這其實(shí) 和 我們 前面介紹 的 特征總共 分成 dense 和 sparse 兩類特征 是 一個(gè)意思。 當(dāng)然,圖中的 一些接口 在 某些場(chǎng)景 里 需要 組合使用 ,也和我們上文 介紹的 特征處理 流程 差不太多。而一列特征要想接入DNN模型,則需要 先轉(zhuǎn)化 為 DenseColumn 才可以。至于 如何組合 ,在下文 我們 會(huì) 進(jìn)行 一些說明,但是從 數(shù)據(jù)取值類型 和 我們 自己的 先驗(yàn)知識(shí) ,推斷 出來 某個(gè)位置 取值 應(yīng)該是 什么 類型 和 形狀 也是 不難 的~
當(dāng)然 除了 圖中 的 一些接口 之外,feature_column 還有一些 以 sequences開頭 的 處理序列特征 的接口,我們的 文章中,一直 強(qiáng)調(diào)了 序列特征 的 重要性,因?yàn)?序列特征 的 出現(xiàn) 讓我們 不再是 孤立的 看待 用戶的行為,而是在 時(shí)間序列 上 連續(xù)的 建模用戶的 特性和偏好,用 歷史的、發(fā)展的,普遍聯(lián)系 的 眼光 來 分析 用戶,在 工業(yè)實(shí)踐 中 具有 舉足輕重 的意義。后面 我們 也會(huì)寫 一些 介紹序列建模 的 文章,感興趣 的 同學(xué)可以 持續(xù)關(guān)注 下哦。
這里 需要注意 的一點(diǎn)是:feature_column 接口在tensorflow 1.x 系列和 2.x 系列 均有支持,但是也有一些 細(xì)微的 差別。對(duì)于普通特征,在 tensorflow 1.x 中,我們可以 通過 tf.feature_column.input_layer
處理 features 得到 dense feature,而 序列特征 可以使用 tf.feature_column.sequence_input_layer
。但是在 2.x 中,該 接口 均不在支持 。在2.x 中 改為了 通過 tf.keras.layers.DenseFeatures
?處理 features 得到 dense feature,序列 特征 可以 通過 tf.keras.experimental.SequenceFeatures
來 得到。而我們 下文的 代碼 均是 基于 tensorflow 2.x 開發(fā)的。
其實(shí)要想對(duì) tensorflow 的 使用 與 設(shè)計(jì)思想 進(jìn)行 更深入 的了解,我們 可以 直接 去看 源碼 ,源碼 的 說明非常詳細(xì) ,并且 對(duì) 參數(shù)進(jìn)行了 保姆級(jí) 的 說明,還列舉出了 使用的demo。 具體路徑見下面這2個(gè)鏈接:
源碼地址 tensorflow源碼
接口介紹 feature_column接口介紹
好吧,文字部分 就 這些吧,代碼才是硬通貨,接下來 就讓 我們 一起開始 接口 的 更進(jìn)一步 的 代碼介紹吧~
(2) 代碼時(shí)光之 feature_column使用說明
我們 這里 只挑一些常用 的 接口說明,主要涵蓋 數(shù)值特征處理接口、類別特征hash化 接口、序列特征和dataset結(jié)合使用 方法、特征交叉、embedding共享等,而其他 的 類似接口 則 可以 常規(guī)類比推算 過去哈。
畢竟 萬(wàn)變不離其宗,掌握 數(shù)據(jù)的流程以及各階段數(shù)據(jù)的形式比會(huì)用多少接口更加重要 。我們 會(huì)在 介紹接口 的時(shí)候 說明 該接口 的 適用場(chǎng)景,注意 看旁白 哦~
(2.1)numeric_column
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
import?tensorflow?as?tf
number?=?tf.feature_column.numeric_column("price")
price_feature_dict?=?{"price":?[[2.0],[3.0],[4.0]]}
#?用這種數(shù)據(jù)解析方法來解析dict數(shù)據(jù)
#?這里感覺更像是定義了一種數(shù)據(jù)解析方法?
output?=?tf.keras.layers.DenseFeatures(number)(price_feature_dict)
print(output)
從名字 我們 就可以 numeric_column
可以讀入 數(shù)值類型 的特征 ,我們輸入 統(tǒng)計(jì)類的浮點(diǎn)數(shù) 特征或則 其他 不需要 分桶、且 也不需要 embeding 的 特征 可以 使用。
這里我們可以重點(diǎn)看一下:output 返回的就是一個(gè) 浮點(diǎn)數(shù)tensor, 維度 沒有 改變。
我們使用 tf.feature_column.numeric_column
接口 定義了 處理 price 列 字段 的方法,這個(gè)方法 ?返回的值,我們可以通過 ?tf.keras.layers.DenseFeatures
接口( tensorflow 2.x 支持 ) 來查看。而 tf.keras.layers.DenseFeatures(number)
到 這里 整體( 包括((number)) )其實(shí) 就定義了 對(duì) 該列特征 的 處理方法, 后面括號(hào) 里的 (price_feature_dict)
是 這個(gè)方法 的 輸入?yún)?shù) 。
這里我們 刻意 把分行的 寫法 合并在了一起,方便理解:注意 兩個(gè)括號(hào) 的 連接,第一個(gè)括號(hào) 是 給方法用的,是 處理方法 的 一部分。第二個(gè)括號(hào) 才是 根據(jù)輸入 得到 具體的值 ,并用 前面定義的方法 來 處理該輸入值 。整體 來看 就是: 對(duì)輸入特征的某個(gè)字段定義了一種什么處理方法。
因?yàn)?tensorflow 2.x 支持 eager 模式,所以輸出 變量取值 就和 python一樣,直接 打印變量 就 OK。
這里 插入一個(gè)深坑, 可能 引起 bug 的 地方 就是: 我們 使用 簡(jiǎn)單 自定義數(shù)據(jù) 測(cè)試接口 和 使用 dataset 數(shù)據(jù) 測(cè)試的 時(shí)候,略有不同 。注意看:代碼里的 price_feature_dict
就是 我們 輸入 的特征,這里要 注意 每一個(gè)元素都是被[]
包裹著的([2.0]
), 是一個(gè)數(shù)組,而我們上一篇文章 tensorflow 六種方法構(gòu)建讀入batch樣本(含序列特征處理),踩坑經(jīng)驗(yàn)值得收藏 介紹的 batch 數(shù)據(jù)里,每一列 特征 都是 僅僅 只有數(shù)值,不被 []
包裹,在 tensor 的 世界里也就是 維度上少了一維 。
解決方法 就是:在上一篇文章里介紹的 train_raw_dataset
后面 可以接入 這段代碼 就可以在 dateset 上 測(cè)試代碼 運(yùn)行通過:
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
final_dataset?=?train_raw_dataset
.apply(tf.data.experimental.ignore_errors())?
.shuffle(2).
.batch(BATCH_SIZE,?drop_remainder=True)
.repeat(NUM_EPOCHS)
.prefetch(tf.data.experimental.AUTOTUNE)
上面是 一個(gè)插曲,僅僅 是 為了說明 批量跑模型的時(shí)候,數(shù)據(jù)格式 略有不同 而已。如果你 不用 dataset 讀入 ?數(shù)據(jù) 來測(cè)試 這個(gè)接口 ?則不用關(guān)注。
上面 打印的 output 輸出的 最后 返回?cái)?shù)據(jù) 長(zhǎng)這樣:
(2.2) bucketized_column
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
import?tensorflow?as?tf
age_feature_dict?=?{"age":?[[2.0],[3.0],[4.0]]}
age_bucket?=?tf.feature_column.bucketized_column(
tf.feature_column.numeric_column(key='age',?shape=(1,),default_value=0,dtype=tf.dtypes.float32),boundaries=[20,?40,?50,?60])
feature_layer?=?tf.keras.layers.DenseFeatures(age_bucket)
output?=?feature_layer(age_feature_dict)
print(output)
#?返回的是onehot值,維度改變?
這里的 bucketized_column
接口 很好理解,就是 根據(jù) 接口限定 的 邊界(boundaries) 進(jìn)行分桶。對(duì)于 浮點(diǎn)數(shù)類型 的 特征,我們 需要分桶的,這里 給定分桶邊界 就 可以 了。
注意 分桶 是 基于 數(shù)組比較 的,所以 這個(gè) 接口 需要先將 輸入數(shù)據(jù) 確定為 數(shù)值 才能 進(jìn)行 比較分桶,所以 只能 和 2.1 介紹的 numeric_column 一起 組合使用。 接口 一起組合 使用 在 feature_column
處理 接口 中是 非常常見 的。
這里 我們要 更詳細(xì) 的 贅述 一下: bucketized_column ?返回的 是 和 boundaries 維度 大小 相同的 onehot 數(shù)組。數(shù)值 落在 哪個(gè)區(qū)間,則 那個(gè)維度 的 取值為 1,其他維度 為0 。
拿到了 onehot 之后,當(dāng)然我們后邊也可以在接入 embeding_column 得到 embeding之后, 通過 DenseFeatures
將 具體的 embeding 展示 出來。這里沒有接入 embeding_column 而直接接了 DenseFeatures ,所以返回的是 onehot
。
(2.3) ?categorical_column_with_identity
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
import?tensorflow?as?tf
features?=?{'video_id':?tf.sparse.from_dense([[2,?85,?0,?0,?0],[33,78,?2,?73,?1]])}
video_id?=?tf.feature_column.categorical_column_with_identity(
??????key='video_id',?num_buckets=100,default_value=0)
#?說明?sparse?tensor?可以直接傳入categorical_column_with_identity?
#?后面直接接入?embedinig?
columns?=?[tf.feature_column.embedding_column(video_id,?9)]
input_layer?=?tf.keras.layers.DenseFeatures(columns)
dense_tensor?=?input_layer(features)
print(dense_tensor)
categorical_column_with_identity
可以 返回 onehot 數(shù)據(jù),我們使用 dd=tf.feature_column.indicator_column(video_id)
將 dd 塞入 DenseFeatures
中查看。這里是 序列數(shù)據(jù),所以 返回的是 multI hot 形式 的數(shù)據(jù)。 這個(gè) 接口 使用的 非常廣泛, identity 屬于 直接類型 , 無需 映射 ,直接 輸入 類別。
這里我們可以看到 tf.sparse.from_dense
是將 輸入的 ?dense 數(shù)據(jù) 轉(zhuǎn)成了 sparse tensor 的 格式。 我們知道:sparse 和 dense 其實(shí)描述的是同一份數(shù)據(jù),只是用的是不同的形式。
從 上面的 代碼 我們 也可以 看出:sparse tensor 可以直接傳入categorical_column_with_identity
。這就 非常強(qiáng)大 了,因?yàn)?我們?cè)?很多時(shí)候用 tf.string_split()
返回的就是 sparse tensor 的格式,這樣 我們 就可以 處理 變長(zhǎng)字符串 了。用 tf.string_split()
切割 字符串,然后 扔進(jìn)categorical_column_with_identity
,后面 再 接入 embeding_column 拿到 embedinig ,這 數(shù)據(jù)不是 處理的 一氣呵成,非常絲滑 嗎 ~
同時(shí) sparse tensor 數(shù)據(jù)也可以直接接入 tf.keras.embeding 哦,非常好用哦?。?!
(2.4) categorical_column_with_hash_bucket
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
hash_word?=?tf.feature_column.embedding_column(
????????tf.feature_column.categorical_column_with_hash_bucket(key='adid',
????????hash_bucket_size=100,?dtype=tf.dtypes.string),4)
feature_dict?=?{"adid":?["20",?"127",?"51",?"3"]}
feature_layer?=?tf.keras.layers.DenseFeatures(hash_word)
output?=?feature_layer(feature_dict)
print(output)
#?在這里將?embedding_column?換成?indicator_column?列將能看到返回的是?onthot??
這個(gè) 接口 ?應(yīng)該是 算法工程師們 使用的 最多 的 接口 了, 顧名思義 ,將 類別id類 的 特征hash數(shù)值化 。在這里 tf.feature_column.categorical_column_with_hash_bucket
返回的 是 onehot 或則 sparse tensor 。onehot 或則 sparse tensor 在 這里 并沒有 嚴(yán)格的區(qū)分,均可以 打印 出來 查看 格式。
本事例中,output 最后輸出embeding 長(zhǎng)這樣:
這些 接口中 ,不帶 sequences開頭 的接口,一般都是 使用 單列 特征 定長(zhǎng) 的 使用,并且 大多數(shù)時(shí)候 一列特征都是一個(gè)取值。categorical_column_with_hash_bucket
這個(gè)接口 比較強(qiáng)大 的一個(gè) 功能 就是 他也可以處理 多個(gè)取值的 multi hot 的類別特征,或則 稱為 序列特征。 我們可以使用下面的代碼來進(jìn)行驗(yàn)證:
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
hash_word?=?tf.feature_column.indicator_column(
????????tf.feature_column.categorical_column_with_hash_bucket(key='id',
????????hash_bucket_size=10,?dtype=tf.dtypes.string))
feature_dict?=?{"id":?[["20","21"],["127","128"]?,["51",'52'],?["3","4"]]}
feature_layer?=?tf.keras.layers.DenseFeatures(hash_word)
output?=?feature_layer(feature_dict)
print(output)
#?在這里將?indicator_column??換成embedding_column??列將能看到返回的是和?onehot?一樣格式embeding?.
這里,我們直接使用 indicator_column 返回了 categorical_column_with_hash_bucket
處理多取值 list 返回的 ?multi hot ,長(zhǎng)這樣:
在這里將 indicator_column
?換成 ?embedding_column
?列 將 能看到 返回的 是 和 上面事例代碼的單列一個(gè)特征的數(shù)據(jù)一樣格式embeding 。
這里明明 id 里輸入 了多個(gè)取值,也返回了 multihot , ?為啥 最后 返回得 embeding 確是 和 onehot 維度一樣呢? 原來是因?yàn)椋?embedding_column 默認(rèn)對(duì)多個(gè)取值返回的 embeding 進(jìn)行了combine, 默認(rèn)的 combine 方式 是 mean.
(2.5) categorical_column_with_vocabulary_file
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
import?tensorflow?as?tf
features?=?{'sex':?tf.sparse.from_dense([["male"],["female"]])}
sex_col?=?tf.feature_column.categorical_column_with_vocabulary_file(
??????key='sex',?vocabulary_file='./voc.txt',?vocabulary_size=2,
????num_oov_buckets=5)
sex_emb=tf.feature_column.embedding_column(sex_col,?4)
columns?=?[sex_emb]
input_layer?=?tf.keras.layers.DenseFeatures(columns)
dense_tensor?=?input_layer(features)
print(dense_tensor)
這里除了使用 hash 的方式 進(jìn)行 特征取值id化 之外,我們也可以使用 categorical_column_with_vocabulary_file
手動(dòng)的 維護(hù) 一個(gè) 字典文件 ,達(dá)到 和 上文 最初 介紹的 手動(dòng)維護(hù) id索引 的 古老做法 類似 的 功能。在 voc.txt
字典 文件 中,我們 只要 每一行放入一個(gè)特征的原始取值 即可,這個(gè) 接口 會(huì) 自動(dòng) 將 原始特征 的 取值 映射 成 索引ID ,非常強(qiáng)大 哦,在 某些場(chǎng)景 下,我們還是 使用 的 非常多 的。
當(dāng)然,這里的 文件路徑 不僅 可以是 單機(jī)版本 的 pc路徑,也可以是 保存 在 大數(shù)據(jù)集群 上的 hdfs路徑 哦 。
對(duì)于 feature_column 眾多接口中,以 *_with_vocabulary_file
結(jié)尾 的 接口, 均可以 使用 這里 說明 的 類似的 做法 進(jìn)行操作,其他的接口 我就 不在贅述 了。
(2.5) sequence_categorical_column_with_hash_bucket
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
import?tensorflow?as?tf
#?定義特征列
click_history_feature_col?=?tf.feature_column.sequence_categorical_column_with_hash_bucket('click_list',?hash_bucket_size=100,?dtype=tf.int64)
click_history_embedding_col?=?tf.feature_column.embedding_column(click_history_feature_col,?dimension=16)
columns?=?[click_history_embedding_col]
#?定義特征層?
list_layer?=?tf.keras.experimental.SequenceFeatures(columns)
max_len=5
#?對(duì)于每個(gè)特征需要構(gòu)建一個(gè)dict
list_dict?=?dict()
list_dict["click_list"]=tf.keras.Input(shape=(max_len,),?dtype=tf.int64,name="click_list")
#?dict里只有一個(gè)元素,然后可以?
sequence_input,?sequence_length=list_layer(list_dict)
sequence_length_mask?=?tf.sequence_mask(sequence_length)
print("sequence_input:",sequence_input.shape)
print("sequence_length_mask:",sequence_length_mask)
#?reduce_mean?的時(shí)候,要注意考慮?batch_size?的維度為0,后面第一層括號(hào)的維度為1?
embeding_mean?=?tf.reduce_mean(sequence_input,1)
print("embeding_mean:",embeding_mean)
#接一層全鏈接層?
den?=?tf.keras.layers.Dense(10,?activation="relu",?name="dense1")(embeding_mean)
model_outputs?=?tf.keras.layers.Dense(1,?activation="sigmoid",?name="final_sigmoid")(den)
model?=?tf.keras.Model(inputs=[list_dict["click_list"]],outputs=model_outputs)
#?model.summary()
model.compile(optimizer='adam',loss="binary_crossentropy",metrics=['accuracy'])
model.fit(final_dataset,?epochs=2)
顧名思義,這個(gè)接口 是以 sequence_categorical_column_*
開頭的,就是 feature_column 提供的 眾多 處理序列特征 的 接口中 的一個(gè)。 序列特征 表示 特征的取值 是一個(gè) list或則 數(shù)組 。
上面的代碼是一個(gè) feature_column 和 tensorflow keras 結(jié)合使用 進(jìn)行 特征處理 和 模型開發(fā) 的 完美樣例代碼。對(duì)于這個(gè) 事例,我將 keras 的 數(shù)據(jù)讀入 也接 進(jìn)來了。
中間一個(gè)隱藏的深坑是 :使用 tf.keras.Input
和 input_layer
結(jié)合 將 特征數(shù)據(jù) 進(jìn)行 固定形式 的 處理 的 時(shí)候,要求 input_layer
?后面 跟著的 keras_input 數(shù)據(jù) 必須是一個(gè) 字典類型 。按照上面 我 提供 的 demo 的 同樣做法,字典里僅僅放入了一個(gè)字段,然后作為參數(shù)傳遞給 特征處理輸入層 input_layer , 他大爺?shù)模羁影。。?!?dāng)初 花了老大時(shí)間解決 這個(gè)問題, 寫到 這里 希望確實(shí) 可以 幫到 還在 困惑中 的 老哥,覺得有用 就幫忙 關(guān)注轉(zhuǎn)發(fā) 一下 吧~
demo 里 我們 直接接入了 上面所說的 final_dataset 的 dataset ,是一個(gè) 相對(duì)完整的工程實(shí)例 。我們 通過 batch 數(shù)據(jù)來訓(xùn)練 模型,在dataset 的 click_list 列,我輸入的是一個(gè)python 數(shù)組。
這里要 注意 到是: click_list 我是padding 之后的,填充得最大長(zhǎng)度是 5 , 是 定長(zhǎng)的list .所以這里也是5 , 代碼里是 tf.keras.Input(shape=(max_len,)
。
中間部分,我們使用了 tf.keras.experimental.SequenceFeatures
來 將 embeding_col 接入網(wǎng)絡(luò),取代了以前 tensorflow 1.x 系列 的 tf.feature_column.sequence_input_layer
,和 前面開篇 的 時(shí)候說 的 是一個(gè)意思。
(2.6) shared_embeddings
在某些場(chǎng)景下,我們 也許有 多列的 field 的特征 需要 **共用一個(gè) shared_embeding&& , feature_column接口 下的 shared_embeddings
可以幫助我們實(shí)現(xiàn)。
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
#?tf.enable_eager_execution()?
#?在tensorflow?2.x?中需要關(guān)閉eager
import?tensorflow?as?tf
tf.compat.v1.disable_eager_execution()
tf.compat.v1.reset_default_graph()
#?特征數(shù)據(jù)
features?=?{
????'department':?['sport',?'good',?'drawing',?'gardening',?'travelling'],
????'display':?['sport',?'yellow',?'light',?'sex',?'bad'],
}
#?特征列
department_hash?=?tf.feature_column.categorical_column_with_hash_bucket('department',?10,?dtype=tf.string)
display_hash=tf.feature_column.categorical_column_with_hash_bucket('display',?10,?dtype=tf.string)
#?print(department_hash)
columns?=?[department_hash,display_hash]
share_columns?=?tf.feature_column.shared_embeddings(columns,?dimension=4,shared_embedding_collection_name="share_embeding")
#?這里2個(gè)?ids?共同構(gòu)建了一個(gè)?share?embeding?column,?查找的時(shí)候,使用公共的variable?查找值。
share_input_layer?=?tf.keras.layers.DenseFeatures(share_columns)
dense_tensor?=?share_input_layer(features)
print(dense_tensor)
這里 需要 注意 的 tensorflow 2.x 使用 shared_embeddings
得話,需要 關(guān)閉 eager 模式,源碼里有說明,應(yīng)該是 底層有沖突 吧。我們 可以使用 tf.compat.v1.disable_eager_execution()
方法 關(guān)閉eager 模式。
并且 需要注意 的一點(diǎn) 是:最后返回的 dense_tensor 得維度,在我們的例子中是:Tensor("dense_features/concat:0", shape=(5, 8), dtype=float32)。
對(duì) 組合 成 共享embeding 集合的每一個(gè)元素,均 返回一個(gè)embeding , 因?yàn)?department_hash 和 display_hash 在batch size 一致,這里均是5,而 8 則是因?yàn)?shared_embeddings 得 每一條 embeding 是 拼接了 2類 得2個(gè) dim =4 的embeding . 用源碼里的解釋是:
返回 embeding 順序 和 輸入的 categorical_column 時(shí)候 順序 致 。
(2.7) crossed_column
@?歡迎關(guān)注作者公眾號(hào)?算法全棧之路
#?這里要求我們輸入特征名稱,而不能是categorical_column_with_hash_bucket,官方解釋說是會(huì)增加沖突。
cross_column?=?tf.feature_column.crossed_column(["department","display"],?100)
cross_emb=tf.feature_column.embedding_column(cross_column,?4)
#?sparsetensor?直接接入?denseFeatures?
cross_input_layer?=?tf.keras.layers.DenseFeatures(cross_emb)
dense_tensor?=?cross_input_layer(features)
print(dense_tensor)
我們 知道 單列特征 僅僅 從 一個(gè)維度 刻畫用戶,而 交叉特征 則是可以 從 交叉的 多列特征中 綜合 刻畫用戶行為 ,例如 刻畫情人節(jié) 這個(gè)日期 和 情趣內(nèi)衣褲 的 購(gòu)買記錄 之間的 關(guān)系,是不是 更能描述 和 反映 某位美女 帥哥 對(duì) 某件衣服 的 購(gòu)買意愿 呢。
在 搜廣推算法 的 實(shí)際 使用場(chǎng)景 中,我們 會(huì) 遇到 大量的交叉特征 。對(duì)于 交叉特征列,我們 可以 輸入原始單列特征 得到 embeding 之后,使用 embeding 相乘 或則 對(duì)位乘 或則 別的 什么做法 達(dá)到 綜合 兩個(gè)特征 建模的 目的,我們 也可以 在 離線 使用 spark 進(jìn)行簡(jiǎn)單 的 字符串拼接 來達(dá)到 高維離散 特征 特征交叉 的目的。本文這里 介紹 了 tensorflow 提供的 一種新的 解決 方案 。
feature_column 里 提供的 特征交叉接口 crossed_column
,看官方 介紹是 將 特征取值之間做了 笛卡爾積 之后在對(duì) ?組合好的字符串進(jìn)行hash操作,將交叉的操作放在了tensorlfow 自己的特征處理過程中,在大數(shù)據(jù)之后,模型之前。
注意,這個(gè)接口 返回的 是一個(gè) 交叉特征列類 (_CrossedColumn
), 后面 依然 需要 接 indicator 或則 embeding 層 輸入 后面 的 模型。這個(gè) 接口 最后 底層 調(diào)用的 是 sparse_cross_hashed
這個(gè) 方法 做的 交叉操作,感興趣 的可以 去 前面提供 的 源碼地址 去 一層一層 點(diǎn)開 看看 哦。
本文到這里,本文共介紹了 7種 tensorflow ?feature_column
提供的 常用接口,中間 也穿插 介紹了 很多 特征處理 技巧 和 踩坑 經(jīng)驗(yàn),具有 很高的 參考價(jià)值 哦。如果 你 還有問題,歡迎 關(guān)注作者 的 公眾號(hào) 留言 一起討論 哦 ~
到這里,機(jī)器學(xué)習(xí)特征處理詳解與 tensorflow feature_column 接口實(shí)戰(zhàn) ?的 全文 就 寫完 了。本文代碼 每個(gè)模塊 均 可以 獨(dú)立 跑 成功, 中間序列特征 處理模塊 是一個(gè)完整的 feature_column結(jié)合keras 開發(fā)模型 的 優(yōu)秀 式例,希望 可以 對(duì) 你 有 參考作用 ~
碼字不易,覺得有收獲就動(dòng)動(dòng)小手轉(zhuǎn)載一下吧,你的支持是我寫下去的最大動(dòng)力 ~
更多更全更新內(nèi)容,歡迎關(guān)注作者的公眾號(hào): 算法全棧之路

- END -