Movie Review Sentiment Analyzer

Train an ML model to classify movie reviews as positive or negative.

Introduction

30 Second Summary

Every time you scroll through movie ratings online, your brain is doing something remarkable. It reads a few sentences and instantly knows whether the reviewer loved or hated the film.

In this project, you will teach a computer to do the same thing. You will build a machine learning model that reads movie reviews and predicts whether each one is positive or negative.

What You'll Build

A sentiment analysis model trained on 50,000 real movie reviews from the Stanford IMDB dataset, using TF-IDF vectorization and Logistic Regression to classify text as positive or negative.

By the end of this project, you'll have:

  • A working Google Colab notebook with your complete machine learning pipeline from raw text to predictions.
  • A trained Logistic Regression model that classifies movie review sentiment with approximately 87% accuracy.
  • A reusable prediction function that analyzes any text you type in and returns an instant positive or negative verdict.
  • Secret Mission: Swap in a Naive Bayes classifier and compare its performance against your Logistic Regression model.

Are there any prerequisites?

You need basic Python knowledge (variables, functions, loops) and a Google account for Google Colab access. Everything else runs in your browser with no installations on your computer.

Set Up Your Colab Notebook

Before you can train a machine learning model, you need a workspace with the right libraries installed.

Google Colab gives you a free Python environment right in your browser. In this step, you'll create a new notebook and import the libraries that power your sentiment analyzer.

In this step, get ready to:
  • Create a new Google Colab notebook.
  • Install the Hugging Face Datasets library.
  • Import all required Python libraries.
Create a new notebook

You need a blank notebook to write and run your code in. Colab notebooks are made up of individual cells that you can run one at a time.

  • In Google Colab, click File in the top menu bar.
  • Click New notebook.
  • A new untitled notebook opens with one empty code cell ready for you.
  • Click the notebook title at the top left (it says Untitled0.ipynb) and rename it to sentiment_analyzer.ipynb.

What is Google Colab?

Google Colab is a free, browser-based notebook environment hosted by Google. It comes with Python and popular data science libraries pre-installed, so you can start coding without any local setup.

You run code in "cells" by pressing Shift+Enter. Each cell's output appears directly below it.

Install and import your libraries

Your sentiment analyzer needs several libraries. Most are pre-installed in Colab, but the Hugging Face Datasets library needs to be installed manually.

  • Paste the following code into the first code cell of your notebook and run it with Shift+Enter:
# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

What does this code do?

  • `!pip install datasets` installs the Hugging Face Datasets library so you can load the IMDB movie review dataset in the next step.
  • `re` is Python's built-in module for cleaning text with regular expressions.
  • `pandas` helps you organize and explore data in table format.
  • `load_dataset` is the function you'll use to download the IMDB reviews from Hugging Face.
  • The scikit-learn imports (TfidfVectorizer, train_test_split, LogisticRegression, accuracy_score, classification_report) provide the full ML toolkit for vectorizing text, splitting data, training a model, and measuring accuracy.
  • Wait for the cell to finish running. You'll see pip download and install the datasets package, followed by a success message.
  • Confirm there are no red error messages below the import lines. If all imports succeed, Colab shows no output for them.

Seeing a red error message?

  • If you see ModuleNotFoundError for datasets, make sure the !pip install datasets line is in the same cell above the imports, and that you ran the entire cell (not just part of it).
  • If you see a network error during install, check your internet connection and try running the cell again.
  • If the cell seems stuck, click Runtime in the menu bar, then Restart session, and run the cell again.

Still stuck? Help me debug my Colab import errors

✔️ Awesome, I've got everything!

Great. Double check you see no red error output below the cell. Your environment is ready to go.

ⓧ I'd like to double check the full code

Here is the complete code for Cell 1. Make sure yours matches exactly:

# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

Your workspace is ready. Next up, you'll load 25,000 real movie reviews and start exploring what positive and negative sentiment looks like in actual text.

Load and Explore the IMDB Dataset

Every machine learning project starts with data. Before you can train a model to understand sentiment, you need to see what real movie reviews look like and how they are labeled.

The Stanford IMDB dataset contains 50,000 real movie reviews, each labeled as either positive or negative. In this step, you will load this dataset and explore its structure so you understand exactly what your model will learn from.

