# Name Finder

## Named Entity Recognition

The Name Finder can detect named entities and numbers in text. To be able to detect entities the Name Finder needs a model. The model is dependent on the language and entity type it was trained for. The OpenNLP projects offers a number of pre-trained name finder models which are trained on various freely available corpora. They can be downloaded at our model download page. To find names in raw text the text must be segmented into tokens and sentences. A detailed description is given in the sentence detector and tokenizer tutorial. It is important that the tokenization for the training data and the input text is identical.

### Name Finder Tool

The easiest way to try out the Name Finder is the command line tool. The tool is only intended for demonstration and testing. Download the English person model and start the Name Finder Tool with this command:

    $ opennlp TokenNameFinder en-ner-person.bin

The name finder now reads a tokenized sentence per line from stdin, an empty line indicates a document boundary and resets the adaptive feature generators. Just copy this text to the terminal:

    Pierre Vinken , 61 years old , will join the board as a nonexecutive director Nov. 29 .
    Mr . Vinken is chairman of Elsevier N.V. , the Dutch publishing group .
    Rudolph Agnew , 55 years old and former chairman of Consolidated Gold Fields PLC , was named
    	a director of this British industrial conglomerate .

the name finder will now output the text with markup for person names:

    <START:person> Pierre Vinken <END> , 61 years old , will join the board as a nonexecutive director Nov. 29 .
    Mr . <START:person> Vinken <END> is chairman of Elsevier N.V. , the Dutch publishing group .
    <START:person> Rudolph Agnew <END> , 55 years old and former chairman of Consolidated Gold Fields PLC ,
    	was named a director of this British industrial conglomerate .

### Name Finder API

To use the Name Finder in a production system it is strongly recommended to embed it directly into the application instead of using the command line interface. First the name finder model must be loaded into memory from disk or another source. In the sample below it is loaded from disk.

    try (InputStream modelIn = new FileInputStream("en-ner-person.bin")){
      TokenNameFinderModel model = new TokenNameFinderModel(modelIn);
    }

There is a number of reasons why the model loading can fail:

- Issues with the underlying I/O

- The version of the model is not compatible with the OpenNLP version

- The model is loaded into the wrong component, for example a tokenizer model is loaded with TokenNameFinderModel class.

- The model content is not valid for some other reason

After the model is loaded the NameFinderME can be instantiated.

    NameFinderME nameFinder = new NameFinderME(model);

The initialization is now finished and the Name Finder can be used. As of OpenNLP 3.0.0 the NameFinderME class is thread safe: a single instance can be shared across multiple threads, which is the recommended deployment pattern because the underlying model is held only once in memory. The legacy ThreadSafeNameFinderME wrapper is retained for backward compatibility and is now deprecated. Note that the per-document adaptive feature generator state is cleared per thread by clearAdaptiveData, so each thread should process its own document stream end-to-end before invoking clearAdaptiveData. The input text should be segmented into documents, sentences and tokens. To perform entity detection an application calls the find method for every sentence in the document. After every document clearAdaptiveData must be called to clear the adaptive data in the feature generators. Not calling clearAdaptiveData can lead to a sharp drop in the detection rate after a few documents. The following code illustrates that:

    for (String document[][] : documents) {
      for (String[] sentence : document) {
        Span nameSpans[] = nameFinder.find(sentence);
        // do something with the names
      }
      nameFinder.clearAdaptiveData()
    }

the following snippet shows a call to find

    String[] sentence = new String[]{
        "Pierre",
        "Vinken",
        "is",
        "61",
        "years"
        "old",
        "."
        };

    Span[] nameSpans = nameFinder.find(sentence);

The nameSpans arrays contains now exactly one Span which marks the name Pierre Vinken. The elements between the start and end offsets are the name tokens. In this case the start offset is 0 and the end offset is 2. The Span object also knows the type of the entity. In this case it is person (defined by the model). It can be retrieved with a call to Span.getType(). Additionally to the statistical Name Finder, OpenNLP also offers a dictionary and a regular expression name finder implementation.

#### Using an ONNX Model

Using an ONNX model is similar, except we will utilize the `NameFinderDL` class instead. You must provide the path to the model file and the vocabulary file to the name finder. (There is no need to load the model as an InputStream as in the previous example.) The name finder requires a tokenized list of strings as input. The output will be an array of spans.

    File model = new File("/path/to/model.onnx");
    File vocab = new File("/path/to/vocab.txt");
    Map<Integer, String> categories = new HashMap<>();
    String[] tokens = new String[]{"George", "Washington", "was", "president", "of", "the", "United", "States", "."};
    NameFinderDL nameFinderDL = new NameFinderDL(model, vocab, false, getIds2Labels());
    Span[] spans = nameFinderDL.find(tokens);

For additional examples, refer to the `NameFinderDLEval` class.

## Name Finder Training

The pre-trained models might not be available for a desired language, can not detect important entities or the performance is not good enough outside the news domain. These are the typical reason to do custom training of the name finder on a new corpus or on a corpus which is extended by private training data taken from the data which should be analyzed.

### Training Tool

OpenNLP has a command line tool which is used to train the models available from the model download page on various corpora.

ONNX model support is not available through the command line tool. The models that can be trained using the tool are OpenNLP models. ONNX models are trained through deep learning frameworks and then utilized by OpenNLP.

The data can be converted to the OpenNLP name finder training format. Which is one sentence per line. Some other formats are available as well. The sentence must be tokenized and contain spans which mark the entities. Documents are separated by empty lines which trigger the reset of the adaptive feature generators. A training file can contain multiple types. If the training file contains multiple types the created model will also be able to detect these multiple types.

Sample sentence of the data:

    <START:person> Pierre Vinken <END> , 61 years old , will join the board as a nonexecutive director Nov. 29 .
    Mr . <START:person> Vinken <END> is chairman of Elsevier N.V. , the Dutch publishing group .

The training data should contain at least 15000 sentences to create a model which performs well. Usage of the tool:

    $ opennlp TokenNameFinderTrainer
    Usage: opennlp TokenNameFinderTrainer[.evalita|.ad|.conll03|.bionlp2004|.conll02|.muc6|.ontonotes|.brat] \
    [-featuregen featuregenFile] [-nameTypes types] [-sequenceCodec codec] [-factory factoryName] \
    [-resources resourcesDir] [-type typeOverride] [-params paramsFile] -lang language \
    -model modelFile -data sampleData [-encoding charsetName]

    Arguments description:
            -featuregen featuregenFile
                    The feature generator descriptor file
            -nameTypes types
                    name types to use for training
            -sequenceCodec codec
                    sequence codec used to code name spans
            -factory factoryName
                    A sub-class of TokenNameFinderFactory
            -resources resourcesDir
                    The resources directory
            -type typeOverride
                    Overrides the type parameter in the provided samples
            -params paramsFile
                    training parameters file.
            -lang language
                    language which is being processed.
            -model modelFile
                    output model file.
            -data sampleData
                    data to be used, usually a file name.
            -encoding charsetName
                    encoding for reading and writing text, if absent the system default is used.

It is now assumed that the english person name finder model should be trained from a file called 'en-ner-person.train' which is encoded as UTF-8. The following command will train the name finder and write the model to en-ner-person.bin:

    $ opennlp TokenNameFinderTrainer -model en-ner-person.bin -lang en -data en-ner-person.train -encoding UTF-8

The example above will train models with a pre-defined feature set. It is also possible to use the -resources parameter to generate features based on external knowledge such as those based on word representation (clustering) features. The external resources must all be placed in a resource directory which is then passed as a parameter. If this option is used it is then required to pass, via the -featuregen parameter, an XML custom feature generator which includes some clustering features shipped with the TokenNameFinder. Currently, three formats of clustering lexicons are accepted:

- Space separated two column file specifying the token and the cluster class as generated by toolkits such as word2vec.

- Space separated three column file specifying the token, clustering class and weight as such as Clark's clusters.

- Tab separated three column Brown clusters as generated by Liang's toolkit.

Additionally it is possible to specify the number of iterations, the cutoff and to overwrite all types in the training data with a single type. Finally, the -sequenceCodec parameter allows to specify a BIO (Begin, Inside, Out) or BILOU (Begin, Inside, Last, Out, Unit) encoding to represent the Named Entities. An example of one such command would be as follows:

    $ opennlp TokenNameFinderTrainer -featuregen brown.xml -sequenceCodec BILOU -resources clusters/ \
    -params PerceptronTrainerParams.txt -lang en -model ner-test.bin -data en-train.opennlp -encoding UTF-8

### Training API

To train the name finder from within an application it is recommended to use the training API instead of the command line tool. Basically three steps are necessary to train it:

- The application must open a sample data stream

- Call the NameFinderME.train method

- Save the TokenNameFinderModel to a file

The three steps are illustrated by the following sample code:

    TokenNameFinderFactory factory = TokenNameFinderFactory.create(null, null, Collections.emptyMap(), new BioCodec());
    File trainingFile = new File("en-ner-person.train");
    ObjectStream<String> lineStream =
    				new PlainTextByLineStream(new MarkableFileInputStreamFactory(trainingFile), StandardCharsets.UTF_8);

    TokenNameFinderModel trainedModel;
    try (ObjectStream<NameSample> sampleStream = new NameSampleDataStream(lineStream)) {
    	trainedModel = NameFinderME.train("eng", "person", sampleStream, TrainingParameters.defaultParams(), factory);
    }

    File modelFile = new File("en-ner-person.bin");
    try (OutputStream modelOut = new BufferedOutputStream(new FileOutputStream(modelFile))) {
    	trainedModel.serialize(modelOut);
    }

### Custom Feature Generation

OpenNLP defines a default feature generation which is used when no custom feature generation is specified. Users which want to experiment with the feature generation can provide a custom feature generator. Either via an API or via a xml descriptor file.

#### Feature Generation defined by API

The custom generator must be used for training and for detecting the names. If the feature generation during training time and detection time is different the name finder might not be able to detect names. The following lines show how to construct a custom feature generator

    AdaptiveFeatureGenerator featureGenerator = new CachedFeatureGenerator(
    		 new AdaptiveFeatureGenerator[]{
    			 new WindowFeatureGenerator(new TokenFeatureGenerator(), 2, 2),
    			 new WindowFeatureGenerator(new TokenClassFeatureGenerator(true), 2, 2),
    			 new OutcomePriorFeatureGenerator(),
    			 new PreviousMapFeatureGenerator(),
    			 new BigramNameFeatureGenerator(),
    			 new SentenceFeatureGenerator(true, false),
    			 new BrownTokenFeatureGenerator(BrownCluster dictResource)
    		 });

which is similar to the default feature generator but with a BrownTokenFeature added. The javadoc of the feature generator classes explain what the individual feature generators do. To write a custom feature generator please implement the AdaptiveFeatureGenerator interface or if it must not be adaptive extend the FeatureGeneratorAdapter. The train method which should be used is defined as

    public static TokenNameFinderModel train(String languageCode, String type,
              ObjectStream<NameSample> samples, TrainingParameters trainParams,
              TokenNameFinderFactory factory) throws IOException

where the TokenNameFinderFactory allows to specify a custom feature generator. To detect names the model which was returned from the train method must be passed to the NameFinderME constructor.

    new NameFinderME(model);

#### Feature Generation defined by XML Descriptor

OpenNLP can also use a xml descriptor file to configure the feature generation. The descriptor file is stored inside the model after training and the feature generators are configured correctly when the name finder is instantiated. The following sample shows a xml descriptor which contains the default feature generator plus several types of clustering features:

    <featureGenerators cache="true" name="nameFinder">
      <generator class="opennlp.tools.util.featuregen.WindowFeatureGeneratorFactory">
        <int name="prevLength">2</int>
        <int name="nextLength">2</int>
        <generator class="opennlp.tools.util.featuregen.TokenClassFeatureGeneratorFactory"/>
      </generator>
      <generator class="opennlp.tools.util.featuregen.WindowFeatureGeneratorFactory">
        <int name="prevLength">2</int>
        <int name="nextLength">2</int>
        <generator class="opennlp.tools.util.featuregen.TokenFeatureGeneratorFactory"/>
      </generator>
      <generator class="opennlp.tools.util.featuregen.DefinitionFeatureGeneratorFactory"/>
      <generator class="opennlp.tools.util.featuregen.PreviousMapFeatureGeneratorFactory"/>
      <generator class="opennlp.tools.util.featuregen.BigramNameFeatureGeneratorFactory"/>
      <generator class="opennlp.tools.util.featuregen.SentenceFeatureGeneratorFactory">
        <bool name="begin">true</bool>
        <bool name="end">false</bool>
      </generator>
      <generator class="opennlp.tools.util.featuregen.WindowFeatureGeneratorFactory">
        <int name="prevLength">2</int>
        <int name="nextLength">2</int>
        <generator class="opennlp.tools.util.featuregen.BrownClusterTokenClassFeatureGeneratorFactory">
          <str name="dict">brownCluster</str>
        </generator>
      </generator>
      <generator class="opennlp.tools.util.featuregen.BrownClusterTokenFeatureGeneratorFactory">
        <str name="dict">brownCluster</str>
      </generator>
      <generator class="opennlp.tools.util.featuregen.BrownClusterBigramFeatureGeneratorFactory">
        <str name="dict">brownCluster</str>
      </generator>
      <generator class="opennlp.tools.util.featuregen.WordClusterFeatureGeneratorFactory">
        <str name="dict">word2vec.cluster</str>
      </generator>
      <generator class="opennlp.tools.util.featuregen.WordClusterFeatureGeneratorFactory">
        <str name="dict">clark.cluster</str>
      </generator>
    </featureGenerators>

