Tensorflow2.x——FeutureColumns特征列詳解


@

特征列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種類。

\[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

參考鏈接:https://www.jianshu.com/p/fceb64c790f3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM