Tensorflow2.0 特征处理

0 / 1293

一、编写数据集导入函数,此函数必须返回2个对象:

1. 字典为 {"特征名称": 特征值Tensor 或 SparseTensor}
2. label的Tensor列表

格式:

def input_fn(dataset):
    ...
    return feature_dict, label    # 这2组合为张量迭代器, 就是 dataset

二、 定义特征列( tf.feature_column)

a = tf.feature_column.numeric_column('a')
b = tf.feature_column.numeric_column('b')
c = tf.feature_column.numeric_column('c', normalizer_fn=lambda x: x)

三、实例化相关的预创建的 Estimator

estimator = tf.estimator.LinearClassifier(
    feature_columns=[a,b,c]
)

四、调用训练、评估或推理方法

.train(数据集处理函数句柄,steps=None)
estimator.predict(数据集处理函数句柄,predict_keys=None, steps=None)
.estimator.evaluate(数据集处理函数句柄,steps=None)

常用 estimator

tf.estimator.DNNClassifier(hidden_units,feature_columns)
tf.estimator.DNNRegressor(hidden_units,feature_columns)

tf.estimator.LinearClassifier(feature_columns)
tf.estimator.LinearRegressor(feature_columns)

tf.estimator.DNNLinearCombinedClassifier()
tf.estimator.DNNLinearCombinedRegressor()

常用 feature_column(指定estimator字段的数据类型)

特征处理几种方式

  • 特征归一化(针对连续型)
  • 特征离散化:很少直接使用连续值作为特征,而是将特征离散化后再输入到模型中。离散化特征对于异常值具有更好的鲁棒性,可以为特征引入非线性的能力。并且,离散化可以更好的进行Embedding,可以通过👉等频分桶对特征值离散化
  • 特征组合:,生成更丰富的行为表征,可加速模型的收敛速度
  • Embedding

分桶,交叉特征

age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
crossed_columns = [
    tf.feature_column.crossed_column(
        ['education', 'occupation'], hash_bucket_size=1000),
    tf.feature_column.crossed_column(
        [age_buckets, 'education', 'occupation'], hash_bucket_size=1000),
]

tf.feature_column

连续值类型特征 转向量

features = {"price": [[1.], [5.]]}
price = tf.feature_column.numeric_column("price")

将连续值类型特征映射为one-hot(需2步)

aa = tf.feature_column.categorical_column_with_identity(
    key='连续整数特征列',
    num_buckets= num # num_buckets 要 大于 此连续整数列里面所有数的最大值
)

👉注:categorical_column_with_identity这种方式输出结果为 类似SparseTensor的稀疏矩阵。
👉所以我们需要用 tf.feature_column.indicator_column 将稀疏矩阵转为 正常的one-hot👇

tf.feature_column.indicator_column( aa ) # 参数是上面identity包装输出的稀疏矩阵

分桶将连续值特征 按照 数值到校 分段 映射为 离散值类型的one-hot特征列

目标格式:

日期范围	                 表示为…
< 1960 年	                [1, 0, 0, 0]
>= 1960 年 and < 1980 年	[0, 1, 0, 0]
>= 1980 年 and < 2000 年	[0, 0, 1, 0]
>= 2000 年	                [0, 0, 0, 1]

实现:

# 首先,将原始输入转换为一个numeric column
numeric_feature_column = tf.feature_column.numeric_column("Year")

# 然后,按照边界[1960,1980,2000]将numeric column进行bucket,然后自动one-hot
bucketized_feature_column = tf.feature_column.bucketized_column(
    source_column = numeric_feature_column,
    boundaries = [1960, 1980, 2000])

上面说的是连续值类型特征,下面开始聊离散值类型特征及其扩展转化👇

离散值类型特征 转向量 👇

离散值类型特征 多半是不规则数据,所以无法直接转对应的离散向量。
但是可以通过 哈希散列 和 词表散列的方式转换 👇

哈希散列 (离散文本被散列离散为 离散值类型的one-hot特征列)

当类别的数量特别大时,若将每个词汇或整数设置单独的类别,会消耗非常大的内存。
我们可大致给定类别值,让模型自己去做hash,然后映射到设定的类别(桶)中,并学习拟合到这些类别

