Gremlin 中的决策树查询

Posted

技术标签:

【中文标题】Gremlin 中的决策树查询【英文标题】:Decision Tree query in Gremlin 【发布时间】:2021-12-28 17:04:02 【问题描述】:

我已经简化了一个决策图。它以开始顶点开始,以决策结束。我的目标是在行驶不同的路径(到达决策顶点)时计算得分的总和(与顶点相关的得分)。

Graph 的输入是 JSON。

顶点之间的边包含可以从输入 JSON 中检查的变量和值。

输入 JSON 示例: "age":45,"income_source":"job"

输出是分数的总和 [10 + 15 + 22] = 47

在 Neo4j 中,Cypher 查询允许您将 JSON 输入作为查询参数传递,但我不知道如何在 Gremlin 中完成。

图表链接:https://gremlify.com/nwgxqs5h7zh/

g.addV('begin').as('beg').
addV('decision').property('score',0).property('decision_code',"minor").as('dec0').

addV('age').property('score',10).as('age10').
addV('age').property('score',20).as('age20').

addV('salary').property('score',15).as('sal15').
addV('salary').property('score',25).as('sal25').

addV('salary').property('score',18).as('sal18').
addV('salary').property('score',30).as('sal30').

addV('decision').property('score',22).property('decision_code',"decision_22").as('dec22').
addV('decision').property('score',45).property('decision_code',"decision_45").as('dec45').

addV('decision').property('score',18).property('decision_code',"decision_18").as('dec18').
addV('decision').property('score',30).property('decision_code',"decision_30").as('dec30').


addE('relation').property('var',"age").property('val',"").property('min',"10").property('max',"18").from('beg').to('dec0').
addE('relation').property('var',"age").property('val',"").property('min',"19").property('max',"48").from('beg').to('age10').
addE('relation').property('var',"age").property('val',"").property('min',"49").property('max',"80").from('beg').to('age20').


addE('relation').property('var',"income_source").property('val',"job").property('min',"-1").property('max',"-1").from('age10').to('sal15').
addE('relation').property('var',"income_source").property('val',"buisness").property('min',"-1").property('max',"-1").from('age10').to('sal25').

addE('relation').property('var',"income_source").property('val',"job").property('min',"-1").property('max',"-1").from('age20').to('sal18').
addE('relation').property('var',"income_source").property('val',"buisness").property('min',"-1").property('max',"-1").from('age20').to('sal30').

addE('relation').property('var',"").property('val',"").property('min',"-1").property('max',"-1").from('sal15').to('dec22').
addE('relation').property('var',"").property('val',"").property('min',"-1").property('max',"-1").from('sal25').to('dec45').
addE('relation').property('var',"").property('val',"").property('min',"-1").property('max',"-1").from('sal18').to('dec18').
addE('relation').property('var',"").property('val',"").property('min',"-1").property('max',"-1").from('sal30').to('dec30') 

lt, gt, inside, between 谓词存在问题。它只接受数字而不接受任何计算为数字的东西。

g.inject(['val1':10,'val2':15]).as('data').V().
where(select('data').select('val1').is(lt(select('data').values('val2'))))

上面的查询失败Cannot compare '10' (Integer) and '[SelectOneStep(last,data), PropertiesStep([val2],value)]'...由于这个问题下面的查询也失败了。

g.withSack(0).inject(['age':45,'source':'job']).as('data').
V().hasLabel('begin').
    repeat(outE().as('e').where(select('data').select(select('e').values('var')).is(eq(select('e').values('val')).or(inside(select('e').values('min'),select('e').values('max'))))).inV().sack(sum).by('score')).
    until(hasLabel('decision')).project('final_score','path').by(sack()).by(path())

如果这个问题可以用不同的方式建模以达到相同的输出分数,请告诉我

感谢您的宝贵时间。

【问题讨论】:

Tinkerpop 在“lt, gt, inside, between” predicate 中比较不同数据类型(字符串和整数)时抛出错误。在像 Neo4j 这样的情况下,它应该评估为 false。不确定 AWS Neptune、Janus 图、Cosmos DB 或其他在不匹配数据类型比较方面的表现 对于启用了 Apache TinkerPop 的图,通常不认为具有相同键名但值不同类型(例如整数和字符串)的属性是一种好的做法。即使在这种情况下返回 false 也不理想(实际上它确实不正确),例如在 Groovy 中,'a' < 1false'a' > 1true。最好对图中的数据进行归一化处理。 没错,相同的键名具有相同的数据类型是有意义的。 【参考方案1】:

我已将输入 JSON 转换为列表。此列表中元素的顺序很重要。它决定了遍历的级别 比较列表中的哪个元素。

 g.withSack(0).
  inject(["age", 45, "income_source", "job"]).as("input").

# initialized sack and input List

  V().hasLabel("begin").
  outE().as('a').local(and(
          select("input").unfold().range(0, 1).as("temp").
              select("a").values("var").where(eq("temp")), # FILTER property "var"

          select("input").unfold().range(1, 2).as("temp").
              select("a").values("max").where(gte("temp")).
              select("a").values("min").where(lte("temp")))). # FILTER by age from input.

  inV().sack(sum).by("score").
  outE().as("b").local(and(
      select("input").unfold().range(2, 3).as("temp").
          select("b").values("var").where(eq("temp")), # FILTER property "var"

      select("input").unfold().range(3, 4).as("temp").
          select("b").values("val").where(eq("temp")))). # FILTER property val 

  inV().sack(sum).by("score").
  out().sack(sum).by("score").
  sack()
             

【讨论】:

这工作正常,但有一个问题。当图形很大(40-50 个顶点),json 有 30-40 个键时,保持输入顺序可能真的很困难,而且图形的任何更改(修改顶点之间的路径)也需要更改查询。【参考方案2】:

您可以将inject 映射到 Gremlin 查询中,该查询与您的 JSON 文档的形状基本相同。查询第一部分的基本构建块如下所示,我使用您的数据和 TinkerGraph 对其进行了测试。

gremlin> g.inject(['age':45,'source':'job']).as('data').
......1>   V().hasLabel('begin').
......2>   outE().as('e1').
......3>   where(gte('e1')).
......4>     by(select('data').select('age')).
......5>     by('min').
......6>   where(lte('e1')).
......7>     by(select('data').select('age')).
......8>     by('max').
......9>   valueMap()  

==>[min:19,max:48,var:age] 

下一步是找到带有job 标签的边。

gremlin> g.inject(['age':45,'source':'job']).as('data').
......1>   V().hasLabel('begin').
......2>   outE().as('e1').
......3>   where(gte('e1')).
......4>     by(select('data').select('age')).
......5>     by('min').
......6>   where(lte('e1')).
......7>     by(select('data').select('age')).
......8>     by('max').
......9>   inV().
.....10>   outE().as('e2').
.....11>   where(eq('e2')).
.....12>     by(select('data').select('source')).
.....13>     by('val').valueMap()

==>[val:job,var:income_source]     

我们现在需要做的就是遍历最后一个节点并计算总和。

gremlin> g.withSack(0).
......1>   inject(['age':45,'source':'job']).as('data').
......2>   V().hasLabel('begin').
......3>   outE().as('e1').
......4>   where(gte('e1')).
......5>     by(select('data').select('age')).
......6>     by('min').
......7>   where(lte('e1')).
......8>     by(select('data').select('age')).
......9>     by('max').
.....10>   inV().
.....11>   sack(sum).
.....12>     by('score').
.....13>   outE().as('e2').
.....14>   where(eq('e2')).
.....15>     by(select('data').select('source')).
.....16>     by('val').
.....17>   inV().
.....18>   sack(sum).
.....19>     by('score').
.....20>   out().
.....21>   sack(sum).
.....22>     by('score').
.....23>   sack() 

==>47 

【讨论】:

这在 Gremlify gremlify.com/k0auxbd8rim 中有一些问题。然而,对于大 json(约 50 个键),计算总和的查询可能会变得很长。我认为在 gremlin 中完成它不会像在 neo4j 中那样简单。在 neo4j 中,它类似于 ` WITH json_object as object MATCH p = (:beg)-[*]->(final:decision) WHERE ALL (r in Relations(p) WHERE (r.val=object[r.var ]) OR (r.min 如果您可以将该信息添加到原始问题中,将会很有帮助。您应该能够在上面的示例中添加一个repeat 步骤,以将树遍历到任意深度。 通过您的查询,我可以构建类似的东西,但受到 Tinkerpop 限制,在有关它的问题中更新它。

以上是关于Gremlin 中的决策树查询的主要内容,如果未能解决你的问题,请参考以下文章

scikit学习决策树导出graphviz - 决策树中的错误类名

决策树是啥东东?

全面解析Apache Spark中的决策树

机器学习之手写决策树以及sklearn中的决策树及其可视化

算法干货----决策树算法中的熵与信息增益

决策树系列决策树基础