文 / Google Cloud Platform 技术负责人 Lak Lakshmanan
如果您有一个稀疏分类变量(可以有多个可能的值的变量),将该变量嵌入到较低维度可能会非常有用。这种最广为人知的嵌入形式就是词嵌套(例如 word2vec 或 Glove 嵌套),即语言中的所有词汇都用一个包含约 50 个元素的向量表示。其理念是相似的词汇在 50 维空间中距离很近。您可以使用分类变量进行相同操作,即使用一个问题训练嵌套,然后再次使用该嵌套,而非对相关问题中的分类变量进行独热编码。嵌套的较低维度空间是连续的,所以该嵌套还可以充当聚类算法的输入 — 您可以找到分类变量的自然分组。
嵌套可以帮助您看到森林,而不仅仅是树木
要提供使用估算器训练的嵌套,您可以连同普通预测输出一起,发出分类变量的较低维度表征。嵌套权重保存在 SavedModel 中,并且有一个分享文件本身的选项。或者,您也可以为机器学习团队的客户端按需提供嵌套,这些客户端现在只是松散耦合到您选择的模型架构,所以这样可能更容易维护。每次有更新更好的版本替代您的模型时,客户端都会获得更新后的嵌套。
在本文中,我会向您展示如何:
在回归模型或分类模型中创建嵌套
以不同方法表示分类变量
使用特征列进行数学计算
分发嵌套及原始模型的输出
您可以在 GitHub 上找到本文中的完整代码,其中包含更多上下文。在这里,我只向您展示关键代码段。
注: GitHub 链接
https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/serving_embed/serving_embed.ipynb
用模型来预测自行车需求
我们来构建一个简单的需求预测模型,在已知星期几和当天是否下雨的前期下,预测自行车租赁站的租车数量。所需数据来自纽约市自行车租赁和 NOAA 气象数据的公开数据集:
模型的输入如下:
星期几(1–7 的整数)
租赁站 ID(由于不知道完整词汇,这里我们使用哈希存储分区。该数据集有大约 650 个唯一值。我们会使用一个很大的哈希存储分区,但之后会将其嵌入到较低维度中)
是否下雨(真/假)
我们想要预测的标签为 num_trips。
我们可以通过在 BigQuery 中运行这个查询来创建数据集,从而加入自行车和天气数据集,并进行必要的聚合:
注:BigQuery 链接
https://cloud.google.com/bigquery/
1 #standardsql
2 WITH bicycle_rentals AS (
3 SELECT
4 COUNT(starttime) as num_trips,
5 EXTRACT(DATE from starttime) as trip_date,
6 MAX(EXTRACT(DAYOFWEEK from starttime)) as day_of_week,
7 start_station_id
8 FROM `bigquery-public-data.new_york.citibike_trips`
9 GROUP BY trip_date, start_station_id
10 ),
11
12 rainy_days AS
13 (
14 SELECT
15 date,
16 (MAX(prcp) > 5) AS rainy
17 FROM (
18 SELECT
19 wx.date AS date,
20 IF (wx.element = 'PRCP', wx.value/10, NULL) AS prcp
21 FROM
22 `bigquery-public-data.ghcn_d.ghcnd_2016` AS wx
23 WHERE
24 wx.id = 'USW00094728'
25 )
26 GROUP BY
27 date
28 )
29
30 SELECT
31 num_trips,
32 day_of_week,
33 start_station_id,
34 rainy
35 FROM bicycle_rentals AS bk
36 JOIN rainy_days AS wx
37 ON wx.date = bk.trip_date
使用估算器编写模型
要编写模型,我们需要在 TensorFlow 中使用自定义估算器。虽然这只是一个线性模型,但我们不能使用 LinearRegressor,因为 LinearRegressor 会隐藏所有底层特征列运算。我们需要访问中间层输出(嵌套特征列的输出),这样我们就可以清晰地编写线性模型。
要使用自定义估算器,您需要编写一个模型函数,并将其传递给估算器构造函数:
1 ef train_and_evaluate(output_dir, nsteps):
2 estimator = tf.estimator.Estimator(
3 model_fn = model_fn,
4 model_dir = output_dir)
自定义估算器中的模型函数包括下列 5 个部分:
1.定义模型:
1 def model_fn(features, labels, mode):
2 # linear model
3 station_col = tf.feature_column.categorical_column_with_hash_bucket('start_station_id', 5000, tf.int32)
4 station_embed = tf.feature_column.embedding_column(station_col, 2) # embed dimension
5 embed_layer = tf.feature_column.input_layer(features, station_embed) 6
7 cat_cols = [
8 tf.feature_column.categorical_column_with_identity('day_of_week', num_buckets = 8),
9 tf.feature_column.categorical_column_with_vocabulary_list('rainy', ['false', 'true'])
10 ]
11 cat_cols = [tf.feature_column.indicator_column(col) for col in cat_cols]
12 other_inputs = tf.feature_column.input_layer(features, cat_cols)
13
14 all_inputs = tf.concat([embed_layer, other_inputs], axis=1)
15 predictions = tf.layers.dense(all_inputs, 1) # linear model
我们将选取租赁站列,并根据其哈希代码将其放入一个存储分区。使用这个技巧可以避免构建完整词汇。纽约只有大约 650 个自行车租赁站,所以拥有 5000 个哈希存储分区,我们就可以大大减少冲突的几率。然后,将租赁站 ID 嵌入到少数维度中,我们还会了解到哪些租赁站彼此相似,至少在雨天租车的情况下相似。最后,用二维向量表示每个租赁站的 ID。数字 2 控制较低维度空间代表分类变量中信息的准确程度。这里我随意选择了 2,但实际上,我们需要调节此超参数,以实现最佳性能。
其他两个分类列均使用其实际词汇创建,然后进行了独热编码(指示器列对数据进行了独热编码)。
这两组输入都经过级联,以创建一个宽输入层,然后通过一个输出节点传递到某个密集层。这样,您就在较低层级编写了一个线性模型。这相当于编写了一个 LinearRegressor,如下所示:
1 station_embed =
2 tf.feature_column.embedding_column(
tf.feature_column.categorical_column_with_hash_bucket('start_station_id', 5000, tf.int32), 2)
3 feature_cols = [
4 tf.feature_column.categorical_column_with_identity('day_of_week', num_buckets = 8),
5 station_embed,
6 tf.feature_column.categorical_column_with_vocabulary_list('rainy', ['false', 'true'])
7 ]
8 estimator = tf.estimator.LinearRegressor(
9 model_dir = output_dir,
10 feature_columns = feature_cols)
请注意,LinearRegressor 会将 input_layer、indicator_column 等全部隐藏起来。但是,我想访问租赁站的嵌套,所以将其显示了出来。
2.使用回归模块设置估算器规范
对于回归问题,我们可以使用 Ftrl 优化器将均方误差最小化( LinearRegressor 默认使用此优化器,所以我也使用这个):
1 my_head = tf.contrib.estimator.regression_head()
2 spec = my_head.create_estimator_spec(
3 features = features, mode = mode,
4 labels = labels, logits = predictions,
5 optimizer = tf.train.FtrlOptimizer(learning_rate = 0.1)
6 )
3 — 4.创建输出字典
通常情况下,我们只会发送预测,但在这个例子中,我们想回送预测和嵌套:
1 # 3. Create predictions
2 predictions_dict = {
3 "predicted": predictions,
4 "station_embed": embed_layer
5 }
6
7 # 4. Create export outputs
8 export_outputs = {
9 "predict_export_outputs": tf.estimator.export.PredictOutput(outputs = predictions_dict)
10 }
这里,我们使用自定义估算器的另一个原因在于它能够更改 export_outputs。
5.回送带有预测结果的 EstimatorSpec,并导出替换过的输出:
1 # 5. Return EstimatorSpec
2 return spec._replace(predictions = predictions_dict,
3 export_outputs = export_outputs)
现在,我们照常训练模型。
调用预测
然后,可以使用 TensorFlow Serving 提供导出的模型,或者可以选择将其部署到 Cloud ML Engine(实际是托管的 TF Serving),随后调用进行预测。您也可以使用 gcloud 调用本地模型(它可以针对此用途提供比 saved_model_cli 更方便的界面):
1 EXPORTDIR=./model_trained/export/exporter/
2 MODELDIR=$(ls $EXPORTDIR | tail -1)
3 gcloud ml-engine local predict --model-dir=${EXPORTDIR}/${MODELDIR} --json-instances=./test.json
test.json 中有什么?
{“day_of_week”: 4, “start_station_id”: 435, “rainy”: “true”}
{“day_of_week”: 4, “start_station_id”: 521, “rainy”: “true”}
{“day_of_week”: 4, “start_station_id”: 3221, “rainy”: “true”}
{“day_of_week”: 4, “start_station_id”: 3237, “rainy”: “true”}
正如您看到的,我发送了 4 个实例,分别对应租赁站 435、521、3221 和 3237。
前面两个站位于曼哈顿,这一区域的租赁活动非常频繁(既为通勤族也为游客提供租赁服务)。后面两个站位于长岛,这个区域的自行车租赁并不普及(可能只在周末提供服务)。产生的输出包含预测的旅行数量(我们的标签)和租赁站的嵌套:
在本例中,嵌套的第一个维度在全部情况下几乎为零。所以,我们只需要一个维度嵌套。查看第二个维度,非常清楚地显示曼哈顿站有正值 (0.0081, 0.0011),而长岛站有负值 (-0.0025, -0.0031)。
这是我们单纯通过机器学习模型得到的信息,仅考察这两个地点在不同日期的自行车租赁情况!如果您的 TensorFlow 模型中有分类变量,可以试试从这些模型中分配嵌套。也许他们会带来新的数据分析!