In the print dialog, choose "Save as PDF" as the destination.
Machine Learning, Week 10
Sentiment Analysis with Binary Classification
Turn a review into word counts, then into a score between 0 and 1 with logistic regression.
Objectives
- ▸Explain sentiment analysis as binary classification: the score is the probability of the positive class
- ▸Describe what
CountVectorizerdoes: lower case, tokens, stop words, unigrams and bigrams, the vocabulary - ▸Compute a review's score by hand from the bias, the weights and the sigmoid
- ▸Train and evaluate the notebook's model: confusion matrix, accuracy, precision, recall, ROC AUC
- ▸Score new reviews with
predict_proba, and explain where a bag of words goes wrong
Week 10 of the plan
Where This Sits in the Course
- ▸The notebook: Sentiment Analysis, logistic regression on 50,000 movie reviews labelled
0or1 - ▸Real-world scenario: give a text a score from 0 to 1, from negative to positive
- ▸Project milestone this week: Project Teams Presentations and Discussions
The Scenario: Score a Restaurant Review
| Review | The plan expects |
|---|---|
| Best meal I've ever had and awesome service, too! | 0.9 or higher |
| Long lines and poor customer service | closer to 0 |
Plan for the Two Hours
| Part | What we do | Time |
|---|---|---|
| 1 and 2 | Sentiment as a probability; text to numbers with CountVectorizer | 40 min |
| 3 | Scoring a review by hand | 20 min |
| 4 | The notebook in Colab: train, evaluate, score | 20 min |
| 5 | Limits of the model, and your project | 10 min |
| 6 | Practice with answers | 30 min |
Part 1
Sentiment as a Probability
Binary classification on text
What Is Sentiment Analysis?
- Sentiment analysis
- Assigning a text a score from 0 to 1, where 0 is negative and 1 is positive sentiment.
| Term | In this week's data |
|---|---|
| Sample | one review, a string of text |
Label y | 0 negative, 1 positive |
Features x | the word counts of the review |
Score p | the probability that the label is 1 |
The Pipeline
Review
a string
Vectorize
CountVectorizer
Score z
b + Σ w·x
Squash
p = σ(z)
Decide
p ≥ 0.5: positive
Part 2
Text to Numbers
CountVectorizer and the bag of words
The Bag of Words
- Bag of words
- Keep a list of known words, the vocabulary, and describe each text by how many times each word of the list appears in it.
- ▸A model cannot work with text: it needs numbers
- ▸One row per text, one column per vocabulary entry, cells are counts
- ▸The order of the words is thrown away: that is why it is a bag
CountVectorizer in the Notebook
vectorizer = CountVectorizer(
ngram_range=(1, 2),
stop_words='english',
min_df=20)
x = vectorizer.fit_transform(
df['Text'])
y = df['Sentiment']sklearn.feature_extraction.text- 1Lower case:
pOOrbecomespoor - 2Tokens: runs of 2 or more letters, digits or
_ - 3Remove stop words: 318 words such as
the,and, evennot - 4Add bigrams: neighbouring pairs such as
customer service - 5Keep the vocabulary: entries found in at least 20 reviews (
min_df=20) - 6Count each entry: one row per review
From the notebook
Worked Example: What the Vectorizer Keeps
text = vectorizer.transform(['The long l3ines and; pOOr customer# service really turned me off...123.'])
text = vectorizer.inverse_transform(text)
print(text)Step 1: Lower Case and Tokens
the, long, l3ines, and, poor, customer, service,
really, turned, me, off, 123Step 2: Remove the Stop Words
long, l3ines, poor, customer, service, really, turned, 123Step 3: Add the Bigrams
long l3ines, l3ines poor, poor customer, customer service,
service really, really turned, turned 123Step 4: Keep the Vocabulary
[array(['customer', 'long', 'poor', 'really', 'service', 'turned'],
dtype='<U25')]Worked Example: Three Reviews as a Count Matrix
Unigrams only. Predict the columns, and the cell of review 2 under awesome: the vectorizer counts
vec = CountVectorizer(stop_words='english')
x = vec.fit_transform(['Long lines and poor customer service',
"Best meal I've ever had and awesome service, too!",
'Awesome meal, awesome service']) awesome best customer lines long meal poor service ve
0 0 0 1 1 1 0 1 1 0
1 1 1 0 0 0 1 0 1 1
2 2 0 0 0 0 1 0 1 0Part 3
Scoring a Review by Hand
The logistic regression of week 4, on word counts
The Formula of Week 4
| Symbol | Meaning |
|---|---|
b | the bias, model.intercept_: here -0.0488 |
w_i | the weight of vocabulary entry i, from model.coef_ |
x_i | how many times entry i appears: mostly 0 |
σ, e | the sigmoid; e = 2.71828, and σ(0) = 0.5 |
Which Words Push Hardest?

