Loading...
Navigation
Table of Contents

TensorFlow TutorialData

Coding Tutorial

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中,线性的模型,如LinearClassifierLinearRegressor可以接受连续和离散的特征作为输入,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.