The root element must be featureGenerators, each sub-element adds a feature generator to the configuration. The sample xml contains additional feature generators with respect to the API defined above.

The following table shows the supported feature generators (you must specify the Factory's FQDN):

Feature Generators

Feature Generator | Parameters
--- | ---
CharacterNgramFeatureGeneratorFactory | min and max specify the length of the generated character ngrams
DefinitionFeatureGeneratorFactory | none
DictionaryFeatureGeneratorFactory | dict is the key of the dictionary resource to use, and prefix is a feature prefix string
PreviousMapFeatureGeneratorFactory | none
SentenceFeatureGeneratorFactory | begin and end to generate begin or end features, both are optional and are boolean values
TokenClassFeatureGeneratorFactory | none
TokenFeatureGeneratorFactory | none
BigramNameFeatureGeneratorFactory | none
TokenPatternFeatureGeneratorFactory | none
POSTaggerNameFeatureGeneratorFactory | model is the file name of the POS Tagger model to use
WordClusterFeatureGeneratorFactory | dict is the key of the clustering resource to use
BrownClusterTokenFeatureGeneratorFactory | dict is the key of the clustering resource to use
BrownClusterTokenClassFeatureGeneratorFactory | dict is the key of the clustering resource to use
BrownClusterBigramFeatureGeneratorFactory | dict is the key of the clustering resource to use
WindowFeatureGeneratorFactory | prevLength and nextLength must be integers and specify the window size

Window feature generator can contain other generators.

## Evaluation

The built in evaluation can measure the named entity recognition performance of the name finder. The performance is either measured on a test dataset or via cross validation.

### Evaluation Tool

The following command shows how the tool can be run:

    $ opennlp TokenNameFinderEvaluator -model en-ner-person.bin -data en-ner-person.test -encoding UTF-8

    Precision: 0.8005071889818507
    Recall: 0.7450581122145297
    F-Measure: 0.7717879983140168

The command line interface does not support cross evaluation in the current version.

### Evaluation API

The evaluation can be performed on a pre-trained model and a test dataset or via cross validation. In the first case the model must be loaded and a NameSample ObjectStream must be created (see code samples above), assuming these two objects exist the following code shows how to perform the evaluation:

    TokenNameFinderEvaluator evaluator = new TokenNameFinderEvaluator(new NameFinderME(model));
    evaluator.evaluate(sampleStream);

    FMeasure result = evaluator.getFMeasure();
    System.out.println(result.toString());

In the cross validation case all the training arguments must be provided (see the Training API section above). To perform cross validation the ObjectStream must be resettable.

    InputStreamFactory dataIn = new MarkableFileInputStreamFactory(new File("en-ner-person.train"));
    ObjectStream<NameSample> sampleStream = new NameSampleDataStream(
        new PlainTextByLineStream(dataIn, StandardCharsets.UTF_8));
    TokenNameFinderCrossValidator evaluator = new TokenNameFinderCrossValidator("eng",
        null, TrainingParameters.defaultParams(), null, (TokenNameFinderEvaluationMonitor) null);
    evaluator.evaluate(sampleStream, 10);

    FMeasure result = evaluator.getFMeasure();
    System.out.println(result.toString());

## Named Entity Annotation Guidelines

Annotation guidelines define what should be labeled as an entity. To build a private corpus it is important to know these guidelines and maybe write a custom one. Here is a list of publicly available annotation guidelines:

- MUC6

- MUC7

- ACE

- CONLL 2002

- CONLL 2003