Coding Tutorial
- Linear Model Tutorial
- CSDN博客
- Wide & Deep 模型详解与应用:详细介绍了wide deep的源代码以及tf上的oop编程
tf的特点是先对特征工程,训练模型,预测方法进行好预定义(在知道数据形式的前提下),然后再传入数据进行实验。
0. 使用tf.data类
tf.data
(参见Programmer's Guide和知乎专栏)是实现pipeline input的最推荐的接口类,其包含tf.data.Dataset
(从内存里读取数据,最常用),tf.data.TextLineDataset
(从文本文件里读取数据)和tf.data.TFRecordDataset
(从record文件里读取数据)等常用子类,里面包含丰富的方法。其优势在于只需预先定义好input格式,训练时再并行按batch从tf.data
里读取数据。
这几个类都有如下常用的的方法(先后顺序无所谓):
.shuffle()
决定是否会在每轮epoch前打乱顺序.repeat()
用于设置一共多少个epoch.batch
搭配.make_one_shot_iterator()
提取每个batch,还有一些其它iterator,参考Programmer's Guide.pop()
配合parse_csv()
决定如何自定义处理每一行样本,如通过dict把string映射为int8。
如果数据量不大,事先已经读到内存里,可以直接用tf.data.Dataset
来制作input. 比如如果已经把特征和标签处理成numpy数组了,则直接使用tf.data.Dataset.from_tensor_slices((features, labels))
即可转成tf.data
,参见Programmer's Guide - Importing Data;如果想从csv文件里读取,则可以用tf.data.TextLineDataset
,以下是来自Tutorials - Linear Models的样例代码。
_CSV_COLUMNS = ['age', 'workclass', 'education'...]
_CSV_COLUMN_DEFAULTS = [[0], [''], [0.0], ...] #每一个特征的缺省值, 分别对应着int32, str, float32等类型
def input_fn(data_file, num_epochs, shuffle, batch_size):
dataset = tf.data.TextLineDataset(data_file).skip(1) #跳过前1行
if shuffle:
dataset = dataset.shuffle(buffer_size=_SHUFFLE_BUFFER)
#每轮epoch前, 新dataset会从旧数据里随机选取_SHUFFLE_BUFFER个得出
#当_SHUFFLE_BUFFER > 所有数据个数时, 即相当于把整个数据集打乱一遍
dataset = dataset.repeat(num_epochs).batch(batch_size) #repeat里可为空或者填None, 即无限个epoch
def parse_csv(value):
print('Parsing', data_file)
columns = tf.decode_csv(value, record_defaults=_CSV_COLUMN_DEFAULTS, field_delim=",")
#record_defaults必选, 值每一列默认类型, 如tf.float32, 也可以是默认值
#field_delim是默认分隔符
features = dict(zip(_CSV_COLUMNS, columns))
#features = {"age": array1, "workclass": array2, ...}, 其中array1.shape=[batch_size]
labels = features.pop('income_bracket')
return features, tf.equal(labels, '>50K')
dataset = dataset.map(parse_csv, num_parallel_calls=5) #对于dataset的每一行进行映射并返回
iterator = dataset.make_one_shot_iterator()
features, labels = iterator.get_next()
return features, labels #返回迭代器
#sess = tf.Session()
#sess.run([feaures, labels]) 测试一下
1. 连续、离散和交叉特征(tf.feature_column)
特征工程都是用的tf.feature_column
里面的函数,高阶特性需要去查API. 注意tf.feature_column
都是类对象,不包含具体数据,需要通过tf.contrib.layers.input_from_feature_columns
接口将数据映射到第一层输入。
Continuous/Numerical特征
SepalLength = tf.feature_column.numeric_column("SepalLength", dtype=tf.float64)
vector_feature = tf.feature_column.numeric_column(key="Bowling", shape=[10])
#用10维的向量表示一个连续特征
Categorical特征
#通过vocabulary list指定gender特征的取值空间
gender = tf.feature_column.categorical_column_with_vocabulary_list("gender", ["Female", "Male"])
#通过vocabulary file指定gender特征的取值空间
gender2 = tf.feature_column.categorical_column_with_vocabulary_file(
key=feature_name_from_input_fn,
vocabulary_file="product_class.txt",
vocabulary_size=3)
#如果不知道vocabulary list, 可以通过自带的hash方法来映射. 虽然hash可能冲突, 但是官网说这在实际中影响不是很大
occupation = tf.feature_column.categorical_column_with_hash_bucket("occupation",
hash_bucket_size=100)
Bucketization 有时候某些features和label之间不是线性关系,比如年龄age和收入income。而机器学习模型只能学到三种情况,即收入随着年龄增加而增加(Positive Correlation),减少(Negative Correlation)或者不变(No Correlation)。所以应该把年龄变成几个buckets,然后像离散特征一样进行处理。
#先把原始特征用连续特征表示
year_column = tf.feature_column.numeric_column("Year")
#然后把连续特征进行bucket化/离散化
bucketized_column = tf.feature_column.bucketized_column(source_column = year_column,
boundaries = [1960, 1980, 2000])
Crossed Column特征交叉一般只能是categorical和bucket特征之间的交叉。这里输入名字可以是字符串name的list,也可以是特征的list,或者彼此都有。
education_x_occupation = tf.feature_column.crossed_column(
["education", "occupation"], hash_bucket_size=1000) #根据name来交叉
age_buckets_x_education_x_occupation = tf.feature_column.crossed_column(
[age_buckets, "education"], hash_bucket_size=1000) #根据变量和name来交叉
one-hot和embedding
categorical特征转onehot给定categorical特征,我们想把它变成one-hot的向量
categorical_column = ... #先创建一个categorical特征
indicator_column = tf.feature_column.indicator_column(categorical_column)
Embedding one-hot之后的特征可能维度太高,我们可以通过lookup的方法将离散特征直接映射到embedding,embedding table是可以训练时候自己学习的。
categorical_column = ... #先创建一个categorical特征
embedding_dimensions = number_of_categories**0.25
embedding_column = tf.feature_column.embedding_column(categorical_column=categorical_column,
dimension=embedding_dimensions)
TBD
input_from_feature_columns
columns_to_tensor = tf.parse_example(...) #一个parser/mapping
first_layer = tf.contrib.layers.input_from_feature_columns(
columns_to_tensors=columns_to_tensor, #{feature_name: array}
feature_columns=columns) #columns是包含feature_columns对象的list(连续特征或embedding)
second_layer = fully_connected(inputs=first_layer, ...)
2. estimator
在tf的estimator
中,线性的模型,如LinearClassifier
和LinearRegressor
可以接受连续和离散的特征作为输入,NN模型只能接受连续特征,onehot后的离散特征和embedding。所有的estimator
模型都需要指定一个目录来存放check points,event files等文件。
gender_column = tf.feature_column.categorical_column_with_vocabulary_list("gender", ["Female", "Male"])
base_columns = [gender_column, education_column, ...]
crossed_columns = [
tf.feature_column.crossed_column(["education", "occupation"], hash_bucket_size=1000), #根据name来交叉
tf.feature_column.crossed_column([age_buckets, "education"], hash_bucket_size=1000)] #根据变量和name来交叉
deep_columns = [age, education_num, #这里当成连续特征看
tf.feature_column.indicator_column(workclass), #onehot后的特征
tf.feature_column.embedding_column(native_country, dimension=8)] #embedding
LR
基于上面的特征工程,我们可以定义一个LR模型(即Linear Classifier)
import tempfile #用于生成临时目录, 来存放estimator的各种文件
model_dir = tempfile.mkdtemp() #model_dir是指向临时目录的string
m = tf.estimator.LinearClassifier(model_dir=model_dir,
feature_columns=base_columns + crossed_columns)
Wide and Deep
model_dir = tempfile.mkdtemp() # 构造一个临时directory
m = tf.estimator.DNNLinearCombinedClassifier(
model_dir=model_dir,
linear_feature_columns=crossed_columns, #传入wide部分的特征
dnn_feature_columns=deep_columns, #传入deep部分的特征
dnn_hidden_units=[100, 50])
当然也可以这样定义
def build_estimator(model_dir, model_type):
"""Build an estimator."""
if model_type == "wide":
m = tf.estimator.LinearClassifier(
model_dir=model_dir, feature_columns=base_columns + crossed_columns)
elif model_type == "deep":
m = tf.estimator.DNNClassifier(
model_dir=model_dir,
feature_columns=deep_columns,
hidden_units=[100, 50])
else:
m = tf.estimator.DNNLinearCombinedClassifier(
model_dir=model_dir,
linear_feature_columns=crossed_columns,
dnn_feature_columns=deep_columns,
dnn_hidden_units=[100, 50])
return m
训练和预测
model.train(input_fn=lambda: input_fn(file_path, num_epochs=100, shuffle=True, batch_size=50)) #训练100epoch
results = m.evaluate(input_fn=input_fn(file_path, num_epochs=1, shuffle=False, batch_size=50)) #测试
注意这里必须有lambda
来保证输入是callable的,参见这里。
自定义模型 如果想实现自己的模型,可以通过传入模型给tf.estimator.Estimator
来实现。以下示例来自于custom_regression.py,其使用了tf.estimator.EstimatorSpec
类来完成自定义模型。
def my_dnn_regression_fn(features, labels, mode, params):
"""A model function implementing DNN regression for a custom Estimator."""
# Extract the input into a dense layer, according to the feature_columns.
top = tf.feature_column.input_layer(features, params["feature_columns"])
# Iterate over the "hidden_units" list of layer sizes, default is [20].
for units in params.get("hidden_units", [20]):
# Add a hidden layer, densely connected on top of the previous layer.
top = tf.layers.dense(inputs=top, units=units, activation=tf.nn.relu)
# Connect a linear output layer on top.
output_layer = tf.layers.dense(inputs=top, units=1)
# Reshape the output layer to a 1-dim Tensor to return predictions
predictions = tf.squeeze(output_layer, 1)
if mode == tf.estimator.ModeKeys.PREDICT:
# In `PREDICT` mode we only need to return predictions.
return tf.estimator.EstimatorSpec(
mode=mode, predictions={"price": predictions})
# Calculate loss using mean squared error
average_loss = tf.losses.mean_squared_error(labels, predictions)
# Pre-made estimators use the total_loss instead of the average,
# so report total_loss for compatibility.
batch_size = tf.shape(labels)[0]
total_loss = tf.to_float(batch_size) * average_loss
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = params.get("optimizer", tf.train.AdamOptimizer)
optimizer = optimizer(params.get("learning_rate", None))
train_op = optimizer.minimize(
loss=average_loss, global_step=tf.train.get_global_step())
return tf.estimator.EstimatorSpec(
mode=mode, loss=total_loss, train_op=train_op)
# In evaluation mode we will calculate evaluation metrics.
assert mode == tf.estimator.ModeKeys.EVAL
# Calculate root mean squared error
rmse = tf.metrics.root_mean_squared_error(labels, predictions)
# Add the rmse to the collection of evaluation metrics.
eval_metrics = {"rmse": rmse}
return tf.estimator.EstimatorSpec(
mode=mode,
# Report sum of error for compatibility with pre-made estimators
loss=total_loss,
eval_metric_ops=eval_metrics)
model = tf.estimator.Estimator(
model_fn=my_dnn_regression_fn,
params={
"feature_columns": feature_columns,
"learning_rate": 0.001,
"optimizer": tf.train.AdamOptimizer,
"hidden_units": [20, 20]
})
Linux环境离线安装
由于linux服务器不能联网,之前的anaconda3也算手动上传安装包来安装的,所以这次也得手动安装tensorflow-CPU Only。环境为linux x64,python3.6(anaconda3)。
无论是单独的python3,还是anaconda,根据官网的Tutorial,本质上都是用pip来安装tensorflow。注意到tensorflow对于python3的每一个子版本支持的都不同,所以需要去Tutorial里给的链接页面里下载对应的whl文件,这里下载tensorflow-1.3.0-cp36-cp36m-linux_x86_64.whl
,其他版本的在安装时会直接报错。
由于直接pip install tensorflow.whl
(不要使用sudo
)会一直联网然后报错,根据stack overflow的方法,离线环境必须使用python -m pip install --upgrade tensorflow.whl
的方法来安装。
然而在安装tensorflow之前,得手动安装其依赖,一般是从这里下载对应的whl文件来安装;找不到满足要求的,再从PiPY下载whl文件或者tar压缩包安装。
rz -bye #先上传文件
#对于whl文件
pip install package.whl
#对于tar.gz文件
tar -ztvf package.tar.gz #解压到当前文件夹
cd package
python setup.py install
tensorflow的依赖里面有几个特别难安装:
- tensorflow-tensorboard 0.18:由于这个tensorflow.whl要求tensorflow-tensorboard版本
< 0.2.0
并且>=0.1.0
,最后从PyPi上找到了符合要求的0.18版本的whl。然而这个版本的tensorflow-tensorboard也有好多依赖,比较难安装的有html5lib (==0.9999999)
; - html5lib 0.9999999:从PyPi找到对应的tar文件,然后安装,注意版本号里的9不能多也不能少;
- future 3.1.1:这个不是依赖,但是也记录一下。future是给python2准备的,不知道为什么有的python3的package也需要安装这个。在PyPI里下载
futures-3.1.1-py2-none-any.whl
文件,然后重命名为futures-3.1.1-py3-none-any.whl
,才可以进行安装。
Last updated on Jun 19, 2019.