# Part-of-Speech Tagger

## Tagging

The Part of Speech Tagger marks tokens with their corresponding word type based on the token itself and the context of the token. A token might have multiple pos tags depending on the token and the context. The OpenNLP POS Tagger uses a probability model to predict the correct pos tag out of the tag set. To limit the possible tags for a token a tag dictionary can be used which increases the tagging and runtime performance of the tagger.

### POS Tagger Tool

The easiest way to try out the POS Tagger is the command line tool. The tool is only intended for demonstration and testing. Download the English maxent pos model and start the POS Tagger Tool with this command:

    $ opennlp POSTagger opennlp-en-ud-ewt-pos-1.3-2.5.4.bin

The POS Tagger now reads a tokenized sentence per line from stdin. Copy these two sentences to the console:

    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 .

The POS Tagger will now echo the sentences with pos tags to the console:

    Pierre_PROPN Vinken_PROPN ,_PUNCT 61_NUM years_NOUN old_ADJ ,_PUNCT will_AUX join_VERB the_DET board_NOUN as_ADP
    		a_DET nonexecutive_ADJ director_NOUN Nov._PROPN 29_NUM ._PUNCT
    Mr._PROPN Vinken_PROPN is_AUX chairman_NOUN of_ADP Elsevier_ADJ N.V._PROPN ,_PUNCT the_DET Dutch_PROPN publishing_VERB group_NOUN .

The tag set used by the English pos model is the Penn Treebank tag set .

### POS Tagger API

The POS Tagger can be embedded into an application via its API. First the pos 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("opennlp-en-ud-ewt-pos-1.3-2.5.4.bin"){
      POSModel model = new POSModel(modelIn);
    }

After the model is loaded the POSTaggerME can be instantiated.

    POSTaggerME tagger = new POSTaggerME(model);

The POS Tagger instance is now ready to tag data. It expects a tokenized sentence as input, which is represented as a String array, each String object in the array is one token. As of OpenNLP 3.0.0 POSTaggerME is thread safe and a single instance can be shared across threads; the legacy ThreadSafePOSTaggerME wrapper is retained for backward compatibility and is now deprecated.

The following code shows how to determine the most likely pos tag sequence for a sentence.

    String[] sent = new String[]{"Most", "large", "cities", "in", "the", "US", "had",
                                 "morning", "and", "afternoon", "newspapers", "."};
    String[] tags = tagger.tag(sent);

The tags array contains one part-of-speech tag for each token in the input array. The corresponding tag can be found at the same index as the token has in the input array. The confidence scores for the returned tags can be easily retrieved from a POSTaggerME with the following method call:

    double[] probs = tagger.probs();

The call to probs is stateful and will always return the probabilities of the last tagged sentence. When the tagger is shared across threads, the "last tagged sentence" is tracked per thread, so each thread must call tag before calling probs on that same thread. The probs method should only be called when the tag method was called before on the current thread, otherwise the behavior is undefined.

Some applications need to retrieve the n-best pos tag sequences and not only the best sequence. The topKSequences method is capable of returning the top sequences. It can be called in a similar way as tag.

    Sequence[] topSequences = tagger.topKSequences(sent);

Each Sequence object contains one sequence. The sequence can be retrieved via Sequence.getOutcomes() which returns a tags array and Sequence.getProbs() returns the probability array for this sequence.

## Training

The POS Tagger can be trained on annotated training material. The training material is a collection of tokenized sentences where each token has the assigned part-of-speech tag. The native POS Tagger training material looks like this:

    About_ADV 10_NUM Euro_PROPN ,_PUNCT I_PRON reckon._PUNCT
    That_PRON sounds_VERB good_ADJ ._PUNCT

Each sentence must be in one line. The token/tag pairs are combined with "_". The token/tag pairs are whitespace separated. The data format does not define a document boundary. If a document boundary should be included in the training material it is suggested to use an empty line.

The Part-of-Speech Tagger can either be trained with a command line tool, or via a training API.

### Training Tool

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

Usage of the tool:

    $ opennlp POSTaggerTrainer
    Usage: opennlp POSTaggerTrainer[.conllx] [-type maxent|perceptron|perceptron_sequence] \
                   [-dict dictionaryPath] [-ngram cutoff] [-params paramsFile] [-iterations num] \
                   [-cutoff num] -model modelFile -lang language -data sampleData \
                   [-encoding charsetName]

    Arguments description:
            -type maxent|perceptron|perceptron_sequence
                    The type of the token name finder model. One of maxent|perceptron|perceptron_sequence.
            -dict dictionaryPath
                    The XML tag dictionary file
            -ngram cutoff
                    NGram cutoff. If not specified will not create ngram dictionary.
            -params paramsFile
                    training parameters file.
            -iterations num
                    number of training iterations, ignored if -params is used.
            -cutoff num
                    minimal number of times a feature must be seen, ignored if -params is used.
            -model modelFile
                    output model file.
            -lang language
                    language which is being processed.
            -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.

The following command illustrates how an English part-of-speech model can be trained:

    $ opennlp POSTaggerTrainer -type maxent -model en-custom-pos-maxent.bin \
                               -lang en -data en-custom-pos.train -encoding UTF-8

### Training API

The Part-of-Speech Tagger training API supports the training of a new pos model. Basically three steps are necessary to train it:

- The application must open a sample data stream

- Call the 'POSTagger.train' method

- Save the POSModel to a file

The following code illustrates that:

    POSModel model = null;

    try {
      ObjectStream<String> lineStream = new PlainTextByLineStream(
      	new MarkableFileInputStreamFactory(new File("en-custom-pos-maxent.bin")), StandardCharsets.UTF_8);

      ObjectStream<POSSample> sampleStream = new WordTagSampleStream(lineStream);

      model = POSTaggerME.train("eng", sampleStream, TrainingParameters.defaultParams(), new POSTaggerFactory());
    } catch (IOException e) {
      e.printStackTrace();
    }

The above code performs the first two steps, opening the data and training the model. The trained model must still be saved into an OutputStream, in the sample below it is written into a file.

    try (OutputStream modelOut = new BufferedOutputStream(new FileOutputStream(modelFile))){
      model.serialize(modelOut);
    }

### Tag Dictionary

The tag dictionary is a word dictionary which specifies which tags a specific token can have. Using a tag dictionary has two advantages, inappropriate tags can not been assigned to tokens in the dictionary and the beam search algorithm has to consider fewer possibilities and can search faster.

The dictionary is defined in a xml format and can be created and stored with the POSDictionary class. Below is an example to train a custom model using a tag dictionary.

Sample POS Training material (file : en-custom-pos.train)

    It_PRON is_OTHER spring_PROPN season_NOUN. The_DET flowers_NOUN are_OTHER red_ADJ and_CCONJ yellow_ADJ ._PUNCT
    Red_NOUN is_OTHER my_DET favourite_ADJ colour_NOUN ._PUNCT

Sample Tag Dictionary (file : dictionary.xml)

    <?xml version="1.0" encoding="UTF-8"?>
     <dictionary case_sensitive="false">
      <entry tags="PRON">
        <token>It</token>
      </entry>
      <entry tags="OTHER">
        <token>is</token>
      </entry>
      <entry tags="PROPN">
        <token>Spring</token>
      </entry>
      <entry tags="NOUN">
        <token>season</token>
      </entry>
      <entry tags="DET">
        <token>the</token>
      </entry>
      <entry tags="NOUN">
        <token>flowers</token>
      </entry>
      <entry tags="OTHER">
        <token>are</token>
      </entry>
      <entry tags="NOUN">
        <token>red</token>
      </entry>
      <entry tags="CCONJ">
        <token>and</token>
      </entry>
      <entry tags="NOUN">
        <token>yellow</token>
      </entry>
      <entry tags="PRON">
        <token>my</token>
      </entry>
      <entry tags="ADJ">
        <token>favourite</token>
      </entry>
      <entry tags="NOUN">
        <token>colour</token>
      </entry>
      <entry tags="PUNCT">
        <token>.</token>
      </entry>
    </dictionary>

Sample code to train a model using above tag dictionary

    POSModel model = null;
    	try {
    		ObjectStream<String> lineStream = new PlainTextByLineStream(
    				new MarkableFileInputStreamFactory(new File("en-custom-pos.train")), StandardCharsets.UTF_8);

    		ObjectStream<POSSample> sampleStream = new WordTagSampleStream(lineStream);

    		TrainingParameters params = ModelUtil.createDefaultTrainingParameters();
    		params.put(TrainingParameters.CUTOFF_PARAM, 0);

    		POSTaggerFactory factory = new POSTaggerFactory();
    		TagDictionary dict = factory.createTagDictionary(new File("dictionary.xml"));
    		factory.setTagDictionary(dict);

    		model = POSTaggerME.train("eng", sampleStream, params, factory);

    		OutputStream modelOut = new BufferedOutputStream(new FileOutputStream("en-custom-pos-maxent.bin"));
    		model.serialize(modelOut);

    	} catch (IOException e) {
    		e.printStackTrace();
    	}

The custom model is then used to tag a sequence.

    String[] sent = new String[]{"Spring", "is", "my", "favourite", "season", "."};
    String[] tags = tagger.tag(sent);
    Arrays.stream(tags).forEach(k -> System.out.print(k + " "));

    				Input
    				    Sentence:	Spring is my favourite season.

    				Output
    				    POS Tags using the custom model (en-custom-pos-maxent.bin): \
    							PROPN OTHER PRON ADJ NOUN PUNCT

    				Output with the default model
    				    POS Tags using the default model (opennlp-en-ud-ewt-pos-1.3-2.5.4.bin):	\
    							NOUN AUX PRON ADJ NOUN PUNCT

## Evaluation

The built-in evaluation can measure the accuracy of the pos tagger. The accuracy can be measured on a test data set or via cross validation.

### Evaluation Tool

There is a command line tool to evaluate a given model on a test data set. The following command shows how the tool can be run:

    $ opennlp POSTaggerEvaluator -model pt.postagger.bin -data pt.postagger.test -encoding utf-8

This will display the resulting accuracy score, e.g.:

    Loading model ... done
    Evaluating ... done

    Accuracy: 0.9659110277825124

There is a command line tool for cross-validation of the test data set. The following command shows how the tool can be run:

    $ opennlp POSTaggerCrossValidator -lang pt -data pt.postagger.test -encoding utf-8

This will display the resulting accuracy score, e.g.:

    Accuracy: 0.9659110277825124