convert to lowercase and strip leading/trailing whitespace
Introduction
Automatically predicting the genre of a movie based on its plot summary is a challenging and interesting task that lies at the intersection of natural language processing (NLP) and machine learning. Movies are complex, multi-faceted works that often span multiple genres, making standard binary or multiclass classification techniques insufficient. Instead, this problem requires a multi-label classification approach.
In multi-label classification, each sample can have multiple labels, in contrast to multiclass classification where each sample has exactly one label out of many possible classes. Some common examples of multi-label problems include tagging a blog post with relevant topics, identifying genres for a song, or in our case, classifying a movie into one or more genre categories like drama, comedy, action, thriller, romance, etc. based on its plot description.
In this article, we‘ll walk through the process of building a multi-label movie genre classifier using NLP and machine learning in Python. We‘ll use a logistic regression model with a one-vs-rest approach to predict the genres for a movie given its plot summary text. Along the way, we‘ll cover important techniques like text preprocessing, feature extraction with TF-IDF, and evaluation metrics for multi-label classification.
While movie genre prediction may seem like a niche problem, the underlying concepts and methods are widely applicable to many real-world use cases involving multi-label text classification, such as document tagging, sentiment analysis, intent detection, and more. Multi-label models enable richer, more nuanced predictions that better capture the complexity of language and human-generated content.
Dataset
For this project, we‘ll be working with the CMU Movie Summary Corpus, a collection of 42,306 movie plot summaries extracted from Wikipedia, along with associated metadata like movie titles and genre tags. The dataset is freely available to download from the Carnegie Mellon University website.
The data comes in two key files:
- movie.metadata.tsv – Contains fields like movie ID, title, and genres
- plot summaries.txt – Each line has the Wikipedia movie ID and plot summary
Here‘s a sample of the movie metadata:

And an example plot summary:
Bright Young Things, adapted by Stephen Fry from Evelyn Waugh‘s novel "Vile Bodies", is a look into the lives of a young novelist, his would-be lover, and a host of young people who beautified London in the 1930s.
The genre field is in a dictionary-like format, with genre names as keys and binary values indicating if that genre applies to the movie. For example:
{"/m/07s9rl0": "Drama", "/m/03q4nz": "World cinema", "/m/03k9fj": "Art film", "/m/03btsm8": "Romance Film", "/m/07s9rl0": "Drama"}
This movie would be considered a positive sample for the drama, world cinema, art film and romance film genre labels.
Exploratory Data Analysis
Before diving into building a model, it‘s always a good idea to explore the data to gain insights and identify any data quality issues or preprocessing needs. Let‘s start by looking at the distribution of genre tags across the dataset.

The dataset covers an impressive 363 unique genre tags, reflecting the diversity of movies and the many ways they can be categorized. However, we can see that the genre distribution is highly imbalanced, with some genres like drama, comedy, and action being much more frequent than others. This imbalance is important to keep in mind when evaluating our model later on.
Next, let‘s analyze the text data by visualizing the most frequent words in the movie plot summaries after basic preprocessing like converting to lowercase and removing punctuation.

Not surprisingly, the most common words are stopwords like "the", "and", "a" which don‘t convey much meaning. To get a better sense of the meaningful words, we can filter out stopwords using a predefined list from NLTK.