In this step, get ready to:
  • Load the IMDB dataset from Hugging Face.
  • Convert the training split to a pandas DataFrame and examine its structure.
  • Explore sample reviews and check the label distribution.
Load the IMDB dataset and explore its structure

The Hugging Face Datasets library makes it easy to load popular ML datasets in one line of code. You will use the Stanford IMDB dataset, which gives you 25,000 training reviews and 25,000 test reviews with balanced positive and negative labels.

  • Click the + Code button in the toolbar at the top of your notebook to add a new code cell below your imports cell.
  • Add the following code to load the dataset, convert it to a pandas DataFrame, and print key details:
# Cell 2: Load the IMDB Dataset
dataset = load_dataset("stanfordnlp/imdb")

# Convert to pandas DataFrame
df = pd.DataFrame(dataset["train"])
print(f"Dataset shape: {df.shape}")
print(f"\nColumn names: {df.columns.tolist()}")
print(f"\nLabel distribution:\n{df['label'].value_counts()}")
print(f"\nSample positive review:\n{df[df['label'] == 1]['text'].iloc[0][:300]}...")
print(f"\nSample negative review:\n{df[df['label'] == 0]['text'].iloc[0][:300]}...")

What does this code do?

  • load_dataset("stanfordnlp/imdb") downloads the full IMDB dataset from Hugging Face's servers. The first time you run this, it fetches the data from the internet.
  • dataset["train"] selects only the training split (25,000 reviews). You will split this further later for training and testing your model.
  • pd.DataFrame() converts the Hugging Face dataset object into a pandas DataFrame, which is easier to explore and manipulate.
  • The print statements reveal the dataset's shape, column names, label balance, and sample reviews so you can see exactly what you are working with.
  • Run the cell by pressing Shift+Enter.
  • You should see the dataset shape printed as (25000, 2), two column names (text and label), a balanced label distribution of roughly 12,500 each, and snippets of actual movie review text.

What do the labels mean?

In this dataset, label = 0 means the review is negative and label = 1 means positive. The labels are balanced, meaning there are approximately equal numbers of positive and negative reviews. This balance is important because it means your model will not be biased toward one class.

Seeing a download error?

The dataset downloads from Hugging Face's servers, so you need an active internet connection. If your Colab session disconnected, click Reconnect in the top-right corner and re-run your cells from the top.

If you see a timeout error, try running the cell again. Large dataset downloads can occasionally stall on the first attempt.

Help me fix my dataset loading error in Google Colab.

✔️ Awesome, I've got everything!

Great. Make sure your cell output shows (25000, 2) for the shape and roughly 12,500 for each label count before moving on.

ⓧ I'd like to double check the full code

Here is the complete notebook code up to this point. Compare it with your cells:

# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# Cell 2: Load the IMDB Dataset
dataset = load_dataset("stanfordnlp/imdb")

# Convert to pandas DataFrame
df = pd.DataFrame(dataset["train"])
print(f"Dataset shape: {df.shape}")
print(f"\nColumn names: {df.columns.tolist()}")
print(f"\nLabel distribution:\n{df['label'].value_counts()}")
print(f"\nSample positive review:\n{df[df['label'] == 1]['text'].iloc[0][:300]}...")
print(f"\nSample negative review:\n{df[df['label'] == 0]['text'].iloc[0][:300]}...")

You now have 25,000 real movie reviews loaded and ready to process. Next up, you will clean this raw text and transform it into numerical features that a machine learning model can actually understand.

Preprocess Text and Extract Features

You have 25,000 movie reviews loaded into a DataFrame. Now comes a critical challenge: machine learning models cannot read text. They only work with numbers.

In this step, you will clean the messy raw text and then convert it into numerical features using a technique called TF-IDF. This transforms each review into a format your model can actually learn from.

In this step, get ready to:
  • Write a text cleaning function to standardize raw movie reviews.
  • Convert cleaned text into numerical feature vectors using TF-IDF.
Write and apply the clean_text() function

Movie reviews scraped from the web contain HTML tags like <br>, random punctuation, and inconsistent capitalization. These add noise that confuses a model. A preprocessing function strips everything down to meaningful lowercase words.

  • Create a new code cell in your Colab notebook and add the following text preprocessing code:
# Cell 3: Text Preprocessing
def clean_text(text):
    text = re.sub(r'<.*?>', '', text)
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    text = text.lower()
    return text

