这里将引导您完成Simple Acquaintances示例的Groovy版本。

一、建立示例项目

  首先,确保您的系统满足先决条件。然后克隆psl-examples存储库:

git clone https://github.com/linqs/psl-examples.git

二、运行

  进入简单groovy示例的根目录:

cd psl-examples/simple-acquaintances/groovy

  在根目录下运行(这里特别容易失败):

mvn compile
mvn eclipse:eclipse     //把项目编译成eclipse版本

  通过eclipse里的maven导入项目后,无报错即可运行,结果如下:

0    [main] INFO  org.linqs.psl.examples.simpleacquaintances.Run  - Defining model rules
139  [main] INFO  org.linqs.psl.examples.simpleacquaintances.Run  - Loading data into database
171  [main] INFO  org.linqs.psl.examples.simpleacquaintances.Run  - Starting inference
389  [main] INFO  org.linqs.psl.application.inference.LazyMPEInference  - Beginning inference round 1.
488  [main] INFO  org.linqs.psl.reasoner.admm.ADMMReasoner  - Optimization completed in 411 iterations. Primal res.: 0.028260373, Dual res.: 9.052008E-4
488  [main] INFO  org.linqs.psl.application.inference.LazyMPEInference  - Inference round 1 complete.
566  [main] INFO  org.linqs.psl.application.inference.LazyMPEInference  - Beginning inference round 2.
566  [main] INFO  org.linqs.psl.reasoner.admm.ADMMReasoner  - Optimization completed in 1 iterations. Primal res.: NaN, Dual res.: NaN
566  [main] INFO  org.linqs.psl.application.inference.LazyMPEInference  - Inference round 2 complete.
584  [main] INFO  org.linqs.psl.examples.simpleacquaintances.Run  - Inference complete
631  [main] INFO  org.linqs.psl.examples.simpleacquaintances.Run  - Accuracy: 0.400000, F1: 0.280000, Positive Class Precision: 0.466667, Positive Class Recall: 0.200000, Negative Class Precision: 0.377778, Negative Class Recall: 0.680000

  要查看示例的输出,请检查groovy下的inferred-predicates/KNOWS.txt文件:

inferred-predicates/KNOWS.txt

  应该看到一些输出,如:

'Arti' 'Ben'  0.48425865173339844
'Arti' 'Steve'       0.5642937421798706
< ... 48 rows omitted for brevity ...>
'Jay'  'Dhanya'      0.4534565508365631
'Alex' 'Dhanya'      0.48786869645118713

  输出的确切顺序可能会改变,为简洁起见,省略了一些行。
  现在我们已经运行了示例,让我们看看示例中唯一的源文件:

src/main/java/org/linqs/psl/examples/simpleacquaintances/Run.groovy。

三、Groovy源码文件解析

1. 配置(Configuration)

  在构造函数中,您可以看到创建的ConfigBundle:

config = ConfigManager.getManager().getBundle("simpleacquaintances");

  请注意,传入的字符串getBundle()是此配置集的前缀。这意味着特定于此程序的所有配置键都应该以前缀simpleacquaintances为前缀。

2. 谓词定义(Defining Predicates)

  该definePredicates()方法为我们的示例定义了三个谓词:

model.add predicate: "Lived", types: [ConstantType.UniqueStringID, ConstantType.UniqueStringID]; 
model.add predicate: "Likes", types: [ConstantType.UniqueStringID, ConstantType.UniqueStringID]; 
model.add predicate: "Knows", types: [ConstantType.UniqueStringID, ConstantType.UniqueStringID];

  这里的每个谓词都有两个唯一的属性为字符串标识符的作为参数,请注意,对于唯一标识符(Unique),ConstantType.UniqueStringID和ConstantType.UniqueIntID都是可用的。值得一提的是,拥有整数标识符通常需要在用户方面进行更多预处理,但能获得更好的性能。

谓词释义:
  • Lived 表示一个人住在特定的位置。例如:Lived(Sammy,SantaCruz)表示Sammy住在Santa Cruz。
  • Likes 表示一个人喜欢某事的程度。例如:Likes(Sammy,Hiking)会表明Sammy喜欢徒步旅行的程度。
  • Knows 表明一个人认识其他人。例如:Knows(Sammy,Jay)会表示Sammy和Jay相互认识。

3. 规则定义(Defining Rules)

  该defineRules()方法为该示例定义了六个规则。有些页面涵盖了PSL 规则规范和Groovy中的规则规范。我们将讨论以下两条规则:

model.add( rule: "20: Lived(P1, L) & Lived(P2, L) & (P1 != P2) -> Knows(P1, P2) ^2" ); 
model.add( rule: "5: !Knows(P1, P2) ^2" );

  第一条规则可以理解为 “ 如果P1和P2是不同的两个人,并且两者都住在同一个地方L,那么他们彼此认识 ” 。此规则需要注意的一些要点是:

  • 变量L在两个Lived原子中重复使用,因此必须引用相同的位置L。
  • (P1 != P2)指的是P1和P2是不同的人(不同的独特ID)。

  第二条规则是作为先验的特殊规则。请注意这条规则与其他所有规则的含义并不相同。相反,此规则可以理解为“默认情况下,人们彼此不了解”。因此,该项目将首先相信没有人相互了解,并且这个先验的观念将被其他定义的规则作为证据来克服。

