Repository: pykeen/pykeen
Issue: Predict tails results in KeyError 
Root cause: Validation Gap

**Describe the bug**
After training a model on fb15k237 dataset, I tried running a test with  `predict_tails`.

When using this function, I had a KeyError resulting from calling `predict_tails`, this is because the predict function calls this line `head_id = self.triples_factory.entity_to_id[head_label]` which is fetching the dictionary entry for the entity, id pair. 

This happened for `/m/05hyf`in the test set of fb15k237 dataset after training and just calling the predict function. Should a keyerror happen if the key is not found? 

**To Reproduce**
Steps to reproduce the behavior:
1. Run this code to train the model 
```python
from pykeen.triples.triples_factory import TriplesFactory
import pandas as pd
import torch
from pykeen.pipeline import pipeline
from numpy import asarray
from numpy import savetxt
import numpy as np

def main():
    result = pipeline(
        dataset= "fb15k237",
        model="DistMult",
        model_kwargs=dict(embedding_dim= 100),
        regularizer= "no",
        optimizer= "Adam",
        optimizer_kwargs=dict(lr=0.001),
        negative_sampler= "basic",
        negative_sampler_kwargs=dict(
            num_negs_per_pos= 500
        ),
        training_kwargs=dict(
            num_epochs=1,  # just to be fast
            batch_size=256,
        ),
    )
    model = result.model
```

2. 
```python
    tf= TriplesFactory(path='test.txt') #this is the file from https://github.com/ZhenfengLei/KGDatasets/tree/master/FB15k-237
    triples = tf.triples
    results = []	
    model.predict_tails("/m/05hyf", "/film/film_subject/films") #KeyError here
```
3. KeyError: '/m/05hyf'


**Expected behavior**
Runs and results in a dataframe of predictions.

**Environment (please complete the following information):**
 - OS: Linux
 - Python version: 3.6.5
 - Version of this software: 1.0.4
 - Versions of required Python packages: # run pip list | grep -Eiw 'torch|numpy|Click|click-default-group|tqdm'
   - Torch: 1.6.0
   - Numpy: 1.19.2
   - Click: 7.1.2
   - Click_default_group: 1.2.2
   - Tqdm: 4.49.0


Hi @PhaelIshall .

This is likely caused by a problem with the FB15k-237 dataset: It contains entities in test, which do not occur in any train triple. In our pre-defined splits, we drop such entities, since it is not exactly clear how to score triples with these entities. Thus, you cannot score triples with entities which did not occur in training set.

Moreover, you have to make sure that the triples factory which you used for training, and the one you use for inference use the same label-to-ID mapping. The easiest way is to use our pre-defined datasets, which take care of this, e.g. by

```python
dataset = Fb15k237()
result = pipeline(
    ...
    training_triples_factory=dataset.training,
    validation_triples_factory=dataset.validation,
    testing_triples_factory=dataset.testing,
)
tf = dataset.testing
```

Otherwise, make sure to provide the training factory's label-to-id mapping to the test one by:
```python
training = result.model.triples_factory
tf = TriplesFactory(path='test.txt', entity_to_id=training.entity_to_id, relation_to_id=training.relation_to_id)
```

This also has the effect that triples containing unknown labels are dropped (you will see a warning about this).


P.S.: There are method to infer representations for entities which did not exist at training time. Often you refer to these methods as "inductive learning methods" as opposed to transductive training where all entities are known at training time. Some approaches:  
* https://arxiv.org/abs/1911.06962 
* https://arxiv.org/abs/2005.00637
* https://arxiv.org/abs/2010.03496

@mberr Thank you for your answer. I have tried both methods and they still result in the same error (still KeyError at '/m/05hyf'). Is it possible that some entities that are not in training have not been dropped in the predefined splits? 

> 
> 
> @mberr Thank you for your answer. I have tried both methods and they still result in the same error (still KeyError at '/m/05hyf'). Is it possible that some entities that are not in training have not been dropped in the predefined splits?

Could you please verify that 
```python
"/m/05hyf" in training.entity_to_id.keys()  # should be True
```

If it is not true, you might have picked one of the entities not present in train. In that case, please try another one from `training.entity_to_id.keys()`.

@mberr This time I did not try to predict from that head/relationship and instead just iterated through the testing set triples as you showed and made a prediction for every pair there. It seems that this pair is in the pykeen test set and not the training set. My understanding was that there are no such triples in the test set split in pykeen? 

@PhaelIshall Could you maybe share (again) a (minimal) code example to reproduce the error you are getting now?

@mberr Sorry, here is the code I am running that results in an error when used trying to predict on the test set. 

```python
dataset = Fb15k237()
result = pipeline(
    ...
    training_triples_factory=dataset.training,
    validation_triples_factory=dataset.validation,
    testing_triples_factory=dataset.testing,
)
tf = dataset.testing
triples = tf.triples
results = []	
for t in range(len(triples)):
    predictions = model.predict_tails(str(triples[t][0]), str(triples[t][1])).to_numpy()  # predict the tail from the head and relation
    results.append(predictions[0])  # to only add the top-1 prediction
```
Results in the same error `KeyError: '/m/05hyf'`

The only way to prevent this is to add an if statement in the loop as you mentioned 

```python
for t in range(len(triples)):
    if triples[t][0] in training.entity_to_id.keys():  # check that the head is in the training set
        predictions = model.predict_tails(str(triples[t][0]), str(triples[t][1])).to_numpy()  # predict the tail from the head and relation
        results.append(predictions[0])  # to only add the top-1 prediction
```

This means that in the Pykeen splits for Fb15k237, the entities that are not in training are not dropped? 

Hi @PhaelIshall .

I just noticed that you use `triples` (label-based) and not `mapped_triples` (id-based). The filtering takes only place when _mapping_ the triples, i.e. transforming label-based to ID-based triples.

A quick fix for your problem is the following modified code directly using the ID-based triples:
```python
dataset = Fb15k237()
result = pipeline(
    ...
    training_triples_factory=dataset.training,
    validation_triples_factory=dataset.validation,
    testing_triples_factory=dataset.testing,
)
model = result.model
results = []
id_to_label = invert_mapping(mapping=dataset.training.entity_to_id)
for triple in dataset.testing.mapped_triples:
    # predict the tail from the head and relation
    scores = model.predict_scores_all_tails(hr_batch=triple[None, :2].to(model.device))
    # keep only top-1
    top_scores, top_ids = scores.topk(k=1)
    # send to cpu
    top_scores, top_ids = top_scores.item(), top_ids.item()
    # translate to label
    results.append((top_ids, id_to_label[top_ids], top_scores))
```

I agree that this is an annoying inconsistency, and we either need to update the description of `predict_tails`, or make sure that the `triples` attribute gets filtered as well.

I am tagging @cthoyt here, since he has been involved in `predict_tails`.