df['clean_text'] = df['text'].apply(clean_text)
print("Text cleaning complete!")
print(f"\nOriginal: {df['text'].iloc[0][:200]}...")
print(f"\nCleaned:  {df['clean_text'].iloc[0][:200]}...")

What does this code do?

  • re.sub(r'<.*?>', '', text) removes HTML tags that appear in the raw reviews.
  • re.sub(r'[^a-zA-Z\s]', '', text) strips everything except letters and spaces. Numbers, punctuation, and special characters all get removed.
  • text.lower() converts to lowercase so "Great" and "great" are treated as the same word.
  • df['text'].apply(clean_text) runs the function on every single review and stores the results in a new clean_text column.
  • Run the cell by pressing the play button or pressing Shift+Enter.
  • You should see "Text cleaning complete!" followed by a side-by-side comparison of the original text (with HTML tags and punctuation) and the cleaned version (lowercase letters only).

Seeing a NameError?

If you see NameError: name 're' is not defined, your imports cell (Cell 1) has not been run in this session. Go back and run Cell 1 first.

If you see NameError: name 'df' is not defined, your dataset loading cell (Cell 2) has not been run. Run Cell 2 before this one.

Help me fix this NameError in my Colab notebook.

Convert text to numerical features with TF-IDF

Your text is now clean, but it is still words. TF-IDF (Term Frequency-Inverse Document Frequency) assigns each word a score based on how important it is to a specific review compared to all reviews. Common words like "movie" get low scores. Distinctive words like "masterpiece" get high scores.

  • Create a new code cell and add the TF-IDF feature extraction code:
# Cell 4: TF-IDF Feature Extraction
vectorizer = TfidfVectorizer(max_features=5000, stop_words='english')
X = vectorizer.fit_transform(df['clean_text'])
y = df['label']

print(f"TF-IDF matrix shape: {X.shape}")
print(f"Number of features: {len(vectorizer.get_feature_names_out())}")
print(f"\nSample feature names: {vectorizer.get_feature_names_out()[:10].tolist()}")

What does this code do?

  • TfidfVectorizer(max_features=5000, stop_words='english') creates a vectorizer that keeps only the 5,000 most important words and ignores common English words like "the" and "is".
  • vectorizer.fit_transform(df['clean_text']) learns the vocabulary from your data and converts each review into a row of 5,000 numbers. Each number represents how important a particular word is to that review.
  • y = df['label'] stores your target variable (0 for negative, 1 for positive) separately. This is what the model will learn to predict.
  • Run the cell.
  • You should see TF-IDF matrix shape: (25000, 5000) confirming that 25,000 reviews are each represented by 5,000 numerical features. You will also see a sample of the feature names (words) the vectorizer selected.

Seeing a different shape or an error?

If you see an error about clean_text not being found, go back and run Cell 3 first. The clean_text column must exist in your DataFrame before vectorizing.

If you see fewer than 5,000 features, double check that max_features=5000 is in your TfidfVectorizer() call.

Help me debug my TF-IDF vectorization output.

Why TF-IDF instead of counting words?

A simple word count treats every word equally. The word "movie" appears in almost every review, so counting it tells you nothing useful about sentiment.

TF-IDF solves this by weighting words based on rarity. A word that appears in one review but rarely across the full dataset gets a high score. This surfaces the words that actually distinguish positive reviews from negative ones.

✔️ Awesome, I've got everything!

Great. Double check you have both Cell 3 and Cell 4 running without errors before moving on.

ⓧ I'd like to double check the full code

Here is the complete code across all cells up to this point. Compare yours against it:

# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# Cell 2: Load the IMDB Dataset
dataset = load_dataset("stanfordnlp/imdb")

# Convert to pandas DataFrame
df = pd.DataFrame(dataset["train"])
print(f"Dataset shape: {df.shape}")
print(f"\nColumn names: {df.columns.tolist()}")
print(f"\nLabel distribution:\n{df['label'].value_counts()}")
print(f"\nSample positive review:\n{df[df['label'] == 1]['text'].iloc[0][:300]}...")
print(f"\nSample negative review:\n{df[df['label'] == 0]['text'].iloc[0][:300]}...")

# Cell 3: Text Preprocessing
def clean_text(text):
    text = re.sub(r'<.*?>', '', text)
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    text = text.lower()
    return text