4. 加载数据(Loading Data)

  该loadData()方法将数据从data目录中的文件加载到PSL正在使用的虚拟数据存储库中。为了便于理解,我们来查看两个文件:

Inserter inserter = dataStore.getInserter(Lived, obsPartition); 
inserter.loadDelimitedData(Paths.get(DATA_PATH, "lived_obs.txt").toString()); 
inserter = dataStore.getInserter(Likes, obsPartition); 
inserter.loadDelimitedDataTruth(Paths.get(DATA_PATH, "likes_obs.txt").toString());

  两个部分都使用一个加载数据Inserter。两个调用之间的主要区别在于第二个调用正在加载真值,而第一个调用默认真值是1。
  如果我们查看文件内容,我们会看到以下行:


../data/lives_obs.txt

Jay    Maryland
Jay    California

../data/likes_obs.txt

Jay    Machine Learning  1
Jay    Skeeball 0.8

  在lives_obs.txt,没有必要使用真值,因为生活在某个地方是一个离散的行为。你要么住在那里,要么没有。然而,喜欢某些东西为连续的软真值会更符合常理。如:Jay有 100%的可能会喜欢机器学习,但他只有80%的可能喜欢Skeeball。

5. 分区(Partitions)

  在PSL中,使用分区来组织数据。分区只不过是数据的容器,但我们使用它们将特定的数据块保存在一起或分开。例如,如果我们正在运行结果评估模块,我们必须确保在训练中不使用我们的测试分区。
  PSL用户通常在至少三个不同的分区中组织他们的数据(在本例中您可以看到所有这些分区):
  •obsPartition 在这个例子中称为观察分区:在这个分区中我们放置了实际的观察数据。在这个例子中,我们把关于谁住在哪里,谁喜欢什么,谁知道谁在观察分区中的所有观察。
  •targetsPartition 在本例中称为目标分区:在这个分区中,我们放置了我们想要推断的值的原子。例如,如果我们想要推断Jay和Sammy相互认识的概率,那么我们会将原子Knows(Jay, Sammy)放入目标分区。
  •truthPartition 在这个例子中称为事实分区:在这个分区中,我们放置了实际值的数据,但不包括在我们的已知观察数据,目的用于评估模型推理效果。例如,如果我们知道Jay和Sammy确实相互了解,我们会将Knows(Jay, Sammy)事实分区放入真值1。

6. 运行推理(Running Inference)

  该runInference()方法处理我们加载的所有数据的运行推理。
  在我们进行推理之前,我们必须建立一个用于推理的数据库:

Database inferDB = dataStore.getDatabase(targetsPartition, [Lived, Likes] as Set, obsPartition);

  getDatabase()方法对于DataStore是获得一个数据库的正确方法。此方法至少需要两个参数:

  •允许此数据库写入的分区targetsPartition。在推理中,我们将原子的推断真值写入目标分区,因此我们需要将其打开以进行写入。
  •要在写入分区中关闭的一组分区obsPartition。即使我们将值写入写分区,我们可能只有一些谓词,我们实际上想要推断它们的值。此参数允许您关闭那些没有更改的谓词(封闭谓词)。最后,getDatabase()获取要包含在此数据库中的任意数量的只读分区。在我们的示例中,我们希望在进行推理时包含我们的观察结果。

  接下来我们准备推理部分:

InferenceApplication inference = new MPEInference(model, inferDB);
inference.inference();
inference.close();
inferDB.close();

  对于MPEInference构造函数,我们提供我们的模型和数据库来推断。要查看结果,我们需要查看目标分区。

7. 输出(Output)

  该方法writeOutput()处理打印(输出)出推断的结果。此方法有两个关键行:

Database resultsDB = ds.getDatabase(targetsPartition);
...
for (GroundAtom atom : resultsDB.getAllGrondAtoms(Knows)) {

  第一行得到一个新的数据库,我们可以从中获取原子。请注意,我们把targetsPartition作为写分区传入,但实际上我们只是从它读取已经推理得到的结果数据。
  第二行使用Queries来迭代此数据库中的所有为 Knows 的原子。

8. 评估(Evaluation)

  最后,用evalResults()方法评判我们的模型的效果。本DiscreteEvaluator类提供基本的工具来比较两个分区。在这个例子中,我们将目标分区与真值分区进行比较。

private void evalResults(Partition targetsPartition, Partition truthPartition) {
        Database resultsDB = dataStore.getDatabase(targetsPartition);
        Database truthDB = dataStore.getDatabase(truthPartition, [Knows] as Set);

        Evaluator eval = new DiscreteEvaluator();
        eval.compute(resultsDB, truthDB, Knows);
        log.info(eval.getAllStats());

        resultsDB.close();
        truthDB.close();
}