@
- 特征列feature_column
- 一,特征列用法概述
- 二、數值列 Numeric column
- 三、分箱列 Bucketized column
- 四、分類識別列Categorical identity column
- 五、分類詞匯列Categorical vocabulary column
- 六、分類詞匯列categorical_column_with_vocabulary_file
- 七、哈希列categorical_column_with_hash_bucket
- 八、交叉列Crossed column
- 九、指示列Indicator Columns和嵌入列Embeding Columns
- 十、特征列使用范例
- 打賞
特征列feature_column
特征列 通常用於對結構化數據實施特征工程時候使用,圖像或者文本數據一般不會用到特征列。可以將特征列視為原始數據和 Estimator 之間的媒介。特征列非常豐富,使您可以將各種原始數據轉換為 Estimators 可用的格式,從而可以輕松進行實驗。
特征列就是對原始數據的特征進行處理,處理成Estimators 可用的格式
一,特征列用法概述
- 將類別特征轉換為one-hot編碼特征
- 將連續特征構建分桶特征
- 對多個特征生成交叉特征等等。
要創建特征列,請調用 tf.feature_column
模塊的函數。該模塊中常用的九個函數如下圖所示,所有九個函數都會返回一個 Categorical-Column 或一個Dense-Column 對象,但卻不會返回 bucketized_column,后者繼承自這兩個類。
注意:所有的Catogorical Column類型最終都要通過indicator_column轉換成Dense Column類型才能傳入模型!
-
numeric_column 數值列,不做任何處理,直接進行格式轉換,最常用。
-
bucketized_column 分桶列,由數值列生成,將一個連續的數值列變成分段的數值特征,one-hot編碼。
-
categorical_column_with_identity 分類標識列,one-hot編碼,相當於分桶列每個桶為1個整數的情況。
-
categorical_column_with_vocabulary_list 分類詞匯列,one-hot編碼,由list指定詞典。
-
categorical_column_with_vocabulary_file 分類詞匯列,由文件file指定詞典。
-
categorical_column_with_hash_bucket 哈希列,整數或詞典較大時采用。
-
indicator_column 指標列,由Categorical Column生成,one-hot編碼
-
embedding_column 嵌入列,由Categorical Column生成,嵌入矢量分布參數需要學習。嵌入矢量維數建議取類別數量的 4 次方根。
-
crossed_column 交叉列,可以由除categorical_column_with_hash_bucket的任意分類列構成。
二、數值列 Numeric column
age = tf.feature_column.numeric_column("age")
age
NumericColumn(key='age', # 列名,也就是特征名
shape=(1,), # 數據形狀
default_value=None, # 缺省值的默認值
dtype=tf.float32, # 數據格式
normalizer_fn=None) # 標准化函數normalizer_fn,可以對每個每行數據進行處理。
數值列,(如果沒有定義normalizer_fn函數)不做任何處理,直接進行格式轉換,最常用。
# 使用normalizer_fn函數將每個數據都+2
age = tf.feature_column.numeric_column("age", normalizer_fn=lambda x:x+2)
三、分箱列 Bucketized column
分箱是指把一個連續的數字范圍分成幾段,以表示房屋建造年份的原始數據為例。我們並非以標量數值列表示年份,而是將年份分成下列四個分桶:
模型將按以下方式表示這些 bucket:
日期范圍 | 表示為… |
---|---|
< 1960 年 | [1, 0, 0, 0] |
>= 1960 年但 < 1980 年 | [0, 1, 0, 0] |
>= 1980 年但 < 2000 年 | [0, 0, 1, 0] |
>= 2000 年 | [0, 0, 0, 1] |
為什么要將數字(一個完全有效的模型輸入)拆分為分類值?首先,該分類將單個輸入數字分成了一個四元素矢量。因此模型現在可以學習四個單獨的權重而不是一個。四個權重能夠創建一個更強大的模型。更重要的是,借助 bucket,模型能夠清楚地區分不同年份類別,因為僅設置了一個元素 (1),其他三個元素則被清除 (0)。例如,當我們僅將單個數字(年份)用作輸入時,線性模型只能學習線性關系,而使用 bucket 后,模型可以學習更復雜的關系。
以下代碼演示了如何創建 bucketized feature:
# 首先,將原始輸入轉換為一個numeric column
numeric_feature_column = tf.feature_column.numeric_column("Year")
# 然后,按照邊界[1960,1980,2000]將numeric column進行bucket
bucketized_feature_column = tf.feature_column.bucketized_column(
source_column = numeric_feature_column,
boundaries = [1960, 1980, 2000])
注意,指定一個三元素邊界矢量可創建一個四元素 bucket 矢量。
四、分類識別列Categorical identity column
語法格式:
categorical_column_with_identity(
key,
num_buckets,
default_value=None
)
相當於把經過標簽數字化的列再轉換成one-hot編碼
import tensorflow as tf
pets = {'pets': [2,3,0,1]} #貓0,狗1,兔子2,豬3
column = tf.feature_column.categorical_column_with_identity(
key='pets',
num_buckets=4)
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(pets, [indicator])
with tf.Session() as session:
print(session.run([tensor]))
輸出結果
[array([[0., 0., 1., 0.], #兔子
[0., 0., 0., 1.], #豬
[1., 0., 0., 0.], #貓
[0., 1., 0., 0.]], dtype=float32)] #狗
五、分類詞匯列Categorical vocabulary column
在上面的示例圖中我們看到,必須手工在excel里面把cat、dog、rabbit、pig轉為0123才行,能不能更快一些?
tf.feature_column.categorical_column_with_vocabulary_list這個方法就是將一個單詞列表生成為分類詞匯特征列的。
語法格式
categorical_column_with_vocabulary_list(
key,
vocabulary_list,
dtype=None,
default_value=-1,
num_oov_buckets=0
)
num_ovv_buckets
,超出詞匯表詞匯箱子標簽數目Out-Of-Vocabulary,如果數據里面的某個單詞沒有對應的箱子,比如出現了老鼠mouse,那么老鼠的類別就會在箱子總數~(num_ovv_buckets+ 箱子總數)之間隨機選擇。
比如num_ovv=3,那么老鼠mouse會被標記為4~7中的某個數字,可能是5,也可能是4或6。num_ovv不可以是負數。
測試代碼
import tensorflow as tf
pets = {'pets': ['rabbit','pig','dog','mouse','cat']}
column = tf.feature_column.categorical_column_with_vocabulary_list(
key='pets',
vocabulary_list=['cat','dog','rabbit','pig'],
dtype=tf.string,
default_value=-1,
num_oov_buckets=3)
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(pets, [indicator])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
輸出結果如下,注意到獨熱list 有7個元素,這是由於【貓狗兔子豬4個+num_oov_buckets】得到的。
[array([[0., 0., 1., 0., 0., 0., 0.], #'rabbit'
[0., 0., 0., 1., 0., 0., 0.], #'pig'
[0., 1., 0., 0., 0., 0., 0.], #'dog'
[0., 0., 0., 0., 0., 1., 0.], #mouse
[1., 0., 0., 0., 0., 0., 0.]], dtype=float32)] #'cat'
六、分類詞匯列categorical_column_with_vocabulary_file
單詞有些時候會比較多,這時候我們可以直接從文件中讀取文字列表:
import os
import tensorflow as tf
pets = {'pets': ['rabbit','pig','dog','mouse','cat']}
dir_path = os.path.dirname(os.path.realpath(__file__))
fc_path=os.path.join(dir_path,'pets_fc.txt')
column=tf.feature_column.categorical_column_with_vocabulary_file(
key="pets",
vocabulary_file=fc_path,
num_oov_buckets=0)
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(pets, [indicator])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
其中pets_fc.txt每行一個單詞如:
cat
dog
rabbit
pig
運行,得到以下結果,這次我們oov使用了0,並沒有增加元素數量,但是也導致了mouse變成了全部是0的列表
[array([[0., 0., 1., 0.], #rabbit
[0., 0., 0., 1.], #pig
[0., 1., 0., 0.], #dog
[0., 0., 0., 0.],#mosue
[1., 0., 0., 0.]], dtype=float32)] #cat
七、哈希列categorical_column_with_hash_bucket
仍然是分箱,但是這一次我們更加關心“我希望有多少分類?”,也許我們有150個單詞,但我們只希望分成100個分類,多下來50個的怎么處理?
取余數!101除以100余1,我們就把第101種單詞也標記為1,和我們的第1種單詞變成了同一類,如此類推,第102種和2種同屬第2類,第103種和3種同屬第3類...
我們把計算余數的操作寫為%;那么第N個單詞屬於N%100類。
feature_id = hash(raw_feature) % hash_buckets_size
哈希列HashedColumn對於大數量的類別很有效(vocabulary的file模式也不錯),尤其是語言文章處理,將文章分句切詞之后,往往得到大數量的單詞,每個單詞作為一個類別,對於機器學習來說,更容易找到潛在的單詞之間的語法關系。
但哈希也會帶來一些問題。如下圖所示,我們把廚房用具kitchenware和運動商品sports都標記成了分類12。這看起來是錯誤的,不過很多時候tensorflow還是能夠利用其他的特征列把它們區分開。所以,為了有效減少內存和計算時間,可以這么做。
語法格式
categorical_column_with_hash_bucket(
key,
hash_bucket_size,
dtype=tf.string
)
測試代碼:
import tensorflow as tf
colors = {'colors': ['green','red','blue','yellow','pink','blue','red','indigo']}
column = tf.feature_column.categorical_column_with_hash_bucket(
key='colors',
hash_bucket_size=5,
)
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(colors, [indicator])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
運行得到如下的輸出,我們注意到red和blue轉化后都是一樣的,yellow,indigo,pink也都一樣,這很糟糕。
[array([[0., 0., 0., 0., 1.],#green
[1., 0., 0., 0., 0.],#red
[1., 0., 0., 0., 0.],#blue
[0., 1., 0., 0., 0.],#yellow
[0., 1., 0., 0., 0.],#pink
[1., 0., 0., 0., 0.],#blue
[1., 0., 0., 0., 0.],#red
[0., 1., 0., 0., 0.]], dtype=float32)]#indigo
八、交叉列Crossed column
交叉列可以把多個特征合並成為一個特征,比如把經度longitude、維度latitude兩個特征合並為地理位置特征location。
如下圖,我們把Atlanda城市范圍的地圖橫向分成100區間,豎向分成100區間,總共分割成為10000塊小區域。(也許接下來我們需要從數據分析出哪里是富人區哪里是窮人區)
import tensorflow as tf
featrues = {
'longtitude': [19,61,30,9,45],
'latitude': [45,40,72,81,24]
}
longtitude = tf.feature_column.numeric_column('longtitude')
latitude = tf.feature_column.numeric_column('latitude')
longtitude_b_c = tf.feature_column.bucketized_column(longtitude, [33,66])
latitude_b_c = tf.feature_column.bucketized_column(latitude,[33,66])
column = tf.feature_column.crossed_column([longtitude_b_c, latitude_b_c], 12)
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(featrues, [indicator])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
上面的代碼中進行了分箱操作,分成~33,33~66,66~三箱,運行得到下面輸出
[array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)]
九、指示列Indicator Columns和嵌入列Embeding Columns
指標列和嵌入列從不直接處理特征,而是將分類列視為輸入。
指示列
把特征轉換成獨熱編碼
當我們遇到成千上萬個類別的時候,獨熱列表就會變的特別長[0,1,0,0,0,....0,0,0]。嵌入列
可以解決這個問題,它不再限定每個元素必須是0或1,而可以是任何數字,從而使用更少的元素數表現數據。
如下圖,我們最初的數據可能是4個單詞比如dog、spoon、scissors、guitar,然后這些單詞被分類特征列Categorical處理成為數字0、32、79、80,接下來我們可以使用指示列來處理成為獨熱的01列表(圖中假設我們有81種單詞分類),也可以按照嵌入Embeding列來處理成小數元素組成的3元素數列。
嵌入列中的小數只在train訓練的時候自動計算生成,能夠有效增加訓練模型的效率和性能,同時又能便於機器學習從數據中發現潛在的新規律。
為什么嵌入Embeding的都是[0.421,0.399,0.512]這樣的3元素列表,而不是4元5元?實際上有下面的參考算法:
嵌入列表的維數等於類別總數開4次方,也就是3的4次方等於81種類。
嵌入列語法:
embedding_column(
categorical_column,
dimension, # 維度,即每個列表元素數
combiner='mean', # 組合器,默認meam,在語言文字處理中選sqrtn可能更好
initializer=None, # 初始器
ckpt_to_load_from=None, # 恢復文件
tensor_name_in_ckpt=None,# 可以從check point中恢復
max_norm=None,
trainable=True
)
示例代碼
import tensorflow as tf
features = {'pets': ['dog','cat','rabbit','pig','mouse']}
pets_f_c = tf.feature_column.categorical_column_with_vocabulary_list(
'pets',
['cat','dog','rabbit','pig'],
dtype=tf.string,
default_value=-1)
column = tf.feature_column.embedding_column(pets_f_c, 3)
tensor = tf.feature_column.input_layer(features, [column])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
運行得到輸出,我們看到由於老鼠mouse沒有對應的箱子,所以元素都為0
[array([[ 0.15651548, -0.620424 , 0.41636208],
[-1.0857592 , 0.03593585, 0.20340031],
[-0.6021426 , -0.48347804, -0.7165713 ],
[-0.36875582, 0.4034163 , -1.0998975 ],
[ 0. , 0. , 0. ]], dtype=float32)]
十、特征列使用范例
以下是一個使用特征列解決Titanic生存問題的完整范例。
import datetime
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers,models
#打印日志
def printlog(info):
nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print("\n"+"=========="*8 + "%s"%nowtime)
print(info+'...\n\n')
#================================================================================
# 一,構建數據管道
#================================================================================
printlog("step1: prepare dataset...")
dftrain_raw = pd.read_csv("./data/titanic/train.csv")
dftest_raw = pd.read_csv("./data/titanic/test.csv")
dfraw = pd.concat([dftrain_raw,dftest_raw])
def prepare_dfdata(dfraw):
dfdata = dfraw.copy()
dfdata.columns = [x.lower() for x in dfdata.columns]
dfdata = dfdata.rename(columns={'survived':'label'})
dfdata = dfdata.drop(['passengerid','name'],axis = 1)
for col,dtype in dict(dfdata.dtypes).items():
# 判斷是否包含缺失值
if dfdata[col].hasnans:
# 添加標識是否缺失列
dfdata[col + '_nan'] = pd.isna(dfdata[col]).astype('int32')
# 填充,如果是數字,那么就添加這一列的平均值,否則空着
if dtype not in [np.object,np.str,np.unicode]:
dfdata[col].fillna(dfdata[col].mean(),inplace = True)
else:
dfdata[col].fillna('',inplace = True)
return(dfdata)
dfdata = prepare_dfdata(dfraw)
dftrain = dfdata.iloc[0:len(dftrain_raw),:]
dftest = dfdata.iloc[len(dftrain_raw):,:]
dfdata
# 從 dataframe 導入數據
def df_to_dataset(df, shuffle=True, batch_size=32):
dfdata = df.copy()
if 'label' not in dfdata.columns:
ds = tf.data.Dataset.from_tensor_slices(dfdata.to_dict(orient = 'list'))
else:
labels = dfdata.pop('label')
ds = tf.data.Dataset.from_tensor_slices((dfdata.to_dict(orient = 'list'), labels))
if shuffle:
ds = ds.shuffle(buffer_size=len(dfdata))
ds = ds.batch(batch_size)
return ds
ds_train = df_to_dataset(dftrain)
ds_test = df_to_dataset(dftest)
feature_columns = []
# 數值列
for col in ['age','fare','parch','sibsp'] + [
c for c in dfdata.columns if c.endswith('_nan')]:
feature_columns.append(tf.feature_column.numeric_column(col))
feature_columns
age = tf.feature_column.numeric_column('age')
age_buckets = tf.feature_column.bucketized_column(age,
boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
feature_columns.append(age_buckets)
feature_columns
#================================================================================
# 二,定義特征列
#================================================================================
printlog("step2: make feature columns...")
feature_columns = []
# 數值列
for col in ['age','fare','parch','sibsp'] + [
c for c in dfdata.columns if c.endswith('_nan')]:
feature_columns.append(tf.feature_column.numeric_column(col))
# 分桶列 # 意思就是我們設置一些分界點,假如是3個分界點,那么這些分界點將數據分成3+1=4類,並且表示為one-hot編碼形式
age = tf.feature_column.numeric_column('age')
age_buckets = tf.feature_column.bucketized_column(age,
boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
feature_columns.append(age_buckets)
# 類別列
# 注意:所有的Catogorical Column類型最終都要通過indicator_column轉換成Dense Column類型才能傳入模型!!
sex = tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(
key='sex',vocabulary_list=["male", "female"]))
feature_columns.append(sex)
pclass = tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(
key='pclass',vocabulary_list=[1,2,3]))
feature_columns.append(pclass)
ticket = tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_hash_bucket('ticket',3))
feature_columns.append(ticket)
'''
********************************哈希列****************************
當類別很多或者我們不知道有多少類的時候,我們不能一個一個的列出來,這時候就可以使用hash_bucket,
第二個參數是我們想把這些數據分成多少類,這個類別數和真實的類別數不一定是一樣的,我們自己設置划分為多少類即可
'''
embarked = tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(
key='embarked',vocabulary_list=['S','C','B']))
feature_columns.append(embarked)
# 嵌入列
cabin = tf.feature_column.embedding_column(
tf.feature_column.categorical_column_with_hash_bucket('cabin',32),2)
feature_columns.append(cabin)
# 交叉列
pclass_cate = tf.feature_column.categorical_column_with_vocabulary_list(
key='pclass',vocabulary_list=[1,2,3])
crossed_feature = tf.feature_column.indicator_column(
tf.feature_column.crossed_column([age_buckets, pclass_cate],hash_bucket_size=15))
feature_columns.append(crossed_feature)
#================================================================================
# 三,定義模型
#================================================================================
printlog("step3: define model...")
tf.keras.backend.clear_session()
model = tf.keras.Sequential([
layers.DenseFeatures(feature_columns), #將特征列放入到tf.keras.layers.DenseFeatures中!!!
layers.Dense(64, activation='relu'),
layers.Dense(64, activation='relu'),
layers.Dense(1, activation='sigmoid')
])
#================================================================================
# 四,訓練模型
#================================================================================
printlog("step4: train model...")
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
history = model.fit(ds_train,
validation_data=ds_test,
epochs=10)
#================================================================================
# 五,評估模型
#================================================================================
printlog("step5: eval model...")
model.summary()
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
def plot_metric(history, metric):
train_metrics = history.history[metric]
val_metrics = history.history['val_'+metric]
epochs = range(1, len(train_metrics) + 1)
plt.plot(epochs, train_metrics, 'bo--')
plt.plot(epochs, val_metrics, 'ro-')
plt.title('Training and validation '+ metric)
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend(["train_"+metric, 'val_'+metric])
plt.show()
plot_metric(history,"accuracy")
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_features (DenseFeature multiple 64
_________________________________________________________________
dense (Dense) multiple 3008
_________________________________________________________________
dense_1 (Dense) multiple 4160
_________________________________________________________________
dense_2 (Dense) multiple 65
=================================================================
Total params: 7,297
Trainable params: 7,297
Non-trainable params: 0
_________________________________________________________________
打賞
碼字不易,如果對您有幫助,就打賞一下吧O(∩_∩)O