dig-team / amie

Mavenized AMIE+Typing

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

amie.eval.rules.Evaluator is buggy for rules that predict constants

falcaopetri opened this issue · comments

Description

amie.eval.rules.Evaluator accepts a training KB, a target KB, and a list of pairs <rule, prediction> (generated by amie.eval.rules.Predictor), and classifies the prediction in 4 cases:

  • 0, if the prediction is in the target dataset.
  • 1, if it contradicts a functional constraint in the training dataset.
  • 2, if it contradicts a functional constraint in the target dataset.
  • 3, otherwise (i.e., the prediction require manual inspection).

Consider a rule that predicts a const, i.e., => r(?a, CONST), and generates predictions such as r(A, CONST). Evaluator will fail to classify the prediction as cases 1 or 2, and will always classify it as case 0 or 3, i.e., it will unnecessarily throw predictions to manual inspection.

Reproducing

Train KB:

$ cat train_kb.txt
<s1>	<relation>	<value1>
<s2>	<relation>	<value3>
<s3>	<relation>	<value4>

Target KB:

$ cat target_kb.txt
<s1>	<relation>	<value1>
<s1>	<relation>	<value2>
<s2>	<relation>	<value3>
<s3>	<relation>	<value4>

Hand-crafted "mined" rules:

$ cat crafted_rules.txt
?a  <relation>  <value1>   => ?a  <relation>  <value2>
?a  <relation>  <value1>   => ?a  <relation>  <value3>

Executing Predictor generates:

$ java -cp amie3.jar amie.rules.eval.Predictor train_kb.txt 5 1 crafted_rules.txt > preds.txt
$ cat preds.txt
Loading files... 
  Starting train_kb.txt
  Finished train_kb.txt, still running: 0
Loaded 1 facts in 12 ms using 0 MB
?a  <relation>  <value1>   => ?a  <relation>  <value2>  <s1>    <relation>      <value2>
?a  <relation>  <value1>   => ?a  <relation>  <value3>  <s1>    <relation>      <value3>

And then using Evaluator:

$ java -cp amie3.jar amie.rules.eval.Evaluator preds.txt train_kb.txt target_kb.txt manual.out auto.out
$ cat manual.out
?a  <relation>  <value1>   => ?a  <relation>  <value3>  <s1>    <relation>      <value3>        ManualEvaluation
$ cat auto.out
?a  <relation>  <value1>   => ?a  <relation>  <value2>  <s1>    <relation>      <value2>        TargetSource    True

As far as I understand, this is wrong, since the prediction <s1> <relation> <value3> contradicts the functional constraint in the training dataset. Indeed, it should have being identified as case 1 here:
https://github.com/lajus/amie/blob/75323c6ed76f7576066ffff41d8bdb9e3ac1c112/rules/src/main/java/amie/rules/eval/Evaluator.java#L89-L102

The bug

The function https://github.com/lajus/amie/blob/75323c6ed76f7576066ffff41d8bdb9e3ac1c112/rules/src/main/java/amie/rules/eval/Evaluator.java#L76-L77

  1. creates the head variable equal to the rule's head,
  2. bounds the functional variable and;
  3. queries training and test for this triple.

If the rule's head is r(?a, ?b), we still have a variable after bounding. But if the rule's head is r(?a, CONST), we will try to query r(CONST1, CONST), which will return 0 if the predicted fact is false.


Fix

        public static int evaluate(Rule rule, 
                        int[] triple, KB training, KB target){
-               // TODO Auto-generated method stub
-               int[] head = rule.getHead();
-               int boundVariable = 0;
+               int[] head = new int[3];
+               head[0] = KB.map("?s");
+               head[1] = triple[1];
+               head[2] = KB.map("?o");
+
                int returnVal = 3;
                boolean relationIsFunctional = 
                                (rule.getFunctionalVariablePosition() == 0 && training.functionality(rule.getHead()[1]) >= 0.9) ||
                                (rule.getFunctionalVariablePosition() == 2 && training.inverseFunctionality(rule.getHead()[1]) >= 0.84);
 
                int boundVarPos = rule.getFunctionalVariablePosition();
 
                //If we know something else about the triple, PCA says it is false
                if(target.count(triple) > 0){
                        //Bingo!
                        returnVal = 0;
                }else{
-                       boundVariable = head[boundVarPos];
                        head[boundVarPos] = triple[boundVarPos];
                        
                        //Here apply PCA on the most functional variable
                        if(training.count(head) > 0 && relationIsFunctional)
                                returnVal = 1;
                        else if(target.count(head) > 0 && relationIsFunctional)
                                returnVal = 2;
                        else
                                returnVal = 3;
-                       
-                       //Restore the head
-                       head[boundVarPos] = boundVariable;
                }

The following also seems to be a fix:

        public static int evaluate(Rule rule, 
                        int[] triple, KB training, KB target){
+              return evaluate(triple, training, target);

i.e., we could get rid of

https://github.com/lajus/amie/blob/75323c6ed76f7576066ffff41d8bdb9e3ac1c112/rules/src/main/java/amie/rules/eval/Evaluator.java#L76-L77

in favor of
https://github.com/lajus/amie/blob/75323c6ed76f7576066ffff41d8bdb9e3ac1c112/rules/src/main/java/amie/rules/eval/Evaluator.java#L24-L25

Note

I also noticed that the buggy evaluate() uses rule.getFunctionalVariablePosition(), which, as far as I debugged, will always be set to 0. The same seems true for when using AMIEParser. Is it true that a rule read from file will always have 0 as the functional variable position? Can this be a problem?

Hi,

Thank you very much for your bug report.

The problem is that AMIEParser only reads the atoms of a rule, whereas its functional variable is context-dependent (being the context a knowledge graph). I agree with you that for evaluating AMIE, we only need the method:

public static int evaluate(int[] triple, KB training, KB target)

However, one may want to test the precision of a rule for both positions of the functional variable. Since we do not need the entire rule for this purpose, I would propose to have a version of the method where we send the functional variable as argument.

public static int evaluate(int[] triple, KB training, KB target, boolean relationIsFunctional) {   
  ...  
}

public static int evaluate(int[] triple, KB training, KB target) {
        boolean relationIsFunctional = training.functionality(triple[1]) >= 0.9;
        return evaluate(triple, training, target, relationIsFunctional)
}

If you agree, do not hesitate to send a pull request. You can even pull request what you are proposing now and I can take care of the wrapping.

Thanks!
Luis