hashed_feature_column =
    tf.feature_column.categorical_column_with_hash_bucket(
        key = "some_feature",    # 指定的列为字符串组成的列,👉字符串转为ID
        hash_bucket_size = random_n
    ) 
# 假如 random_n 为 100,那么最终的离散值就会随机选取[0-100)闭开区间的 K 个值]
# K为输入的数据类型保持一致的数
# 👉此结果依然是稀疏向量, 需要转为稠密向量 one-hot👇

和将连续值类型特征映射为one-hot的做法类似:
👉我们需要用 tf.feature_column.indicator_column 将稀疏矩阵转为 正常的one-hot👇
具体例子如下:

tf.feature_column.indicator_column(hashed_feature_column)

👉这里还有一个做法,可以把上面的稀疏矩阵转为一个具体的Tensor。但不是one-hot:

import tensorflow as tf
from tensorflow.python.feature_column import feature_column_lib

feature_cache = feature_column_lib.FeatureTransformationCache(features={
    # feature对应的值可以为Tensor,也可以为SparseTensor
    "feature": tf.constant(value=
        [
        ["a", "b"], ["c", "d"]
        ],
    )
})
id_weight_pair = tf.feature_column.categorical_column_with_hash_bucket(
    key='feature',
    hash_bucket_size=100
).get_sparse_tensors(feature_cache, None)
sparse_tensor = id_weight_pair.id_tensor   # id_weight_pair 转为 SparseTensor
tensor = tf.sparse.to_dense(sparse_tensor)
print(tensor)

# 输出结果
# tf.Tensor(
# [[39 22]
#  [72 65]], shape=(2, 2), dtype=int64)

词表散列 (离散文本被散列离散为整数)

vocabulary_feature_column =
    tf.feature_column.categorical_column_with_vocabulary_list(
        key='some_feature',
        vocabulary_list=["哈", "蜜", "瓜"],
        # num_oov_buckets=2,
        # default_value=-1,
)

👉Note: 如果num_oov_buckets这个参数不传,则代表vocabulary_list列表中的vocab被一一映射为和categorical_column_with_hash_bucket类似的效果
👉和categorical_column_with_hash_bucket稍有不同的是:

categorical_column_with_vocabulary_list不是随机映射的
而是从0开始  [0,1,2]对应哈,密,瓜,三个字

👉至于参数 num_oov_buckets=10,它代表着,如果vocabulary_list的词不在 some_feature这个列的值中,那么则会用 hash算法对vocabulary_list中的(不在 some_feature这个列)的值做增扩散列,num_oov_buckets=2 传的是2, 则可能被随机散列为 3或者4,因为(0,1,2)都被占用了。
👉至于参数default_value=-1,代表如果vocabulary_list的词不在 some_feature这个列的值中,那么会被散列,且散列的值设为-1

👉同样的和categorical_column_with_hash_bucket一样,结果也是稀疏矩阵。

所以需要用feature_column把其转为one-hot:

tf.feature_column.indicator_column(vocabulary_feature_column)

将离散文本特征转为embedding特征

👉前提:是在做过categorical_column_with_vocabulary_list 或categorical_column_with_hash_bucket 的离散化基础上,再继续embedding的。
写法:

tf.feature_column.embedding_column(被离散后的特征变量, dimension=5)
# dimension=5 代表转换后每个样本词有5维度的特征。 shape即 [样本词数, 5]

eg:

vocabulary_feature_column = \
    tf.feature_column.categorical_column_with_vocabulary_list(
        key='some_feature',
        vocabulary_list=["哈", "蜜", "瓜"],
        # num_oov_buckets=2,
        # default_value=-1,
)
tf.feature_column.embedding_column(vocabulary_feature_column, dimension=3)

交叉特征

👉Note: 要注意,特征交叉是对应维度做交叉,一般是只对特征做交叉,样本个数不变,eg:

a = [
    [1,2], 
    [3,4] 
]
b = [ 
    [5,6], 
    [7,8] 
]
a与b交叉后得到👇:
[
    [1,5], [1,6], [2,5], [2,6],
    [3,7], [3,8], [4,7], [4,8],
]

tf.feature_column.crossed_column使用例子:

import tensorflow as tf
import tensorflow.feature_column as fc

actual_sex = {'sex': tf.Variable(['male', 'female', 'female', 'male'], dtype=tf.string)}
actual_nationality = {'nationality': tf.Variable(['belgian', 'french', 'belgian', 'belgian'], dtype=tf.string)}
actual_sex_nationality = dict(actual_sex, **actual_nationality)

# hashed_column
sex_hashed_raw = fc.categorical_column_with_hash_bucket("sex", 10)
sex_hashed = fc.indicator_column(sex_hashed_raw)

# crossed column
crossed_sn_raw = fc.crossed_column(['sex', 'nationality'], hash_bucket_size=20)
crossed_sn = fc.indicator_column(crossed_sn_raw) # 交叉后是稀疏矩阵,转稠密

👉Note1: fc.numeric_column不能直接做交叉,需要先转为离散,然后再交叉
👉Note2: hash_bucket_size代表特征交叉后,用hash函数将每个样本的交叉特征映射为0-19的数字
👉Note3: 交叉后的特征为sparse矩阵, 需要用indicator_column转为one-hot或multi-hot形式的dense(稠密)矩阵,(具体是one-hot还是multi-hot,根据稀疏矩阵的格式来转)

将特征输入到模型的2种方式

一. tk.layers.DenseFeatures()

numerical_columns 是没有做任何处理的,就可以直接输入到DNN中

categorical_columns,是经过embedding处理后的

👉Note:训练得出的 User Embed 和 Item Embed 不能直接运算,因为不在同一个向量空间

preprocessing_layer = tf.keras.layers.DenseFeatures(numerical_columns + categorical_columns)

model = tf.keras.Sequential([
    preprocessing_layer,
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid'),
])

model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy']
)

model.fit(train_dataset, epochs=10)

test_loss, test_accuracy = model.evaluate(test_dataset)

print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy)

二. tf.estimator.模型()

下面会着重说这个方式,此处略

feature_column实例

a = tf.feature_column.numeric_column("数值字段")

# 已知类别个数
b = tf.feature_column.categorical_column_with_vocabulary_file(
    "类别字段名称",
    ["类别", "类别2",...,"类别n"]  # 把所有类别字符串转为 ID 0,1,2,...,n
)

# 和上述类似,未知类别个数时,用分桶
c = tf.feature_column.categorical_column_with_hash_bucket(
    "类别字段名称",
    hash_bucket_size=2000       # 越大,冲突越少
)

最后将 feature_column的结果,组装成列表, 返回给模型构造。
👉eg:

feature_columns = [a, b, c]
classifier = tf.estimator.LinearClassifier(feature_columns)

👉正式训练模型:

classifier.train(数据集处理函数句柄(就是上面返回dataset的那个),steps=None)

👉上面的dataset函数句柄如下(官方CSV操作实例):

TRAIN_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/train.csv"
TEST_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/eval.csv"

# 把数据下载到此路径下
train_file_path = tf.keras.utils.get_file("train.csv", TRAIN_DATA_URL)
# 'C:\\Users\\lin\\.keras\\datasets\\train.csv'
test_file_path = tf.keras.utils.get_file("eval.csv", TEST_DATA_URL)
# 'C:\\Users\\lin\\.keras\\datasets\\eval.csv'

CSV_COLUMNS = ['survived', 'sex', 'age', 'n_siblings_spouses', 'parch', 'fare', 'class', 'deck', 'embark_town', 'alone']
LABEL_COLUMN = 'survived'
LABELS = [0, 1]

def get_dataset(file_path):
    """
    真正操作CSV的函数
    """
    dataset = tf.data.experimental.make_csv_dataset(
        file_path,
        batch_size=12,
        column_names=CSV_COLUMNS,
        label_name=LABEL_COLUMN,
        na_value="?",
        num_epochs=1,
        ignore_errors=True)
    return dataset
    # 只要我们将  column_names, label_name 这2个字段指定好
    # make_csv_dataset 将会自动帮我们组装 dataset格式 (包括 feature_dict 和 lable的迭代器)并return

👉因为要传函数句柄(就是函数名),而不是调用,所以额外的参数,需要用到偏函数解决

from functools import partial
# 读