← All courses CSIS 3352 · Module 6

Hands-on AI Classification with Logistic Regression

📅 Assignment 2 (50 pts) — Jupyter notebook (.ipynb) · posted in Blackboard · due date: check Blackboard · Also on the Midterm (Modules 1–9) — moved to Tue Oct 27
📌 The Lecture 6 deck states no learning objectives. The 16 objectives below are derived from the deck's sections and the class notebook's steps — 5 groups: concepts · sigmoid & threshold · training & types · code: prep & train · code: evaluate & plot. Each objective's bullets are also its Concept Check rubric.
📌 Assignment 2: run logistic regression on a new dataset following the same steps as the class notebook, then submit your .ipynb. Dean's Oct 4 Blackboard scan showed Assignments 2–5 not yet posted, so its due date isn't on record here — check Blackboard. The syllabus prohibits generative AI on assignments; this page explains the class notebook only.

Study Guide

Module 6 · 5 parts · Lecture 6 deck + the class notebook
📌 Parts 1–3 come from the Lecture 6 deck. Parts 4–5 walk through the class notebook (Social_Network_Ads.csv), whose outputs were reproduced to confirm every number. The notebook has no written explanations, so code behavior there comes from the scikit-learn / NumPy / Matplotlib documentation (links on the Notes tab). Purple Study aid boxes are memory tricks, OOP reminders, and links to earlier modules — not lecture content.

What it is

  • Definition — a statistical / machine learning algorithm used to predict the probability of a categorical outcome.
  • Classification, not regression — despite the name, it's used for classification tasks.
  • Output — a probability between 0 and 1, which is then mapped to a class label.
  • Why it's popular — one of the most widely used, fast, and interpretable models in industry.
  • Goal — estimate the probability of an outcome (between 0 and 1) and make a decision.
⚠️ Not explained in the source: what makes a model "interpretable" — the deck uses the word without defining it.

Classification or regression? Clearing up the name

The name is genuinely confusing: logistic regression is built on regression math, but it solves classification problems.

Linear regressionLogistic regression
PredictsA continuous numberA category — by predicting the probability of belonging to a class
ExamplesA price, a temperature, a scoreYes / no, spam / not spam
Use when the target is…ContinuousCategorical

When do we use it?

Best suited for binary classification — problems with exactly two possible outcomes. It can also be extended to multi-class problems (see the types in Part 3).

AreaThe two outcomes
📧 EmailSpam or not spam
🩺 MedicalDisease present or not present
🏦 FinanceLoan approved or denied
📣 MarketingCustomer will churn or stay

Why it works: it models the relationship between the features and the probability of an outcome using an S-shaped (sigmoid) curve.

Why not just use linear regression?

Linear regression01✗ below 0✗ above 1Logistic regression01✓ stays between 0 and 1
  • Unbounded output — linear regression can output any value from −∞ to +∞. That isn't a valid probability, which must be in [0, 1]. It can predict "impossible" values below 0 or above 1.
  • Poor separation — a straight line doesn't naturally separate two classes well, especially with outliers. A poor fit can misclassify the outliers.
  • The fix — we need an output squeezed between 0 and 1. That's exactly what logistic regression provides: its S-curve asymptotically approaches 0 and 1 (gets ever closer without crossing).
💡 Study aid — one-line memory hook. "Linear goes anywhere; logistic stays in the box." The box is 0 to 1.

The sigmoid function — the heart of logistic regression

σ(z) = 1 / (1 + e^(−z))

It takes any real number z (from −∞ to +∞) and "squashes" it into a range between 0 and 1. The output is read directly as a probability.

−6−4−20246 00.51 z = −6 → σ ≈ 0.0025 z = 0 → σ = 0.5 (equally unsure) z = 6 → σ ≈ 0.9975 p ≥ 0.5 → predict Class 1 p < 0.5 → predict Class 0 steep middle: small change in z →big change in probability z (any real number) p = σ(z)
Input zσ(z)Meaning
Large positive (e.g., 6)≈ 1 (0.9975)Very likely Class 1
0exactly 0.5Equally unsure — the most uncertain point
Large negative (e.g., −6)≈ 0 (0.0025)Very likely Class 0
  • Steep region — near the middle, a small change in input causes a large change in probability.
  • Why it matters — binary classification needs probabilities, not raw scores; the sigmoid turns predictions into probabilities.
⚠️ Not explained in the source: what e is. The deck uses it in the formula without defining it. The exact values 0.9975 and 0.0025 are calculated from the formula; the deck only says "≈ 1" and "≈ 0".

From linear equation to probability (two steps)

features x₁ … xₙ→ z = w₁x₁ + … + wₙxₙ + b→ σ(z)→ p (0 to 1)→ threshold 0.5→ Class 0 or 1
StepFormulaWhat happens
1 · Linear partz = w₁x₁ + w₂x₂ + … + wₙxₙ + bA weighted sum of the inputs — exactly like linear regression
2 · Squashp = σ(z) = 1 / (1 + e^(−z))Pass z through the sigmoid to get a probability
  • What training learns — the best weights (w) and bias (b).
  • The foundation — linear combination, then sigmoid: that two-step process is the entire foundation of logistic regression.

Worked example (practice numbers)

📌 These weights are made up for practice. Only the formulas come from the lecture.
WeightsInputzp = σ(z)Class (0.5 threshold)
w₁ = 2, w₂ = −1, b = 1x₁ = 1, x₂ = 32 − 3 + 1 = 00.5Class 1 (p ≥ 0.5) — but the model is equally unsure
x₁ = 3, x₂ = 16 − 1 + 1 = 6≈ 0.9975Class 1

Decision boundary & threshold

  • Probability first — the model outputs a probability, e.g., p = 0.82.
  • Threshold — we convert that probability into a class using a threshold, commonly 0.5. The 0.5 point is the decision boundary.
  • Decision rule — p ≥ 0.5 → Class 1 · p < 0.5 → Class 0. So p = 0.82 → Class 1.
  • Adjustable — the threshold can move, depending on whether false positives or false negatives matter more.
Move the threshold…EffectChoose this when…
Lower (below 0.5)Fewer false negatives · more false positivesMissing a real positive is the worse mistake
Higher (above 0.5)Fewer false positives · more false negativesA false alarm is the worse mistake
📌 The "choose this when" column is a plain restatement of the lecture's trade-off; the lecture doesn't give scenario examples. The practice table below uses made-up probabilities.
Model's p →0.200.400.550.650.90Predicted Class 1
Threshold 0.3011114 of 5 — catches more, more false alarms
Threshold 0.5001113 of 5 — the default
Threshold 0.7000011 of 5 — fewer false alarms, misses more
⚠️ Internal inconsistency in the lecture: one diagram labels the high-probability zone "P > 0.5 → Class 1", while the decision rule everywhere else says p ≥ 0.5. Use ≥ 0.5; it only matters when p is exactly 0.5.

The cost function: log loss (binary cross-entropy)

Cost = −[ y·log(p) + (1 − y)·log(1 − p) ] # y = the true label (0 or 1) p = the predicted probability
  • What it measures — how wrong a single prediction is. During training it's averaged across all data points.
  • Penalty shape — it heavily penalizes confident, wrong predictions, much more than a small, cautious miss.
  • How training uses it — the model adjusts its weights (via gradient descent) to minimize this cost over many training iterations.
123400.250.50.751 0 p = 0.9 → 0.11p = 0.1 → 2.30 (confident & wrong) true label y = 1true label y = 0 predicted probability p cost
💡 Study aid — reading the formula. Only one half of the bracket is ever "on". If y = 1, the second term is multiplied by 0, so Cost = −log(p): it wants p near 1. If y = 0, the first term drops out, so Cost = −log(1 − p): it wants p near 0.
⚠️ Not explained in the source: gradient descent (named only — how it adjusts the weights isn't covered) and the base of "log". The chart and the 0.11 / 2.30 values use the natural log; they illustrate the shape the lecture describes.

Types of logistic regression

TypeClassesExample
BinaryExactly two — the most common caseSpam vs. not spam · 0 vs. 1
MultinomialThree or more, no natural orderCat vs. dog vs. bird
OrdinalThree or more, with a natural orderLow vs. medium vs. high risk
💡 Study aid — two questions. How many classes? Two → binary. Three or more → can you rank them? Yes → ordinal (ordered). No → multinomial.
⚠️ Not explained in the source: how logistic regression is actually extended to multinomial or ordinal problems — the lecture only names and defines the types.

Real-world use cases

Use caseThe question it answers
Spam detectionIs this email spam, or not spam?
Medical diagnosisIs a disease present, or absent?
Credit scoringShould this loan be approved, or denied?
Customer churnWill this customer stay, or leave?
Ad targetingWill this user click the ad, or not?

Summary & key takeaways

  • Logistic regression predicts probabilities for classification, not continuous values.
  • Core math: linear combination → sigmoid → probability → threshold → class.
  • Trained by minimizing log loss using gradient descent.
  • A simple, fast, highly interpretable baseline model for binary classification.

The whole notebook on one line

import→ load CSV → X, y→ split 75 / 25→ scale→ train (.fit)→ predict→ confusion matrix + accuracy→ plot
🧩 Study aid — Modules 3 & 4 already covered the first four steps. Import, load, split, and scale are the same preprocessing pipeline. What's new here is LogisticRegression, .fit, .predict, the evaluation tools, and the plot.

1 · Import the libraries

import numpy as np import matplotlib.pyplot as plt import pandas as pd
  • np — arrays and math (used later for concatenate, meshgrid, arange).
  • plt — plotting (used for the decision-boundary chart).
  • pd — loading the CSV into a DataFrame.

2 · Import the dataset → features X, target y

dataset = pd.read_csv('Social_Network_Ads.csv') X = dataset.iloc[:, :-1].values # features: Age, EstimatedSalary y = dataset.iloc[:, -1].values # target: Purchased (0 or 1)
AgeEstimatedSalaryPurchased
19190000
35200000
26430000
………
400 rows total · 257 rows with 0, 143 rows with 1 (counted from the class CSV)
  • iloc[:, :-1] — all rows, every column except the last → the features.
  • iloc[:, -1] — all rows, last column only → the target.
  • .values — hands back the plain NumPy array.
  • Why logistic regression fits — Purchased has only two values, 0 and 1 → binary classification.
🔤 X / y reminder. Uppercase X = the feature matrix (2-D: 400 rows × 2 columns). Lowercase y = the target vector (1-D: 400 values). Keep that casing in every variable that follows: X_train, X_test, y_train, y_test, y_pred.

3 · Split into training and test sets

from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0)
train · 300 rows (75%)
test · 100 (25%)
  • test_size = 0.25 — 25% of 400 rows → 100 test rows, 300 training rows.
  • Return order — X_train, X_test, y_train, y_test (both X pieces, then both y pieces).
  • random_state = 0 — fixes the shuffle so the same rows land in the test set every run.
  • Class balance after the split — y_test holds 68 zeros and 32 ones (from the printed y_test).

4 · Feature scaling

from sklearn.preprocessing import StandardScaler sc = StandardScaler() X_train = sc.fit_transform(X_train) # learn mean & std from TRAIN, then scale X_test = sc.transform(X_test) # reuse the TRAIN mean & std
AgeEstimatedSalary
First training row, before4439000
Same row, after scaling0.58164944−0.88670699
  • Why — age (18–60) and salary (15,000–150,000) are on wildly different scales; after scaling both sit around 0.
  • Fit on train only — the scaler learns its numbers from training data and reuses them on test data, so no test information leaks into training.
🧩 OOP reminder — the scaler remembers. sc = StandardScaler() creates an object (an instance of the StandardScaler class, like new StandardScaler() in Java). fit_transform stores the training mean and standard deviation inside sc (its fields). Every later sc.transform(...) — on X_test, on the new person, on the plot grid — reuses those stored values. That's why the same sc object shows up four times in the notebook.

5 · Create and train the logistic regression model

from sklearn.linear_model import LogisticRegression classifier = LogisticRegression(random_state = 0) # create: an untrained model classifier.fit(X_train, y_train) # train: learn weights & bias
LogisticRegression(random_state=0)
  • Create ≠ train — the first line only builds the model object. Nothing is learned until .fit.
  • What .fit learns — the weights (one per feature: Age, EstimatedSalary) and the bias from Part 2's z = w₁x₁ + w₂x₂ + b, by minimizing log loss.
  • Training data only — .fit sees X_train and y_train; the test set stays hidden for grading the model.
  • The printed line — Jupyter just echoes the fitted model's settings; it isn't a result.
