這篇文章主要介紹tensorflow對數據的處理知識:特征列feature columns。
特征列FeatureColumns
特征列是指一組數據的相關特征,包含了數據的相關類型和長度等信息。
在前面的鳶尾花案例中,我們使用了下面的代碼拼合特征列,你可以在iris項目中添加新的測試文件test.py并運行它觀看輸出效果:
import os
import pandas as pd
import tensorflow as tf
FUTURES = ['SepalLength', 'SepalWidth','PetalLength', 'PetalWidth', 'Species']
SPECIES = ['Setosa', 'Versicolor', 'Virginica']
#格式化數據文件的目錄地址
dir_path = os.path.dirname(os.path.realpath(__file__))
train_path=os.path.join(dir_path,'iris_training.csv')
test_path=os.path.join(dir_path,'iris_test.csv')
#載入訓練數據
train = pd.read_csv(train_path, names=FUTURES, header=0)
train_x, train_y = train, train.pop('Species')
#載入測試數據
test = pd.read_csv(test_path, names=FUTURES, header=0)
test_x, test_y = test, test.pop('Species')
#拼合特征列
feature_columns = []
for key in train_x.keys():
feature_columns.append(tf.feature_column.numeric_column(key=key))
print(feature_columns);
以上代碼輸出如下內容(整理后)
[
_NumericColumn(key='SepalLength', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
_NumericColumn(key='SepalWidth', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
_NumericColumn(key='PetalLength', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
_NumericColumn(key='PetalWidth', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None)
]
feature_columns = [],它是一個列表,被添加append了多個包含了多個feature_column.numeric_column()方法生成的特征列,每個特征列包含五個字段:
- 關鍵字key,用來標識每一列的名稱,避免混淆。
- 形狀shape, 數據的形狀,見下面。
- 默認值default_value。
- 數據類型dtype,默認是浮點小數tf.float32。
- 標準化函數normalizer_fn,可以對每個每行數據進行處理。
形狀shape
關于shape,比如shape=3表示[r,g,b]類型的三元列表,類似[0,100,255]。shape=[4,3]表示下圖的4行3列的矩陣,類似[[1,0,0] [0,1,0] [0,0,1] [0,0,0]]。
在python中還有一類和方括號[]列表類似的數據格式:小括號元組()。元組基本上就是每個元素都不重復的列表。
但是這里就有一個問題,mylist=(3)這句話到底是生成(3)這樣的元組呢,還是生成一個類似(x,y,z)有三個元素的元組呢?答案是后者!
如果需要生成只包含3這個數字的元組,你需要在3后面強加一個逗號:
mylist=(3,)
轉化過程
我們把這段for循環改一下,把train_x,train_x.keys()和key以及轉化完畢的特征列column打印出來,仔細看看數據的變化過程:
feature_columns = []
print(train_x);
print(train_x.keys());
for key in train_x.keys():
print(key);
print(tf.feature_column.numeric_column(key=key));
feature_columns.append(tf.feature_column.numeric_column(key=key))
它們的情況:
- iris_training.csv是我們的原始數據,如下,具體情況之前文章詳細解說過:
120,4,setosa,versicolor,virginica
6.4,2.8,5.6,2.2,2
5.0,2.3,3.3,1.0,1
4.9,2.5,4.5,1.7,2
4.9,3.1,1.5,0.1,0
...
- train_x是pandas模塊讀取的csv數據,使用pd.read_csv()方法,得到的是120行乘以4列的數據表[120 rows x 4 columns]
SepalLength SepalWidth PetalLength PetalWidth
0 6.4 2.8 5.6 2.2
1 5.0 2.3 3.3 1.0
2 4.9 2.5 4.5 1.7
...
[120 rows x 4 columns]
- train_x.keys()包含了四個關鍵字和一個數據類型對象dtype
Index(['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth'], dtype='object')
- key是關鍵字名稱,numeric_column(key=key)得到的單個特征列和上面展示的一樣
SepalLength
_NumericColumn(key='SepalLength', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None)
數據流程
在之前的鳶尾花案例中,我們從csv讀取數據,組織成為特征列feature_columns并編寫了喂食數據的函數input_fn,然后利用特征列創建了qiu zh器中的深度神經網絡分類器estimator.DNNClassifier,然后利用喂食函數input_fn把讀取的數據喂食到DNNClassifier中進行訓練、評估和預測。
數值列numeric_column
我們在上面使用的就是數值列numeric_column,它的默認格式如下
numeric_column(
key,
shape=(1,),
default_value=None,
dtype=tf.float32,
normalizer_fn=None
)
你可以用下面的代碼測試:
import tensorflow as tf
price = {'price': [[1.], [2.], [3.], [4.]]} # 4行樣本
column = tf.feature_column.numeric_column('price', normalizer_fn=lambda x:x+2)
tensor = tf.feature_column.input_layer(price,[column])
with tf.Session() as session:
print(session.run([tensor]))
將會輸出以下內容,每個數值都被+2處理了:
[array([[3.],
[4.],
[5.],
[6.]], dtype=float32)]
分箱列Bucketized column
分箱是指把一個連續的數字范圍分成幾段,比如我們經常說的【80后,90后,00后,10后】這些就是把一個連續的年份(1980~現在2018)分成了4段,我們把這些年份分別寫在39張卡片上,然后準備四個箱子,分別標上【0號箱80后】【1號箱90后】【2號箱00后】【3號箱10后】,卡片1980~1989放入【0號箱】,卡片1990~1999放入【1號箱】...
為什么這樣做?
在我們分箱之前,我們的用excel填寫100個人的出生年代,那么會是
years
0 1988
1 1999
2 2013
3 2004
...
如果我們要用這些數據分析90后的身高分布情況,那么這樣的數據看起來就比較麻煩。而分箱后的數據就好多了,我們用0表示80后,1表示90后,2表示00后,3表示10后:
years
0 0 #1988
1 1 #1999
2 3 #2013
3 2 #2004
...
我們繼續更進一步,比起簡單的數字,Tensorflow更喜歡列表類型,更善于從列表或矩陣中估算出變化規律。
共有4段年代,我們表示為包含4個元素的列表[y0,y1,y2,y3],80后第一個元素中招,標記為[1,0,0,0];90后第二個元素中招,[0,1,0,0],以此類推,我們得到
0 [1,0,0,0] #1988
1 [0,1,0,0] #1999
2 [0,0,0,1] #2013
3 [0,0,1,0] #2004
在這個例子中,我們把1980~now年份用3個邊界(1990,2000,2010)劃為4段(假設我們的數據不存在早于1980出生的人),我們可以使用下面的代碼進行測試:
import tensorflow as tf
years = {'years': [1999,2013,1987,2005]}
years_fc = tf.feature_column.numeric_column('years')
column = tf.feature_column.bucketized_column(years_fc, [1990, 2000, 2010])
tensor = tf.feature_column.input_layer(years, [column])
with tf.Session() as session:
print(session.run([tensor]))
運行得到下面的輸出,可以看到每一個年份的列表對應的元素變成了1,而其他元素為0:
[array([[0., 1., 0., 0.], #1999
[0., 0., 0., 1.], #2013
[1., 0., 0., 0.], #1987
[0., 0., 1., 0.]], #2005
dtype=float32)]
Bucketized也被稱作分桶,在這里參考谷歌官方說法統一稱為分箱。
關于獨熱編碼one-hot
顧名思義,在上面的列表中[0,1,0,0]中只有第二個元素為1,熱起來了,其他元素都是0。所以叫獨熱。
可以這樣說,如果某個特征有M種可能,而我們有三個數據[m1,m2,m3],可以把它變成一個由0和1組成3xM的二維矩陣,類似下圖:
分箱特征欄Bucketized column就是把一維的普通列表變成了二維矩陣,升維了!
為什么會這樣做?不是把數據復雜化了嗎?
其實是把數據簡化了,獨熱之后我們只剩下0或1,1只是代表一個位置,并不關注這一行具體代表什么含義。
首先,升維往往能讓數據直接的關系更加清楚,更易于找到規律。其次,也是更重要的,分箱之后可以讓無序數據之間關系更加正確。
比如我們有三種商品分類,['服裝','食品','化妝品'],如果我們只是把它轉化為[0,1,2],那么我們如果在幾何空間中計算它們之間的距離關系,三個類別代表線段上的三個點,我們會得到服裝距離食品是1-0=1,食品距離化妝品是2-1=1,而服裝距離化妝品是2-0=2;計算機就會誤以為化妝品和服裝關系很遠。但實際上這毫無依據,這種錯誤關系完全是由于我們的數據格式引發的。
再看分箱后的結果,三種類別變為服裝[1,0,0],食品[0,1,0],化妝品[0,0,1],它們代表三維空間中的三個點,利用距離平方等于每個維度平方之和,我們得到它們三者之間的距離都是根號2,完全相等,沒有任何偏倚。
分類識別列Categorical identity column
很多數據都不是數字格式的,比如動物的類別“貓狗牛羊”、商品的類別“食品服裝數碼”、人的姓氏“張王李趙”...這些都是文字格式的。
但是,Tensorflow只能處理數字。
我們必須把字符名稱變為數字模式,或者說我們必須用數字來表示文字。
參照上面的分箱的方法,我們可以創建很多箱子表示各種動物,把每個種類動物名稱寫在卡片上,放到對應的箱子里。
假設我們有4種寵物分類:貓,狗,兔子,豬,對應列表[a1,a2,a3,a4]那么就有:
語法格式
categorical_column_with_identity(
key,
num_buckets,
default_value=None
)
測試代碼
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,那么就會在【箱子總數4~num_ovv_buckets+ 箱子總數=7】,如果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'
單詞有些時候會比較多,這時候我們可以直接從文件中讀取文字列表:
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
哈希欄Hashed Column
仍然是分箱,但是這一次我們更加關心“我希望有多少分類?”,也許我們有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
將hash_bucket_size箱子數量設置為10,這個問題可以得到解決。箱子數量的旋轉很重要,越大獲得的分類結果越精確。
交叉列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
我們在上面的代碼中使用了很多次指示列命令
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(featrues, [indicator])
指示列并不直接操作數據,但它可以把各種分類特征列轉化成為input_layer()方法接受的特征列。
當我們遇到成千上萬個類別的時候,獨熱列表就會變的特別長[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',
initializer=None,
ckpt_to_load_from=None,
tensor_name_in_ckpt=None,
max_norm=None,
trainable=True
)
dimention維度,即每個列表元素數
combiner組合器,默認meam,在語言文字處理中選sqrtn可能更好
initializer初始器
tensor_name_in_ckpt可以從check point中恢復
ckpt_to_load_from恢復文件
示例代碼
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)]
特征列和估算器Estimator
在鳶尾花的案例中,我們拼接多個數值特征列numeric column成為feature_columns列表
feature_columns = []
for key in train_x.keys():
feature_columns.append(tf.feature_column.numeric_column(key=key))
然后利用特征列創建了深度神經網絡分類器tf.estimator.DNNClassifier:
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10, 10],
n_classes=3,
model_dir=models_path,
config=ckpt_config) #
有了這個DNNClassifier我們就能對原始數據進行train、evaluate、pedict。
Tensorflow提供了多個評估器,但不是每種評估器都能夠接收所有類型的特征列feature column。
- 線性分類器 linearClassifier和線性回歸器linearRegressor,接收所有類型特征列;
- 深度神經網絡分類器DNNClassifier和深度神經網絡回歸器DNNRegressor,僅接收密集特征列dense column,其他類型特征列必須用指示列indicatorColumn或嵌入列embedingColumn進行包裹
- 線性神經網絡合成分類器linearDNNCombinedClassifier和線性神經網絡合成回歸器linearDNNCombinedRegressor:
- linear_feature_columns參數接收所有類型特征列
- dnn_feature_columns只接收密度特征列dense column
分類列CategoricalColumn和密集列DenseColumn
上面介紹了Tensorflow用于生成特征列的9個方法(tf.feature_column...),每個方法最終都會得到分類列或者密集列:
權重分類列WeightedCategoricalColumn
默認的CategoricalColumn所有分類的權重都是一樣的,沒有輕重主次。而權重分類特征列則可以為每個分類設置權重。
語法格式
weighted_categorical_column(
categorical_column,
weight_feature_key,
dtype=tf.float32
)
測試代碼
import tensorflow as tf
from tensorflow.python.feature_column.feature_column import _LazyBuilder
features = {'color': [['R'], ['A'], ['G'], ['B'],['R']],
'weight': [[1.0], [5.0], [4.0], [8.0],[3.0]]}
color_f_c = tf.feature_column.categorical_column_with_vocabulary_list(
'color', ['R', 'G', 'B','A'], dtype=tf.string, default_value=-1
)
column = tf.feature_column.weighted_categorical_column(color_f_c, 'weight')
indicator = tf.feature_column.indicator_column(column)
tensor = tf.feature_column.input_layer(features, [indicator])
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run([tensor]))
運行之后得到下面輸出,權重改變了獨熱模式,不僅包含0或1,還帶有權重值
[array([[1., 0., 0., 0.],
[0., 0., 0., 5.],
[0., 4., 0., 0.],
[0., 0., 8., 0.],
[3., 0., 0., 0.]], dtype=float32)]
線性模型LinearModel
對所有特征進行線性加權操作(數值和權重值相乘)。
語法格式
linear_model(
features,
feature_columns,
units=1,
sparse_combiner='sum',
weight_collections=None,
trainable=True
)
測試代碼
import tensorflow as tf
from tensorflow.python.feature_column.feature_column import _LazyBuilder
def get_linear_model_bias():
with tf.variable_scope('linear_model', reuse=True):
return tf.get_variable('bias_weights')
def get_linear_model_column_var(column):
return tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
'linear_model/' + column.name)[0]
featrues = {
'price': [[1.0], [5.0], [10.0]],
'color': [['R'], ['G'], ['B']]
}
price_column = tf.feature_column.numeric_column('price')
color_column = tf.feature_column.categorical_column_with_vocabulary_list('color',
['R', 'G', 'B'])
prediction = tf.feature_column.linear_model(featrues, [price_column, color_column])
bias = get_linear_model_bias()
price_var = get_linear_model_column_var(price_column)
color_var = get_linear_model_column_var(color_column)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
sess.run(tf.local_variables_initializer())
sess.run(tf.tables_initializer())
sess.run(bias.assign([7.0]))
sess.run(price_var.assign([[10.0]]))
sess.run(color_var.assign([[2.0], [2.0], [2.0]]))
predication_result = sess.run([prediction])
print(prediction)
print(predication_result)
運行結果得到
[array([[ 19.],
[ 59.],
[109.]], dtype=float32)]
以上全部演示代碼都可以從百度網盤下載(提取密碼:qdvx)
探索人工智能的新邊界
如果您發現文章錯誤,請不吝留言指正;
如果您覺得有用,請點喜歡;
如果您覺得很有用,感謝轉發~
END