-
Notifications
You must be signed in to change notification settings - Fork 0
Example: Lives & Knows
A very simple project demonstrating how to use the psl-infrastructure and psl-ragviewer packages. This demo project is based on a dummy PSL problem for inferring whether a pair of people knows each other based on information on whether they share the same address.
We have two predicates:
-
LivesPred:
Lives(P,L)
-- "Person P lives at address L" -
KnowsPred:
Knows(P1,P2)
-- "Person P1 knows person P2"
Both predicate classes contain templates for describing a ground atom as a noun phrase ("Alice knowing Bob") or a sentence ("Alice probably knows Bob").
The SampleConstantRenderer provides pretty-print functions for some of the atom arguments.
We have one weighted logical rule and one arithmetic constraint:
-
LivesToKnowsRule:
1.0: Lives(P1,L) & Lives(P2,L) & (P1 != P2) -> Knows(P1,P2)
-- "If there is evidence that two people live at the same address, this makes it more likely that they know each other." -
KnowsSymmetryConstraint:
Knows(P1,P2) = Knows(P2,P1) .
-- "The knows relationship is symmetric."
Each rule overrides the method generateExplanation
to provide explanation templates that match the rule logic more naturally than the default explanations might.
Each custom rule class needs to extend one of the following classes:
-
TalkingLogicalRule
: weighted logical rules -
TalkingLogicalConstraint
: logical constraints -
TalkingArithmeticRule
: weighted arithmetic rules -
TalkingArithmeticConstraint
: arithmetic constraints
In the SamplePslProblem, we add the two predicates to our problem set-up:
public void declarePredicates() {
declareClosedPredicate(new LivesPred());
declareOpenPredicate(new KnowsPred());
}
Lives
is a closed predicate since we know all atom values in advance.
Knows
is an open predicate since we want to infer the atom values.
If we had any predicates with some known atom values and some unknown ones, those predicates would also be added as open predicates.
If we wanted to add a predicate that doesn't have its own TalkingPredicate class, we could do so via declareClosedPredicate("MyPred", 2);
(where 2
is the predicate's arity).
We also add the PSL rules:
@Override
public void addInteractionRules() {
addRule(new LivesToKnowsRule(this));
addRule(new KnowsSymmetryConstraint(this));}
To add rules that aren't defined as TalkingRule classes, you can call addRule("MyRule", "MyPred(A,B) = MyPred(B,A).");
.
The SampleIdeaGenerator contains the code for grounding the atoms.
In our example case, the idea generator reads a file containing information on the Lives
atoms.
We add those atoms and their fixed values to the problem's inference scope as observations (value
is a double; person
and address
are strings):
super.pslProblem.addObservation(LivesPred.NAME, value, person, address);
We then also add atoms for every pair of possible arguments for Knows
, as targets whose values we want to infer (person1
and person2
are strings):
super.pslProblem.addTarget(KnowsPred.NAME, person1, person2);
The code for preparing and running the inference is in EntryClass.java:
We first create the classes in charge of managing the database partitions and saving the inference results:
ProblemManager problemManager = ProblemManager.defaultProblemManager();
DatabaseManager dbManager = problemManager.getDbManager();
String problemId = problemManager.getNewId("SampleProblem");
Then declare the rules and predicates of our PSL problem and generate the ground atoms:
SamplePslProblem problem = new SamplePslProblem(dbManager, problemId);
SampleIdeaGenerator ideaGen = new SampleIdeaGenerator(problem);
ideaGen.generateAtoms("/examples/livesknows/addresses.tsv");
We then run the inference:
problemManager.registerProblem(problemId, problem);
InferenceLogger logger = new InferenceLogger();
problemManager.preparePartitionsAndRun(Collections.singletonList(problem), logger);
...and extract the results:
InferenceResult result = problemManager.getLastResult(problemId);
RuleAtomGraph rag = result.getRag();
In the console, we can view the rule-atom graph and the pressure that each ground rule exerts on each of its associated atoms:
rag.printToStream(System.out);
We can, for instance, see the grounding #10 of the KnowsSymmetry rule puts both upward and downward pressure on the value of Knows(Alice, Bob)
, and that grounding #19 of LivesToKnows puts upward pressure on Knows(Alice, Bob)
but downward pressure on Lives(Alice, 18)
and Lives(Bob, 18)
:
Knows(Alice, Bob) =~ KnowsSymmetry[10]
...
Knows(Alice, Bob) +~ LivesToKnows[19]
...
Lives(Alice, 18) -~ LivesToKnows[19]
...
Lives(Bob, 18) -~ LivesToKnows[19]
We can also look up the atom values after the inference:
result.printInferenceValues();
Knows(Alice, Bob) 0.8520649075508118
Knows(Alice, Charlie) 0.7276508808135986
...
To visually explore the rule-atom graph and to read the verbalized explanations for the atom values, run the EntryClass in the examples.livesknows subpackage of psl-ragviewer.