🧩 OOP reminder — methods change an object's state. classifier is an object; .fit() is a method called on it. Like a Java method that updates private fields, fit stores the learned weights inside classifier. Afterward, .predict() — another method on the same object — uses them. Same object, two methods: one to learn, one to answer.
📌 From the scikit-learn docs: random_state on LogisticRegression only matters for certain solvers; with the default solver it changes nothing. It's in the class code for consistency.

6 · Predict a single new result

print(classifier.predict(sc.transform([[30, 87000]])))
[0]
  • Read it inside-out — [[30, 87000]] → sc.transform scales it → classifier.predict → print.
  • Result — [0]: a 30-year-old earning 87,000 is predicted not to purchase.
  • Why scale — the model was trained on scaled numbers, so new inputs must be scaled the same way.
  • Why double brackets — one row with two feature values: a 2-D input shaped like X.
  • predict returns the class (0 or 1), with the threshold already applied — not the probability.
💡 Study aid — seeing the probability (not run in class). scikit-learn also has classifier.predict_proba(...). On this same person it returns about [[0.887 0.113]]: an 11% chance of Purchased = 1. That's below 0.5, so predict said 0 — Part 2's threshold rule in action. Not on the quiz.

7 · Predict the test set results

y_pred = classifier.predict(X_test) print(np.concatenate((y_pred.reshape(len(y_pred),1), y_test.reshape(len(y_test),1)),1))
[[0 0] [0 0] … [1 1] # left = predicted (y_pred), right = actual (y_test) [1 0] # predicted 1, actually 0 → false positive [0 1] # predicted 0, actually 1 → false negative …]
  • y_pred — one 0/1 prediction for each of the 100 test rows.
  • .reshape(len(y_pred), 1) — turns the flat array of 100 values into one column (100 × 1).
  • np.concatenate((a, b), 1) — glues the two columns side by side (the 1 means "join as columns"), giving a 100 × 2 table.
  • Reading it — matching numbers = correct; different numbers = a mistake.
🧩 OOP reminder — methods on arrays, functions from modules. y_pred.reshape(...) is a method: y_pred is a NumPy array object and reshape belongs to it. np.concatenate(...) is a function from the np module — no object before the dot, just the library name. Rule of thumb: object.method() vs. library.function().

8 · Make the confusion matrix and accuracy

from sklearn.metrics import confusion_matrix, accuracy_score cm = confusion_matrix(y_test, y_pred) # actual first, predicted second print(cm) accuracy_score(y_test, y_pred)
[[65 3] [ 8 24]] 0.89

scikit-learn's layout: rows = actual, columns = predicted, class 0 first.

Predicted 0Predicted 1
Actual 0TN = 65
correctly said "won't buy"
FP = 3
said "buy" — didn't
Actual 1FN = 8
said "won't buy" — did
TP = 24
correctly said "buy"
  • Accuracy — (TP + TN) / total = (24 + 65) / 100 = 0.89.
  • Mistakes — 3 + 8 = 11 of 100.
  • Row totals — 65 + 3 = 68 actual 0s; 8 + 24 = 32 actual 1s (matches y_test).
⚠️ Layout trap. Module 5's confusion matrix listed the positive class first (TP top-left, TN bottom-right). scikit-learn prints class 0 first, so the same model reads [[TN FP] [FN TP]] — TN top-left, TP bottom-right. Same four numbers, flipped positions. The layout itself is from the scikit-learn documentation; the notebook doesn't label it.
Same result in Module 5's layoutPredicted positive (1)Predicted negative (0)
Actual positive (1)TP = 24FN = 8
Actual negative (0)FP = 3TN = 65
💡 Study aid — Module 5 formulas on the class result (not computed in class). Precision = TP / (TP + FP) = 24 / 27 ≈ 0.89. Recall = TP / (TP + FN) = 24 / 32 = 0.75. F1 = 2·P·R / (P + R) ≈ 0.81. Recall is the weak spot: the model missed 8 of the 32 real buyers.
🧩 OOP reminder — a function, not a method. confusion_matrix(y_test, y_pred) has no object in front of it. It was imported straight from sklearn.metrics, so you call it like a static utility function in Java. Compare classifier.predict(...), which needs the trained classifier object.

9 · Visualize the training set results

from matplotlib.colors import ListedColormap X_set, y_set = sc.inverse_transform(X_train), y_train # A · back to real units X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 10, stop = X_set[:, 0].max() + 10, step = 0.25), np.arange(start = X_set[:, 1].min() - 1000, stop = X_set[:, 1].max() + 1000, step = 0.25)) # B · grid plt.contourf(X1, X2, classifier.predict(sc.transform(np.array([X1.ravel(), X2.ravel()]).T)).reshape(X1.shape), alpha = 0.75, cmap = ListedColormap(('red', 'green'))) # C · color regions plt.xlim(X1.min(), X1.max()) plt.ylim(X2.min(), X2.max()) for i, j in enumerate(np.unique(y_set)): plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1], c = ListedColormap(('red', 'green'))(i), label = j) # D · dots plt.title('Logistic Regression (Training set)') plt.xlabel('Age') plt.ylabel('Estimated Salary') plt.legend() plt.show()
PieceWhat it does
A · sc.inverse_transform(X_train)Undoes the scaling so the axes show real ages and salaries.
B · np.meshgrid(np.arange(...), np.arange(...))Builds a dense grid of (age, salary) points covering the chart: from 10 years below the youngest to 10 above the oldest, and 1,000 below to 1,000 above the salary range, in steps of 0.25. X1 holds each point's age, X2 its salary.
C · plt.contourf(...)ravel() flattens the grids, np.array([...]).T stacks them into rows of (age, salary), sc.transform scales them, classifier.predict labels every point, and .reshape(X1.shape) folds the answers back into the grid. contourf then fills the background: red = predicted 0, green = predicted 1 (alpha = 0.75 = slightly see-through).
D · plt.scatter loopPlots each real training point, colored by its true label: red for 0, green for 1. enumerate(np.unique(y_set)) loops over the two classes.
LabelsTitle, axis names, and a legend showing 0 (red) and 1 (green).
Class notebook output: Logistic Regression (Training set). Age on the horizontal axis from about 10 to 70, Estimated Salary on the vertical axis from about 15,000 to 150,000. A straight diagonal edge splits a red region on the left (younger, lower salary, predicted 0) from a green region on the right (older or higher salary, predicted 1). Red and green dots are the training points; a few sit on the wrong color.
  • The straight edge between red and green is the decision boundary — where the predicted probability is 0.5.
  • Pattern — older people and higher earners mostly fall in green (purchased). The edge runs from about age 30 at 150,000 down to about age 51 at 20,000.
  • Misclassified points — a red dot in green, or a green dot in red, is a training point the model got wrong.
  • Training set only — the notebook plots only the training data.
  • The warning printed above the chart — a Matplotlib UserWarning about the c argument. It's harmless; the chart still draws.
💡 Study aid — why the boundary is a straight line. p = 0.5 exactly when z = 0 (Part 2). And z = w₁·Age + w₂·Salary + b = 0 is the equation of a straight line. So logistic regression with two features always draws a straight decision boundary.
🔤 X / y reminder. X1 and X2 are grid coordinates (every age and every salary on the chart), not new features. X_set / y_set are just the unscaled training features and target, renamed for plotting.

Flashcards

Question
Answer

Practice Quiz

📌 Questions tagged Practice numbers use made-up weights, or apply Module 5's formulas to the class result, so you can drill the math. Fill-in-the-blank answers must match exactly (case doesn't matter).

Concept Check

Explain each objective in your own words, then self-grade against the rubric

Notes Template

Module 6 · Print this tab for handwritten recall practice

📚 Reference documentation (official — for lookup, not page content)

Linear vs logistic regression

Why not a straight line? (3)

Sigmoid formula + 3 key values

Two steps: z, then σ(z)

Threshold rule + lower/raise trade-off

Log loss formula — what it punishes

Binary · multinomial · ordinal

5 real-world use cases

Notebook steps 1–9 from memory

fit_transform vs transform

sklearn confusion matrix: [[? ?] [? ?]]

Reading the boundary plot