一、编写数据集导入函数,此函数必须返回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
# 读