df['clean_text'] = df['text'].apply(clean_text)
print("Text cleaning complete!")
print(f"\nOriginal: {df['text'].iloc[0][:200]}...")
print(f"\nCleaned:  {df['clean_text'].iloc[0][:200]}...")

# Cell 4: TF-IDF Feature Extraction
vectorizer = TfidfVectorizer(max_features=5000, stop_words='english')
X = vectorizer.fit_transform(df['clean_text'])
y = df['label']

print(f"TF-IDF matrix shape: {X.shape}")
print(f"Number of features: {len(vectorizer.get_feature_names_out())}")
print(f"\nSample feature names: {vectorizer.get_feature_names_out()[:10].tolist()}")

Your 25,000 reviews are now 25,000 rows of numbers. Your model has something it can work with. Next up, you will split this data and train a Logistic Regression classifier to predict sentiment.

Train and Evaluate Your Model

Your reviews are now numbers. Every review is a row of 5,000 TF-IDF scores, ready for a machine learning algorithm to learn patterns from.

In this step, you will split your data into training and test sets, train a Logistic Regression classifier, and measure how accurately it predicts sentiment on reviews it has never seen before.

In this step, get ready to:
  • Split your data into training (80%) and test (20%) sets.
  • Train a Logistic Regression model on the training data.
  • Evaluate your model's accuracy and per-class performance.
Split your data and train the model

A model needs to prove it works on data it was not trained on. Holding back 20% of your reviews as a test set gives you an honest measure of performance.

  • Create a new code cell in your Colab notebook and add the following code to split the data and train your model:
# Cell 5: Train/Test Split and Model Training
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"Training set size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
print("\nModel training complete!")

What does this code do?

This cell does two things: splitting and training.

  • train_test_split shuffles and divides your 25,000 reviews into 20,000 for training and 5,000 for testing. The random_state=42 ensures the split is reproducible every time.
  • LogisticRegression(max_iter=1000) creates a classifier. The max_iter=1000 gives the solver enough iterations to converge on this dataset without throwing a warning.
  • model.fit(X_train, y_train) is where the actual learning happens. The model finds which TF-IDF features are most associated with positive vs. negative labels.
  • Run the cell by pressing the play button or pressing Shift+Enter.
  • You should see output showing Training set size: 20000, Test set size: 5000, and Model training complete! printed below the cell.

What is a train/test split?

Imagine studying for an exam by memorizing every question and answer. You would score perfectly on those exact questions, but fail on new ones. The same problem exists in machine learning.

By holding back 20% of your data, you create a fair test. The model trains on 80% and proves itself on the remaining 20% it has never seen. This gives you an honest accuracy number.

Seeing a ConvergenceWarning?

If you see a warning about convergence, double check that your LogisticRegression line has max_iter=1000. The default value is only 100, which is not enough for this dataset.

Help me fix the ConvergenceWarning in my Logistic Regression model.

Evaluate your model's performance

Training is done. Now you need to measure how well your model actually performs on the 5,000 test reviews it has never seen.

  • Create a new code cell and add this evaluation code:
# Cell 6: Evaluate the Model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:\n")
print(classification_report(y_test, y_pred, target_names=['Negative', 'Positive']))

What does this code do?

  • model.predict(X_test) runs each of the 5,000 test reviews through the trained model and returns a predicted label (0 or 1) for each.
  • accuracy_score compares the predictions against the true labels and gives you a single percentage. This is the top-level score for your model.
  • classification_report breaks performance down by class. You can see how well the model handles positive reviews separately from negative reviews.
  • Run the cell.
  • You should see an accuracy of approximately 0.87 to 0.89 (87-89%), followed by a classification report table showing precision, recall, and F1-score for both Negative and Positive classes.

How do I read the classification report?

  • Precision: Of all reviews the model called positive, what fraction were actually positive.
  • Recall: Of all actually positive reviews, what fraction did the model find.
  • F1-score: The harmonic mean of precision and recall. A balanced measure of both.

Balanced precision and recall across both classes means your model is equally good at catching positive and negative reviews. It is not biased toward one class.

Accuracy below 80%?

If your accuracy is significantly lower than expected, scroll up and confirm your TF-IDF cell used max_features=5000 and stop_words='english'. Also confirm that your clean_text() function ran successfully on all rows.

Help me debug my low model accuracy on the IMDB dataset.

