钟柱 2018-04-26
我们将展示如何使用TensorFlow机器学习框架和Neo4j图形数据库来预测产品评论。它达到了97%的验证准确度。
商业上的一个普遍问题是产品推荐。考虑到一个人喜欢什么,我们接下来应该建议购买什么?正如服务员问你是否想再喝一杯酒会带来更高的收入,成功的建议也是如此。
有很多方法可以推荐。我们将重点关注评论预测,我们可以向其推荐我们预测的产品。
Neo4j
我们将使用图形数据库作为该系统的数据源。图形数据库是存储和分析数据的强大方法。通常情况下,例如人与人之间的关系与这些事物本身的属性同样重要。在图形数据库中,很容易存储和分析这些关系。在这个评论预测系统中,我们将分析不同人员和不同产品之间的评论网络。
我们将使用Neo4j作为我们的图形数据库。Neo4j是一个流行的,快速且免费使用的图形数据库。
TensorFlow
对于这个系统的机器学习部分,我们将使用TensorFlow。TensorFlow主要是一个模型构建和训练框架,让我们表达我们的模型并在数据上进行训练。TensorFlow已经迅速成为最流行和积极开发的机器学习库之一。
TensorFlow可以节省大量的开发时间。一旦在TensorFlow中建立了模型,相同的代码就可以用于实验,开发和部署到生产中。像Google的CloudML这样的平台将模型托管作为服务提供,将您模型的预测作为REST API提供。
我们将会预测产品评论。有一些人,他们写产品的评论。图中是这样的:
在图形数据库中,我们可以根据模式查询信息。我们将在这里使用的数据库Neo4j使用了一种称为Cypher的查询语言。上图是通过一个简单的查询生成的:
MATCH g=(:PERSON) -[:WROTE]-> (:REVIEW) -[:OF]-> (:PRODUCT)
RETURN g LIMIT 1
这查找标签PERSON的节点,其标签WROTE与标签REVIEW的节点的关系具有标签OF与标签PRODUCT的节点之间的关系。限定符“LIMIT 1”要求数据库只返回一个匹配此模式的实例。
我们的数据集包含250个人和50个产品。每个人有40条评论,总共有10,000条评论。
您可以使用托管数据库(https://github.com/Octavian-ai/article-1/blob/master/src/data.py),或使用代码库(https://github.com/Octavian-ai/generate-data)将数据生成到您自己的Neo4j实例中:
./generate.sh --dataset article_1
数据集是合成的 - 我们从概率模型中自己生成它。
在模型开发过程中使用综合数据集是非常有用的技术。如果您对未知数据应用未经证实的方法并且未能训练,则无法确定问题是数据还是模型。通过合成一个未知的数据,您可以专注于找到一个成功的模型。
合成数据集具有局限性:它缺少现实世界数据中典型的不规则性和错误。对于这种学习练习,合成数据集非常有用,但任何真实世界的系统都需要更多的步骤清理数据并尝试找到适合它的模型。
我们的综合数据生成使用简单的概率模型。在生成过程中,每个产品和个人都有一个随机选择的类别,这些类别用于生成评论分数。我们将人,产品和评论保存到数据库中,并放弃类别分配。
更详细地说:
我们产生了一组250人和50个产品。每个人审查40个随机选择的产品。
每个人和产品都有一个宽度为6 的单编码矢量。将此视为从六个选项中选择一个类别。例如,每种产品都可以是六种颜色之一(其风格)。每个人喜欢这六种颜色之一(他们的偏好)。
从一个人到一个产品的每次评论都是作为他们媒介的点积来计算的,如果他们共享相同的风格和偏好,则给出1.0 ,否则为0.0。
最后,我们将测试属性分配给随机选择的10%的评论。该数据用于评估模型,不用于训练。
由于每个人对40种随机选择的产品进行评估,很有可能(虽然不确定)他们将评估六种风格中的每种风格的产品 - 因此我们的评估预测挑战受到很好的约束,我们应该能够接近100%。
在学术文献中,我们的问题被称为“协作过滤”。通过结合许多人的评论('协作'),我们可以更好地推荐一个人的产品('过滤')。
我们将通过估计每个产品的风格矢量和每个人的偏好矢量来解决这个评论预测问题。我们将通过获取这两个向量的点积来预测评论分数(因为我们知道数据是使用点积生成的,所以我们很容易猜测这可能是一个成功的解决方案)。
我们模型的输入是人的ID和产品的ID。模型的输出是评分。
评论预测是一个有趣的问题,因为我们不知道每个产品的风格,也不知道每个人的偏好,因此我们必须同时确定。预测产品风格的错误会导致预测人们偏好的错误,因此解决这个问题并不是微不足道的。
应该指出,这不是“深度学习”(尽管它是机器学习)。我们使用TensorFlow作为一个方便的框架来通过梯度下降训练浅层模型。
嵌入变量
我们模型的第一步是将人员ID和产品ID转换为估计的偏好和风格张量。这在TensorFlow中非常简单。
我们将所有人员和产品的偏好和风格估计存储为shape [number_of_ids,width_of_tensor]的两个变量:
product = tf.get_variable(“product”,[n_product,embedding_width])
person = tf.get_variable(“person”,[n_person,embedding_width])
我们可以使用tf.nn.embedding_lookup(product, product_id)将ID转换为形状的张量[width_of_tensor]
嵌入张量的格式
每个嵌入张量将是20个维度的浮点。与数据生成不同,对于单值编码的值没有限制。
总之,这种设计允许模型在训练过程中有很大的空间机动 - 这是有益的,因为梯度下降用很小的步骤更新变量。这个设计是通过实验和网格搜索来确定的。
模型实现
我们预测的模型只有八行:
# Allocate storage for the estimations
product = tf.get_variable("product", [n_product, embedding_width])
person = tf.get_variable("person", [n_person, embedding_width])
# Retrieve the embedding tensors
product_emb = tf.nn.embedding_lookup(product, product_id)
person_emb = tf.nn.embedding_lookup(person, person_id)
# Dot product
m = tf.multiply(product_emb, person_emb)
m = tf.reduce_sum(m, axis=-1)
m = tf.expand_dims(m, -1) # So this fits as input for dense()
# A dense layer to fit the score to the range in the data
review_score = tf.layers.dense(m, (1), tf.nn.sigmoid)
下一步是将这个模型包含在其他需要训练的部分中。我们将使用高级别的Estimator API,因为它具有预先构建的例程,用于训练,评估和服务模型,否则我们不得不重新编写模型。
The model function
Estimator框架的核心是一个模型函数。
这是我们编写并交给TensorFlow的一个函数,这样框架就可以根据需要经常实例化模型(例如,它可以在不同的GPU /机器上运行多个模型,也可以用不同的模型重新运行模型学习率确定最好)。
模型函数是一个python函数,它接受输入特征张量(以及其他一些参数)并返回一个EstimatorSpec包含以下内容的函数:
衡量模型的损失(例如它适合训练数据的程度)
训练操作(在每一步中执行以训练模型的'代码')
评估指标(我们将在TensorBoard中查看模型成功度量)
为了测量损失,我们将使用内置均方误差:
loss = mean_squared_error(pred_review_score, label_review_score )
对于训练操作,我们将使用内置的Adam优化器来减少损失:
train_op = tf.train.AdamOptimizer(params["lr"]).minimize(loss)
我们将测量一个评估指标,准确度:
eval_metric_ops = {
“accuracy”:tf.metrics.accuracy(pred_review_score,label_review_score)
}
最后返回EstimatorSpec:
return tf.estimator.EstimatorSpec(
mode,
loss = loss,
train_op = train_op,
eval_metric_ops = eval_metric_ops
)
我们将使用Cypher查询从我们的图形数据库中获取数据并将其格式化以进行训练:
MATCH p=
(person:PERSON)
-[:WROTE]->
(review:REVIEW {dataset_name:"article_1", test:{test}})
-[:OF]->
(product:PRODUCT)
RETURN
person.id as person_id,
product.id as product_id,
review.score as review_score
这会为我们的数据库中的每个评论返回一行。然后,我们将TensorFlow的每一行格式化为(input_dict,expected_output_score)的元组:
raw_data = session.run(query, **query_params).data()
def format_row(i):
return (
{
"person": {
"id": self._get_index(i, "person"),
"style": i["person_style"],
},
"product": {
"id": self._get_index(i, "product"),
"style": i["product_style"],
},
"review_score": i["review_score"],
},
i["review_score"]
)
data = [format_row(i) for i in raw_data]
接下来,我们构建一个TensorFlow 数据集。这是高级别的TensorFlow API,它允许框架进行大量艰苦的转换和分发我们的数据以进行训练。我们将使用API从我们的生成器创建数据集,对数据进行洗牌并批量处理:
t = tf.data.Dataset.from_generator(
lambda: (i for i in data),
self.dataset_dtype,
self.dataset_size
)
t = t.shuffle(len(self))
t = t.batch(batch_size)
Shuffling有助于网络的学习,因为它会在每批中遇到不同的人员和产品组合。
与我们之前创建的模型函数类似,我们现在将创建一个输入函数。TensorFlow将在训练期间多次构建一个数据集(例如,当它到达数据的末尾并希望重新启动时),并且输入函数使其有能力这样做。
我们创建一个input_fn不需要参数的TensorFlow:
input_fn = lambda:data.gen_dataset()
现在,我们有我们的model_fn,我们的input_fn,我们已经准备好训练!我们将使用train_and_evaluateEstimator API 的方法来协调我们的训练和评估。
我们构建一个Estimator,在TrainSpec中指定训练数据和步数,并在EvalSpec中指定评估数据:
estimator = tf.estimator.Estimator(model_fn,model_dir,vars(args))
train_spec = tf.estimator.TrainSpec(data_train.input_fn)
eval_spec = tf.estimator.EvalSpec(data_eval.input_fn,
steps = None)
我们指定steps=None使用整个评估集(而不仅仅是前100个项目)。
现在我们准备好了,我们可以运行整个训练和评估:
tf.estimator.train_and_evaluate(estimator,train_spec,eval_spec)
Estimator框架将保存我们的模型和输出摘要,TensorBoard可以为我们显示。
启动TensorBoard并观察进展情况:
tensorboard --logdir ./output
经过10,000次训练后,该模型达到92%的准确度:
这个结果对于这样一个简单的实现来说并不算太坏,但我们可以做得更好。
我们的代码有一个简短的扩展,可以帮助我们的模型训练达到97%的准确性。
我们将在整个图表上进行Random walks。Random walks意味着从一个图形节点开始,在它所连接的节点之间随机选择,然后从该节点执行相同操作,将路径保留在列表中。这与醉酒的人穿过城市有些相似。
机器学习的典型输入是固定大小的表格数据。图形可以具有任意数量的连接和节点,因此它们不容易适应固定大小的结构。这使得图形很难进入机器学习。
Random walks是捕捉简单数据结构中图形连通性的一种非常强大的方式。每个步行输出一个固定长度的列表。每次散步都是图的一个样本,并且有足够数量的随机散步来表示图的整个连通性。他们在包括建模语言,社交网络和蛋白质结构等多个领域取得了非常成功的成果。
random walks有助于在我们的图中构建人员和产品的样式和偏好向量之间的兼容性。当我们估计某人对某一产品的评价时,他们从未回顾过(也许图中附近的人也没有看过),他们的偏好向量与目标产品的风格向量相同的可能性更大。
实施有两个步骤:
通过产品和人员ID对行数据进行索引
样本batch_size的长度从索引数据开始
现在训练模型可以达到97%的评估准确度。
请注意,模型从随机初始化变量开始,并接收随机排序的训练数据,因此它可以在每次训练中获得不同的结果。该模型并不总是收敛,并不总是达到其最高性能。我已经展示了20个以下单独的模型训练,其中有些训练偶尔会达到高达98%的准确性: