<jupyter_start><jupyter_text>Python Tutorial 1: Part-of-Speech Tagging 1 **(C) 2016 by [<NAME>](http://cavar.me/damir/) >** **Version:** 1.0, September 2016 **License:** [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/) ([CA BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))  Introduction This is a tutorial about developing simple [Part-of-Speech taggers](https://en.wikipedia.org/wiki/Part-of-speech_tagging) using Python 3.x and the [NLTK](http://nltk.org/). This tutorial was developed as part of the course material for the course Advanced Natural Language Processing in the [Computational Linguistics Program](http://cl.indiana.edu/) of the [Department of Linguistics](http://www.indiana.edu/~lingdept/) at [Indiana University](https://www.indiana.edu/). The [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) in distributed as part of the [NLTK Data](http://www.nltk.org/data.html). To be able to use the [NLTK Data](http://www.nltk.org/data.html) and the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) on your local machine, you need to install the data as described on [the Installing NLTK Data page](http://www.nltk.org/data.html). If you want to use iPython on your local machine, I recommend installing a Python 3.x distribution, for example the most recent [Anaconda release](https://www.continuum.io/downloads), and reading the instructions how to run [iPython on Anaconda](http://jupyter.readthedocs.io/en/latest/install.html).  Part-of-Speech Tagging We refer to Part-of-Speech (PoS) tagging as the task of assigning class information to individual words (tokens) in some text. The tags are defined in tagsets that specify character sequences that represent sets of for example lexical, morphological, syntactic, or semantic features. See for more details the [Categorizing and Tagging Words chapter](http://www.nltk.org/book/ch05.html) of the [NLTK book](http://www.nltk.org/book/).  Using the Brown Corpus The documentation of the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) design and properties can be found on [this page](http://clu.uni.no/icame/brown/bcm.html). Using the following line of code we are importing the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) into the running Python instance. This will make the tokens and PoS-tags from the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) available for further processing.<jupyter_code>from nltk.corpus import brown<jupyter_output><empty_output><jupyter_text>Our goal is to assign PoS-tags to a sequence of words that represent a phrase, utterance, or sentence. Let us assume that the probability of a sequence of 5 tags $t_1\ t_2\ t_3\ t_4\ t_5$ given a sequence of 5 tokens $w_1\ w_2\ w_3\ w_4\ w_5$ is $P(t_1\ t_2\ t_3\ t_4\ t_5\ |\ w_1\ w_2\ w_3\ w_4\ w_5)$ and can be computed as the product of the probability of one tag given another, e.g. the probability of tag 2 given that tag 1 occurred: $P(t_2\ |\ t_1)$, and the probability of one word and a specific tag, e.g. the probability of word 2 given that tag 2 occurred: $P(w_2\ |\ t_2)$. Let us assume that we use two extra symbols *S* and *E*. *S* stands for sentence beginning and *E* for sentence end. We use these symbols to keep track of different distributions of tags and tokens relative to sentence positions. The token *the* for example is very unlikely to occur in sentence final and more likely to occur in sentence initial position. $$P(t_1 \dots t_5\ |\ w_1 \dots w_5) = P(t_1|S)\ P(w_1|t_1)\ P(t_2|t_1)\ P(w_2|t_2)\ P(t_3|t_2)\ P(w_3|t_3)\ P(t_4|t_3)\ P(w_3|t_3)\ P(t_5|t_4)\ P(w_4|t_4)\ P(E|t_4)\ P(w_5|t_5)$$ This equation can be abbreviated as follows: $$P(t_1 \dots t_5\ |\ w_1 \dots w_5) = P(t_1\ |\ S)\ P(E\ |\ t_5)\ \prod_{i=1}^{5} P(t_{i+1}\ |\ t_i)\ P(w_{i+1}\ |\ t_{i+1})$$ We extract the probabilities for a word (or token) given that a certain tag occurred, that is $P(w_1\ |\ t_1)$, form the frequency profile for tags and tokens from the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus). The necessary data-structure should be loaded and in memory after executing the code cell above. Since we loaded the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) into memory, we can now use specific methods to access tokens and PoS-tags from the corpus. The following line of code unzips the list of tuples that contain tokens and tags in sequence as found in the corpus and stores the tokens in the *tokens* list and tags in the *tags* list. Note that the \* operator is used here to unzip a list. See for more details on the [Python zip-function the documentation page](https://docs.python.org/3.5/library/functions.htmlzip). The function *brown.tagged_words()* returns a list of tuples *(word, token)*. The *zip*-function creates two lists and assigns those to the variables *tokens* and *tags* respectively.<jupyter_code>tokens, tags = zip(*brown.tagged_words())<jupyter_output><empty_output><jupyter_text>You can inspect the resulting list of *tokens* by printing it out:<jupyter_code>tokens<jupyter_output><empty_output><jupyter_text>You can print the *tags* as well:<jupyter_code>tags<jupyter_output><empty_output><jupyter_text>The sequence of *tokens* and *tags* is aligned, that is, the first tag in the *tags* list belongs to the first token in the *tokens* list. You can print the token-tag pair out in the following way:<jupyter_code>print("Token:", tokens[0], "Tag:", tags[0])<jupyter_output><empty_output><jupyter_text>To create a frequency profile of tags for example, we can make use of the [*Counter* container datatype](http://docs.python.org/3/library/collections.htmlcollections.Counter) from the [*collections* module](http://docs.python.org/3/library/collections.html). We import the [*Counter* datatype](http://docs.python.org/3/library/collections.htmlcollections.Counter) with the following code:<jupyter_code>from collections import Counter<jupyter_output><empty_output><jupyter_text>We can create a frequency profile of the tags from the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) and store it in the variable *tagCounts* using the following code:<jupyter_code>tagCounter = Counter(tags)<jupyter_output><empty_output><jupyter_text>The *tagCounter* datatype now contains a hash-table with *tags* as keys and their frequencies as values. Accessing the frequency of a specific *tag* can be achieved using the following code:<jupyter_code>tagCounter["NNS"]<jupyter_output><empty_output><jupyter_text>The frequency of a specific *token* can be accessed by generating a frequency profile from the *token*-list in the same way as for *tags*:<jupyter_code>tokenCounter = Counter(tokens)<jupyter_output><empty_output><jupyter_text>We access the *token* frequency in the same way as for *tags*:<jupyter_code>tokenCounter["the"]<jupyter_output><empty_output><jupyter_text>Since one type (or word) in the [Brown corpus](https://en.wikipedia.org/wiki/Brown_Corpus) can have more than one corresponding tag with a specific frequency, we need to store this information in a specific datastructure.<jupyter_code>from collections import defaultdict<jupyter_output><empty_output><jupyter_text>The following loop reads from the list of *token-tag*-tuples in *brown.tagged_words* the individual *token* and *tag* pairs and sets their counter in the *dictionary* of *Counter* datastructures.<jupyter_code>tokenTags = defaultdict(Counter)
for token, tag in brown.tagged_words():
    tokenTags[token][tag] +=1<jupyter_output><empty_output><jupyter_text>We can now ask for the *Counter* datastructure for the key *the*. The *Counter* datastructure is a hash-table with tags as keys and the corresponding frequency as values.<jupyter_code>tokenTags["John"]
tokenTags["the"]["AT"]<jupyter_output><empty_output><jupyter_text>For the calculation of the probability of a $tag_2$ given that a $tag_1$ occured, that is $P(tag_2\ |\ tag_1)$ we will need to count the bigrams from the *tags* list. The NLTK ngram module provides a convenient set of functions and datastructures to achieve this:<jupyter_code>from nltk.util import ngrams<jupyter_output><empty_output><jupyter_text>As for the *tokenTags* datatype above, we can create a *tags* bigram model using a dictionary of *Counter* datatypes. The dictionary keys will be the first tag of the tag-bigram. The value will contain a Counter datatype with the second tag of the tag-bigram as the key and the frequency of the bigram as value.<jupyter_code>tagTags = defaultdict(Counter)<jupyter_output><empty_output><jupyter_text>Using the *ngrams* module we generate a bigram model from the tags list and store it in the variable *posBigrams* using the following code:<jupyter_code>posBigrams = list(ngrams(tags, 2))<jupyter_output><empty_output><jupyter_text>The following loop goes through the list of bigram tuples, assigned the left bigram tag to the variable *tag1* and the right bigram tag to variable *tag2*, and stores the count of the bigram in the *tagTags* datastructure:<jupyter_code>for tag1, tag2 in posBigrams:
    tagTags[tag1][tag2] += 1<jupyter_output><empty_output><jupyter_text>We can now list all *tags* that follow the *AT* tag with the corresponding frequency:<jupyter_code>tagTags["AT"]<jupyter_output><empty_output><jupyter_text>We can request the frequency of the tag-bigram *AT NN* using the following code:<jupyter_code>tagTags["AT"]["NN"]<jupyter_output><empty_output><jupyter_text>We can calculate the total number of bigrams and relativize the count of any particular bigram:<jupyter_code>total = len(tags)
print(total)
tagTags["NNS"]["NNS"]/float(total-1)<jupyter_output><empty_output><jupyter_text>If we want to know how many times a certain tag occurs in sentence initial position, to estimate initial probabilities for startstates in a Hidden Markov Model for example, we can loop through the sentences and count the tags in initial position.<jupyter_code>offset = 0
initialTags = Counter()
for x in brown.sents():
    initTag = tags[offset]
    initialTags[initTag] += 1
    offset += len(x)
print("Example:")
print("AT:", initialTags["AT"])<jupyter_output><empty_output><jupyter_text>Note, for the code above, I do not know how to access the initial sentence tag directly, thus I am indirectly accessing the tag over an offset count. If you know a better way, let me know, please. We can now estimate the probability of any tag being in sentence initial position in the following way:<jupyter_code>initialTags["AT"]/float(total)<jupyter_output><empty_output><jupyter_text>We can estimate the probability of any tag being followed by any other, in the following way:<jupyter_code>tagTags["AT"]["NN"]/float(total-1)<jupyter_output><empty_output><jupyter_text>Note, we are dividing by *total - 1*, since the number of bigrams in the *tagTags* data structure is exactly this.  We can estimate the likelihood of a tag token combination using the *tokenTags* data-structure:<jupyter_code>tokenTags["John"]["NN"]/float(total)<jupyter_output><empty_output><jupyter_text>Given the data structures *tokenTags* and *tagTags* we can now estimate the probability of a word given a specific tag, or intuitively, the probability that a specific word is assigned a tag, that is for the token *cat* and the tag *NN*: $P(cat\ |\ NN)$ using the following equation and corresponding code (with $C(cat\ NN)$ as the absolute frequency or count of the *cat NN* tuple, and $C(NN)$ the count of the *NN*-tag): $$P(w_n\ |\ t_n) = \frac{C(w_n\ t_n)}{C(t_n)}$$<jupyter_code>tokenTags["cat"]["NN"] / tagCounter["NN"]<jupyter_output><empty_output><jupyter_text>We can estimate the probability of a $tag_2$ following a $tag_1$ using a similar approach: $$P(t_n\ |\ t_{n-1}) = \frac{C(t_{n-1}\ t_n)}{C(t_{n-1})}$$ Here $C(t_{n-1}\ t_n)$ is the count of the bigram of these two tags in sequence. $C(t_{n-1})$ is the count or absolute frequency of the first or left tag in the bigram. Let us assume that the input sequence was *the cat ...* and that the most likely initial tag for *the* was *AT*, then the probability of the tag *NN* given that a tag *AT* occurred can be estimated as:<jupyter_code>tagTags["AT"]["NN"] / tagCounter["AT"]<jupyter_output><empty_output><jupyter_text>The product of the two probabilities $P(w_n\ |\ t_n)\ P(t_n\ |\ t_{n-1})$ for the tokens *the cat* and the possible tags *AT NN* should be:<jupyter_code>(tokenTags["cat"]["NN"] / tagCounter["NN"]) * (tagTags["AT"]["NN"] / tagCounter["AT"])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11003753094306476, 'fraction_numerical': 0.010061486864169928, 'mean_word_length': 5.619450317124736, 'pattern_counts': {'":': 0, '<': 110, '<?xml version=': 0, '>': 111, 'https://': 15, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '181777', 'license': 'Apache-2.0', 'n_tokens_mistral': 3997, 'n_tokens_neox': 3874, 'n_words': 1454, 'path': 'notebooks/Python Tutorial PoS Tagging.ipynb', 'repo_name': 'arieling/python-tutorial-for-ipython', 'size': '12528'}