✔️ Awesome, I've got everything!

Great. Double check you have both Cell 5 and Cell 6 saved in your notebook before moving on.

ⓧ I'd like to double check the full code

Here is the complete notebook code up to this point. Compare it against your cells to spot any differences.

# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# Cell 2: Load the IMDB Dataset
dataset = load_dataset("stanfordnlp/imdb")

# Convert to pandas DataFrame
df = pd.DataFrame(dataset["train"])
print(f"Dataset shape: {df.shape}")
print(f"\nColumn names: {df.columns.tolist()}")
print(f"\nLabel distribution:\n{df['label'].value_counts()}")
print(f"\nSample positive review:\n{df[df['label'] == 1]['text'].iloc[0][:300]}...")
print(f"\nSample negative review:\n{df[df['label'] == 0]['text'].iloc[0][:300]}...")

# Cell 3: Text Preprocessing
def clean_text(text):
    text = re.sub(r'<.*?>', '', text)
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    text = text.lower()
    return text

df['clean_text'] = df['text'].apply(clean_text)
print("Text cleaning complete!")
print(f"\nOriginal: {df['text'].iloc[0][:200]}...")
print(f"\nCleaned:  {df['clean_text'].iloc[0][:200]}...")

# Cell 4: TF-IDF Feature Extraction
vectorizer = TfidfVectorizer(max_features=5000, stop_words='english')
X = vectorizer.fit_transform(df['clean_text'])
y = df['label']

print(f"TF-IDF matrix shape: {X.shape}")
print(f"Number of features: {len(vectorizer.get_feature_names_out())}")
print(f"\nSample feature names: {vectorizer.get_feature_names_out()[:10].tolist()}")

# Cell 5: Train/Test Split and Model Training
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"Training set size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
print("\nModel training complete!")

# Cell 6: Evaluate the Model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:\n")
print(classification_report(y_test, y_pred, target_names=['Negative', 'Positive']))

Your model is trained and verified. Next up, you will build a prediction function so you can type any movie review and get an instant sentiment prediction from your model.

Predict Sentiment on Your Own Reviews

Your model achieved ~87-89% accuracy on test data it has never seen before. That's a strong result, but accuracy on a pre-split dataset feels abstract.

In this step, you will build a prediction function that takes any review you type in and returns a sentiment label with a confidence score. You will also peek inside the model to see which specific words drive its decisions.

In this step, get ready to:
  • Write a reusable prediction function that works on any text input.
  • Test your model with custom reviews you write yourself.
  • Examine which words the model considers most predictive for each class.
Write the prediction function

Right now, your model can only predict on data that has already been transformed into a TF-IDF matrix. To make it useful on new text, you need a function that handles the full pipeline: cleaning, vectorizing, and predicting in one call.

  • Create a new code cell in your Colab notebook and add the prediction function by pasting this code:
# Cell 7: Predict Sentiment on Custom Reviews
def predict_sentiment(text):
    cleaned = clean_text(text)
    features = vectorizer.transform([cleaned])
    prediction = model.predict(features)[0]
    probability = model.predict_proba(features)[0]
    sentiment = "Positive" if prediction == 1 else "Negative"
    confidence = max(probability) * 100
    return f"{sentiment} (confidence: {confidence:.1f}%)"

What does this code do?

This function wraps the entire prediction pipeline into a single call.

  • clean_text(text) applies the same HTML removal, character stripping, and lowercasing you used on the training data.
  • vectorizer.transform([cleaned]) converts the cleaned text into the same 5,000-feature TF-IDF representation the model was trained on.
  • model.predict_proba() returns probability estimates for each class, so you can report a confidence percentage alongside the label.

Why transform instead of fit_transform?

You used fit_transform earlier to build the vocabulary from training data. For new text, you use transform only. This ensures new reviews are measured against the same vocabulary the model learned from.

Test your model with custom reviews

The real test of a model is how it handles text it has never seen. You will feed it three different reviews: one clearly positive, one clearly negative, and one deliberately ambiguous.

  • In the same code cell, add the test reviews and prediction loop below the function definition:
# Test with custom reviews
test_reviews = [
    "This movie was absolutely fantastic! The acting was superb and the plot kept me on the edge of my seat.",
    "Terrible film. The script was awful, the acting was wooden, and I wanted to leave after 20 minutes.",
    "It was okay. Some parts were good but overall nothing special.",
]

print("Custom Review Predictions:")
print("=" * 50)
for review in test_reviews:
    result = predict_sentiment(review)
    print(f"\nReview: {review[:80]}...")
    print(f"Prediction: {result}")

What does this code do?

The test_reviews list contains three hand-written reviews designed to test different scenarios. The first is strongly positive, the second is strongly negative, and the third is a mixed review that tests how the model handles ambiguity.

The loop runs each review through your predict_sentiment() function and prints a truncated preview of the text alongside the prediction and confidence score.

  • Run the cell by pressing the play button or Shift+Enter.
  • You should see three predictions printed. The first review should be classified as Positive with high confidence, the second as Negative with high confidence, and the third with lower confidence in either direction.

Seeing a NameError?

If you see an error mentioning that clean_text, vectorizer, or model is not defined, make sure you ran all previous cells first. Colab cells must be executed in order.

help me fix a NameError in my prediction function

Examine the most predictive words

One advantage of Logistic Regression is interpretability. Each feature (word) gets a coefficient that tells you how strongly it pushes the prediction toward positive or negative. Looking at these coefficients reveals what your model actually learned.

  • Create a new code cell and add this code to extract and display the top predictive words:
# Cell 8: Most Predictive Words
feature_names = vectorizer.get_feature_names_out()
coefficients = model.coef_[0]

top_positive_indices = coefficients.argsort()[-10:][::-1]
top_negative_indices = coefficients.argsort()[:10]

print("Top 10 words predicting POSITIVE reviews:")
for idx in top_positive_indices:
    print(f"  {feature_names[idx]}: {coefficients[idx]:.4f}")

print("\nTop 10 words predicting NEGATIVE reviews:")
for idx in top_negative_indices:
    print(f"  {feature_names[idx]}: {coefficients[idx]:.4f}")

What does this code do?

  • model.coef_[0] gives you the weight the model assigns to each of the 5,000 features. Positive coefficients push toward "Positive" and negative coefficients push toward "Negative."
  • argsort() returns the indices that would sort the array from smallest to largest. Slicing the last 10 in reverse gives the top positive words. Slicing the first 10 gives the top negative words.
  • The output shows you exactly which words your model relies on most. Words like "excellent" or "worst" will likely appear, which makes intuitive sense for sentiment.
  • Run the cell. You should see two lists of 10 words each, along with their coefficient values.
  • The positive list should contain clearly positive words (like "excellent" or "best"). The negative list should contain clearly negative words (like "worst" or "bad").

Seeing an IndexError?

Make sure you are using model.coef_[0] with the index [0]. The coef_ attribute is a 2D array, so you need to access the first row.

help me fix an IndexError when accessing model coefficients

✔️ Awesome, I've got everything!

Great. Double check that both cells ran without errors and that you can see predictions for all three custom reviews plus the two lists of top predictive words.

ⓧ I'd like to double check the full code

Here is the complete notebook code up to this point. Compare it against your cells to find any differences.

# Cell 1: Setup and Imports
!pip install datasets

import re
import pandas as pd
from datasets import load_dataset
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# Cell 2: Load the IMDB Dataset
dataset = load_dataset("stanfordnlp/imdb")

# Convert to pandas DataFrame
df = pd.DataFrame(dataset["train"])
print(f"Dataset shape: {df.shape}")
print(f"\nColumn names: {df.columns.tolist()}")
print(f"\nLabel distribution:\n{df['label'].value_counts()}")
print(f"\nSample positive review:\n{df[df['label'] == 1]['text'].iloc[0][:300]}...")
print(f"\nSample negative review:\n{df[df['label'] == 0]['text'].iloc[0][:300]}...")

# Cell 3: Text Preprocessing
def clean_text(text):
    text = re.sub(r'<.*?>', '', text)
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    text = text.lower()
    return text

df['clean_text'] = df['text'].apply(clean_text)
print("Text cleaning complete!")
print(f"\nOriginal: {df['text'].iloc[0][:200]}...")
print(f"\nCleaned:  {df['clean_text'].iloc[0][:200]}...")

# Cell 4: TF-IDF Feature Extraction
vectorizer = TfidfVectorizer(max_features=5000, stop_words='english')
X = vectorizer.fit_transform(df['clean_text'])
y = df['label']