With stopwords removed, we start to see more relevant words bubble up like "police", "murder", "family", "war", etc. which align with common movie genres and plot elements. These words will likely be informative when training our genre prediction model.
Text Preprocessing
To prepare our text data for machine learning, we need to clean and normalize it. This typically involves:
- Lowercasing: Convert all text to lowercase to treat words like "The" and "the" the same
- Removing punctuation: Strip out punctuation marks like commas, periods, exclamation points, etc.
- Removing stopwords: Filter out common words that add little meaning like "the", "and", "is"
- Tokenization: Split text into individual words or tokens
- Stemming/Lemmatization: Reduce words to their base or dictionary forms, so "running" and "ran" would map to "run"
Here‘s an example of cleaning a raw plot summary with these steps in Python:
import re
from nltk.corpus import stopwords
def clean_text(text):
text = text.lower().strip()
# remove punctuation
text = re.sub(r‘[^\w\s]‘, ‘‘, text)
# tokenize
tokens = text.split()
# remove stopwords
stop_words = set(stopwords.words(‘english‘))
tokens = [t for t in tokens if not t in stop_words]
return ‘ ‘.join(tokens)
After preprocessing, a sample plot summary might go from:
Two friends, Dignan (Owen Wilson) and Anthony (Luke Wilson), decide to pull off a heist, along with a getaway driver called Bob (Robert Musgrave). The inexperienced criminals botch their first attempt and end up robbing a bookstore instead. On the lam, Anthony stops to see his sister, Grace (Jenni Tooley), and begins to reconsider his life choices. Meanwhile, Dignan plots their next caper and tries to get the team back together.
To:
two friends dignan owen wilson anthony luke wilson decide pull heist along getaway driver called bob robert musgrave inexperienced criminals botch first attempt end robbing bookstore instead lam anthony stops see sister grace jenni tooley begins reconsider life choices meanwhile dignan plots next caper tries get team back together
The cleaned version is lowercased, stripped of punctuation and stopwords, and ready for feature extraction.
Feature Extraction
Machine learning models require numeric feature vectors as input, not raw text. So we need a way to convert our preprocessed plot summaries into numeric representations. A common approach is to use the bag-of-words model with TF-IDF (term frequency-inverse document frequency) weighting.
The idea is to represent each plot summary as a vector where each element corresponds to a unique word in the vocabulary, and the value is the TF-IDF weight of that word in that document. TF-IDF weights words by how frequently they appear in a document, while downweighting words that are very common across all documents.
Scikit-learn provides an easy way to compute TF-IDF features with its TfidfVectorizer class:
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer(max_df=0.8, max_features=10000)
X_tfidf = tfidf_vectorizer.fit_transform(plot_summaries)
This takes in a list of plot summaries, preprocesses them with the default options (lowercase, tokenize, etc.), builds a vocabulary of the top 10,000 words, and outputs a sparse matrix where each row is a plot summary and each column is a word weighted by TF-IDF.
We‘re now ready to train a genre prediction model on our TF-IDF feature matrix!
Building a Multi-Label Genre Classifier
For our genre prediction task, we‘ll use logistic regression, a simple yet effective model for binary classification problems. To extend it to the multi-label case, we‘ll use the one-vs-rest approach, training a separate binary classifier for each genre label.
Scikit-learn makes this easy with the OneVsRestClassifier meta-estimator:
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsRestClassifier
lr = LogisticRegression()
clf = OneVsRestClassifier(lr)
To evaluate the model‘s performance, we‘ll split our data into train and test sets and use the F1 score, a common metric for multi-label classification that balances precision and recall.
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
X_train, X_test, y_train, y_test = train_test_split(X_tfidf, genres, test_size=0.2, random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
f1 = f1_score(y_test, y_pred, average=‘micro‘)
print(f‘Test F1 Score: {f1:.3f}‘)
On the CMU Movie Summary Corpus, this simple logistic regression model achieves a respectable test F1 score of 0.44. Not bad for a first attempt!
However, we can likely improve performance with some tuning, like adjusting the prediction threshold. By default, the classifier predicts a genre label if the probability exceeds 0.5. But we can adjust this threshold based on the genre frequencies in our dataset.
Since some genre labels are much rarer than others, a high threshold might miss them entirely. We can lower the threshold for less frequent genres to increase recall. Here‘s an example of how to do this:
import numpy as np
genres_counts = y_train.sum(axis=0)
threshold = genres_counts / genres_counts.sum()
probs = clf.predict_proba(X_test)
y_pred = (probs >= threshold).astype(int)
f1 = f1_score(y_test, y_pred, average=‘micro‘)
print(f‘Test F1 Score with Genre-Specific Thresholds: {f1:.3f}‘)
This boosts our F1 score to 0.52, a nice gain from tuning the prediction thresholds.
Inference on New Data
To use our trained genre classifier on new, unseen movie plots, we need to preprocess the raw text and extract TF-IDF features the same way we did for the training data. We can wrap this logic up in a convenient predict_genres function:
def predict_genres(plot):
plot = clean_text(plot)
x = tfidf_vectorizer.transform([plot])
probs = clf.predictproba(x)
genres = [mlb.classes[i] for i in np.where(probs >= threshold)[1]]
return genres
Let‘s test it out on a sample plot:
An American military advisor embraces the Samurai culture he was hired to destroy after he is captured in battle.
predict_genres(plot)
[‘Action‘, ‘Drama‘, ‘War‘]
Looks reasonable! We could imagine this being the plot for a movie like The Last Samurai.
Next Steps
In this article, we walked through the basics of building a multi-label movie genre classifier using plot summary text. But there are many ways we could extend and improve on this:
- Try more advanced NLP techniques like word embeddings, topic modeling, or transformer models
- Use an ensemble of different models and featurization approaches
- Experiment with other multi-label classification algorithms like classifier chains
- Incorporate other metadata like cast, crew, keywords as additional features
- Analyze and address genre imbalances through resampling techniques
- Deploy the model as a web service that can predict genres for new movie plots in real-time
I encourage you to try out some of these ideas and see how far you can push the model‘s performance!
Conclusion
We live in an age of information overload, with more movies, books, articles, and other content being produced than ever before. Multi-label classification provides a powerful tool for automatically organizing and making sense of all this unstructured text data.
In this article, we saw how it can be used to predict the genres for a movie based on its plot summary, by combining natural language processing and machine learning techniques. But the applications go far beyond just movie genres.
Think of all the ways multi-label classification could help bring structure and insight to vast troves of text data:
- Categorizing books into subjects based on descriptions
- Tagging articles with relevant topics for better recommendation and search
- Classifying customer support queries by issue type to route to the right team
- Analyzing sentiment in social media posts across multiple dimensions like emotions, sarcasm, etc.
The possibilities are endless and exciting! As you continue on your machine learning and NLP journey, I hope this article has given you a practical foundation to dive deeper into the world of multi-label text classification.
By harnessing the power of these techniques, we can bring a new level of understanding and organization to the vast sea of human knowledge encoded in the written word. Let‘s put them to use to make information more discoverable, insightful, and actionable!