This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Repository files navigation

tagger

Module for extracting tags from text documents.

Copyright (C) 2011 by Alessandro Presta

Configuration

Dependencies: python2.5, stemming, nltk (optional), lxml (optional)

You can install the stemming package with:

$ easy_install stemming

Usage

Tagging a text document from Python:

import tagger
weights = pickle.load(open('data/dict.pkl', 'rb')) # or your own dictionary
myreader = tagger.Reader() # or your own reader class
mystemmer = tagger.Stemmer() # or your own stemmer class
myrater = tagger.Rater(weights) # or your own... (you got the idea)
mytagger = Tagger(myreader, mystemmer, myrater)
best_3_tags = mytagger(text_string, 3)

Running the module as a script:

$ ./tagger.py <text document(s) to tag>

Example:

$ ./tagger.py tests/*
Loading dictionary...
Tags for tests/bbc1.txt :
['bin laden', 'obama', 'pakistan', 'killed', 'raid']
Tags for tests/bbc2.txt :
['jo yeates', 'bristol', 'vincent tabak', 'murder', 'strangled']
Tags for tests/bbc3.txt :
['snp', 'party', 'election', 'scottish', 'labour']
Tags for tests/guardian1.txt :
['bin laden', 'al-qaida', 'killed', 'pakistan', 'al-fawwaz']
Tags for tests/guardian2.txt :
['clegg', 'tory', 'lib dem', 'party', 'coalition']
Tags for tests/post1.txt :
['sony', 'stolen', 'playstation network', 'hacker attack', 'lawsuit']
Tags for tests/wikipedia1.txt :
['universe', 'anthropic principle', 'observed', 'cosmological', 'theory']
Tags for tests/wikipedia2.txt :
['beetroot', 'beet', 'betaine', 'blood pressure', 'dietary nitrate']
Tags for tests/wikipedia3.txt :
['the lounge lizards', 'jazz', 'john lurie', 'musical', 'albums']

A brief explanation

Extracting tags from a text document involves at least three steps: splitting the document into words, grouping together variants of the same word, and ranking them according to their relevance. These three tasks are carried out respectively by the Reader, Stemmer and Rater classes, and their work is put together by the Tagger class.

A Reader object may accept as input a document in some format, perform some normalisation of the text (such as turning everything into lower case), analyse the structure of the phrases and punctuation, and return a list of words respecting the order in the text, perhaps with some additional information such as which ones look like proper nouns, or are at the end of a phrase. A very straightforward way of doing this would be to just match all the words with a regular expression, and this is indeed what the SimpleReader class does.

The Stemmer tries to recognise the root of a word, in order to identify slightly different forms. This is already a quite complicated task, and it's clearly language-specific. The stem module in the NLTK package provides algorithms for many languages and integrates nicely with the tagger:

import nltk
# an English stemmer using Lancaster's algorithm
mystemmer = Stemmer(nltk.stem.LancasterStemmer)
# an Italian stemmer
class MyItalianStemmer(Stemmer):
def __init__(self):
Stemmer.__init__(self, nltk.stem.ItalianStemmer)
def preprocess(self, string):
# do something with the string before passing it to nltk's stemmer

The Rater takes the list of words contained in the document, together with any additional information gathered at the previous stages, and returns a list of tags (i.e. words or small units of text) ordered by some idea of "relevance".

It turns out that just working on the information contained in the document itself is not enough, because it says nothing about the frequency of a term in the language. For this reason, an early "off-line" phase of the algorithm consists in analysing a corpus (i.e. a sample of documents written in the same language) to build a dictionary of known words. This is taken care by the build_dict() function. It is advised to build your own dictionaries, and the build_dict_from_nltk() function in the extras module enables you to use the corpora included in NLTK:

build_dict_from_nltk(output_file, nltk.corpus.brown,
nltk.corpus.stopwords.words('english'), measure='ICF')

So far, we may define the relevance of a word as the product of two distinct functions: one that depends on the document itself, and one that depends on the corpus. A standard measure in information retrieval is TF-IDF (term frequency-inverse document frequency): the frequency of the word in the document multiplied by the (logarithm of) the inverse of its frequency in the corpus (i.e. the cardinality of the corpus divided by the number of documents where the word is found). If we treat the whole corpus as a single document, and count the total occurrences of the term instead, we obtain ICF (inverse collection frequency). Both of these are implemented in the build_dict module, and any other reasonable measure should be fine, provided that it is normalised in the interval [0,1]. The dictionary is passed to the Rater object as the weights argument in its constructor. We might also want to define the first term of the product in a different way, and this is done by overriding the rate_tags() method (which by default calculates TF for each word and multiplies it by its weight):

class MyRater(Rater):
def rate_tags(self, tags):
# set each tag's rating as you wish

If we were not too picky about the results, these few bits would already make an acceptable tagger. However, it's a matter of fact that tags formed only by single words are quite limited: while "obama" and "barack obama" are both reasonable tags (and it is quite easy to treat cases like this in order to regard them as equal), having "laden" and "bin" as two separate tags is definitely not acceptable and misleading. Compare the results on the same document using the NaiveRater class (defined in the module extras) instead of the standard one.

The multitag_size parameter in the Rater's constructor defines the maximum number of words that can constitute a tag. Multitags are generated in the create_multitags() method; if additional information about the position of a word in the phrase is available (i.e. the terminal member of the class Tag), this can be done in a more accurate way. The rating of a MultiTag is computed from the ratings of its unit tags. By default, the combined_rating() method uses the geometric mean, with a special treatment of proper nouns if that information is available too (in the proper member). This method can be overridden too, so there is room for experimentation.

With a few "common sense" heuristics the results are greatly improved. The final stage of the default rating algorithm involves discarding redundant tags (i.e. tags that contain or are contained in other, less relevant tags).

It should be stressed that the default implementation doesn't make any assumption on the type of document that is being tagged (except for it being written in English) and on the kinds of tags that should be given priority (which sometimes can be a matter of taste or depend on the particular task we are using the tags for). With some additional assumptions and an accurate treatment of corner cases, the tagger can be tailored to suit the user's needs.

This is proof-of-concept software and extensive experimentation is encouraged. The design of the base classes should allow for this, and the few examples in the extras module are a good starting point for customising the algorithm.

About

A Python module for extracting relevant tags from text documents. This branch is Python 2.5 & 2.6 compatible.

Resources

Stars

2 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages