概率软逻辑(PSL,Probabilistic soft logic)示例演练和模块解释
这里将引导您完成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类提供基本的工具来比较两个分区。在这个例子中,我们将目标分区与真值分区进行比较。
1 private void evalResults(Partition targetsPartition, Partition truthPartition) { 2 Database resultsDB = dataStore.getDatabase(targetsPartition); 3 Database truthDB = dataStore.getDatabase(truthPartition, [Knows] as Set); 4 5 Evaluator eval = new DiscreteEvaluator(); 6 eval.compute(resultsDB, truthDB, Knows); 7 log.info(eval.getAllStats()); 8 9 resultsDB.close(); 10 truthDB.close(); 11 }