Output: "this is some example text!"
Natural Language Processing, or NLP for short, is a fascinating field of artificial intelligence that focuses on enabling computers to understand, interpret, and generate human language. NLP powers many applications we use every day, from virtual assistants like Siri and Alexa to language translation, sentiment analysis, and text summarization.
However, before we can build sophisticated NLP models, there‘s a crucial first step: preprocessing the text data. Raw text data is messy and unstructured, full of inconsistencies, noise and unwanted artifacts. The goal of preprocessing is to clean and normalize the text data, transforming it into a suitable format to train machine learning models.
In this in-depth guide, we‘ll walk through the most important NLP preprocessing steps. We‘ll cover why each step is necessary, how to implement it in Python, and tips and best practices along the way. Let‘s dive in!
1. Lowercasing
The first and most basic step is to convert all the text to lowercase. Since Python is case-sensitive, it will treat words like "Apple" and "apple" as distinct tokens. Lowercasing ensures that all words are normalized and treated the same, regardless of how they originally appeared.
In Python, you can easily convert a string to lowercase using the built-in .lower() method:
text = "This is some Example Text!"
text = text.lower()
print(text)
2. Removing HTML and URLs
A lot of text data, especially data scraped from the web, contains unwanted noise like HTML tags, JavaScript, and URLs. These artifacts are irrelevant for most NLP tasks and should be removed.
The easiest way to strip HTML tags and URLs is to use regular expressions. Here‘s an example that removes HTML tags:
import re
def remove_html(text):
html_pattern = re.compile(‘<.*?>‘)
return html_pattern.sub(r‘‘, text)
text = "
Some text with HTML tags.
"
print(remove_html(text))
And here‘s a regex pattern that matches and removes URLs:
url_pattern = re.compile(r‘https?://\S+|www.\S+‘)
text = url_pattern.sub(r‘‘, text)
3. Removing Numbers and Punctuation
Numbers and punctuation marks are often not useful for text analysis and can be safely removed. However, sometimes numbers may be relevant, like in the phrase "$100". It‘s up to you to decide based on your specific use case.
To remove punctuation, you can use Python‘s built-in string library:
import string
text = "This, is a sentence with punctuation!"
text = text.translate(str.maketrans(‘‘, ‘‘, string.punctuation))
print(text)
4. Tokenization
Tokenization is the process of splitting text into smaller units called tokens, usually individual words. Tokenization is a foundational step since the resulting tokens are the basic units passed to later steps and algorithms.
There are different ways to perform tokenization:
- Whitespace tokenization: Simply split on whitespace to obtain tokens. Fast but doesn‘t handle punctuation.
- NLTK word_tokenize(): Uses a pre-trained tokenizer model. Splits contractions and handles punctuation.
- spaCy tokenizer: Even more advanced, understands rules for quotes, email addresses, etc.
Here‘s an example using NLTK:
from nltk.tokenize import word_tokenize
text = "Don‘t be fooled by the dark sounding name, Mr. Jone‘s Orphanage is as cheery as cheery goes for a pastry shop."
print(word_tokenize(text))
5. Removing Stop Words
Stop words are common words that appear very frequently in language but don‘t contribute much meaning, like "the", "a", "and", "is". Removing stop words can help focus on the more meaningful words.
NLTK provides a built-in list of stop words that you can easily use:
from nltk.corpus import stopwords
stop_words = set(stopwords.words(‘english‘))
tokens = [‘i‘, ‘am‘, ‘going‘, ‘to‘, ‘go‘, ‘to‘, ‘the‘, ‘store‘, ‘and‘, ‘park‘] filtered_tokens = [token for token in tokens if token not in stop_words] print(filtered_tokens)
6. Text Normalization
Stemming and lemmatization are text normalization techniques used to reduce words to their base or dictionary form. They help group together different inflected forms of the same word.
Stemming operates on a single word without knowledge of the context. It just strips off the ends of words using heuristics, like removing "-ing", "-ly", "-ment", etc. This sometimes leads to incorrect stems.
Lemmatization, on the other hand, uses a dictionary to look up a word‘s lemma – its base or dictionary form. It also takes the word‘s part-of-speech into account.
For stemming, you can use NLTK‘s PorterStemmer or SnowballStemmer:
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = ["python", "pythoner", "pythoning", "pythoned", "pythonly"]
for word in words:
print(stemmer.stem(word))
For lemmatization, NLTK provides WordNetLemmatizer but it requires specifying the word‘s part-of-speech. Alternatively, spaCy has an efficient built-in lemmatizer:
import spacy
nlp = spacy.load(‘en_core_websm‘)
doc = nlp("I am a runner running in a race because I love to run since I ran today")
for token in doc:
print(f‘{token} –> {token.lemma}‘)
7. Expanding Contractions
Contractions are shortened versions of words like "isn‘t" for "is not". It‘s a good idea to expand contractions to their original form for consistency.
You can easily do this in Python by defining a dict of contractions and their expanded forms:
contractions = {
"ain‘t": "is not",
"aren‘t": "are not",
"can‘t": "cannot",
"could‘ve": "could have",
"didn‘t": "did not",
"doesn‘t": "does not",
…
}
def expand_contractions(text):
for contraction, expansion in contractions.items():
text = text.replace(contraction, expansion)
return text
8. Handling Emojis and Emoticons
With the rise of social media, emojis and emoticons are increasingly common in text data. While they can provide useful sentiment signals, they pose challenges for NLP.
The best way to handle emojis is to convert them into their text descriptions. You can use the emoji library in Python for this:
import emoji
text = "This movie was 🙌 amazing! 😁👍"
print(emoji.demojize(text))
9. Spelling Correction
Spelling mistakes are common in user-generated content. Correcting spellings can improve the quality of tokens and increase the vocabulary coverage.
The TextBlob library provides a simple API for spelling correction:
from textblob import TextBlob
blob = TextBlob("I havv goood speling!")
print(blob.correct())
More advanced techniques involve using word embeddings to find the most similar correctly spelled word.
10. Part-of-Speech Tagging
Part-of-speech (POS) tagging is the process of labeling each token with its part of speech (noun, verb, adjective, etc.). POS tags can be useful features for downstream tasks like named entity recognition and dependency parsing.
NLTK provides a pre-trained POS tagger:
from nltk import pos_tag
text = "The quick brown fox jumped over the lazy dog."
tokens = word_tokenize(text)
print(pos_tag(tokens))
spaCy also offers POS tagging through its efficient processing pipeline.
11. Named Entity Recognition
Named entity recognition (NER) is the task of identifying and categorizing key information (entities) in text, like person names, organizations, locations, etc. This can be useful for information extraction, question answering and other applications.
spaCy provides an easy way to perform NER:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple is looking at buying U.K. startup for $1 billion")
for ent in doc.ents:
print(ent.text, ent.label_)
12. Generating N-Grams
N-grams are contiguous sequences of n items from a text. They are a simple but surprisingly powerful feature representation. Unigrams are single tokens, bigrams are pairs of adjacent tokens, trigrams are triplets, and so on.
NLTK provides convenient functions to generate n-grams:
from nltk import ngrams
text = "The quick brown fox jumped over the lazy dog."
tokens = word_tokenize(text)
unigrams = ngrams(tokens, 1)
bigrams = ngrams(tokens, 2)
trigrams = ngrams(tokens, 3)
print(list(unigrams))
print(list(bigrams))
print(list(trigrams))
13. Text Vectorization
The final step before we can train machine learning models on text is to convert the tokens into numerical feature vectors. There are different approaches for this:
-
Bag-of-words: Represent each document as a vector of token counts, disregarding grammar and word order. Easy to understand but sparse representation that loses context.
-
TF-IDF: Stands for Term Frequency-Inverse Document Frequency. Reflects how important a word is in a document compared to the whole corpus. More informative than raw counts.
-
Word embeddings: Dense vector representations that capture semantic relationships between words. Trained on large text corpora. Examples include word2vec, GloVe, and FastText.
Here‘s an example of generating TF-IDF vectors with scikit-learn:
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
‘This is the first document.‘,
‘This document is the second document.‘,
‘And this is the third one.‘,
‘Is this the first document?‘,
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(X.shape)
Conclusion
We covered a lot of ground in this guide to NLP preprocessing! To recap, the key steps are:
- Lowercasing
- Removing HTML and URLs
- Removing numbers and punctuation
- Tokenization
- Removing stop words
- Text normalization (stemming and lemmatization)
- Expanding contractions
- Handling emojis
- Spelling correction
- Part-of-speech tagging
- Named entity recognition
- Generating n-grams
- Text vectorization
The specific preprocessing steps you need will depend on your NLP task and dataset. You might not need all of these steps, or you might need additional custom steps. That‘s why it‘s important to understand what each step does and consider it in the context of your problem.
I encourage you to experiment with the different techniques covered here. And don‘t be afraid to dive deeper – there‘s always more to learn in the exciting field of NLP!
Here are some great resources if you want to learn more:
- Natural Language Processing with Python (Book)
- Coursera Natural Language Processing Specialization
- spaCy Course
- Stanford CS224N: NLP with Deep Learning
Feel free to reach out if you have any questions. Happy NLP preprocessing!