- ▸A weight above 0 pushes to positive, below 0 to negative
- ▸Most negative:
disappointment-2.1646,waste-2.1606,worst-2.1519 - ▸Most positive:
funniest1.6471,10 101.4037,excellent1.3094 - ▸No known word at all:
p = σ(-0.0488) = 0.4878
By hand, step 1
Worked Example: Long Lines and Poor Customer Service
Which features does the model see?
| Feature | x | w | w·x |
|---|---|---|---|
customer | 1 | -0.1980 | -0.1980 |
lines | 1 | -0.1184 | -0.1184 |
long | 1 | -0.0284 | -0.0284 |
poor | 1 | -1.4874 | -1.4874 |
service | 1 | -0.4832 | -0.4832 |
Step 2: Compute z
b = -0.0488, weights rounded to 4 decimals
Step 3: The Sigmoid and the Decision
By hand, step 1
Worked Example: Best Meal I've Ever Had
Best meal I've ever had and awesome service, too!
| Feature | x | w | w·x |
|---|---|---|---|
awesome | 1 | 0.7310 | 0.7310 |
best | 1 | 0.7090 | 0.7090 |
meal | 1 | 0.0062 | 0.0062 |
service | 1 | -0.4832 | -0.4832 |
ve | 1 | -0.0820 | -0.0820 |
Step 2: z and p
The plan says 0.9 or higher
Why Not 0.9?
Positive, but predict_proba gives 0.69682
- ▸The model learned from movie reviews: there
serviceis a negative word (-0.4832) - ▸
staffis -0.4587 too, whilerudeis only -0.0364 - ▸A short text has few features, so
Σ w·xstays small andpstays near the middle - ▸The number depends on the training data; the same code trained on restaurant reviews would learn restaurant weights
Try It: The Sentiment Meter
Part 4
The Notebook in Colab
Train, evaluate, score
Open the Notebook in Colab
https://colab.research.google.com/github/
jeffprosise/Machine-Learning/blob/master/
Sentiment%20Analysis.ipynb| In the notebook | Change it to |
|---|---|
read_csv('Data/reviews.csv', ...) | read the file from the repository (next slide) |
plot_confusion_matrix(...) | ConfusionMatrixDisplay.from_estimator(...) |
pickle.dump(..., open('Data/sentiment.pkl', 'wb')) | save to 'sentiment.pkl' |
Load the Data, Remove Duplicates
url = ("https://raw.githubusercontent.com/"
"jeffprosise/Machine-Learning/master/Data/reviews.csv")
df = pd.read_csv(url, encoding="ISO-8859-1")
df.groupby('Sentiment').describe()
df = df.drop_duplicates()Vectorize and Split
x = vectorizer.fit_transform(df['Text'])
y = df['Sentiment']
x_train, x_test, y_train, y_test = train_test_split(
x, y, test_size=0.5, random_state=0)- ▸
x.shapeis(49581, 33752): 17,528 unigrams and 16,224 bigrams - ▸Only 0.30% of the 1.67 billion cells are not 0: a sparse matrix
- ▸50/50 split: 24,790 reviews to train, 24,791 to test
Train the Model
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000, random_state=0)
model.fit(x_train, y_train)- ▸
fitlearns 33,752 weights, one per vocabulary entry, plus the biasb - ▸
max_iter=1000gives the solver room to converge on so many features; our run needed 78 - ▸Nothing new: the same
LogisticRegressionas week 4
The Confusion Matrix
On the 24,791 test reviews

ConfusionMatrixDisplay.from_estimator(
model, x_test, y_test,
display_labels=['Negative', 'Positive'],
cmap='Blues',
xticks_rotation='vertical')Worked Example: Score the Model
TN 10792, FP 1577, FN 1456, TP 10966. Predict A accuracy, and P precision and R recall of positive
ROC AUC: Judging the Scores
from sklearn.metrics import roc_auc_score
probabilities = model.predict_proba(x_test)
roc_auc_score(y_test, probabilities[:, 1])Try It: Move the Threshold
Score New Reviews
You computed both by hand. Predict the output
reviews = ['Long lines and poor customer service',
"Best meal I've ever had and awesome service, too!"]
model.predict_proba(vectorizer.transform(reviews))[:, 1][0.08594148 0.69681757]Part 5
Where a Bag of Words Goes Wrong
88% accurate, and still easy to fool
Negation Is Thrown Away
| Review | Features | p |
|---|---|---|
| The food was good | food, good | 0.5885 |
| The food was not good | ? | ? |
Word Order Is Thrown Away
| Review | p |
|---|---|
| Great food but terrible service | 0.2591 |
| Terrible food but great service | 0.2591 |
Before you practise
Common Mistakes
- ▸Reading
Data/reviews.csvin Colab from a folder that does not exist there - ▸Calling
plot_confusion_matrix: useConfusionMatrixDisplay.from_estimator - ▸
fit_transformon new reviews: new text needsvectorizer.transform([...]) - ▸Passing a plain string to
transform: it needs a list of texts - ▸Reading
predict_proba(...)[0][0]: the sentiment score is column 1 - ▸Expecting
notto flip a score whilestop_words='english'removes it
Your team project
Project Milestone: Presentations and Discussions
- ▸This week: Project Teams Presentations and Discussions
- ▸Next week, final submission: code on a GitHub repository, a 5-minute video, the proposal, the presentation, the logo
- ▸Additionally: a project user interface (mobile, web or desktop)
- ▸Text in your project? Vectorize, train, score with
predict_proba, and save the model and the vocabulary
Key Takeaways
- 1Sentiment analysis is binary classification: the score is
p, the probability of positive - 2
CountVectorizer: lower case, tokens, stop words out, unigrams and bigrams, vocabulary only - 3The score is
σ(b + Σ w·x): every known word pushes by its weight - 4The notebook's model: accuracy 0.8777, ROC AUC 0.9452 on 24,791 test reviews
- 5A bag of words ignores order and, without stop words, negation; the weights reflect the training data
Part 6
Practice: Your Turn
About 30 minutes, answers follow each task
About 5 minutes
Practice 1: Vectorize by Hand
The waiter was rude and the food was cold.
tokens: the, waiter, was, rude, and, the, food, was, cold
kept: waiter, rude, food, cold
n-grams: waiter, rude, food, cold, waiter rude, rude food, food coldAbout 5 minutes
Practice 2a: Score It by Hand
The waiter was rude and the food was cold. Only these four words are in the vocabulary; b = -0.0488. Compute z, p and the prediction.
| Feature | w |
|---|---|
waiter | -0.0937 |
rude | -0.0364 |
food | 0.2302 |
cold | -0.2052 |
About 5 minutes
Practice 2b: A Very Positive Review
Excellent food, friendly staff, highly recommend! b = -0.0488
About 5 minutes
Practice 3: Choose a Threshold
Goal: find the most negative reviews. Compute TN / (TN + FP) at both. Which wins, at what cost?
| t | TN, FP | FN, TP |
|---|---|---|
| 0.3 | 10232, 2137 | 1028, 11394 |
| 0.7 | 11251, 1118 | 2054, 10368 |
About 10 minutes
Practice 4: In Colab
- 1Run the notebook with the three changes; score both scenario reviews in one call
- 2Score
The food was goodandThe food was not good - 3Refit with
CountVectorizer(ngram_range=(1, 2), min_df=20): stop words kept - 4Same split (
random_state=0), retrain: number of features, accuracy, ROC AUC? - 5Score the two food reviews again. What changed, and why?
Answers
Practice 4: Answer
| Question | Our run |
|---|---|
| Scenario reviews | [0.08594148 0.69681757] |
| Food reviews, stop words removed | 0.5885 and 0.5885 |
| Stop words kept: features, accuracy, AUC | 74,938, 0.8967, 0.9574 |
| Food reviews, stop words kept | good 0.7082, not good 0.2912 |
| Why | not good is now a feature, weight -0.7838 |
Open this lesson
Mahmoud Abas|Sentiment Analysis with Binary Classification