print(f"TF-IDF matrix shape: {X.shape}")
print(f"Number of features: {len(vectorizer.get_feature_names_out())}")
print(f"\nSample feature names: {vectorizer.get_feature_names_out()[:10].tolist()}")

# Cell 5: Train/Test Split and Model Training
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"Training set size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
print("\nModel training complete!")

# Cell 6: Evaluate the Model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:\n")
print(classification_report(y_test, y_pred, target_names=['Negative', 'Positive']))

# Cell 7: Predict Sentiment on Custom Reviews
def predict_sentiment(text):
    cleaned = clean_text(text)
    features = vectorizer.transform([cleaned])
    prediction = model.predict(features)[0]
    probability = model.predict_proba(features)[0]
    sentiment = "Positive" if prediction == 1 else "Negative"
    confidence = max(probability) * 100
    return f"{sentiment} (confidence: {confidence:.1f}%)"

# Test with custom reviews
test_reviews = [
    "This movie was absolutely fantastic! The acting was superb and the plot kept me on the edge of my seat.",
    "Terrible film. The script was awful, the acting was wooden, and I wanted to leave after 20 minutes.",
    "It was okay. Some parts were good but overall nothing special.",
]

print("Custom Review Predictions:")
print("=" * 50)
for review in test_reviews:
    result = predict_sentiment(review)
    print(f"\nReview: {review[:80]}...")
    print(f"Prediction: {result}")

# Cell 8: Most Predictive Words
feature_names = vectorizer.get_feature_names_out()
coefficients = model.coef_[0]

top_positive_indices = coefficients.argsort()[-10:][::-1]
top_negative_indices = coefficients.argsort()[:10]

print("Top 10 words predicting POSITIVE reviews:")
for idx in top_positive_indices:
    print(f"  {feature_names[idx]}: {coefficients[idx]:.4f}")

print("\nTop 10 words predicting NEGATIVE reviews:")
for idx in top_negative_indices:
    print(f"  {feature_names[idx]}: {coefficients[idx]:.4f}")

You now have a fully working sentiment analysis system that predicts on any review you type in. Next up, take on the Secret Mission and pit your Logistic Regression model against a Naive Bayes challenger.

Secret mission

Battle of the Algorithms

Your Logistic Regression model works well, but is it the best option? In this secret mission, you will train a completely different algorithm on the same data and compare them head-to-head. You might be surprised by the results.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs. Decide whether to keep your notebook for reference, or delete it entirely.

Resources you used:

  • Google Colab notebook (stored in your Google Drive).
  • Colab runtime session (holds your trained models and data in memory).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your sentiment analyzer or try new reviews.

  • Your notebook stays in Google Drive and you can reopen it anytime.
  • The Colab runtime will automatically disconnect after a period of inactivity, freeing memory. You can reconnect and re-run the cells later.

Pause - I'll come back to this later

Disconnect the Colab runtime to free up memory. Your notebook file stays safe in Google Drive.

  • In your Colab notebook, click Runtime in the top menu bar.
  • Click Disconnect and delete runtime.
  • This frees the session memory. Your notebook code and output stay saved in Google Drive.
  • When you come back, click Connect and re-run all cells to restore your models.

Delete - I don't want to use this again

Remove your notebook from Google Drive entirely.

  • In your Colab notebook, click File in the top menu bar.
  • Click Move to trash.
  • To permanently delete it, open Google Drive at drive.google.com, click Trash in the left sidebar, then right-click the notebook and select Delete forever.
  • The Colab runtime is automatically freed once the notebook is deleted.

That's a Wrap!

That's a Wrap!

Nice work! You just built a sentiment analysis model that can read a movie review and predict whether it is positive or negative. That is a complete machine learning pipeline, from raw text all the way to real-time predictions.

You've learned how to:

  • Load and explore a real-world dataset (the Stanford IMDB dataset with 50,000 movie reviews) and transform raw text into numerical features using TF-IDF vectorization.
  • Train a Logistic Regression classifier that achieves approximately 87-89% accuracy on sentiment classification.
  • Build a reusable prediction function that works on any text input and inspect the model's most predictive words to understand how it makes decisions.
  • Secret Mission: Compared Logistic Regression vs. Naive Bayes classifiers and understood their trade-offs on the same dataset.

Ready to quiz yourself?