import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# this is probably the default, but just in case
%matplotlib inlineCLASSIFICATION - Part 2
A more complex example
# More points: teal above/right of boundary, red below/left
teal_pts = np.array([
[ 2.0, 3.0],
[ 3.0, 2.5],
[ 2.5, 3.5],
[ 3.5, 2.0],
[ 1.5, 2.8],
[ 2.8, 3.2],
[-2, 3.1],
[-1, 2.4],
[-0.5, 3.8],
[2.5, 1.0],
[3.5, 0.5],
[0.5, 2.5],
[1.5, 1.2],
[2.1, 2.2],
[3.5, -0.5],
[0.8, 3.2],
[-0.5, 2.8],
[2.2, 0.2]
])
red_pts = np.array([
[-0.5, 1.1],
[-1.0, 0.5],
[-2.5, 1.0],
[ 0.5, -0.5],
[-1.5, 0.2],
[ 0.2, -1.0],
[-1.5, 1.5],
[-2.5, 2.2],
[-1.5, -1],
[-0.5, -1.5],
[2,-1],
[1.5, -.25],
[0.9, -1.5],
[-2.5, -2],
[-3.2, -0.4],
[-1.5, -2.8],
[2.5, -2.8],
[0.5, -2.5]
])
with plt.style.context("dark_background"):
fig, ax = plt.subplots(figsize=(6, 6))
# plot points
ax.scatter(teal_pts[:,0], teal_pts[:,1], s=35, color="teal", zorder=3)
ax.scatter(red_pts[:,0], red_pts[:,1], s=35, color="orangered", zorder=3)
# axes + grid
ax.axhline(0, color="lightgray", linewidth=1)
ax.axvline(0, color="lightgray", linewidth=1)
ax.set_aspect("equal", adjustable="box")
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.grid(True, alpha=0.3)
ax.set_xlabel(r"$x_1$")
ax.set_ylabel(r"$x_2$")
ax.set_title(r"How to separate these points?", fontsize="10")
plt.show()
# Perceptron with offset
# Run the previous cell first so that the data points are in memory
# Perceptron class with bias
class PerceptronWithBias:
def __init__(self, learning_rate=0.1):
self.lr = learning_rate
self.w = np.random.randn(2) * 0.1 # Weight vector
self.b = 0.0 # Bias term
self.history_w = [self.w.copy()]
self.history_b = [self.b]
def predict(self, x):
"""Return 1 if w^T x + b >= 0, else -1"""
dot_prod = np.dot(self.w, x) + self.b
return 1 if dot_prod >= 0 else -1
def train_epoch(self, X, y):
"""Train for one epoch, return number of misclassifications and details"""
mistakes = 0
mistake_details = []
all_predictions = []
for idx, (xi, yi) in enumerate(zip(X, y)):
dot_prod = np.dot(self.w, xi) + self.b
pred = self.predict(xi)
actual_label = "teal" if yi == 1 else "red"
pred_label = "teal" if pred == 1 else "red"
is_correct = pred == yi
all_predictions.append({
'idx': idx,
'point': xi,
'actual': actual_label,
'predicted': pred_label,
'dot_product': dot_prod,
'correct': is_correct
})
# If prediction is wrong, update w and b
if not is_correct:
# Update: w = w + lr * yi * xi and b = b + lr * yi
self.w += self.lr * yi * xi
self.b += self.lr * yi
mistakes += 1
mistake_details.append(all_predictions[-1])
self.history_w.append(self.w.copy())
self.history_b.append(self.b)
return mistakes, mistake_details, all_predictions
# Prepare data: teal=1, red=-1
X = np.vstack([teal_pts, red_pts])
y = np.hstack([np.ones(len(teal_pts)), -np.ones(len(red_pts))])
# Create an instance of the Perceptron class
perceptron = PerceptronWithBias(learning_rate=0.1)
mistakes_per_epoch = [0]
# Train
for epoch in range(100): # Allow up to 100 iterations
mistakes, mistake_details, all_predictions = perceptron.train_epoch(X, y)
mistakes_per_epoch.append(mistakes)
print(f"\n{'='*80}")
print(f"EPOCH {epoch+1}")
print(f"{'='*80}")
print(f"Weight vector: w = {perceptron.w}")
print(f"Bias term: b = {perceptron.b:.3f}")
print(f"Total misclassifications: {mistakes}\n")
# Show all predictions
print("All Predictions:")
print(f"{'Idx':<5} {'Point':<20} {'Actual':<10} {'Predicted':<10} {'w^T x + b':<15} {'Status':<10}")
print("-" * 80)
for pred in all_predictions:
status = "✓ CORRECT" if pred['correct'] else "✗ WRONG"
print(f"{pred['idx']:<5} {str(pred['point']):<20} {pred['actual']:<10} {pred['predicted']:<10} {pred['dot_product']:<15.3f} {status:<10}")
# Show misclassifications in detail
if mistake_details:
print(f"\nMisclassified points (that were corrected):")
print("-" * 80)
for detail in mistake_details:
print(f" Point {detail['idx']}: {detail['point']} -> "
f"Predicted {detail['predicted']}, but was actually {detail['actual']} "
f"(w^T x + b: {detail['dot_product']:.3f})")
else:
print("\n✓ NO MISCLASSIFICATIONS - CONVERGED!")
if mistakes == 0:
print(f"\nConverged after {epoch+1} epochs!")
break
# Visualize iterations until convergence
num_iterations = len(perceptron.history_w)
cols = 5
rows = (num_iterations + cols - 1) // cols
with plt.style.context("dark_background"):
fig, axes = plt.subplots(rows, cols, figsize=(16, 3.5*rows))
axes = axes.flatten()
for iteration, (w, b, mistakes) in enumerate(zip(perceptron.history_w, perceptron.history_b, mistakes_per_epoch)):
ax = axes[iteration]
# Plot points
ax.scatter(teal_pts[:,0], teal_pts[:,1], s=25, color="teal", zorder=3)
ax.scatter(red_pts[:,0], red_pts[:,1], s=25, color="orangered", zorder=3)
# Decision boundary: w^T x + b = 0
x_range = np.linspace(-4, 4, 100)
if abs(w[1]) > 0.01: # w2 is not ~0
x2_boundary = -(w[0] * x_range + b) / w[1]
ax.plot(x_range, x2_boundary, color="yellow", linewidth=2)
else: # boundary is vertical
ax.axvline(-b / w[0], color="yellow", linewidth=2)
# Weight vector from origin
ax.arrow(0, 0, w[0], w[1],
length_includes_head=True,
head_width=0.12, head_length=0.2,
linewidth=2, color="fuchsia")
ax.text(w[0] + 0.1, w[1] + 0.1, "w", color="fuchsia", fontsize=11)
# Visualize bias as perpendicular offset from origin to boundary
if abs(w[1]) > 0.01 or abs(w[0]) > 0.01:
w_norm = np.linalg.norm(w)
# Closest point on boundary to origin
closest_point = -(b / (w_norm ** 2)) * w
# Perpendicular direction
w_perp = np.array([-w[1], w[0]]) / w_norm
offset = 0.25
# Bias measurement line
start = -0.3 * w + w_perp * offset
end = closest_point + w_perp * offset
ax.annotate("", xy=end, xytext=start,
arrowprops=dict(arrowstyle="<->", color="lawngreen", lw=1.5, alpha=0.8))
mid_point = (start + end) * 0.5
ax.text(mid_point[0] + 0.2, mid_point[1] - 0.3, f"b={b:.2f}",
color="lawngreen", fontsize=9)
# Axes and grid
ax.axhline(0, color="grey", linewidth=1)
ax.axvline(0, color="grey", linewidth=1)
ax.set_aspect("equal", adjustable="box")
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.grid(True, alpha=0.3)
ax.set_xlabel(r"$x_1$", fontsize=9)
ax.set_ylabel(r"$x_2$", fontsize=9)
ax.set_title(f"Iteration {iteration}: {mistakes} misclassifications", fontsize=10)
# Hide unused subplots
for idx in range(num_iterations, len(axes)):
axes[idx].set_visible(False)
plt.tight_layout()
plt.show()
print(f"\n{'='*80}")
print(f"Final weight vector: w = {perceptron.w}")
print(f"Final bias: b = {perceptron.b:.3f}")
print(f"{'='*80}")
================================================================================
EPOCH 1
================================================================================
Weight vector: w = [-0.00976521 0.16060546]
Bias term: b = -0.200
Total misclassifications: 2
All Predictions:
Idx Point Actual Predicted w^T x + b Status
--------------------------------------------------------------------------------
0 [2. 3.] teal teal 0.792 ✓ CORRECT
1 [3. 2.5] teal teal 0.847 ✓ CORRECT
2 [2.5 3.5] teal teal 0.948 ✓ CORRECT
3 [3.5 2. ] teal teal 0.832 ✓ CORRECT
4 [1.5 2.8] teal teal 0.688 ✓ CORRECT
5 [2.8 3.2] teal teal 0.939 ✓ CORRECT
6 [-2. 3.1] teal teal 0.248 ✓ CORRECT
7 [-1. 2.4] teal teal 0.269 ✓ CORRECT
8 [-0.5 3.8] teal teal 0.578 ✓ CORRECT
9 [2.5 1. ] teal teal 0.521 ✓ CORRECT
10 [3.5 0.5] teal teal 0.576 ✓ CORRECT
11 [0.5 2.5] teal teal 0.497 ✓ CORRECT
12 [1.5 1.2] teal teal 0.415 ✓ CORRECT
13 [2.1 2.2] teal teal 0.670 ✓ CORRECT
14 [ 3.5 -0.5] teal teal 0.406 ✓ CORRECT
15 [0.8 3.2] teal teal 0.658 ✓ CORRECT
16 [-0.5 2.8] teal teal 0.408 ✓ CORRECT
17 [2.2 0.2] teal teal 0.343 ✓ CORRECT
18 [-0.5 1.1] red teal 0.118 ✗ WRONG
19 [-1. 0.5] red red -0.260 ✓ CORRECT
20 [-2.5 1. ] red red -0.515 ✓ CORRECT
21 [ 0.5 -0.5] red red -0.035 ✓ CORRECT
22 [-1.5 0.2] red red -0.373 ✓ CORRECT
23 [ 0.2 -1. ] red red -0.123 ✓ CORRECT
24 [-1.5 1.5] red red -0.294 ✓ CORRECT
25 [-2.5 2.2] red red -0.442 ✓ CORRECT
26 [-1.5 -1. ] red red -0.446 ✓ CORRECT
27 [-0.5 -1.5] red red -0.286 ✓ CORRECT
28 [ 2. -1.] red teal 0.220 ✗ WRONG
29 [ 1.5 -0.25] red red -0.255 ✓ CORRECT
30 [ 0.9 -1.5] red red -0.450 ✓ CORRECT
31 [-2.5 -2. ] red red -0.497 ✓ CORRECT
32 [-3.2 -0.4] red red -0.233 ✓ CORRECT
33 [-1.5 -2.8] red red -0.635 ✓ CORRECT
34 [ 2.5 -2.8] red red -0.674 ✓ CORRECT
35 [ 0.5 -2.5] red red -0.606 ✓ CORRECT
Misclassified points (that were corrected):
--------------------------------------------------------------------------------
Point 18: [-0.5 1.1] -> Predicted teal, but was actually red (w^T x + b: 0.118)
Point 28: [ 2. -1.] -> Predicted teal, but was actually red (w^T x + b: 0.220)
================================================================================
EPOCH 2
================================================================================
Weight vector: w = [0.09023479 0.25060546]
Bias term: b = -0.300
Total misclassifications: 3
All Predictions:
Idx Point Actual Predicted w^T x + b Status
--------------------------------------------------------------------------------
0 [2. 3.] teal teal 0.262 ✓ CORRECT
1 [3. 2.5] teal teal 0.172 ✓ CORRECT
2 [2.5 3.5] teal teal 0.338 ✓ CORRECT
3 [3.5 2. ] teal teal 0.087 ✓ CORRECT
4 [1.5 2.8] teal teal 0.235 ✓ CORRECT
5 [2.8 3.2] teal teal 0.287 ✓ CORRECT
6 [-2. 3.1] teal teal 0.317 ✓ CORRECT
7 [-1. 2.4] teal teal 0.195 ✓ CORRECT
8 [-0.5 3.8] teal teal 0.415 ✓ CORRECT
9 [2.5 1. ] teal red -0.064 ✗ WRONG
10 [3.5 0.5] teal teal 0.871 ✓ CORRECT
11 [0.5 2.5] teal teal 0.672 ✓ CORRECT
12 [1.5 1.2] teal teal 0.573 ✓ CORRECT
13 [2.1 2.2] teal teal 0.978 ✓ CORRECT
14 [ 3.5 -0.5] teal teal 0.611 ✓ CORRECT
15 [0.8 3.2] teal teal 0.926 ✓ CORRECT
16 [-0.5 2.8] teal teal 0.510 ✓ CORRECT
17 [2.2 0.2] teal teal 0.481 ✓ CORRECT
18 [-0.5 1.1] red teal 0.067 ✗ WRONG
19 [-1. 0.5] red red -0.415 ✓ CORRECT
20 [-2.5 1. ] red red -0.775 ✓ CORRECT
21 [ 0.5 -0.5] red red -0.130 ✓ CORRECT
22 [-1.5 0.2] red red -0.605 ✓ CORRECT
23 [ 0.2 -1. ] red red -0.293 ✓ CORRECT
24 [-1.5 1.5] red red -0.409 ✓ CORRECT
25 [-2.5 2.2] red red -0.594 ✓ CORRECT
26 [-1.5 -1. ] red red -0.786 ✓ CORRECT
27 [-0.5 -1.5] red red -0.571 ✓ CORRECT
28 [ 2. -1.] red teal 0.230 ✗ WRONG
29 [ 1.5 -0.25] red red -0.227 ✓ CORRECT
30 [ 0.9 -1.5] red red -0.595 ✓ CORRECT
31 [-2.5 -2. ] red red -1.027 ✓ CORRECT
32 [-3.2 -0.4] red red -0.689 ✓ CORRECT
33 [-1.5 -2.8] red red -1.137 ✓ CORRECT
34 [ 2.5 -2.8] red red -0.776 ✓ CORRECT
35 [ 0.5 -2.5] red red -0.881 ✓ CORRECT
Misclassified points (that were corrected):
--------------------------------------------------------------------------------
Point 9: [2.5 1. ] -> Predicted red, but was actually teal (w^T x + b: -0.064)
Point 18: [-0.5 1.1] -> Predicted teal, but was actually red (w^T x + b: 0.067)
Point 28: [ 2. -1.] -> Predicted teal, but was actually red (w^T x + b: 0.230)
================================================================================
EPOCH 3
================================================================================
Weight vector: w = [0.24023479 0.30060546]
Bias term: b = -0.300
Total misclassifications: 2
All Predictions:
Idx Point Actual Predicted w^T x + b Status
--------------------------------------------------------------------------------
0 [2. 3.] teal teal 0.632 ✓ CORRECT
1 [3. 2.5] teal teal 0.597 ✓ CORRECT
2 [2.5 3.5] teal teal 0.803 ✓ CORRECT
3 [3.5 2. ] teal teal 0.517 ✓ CORRECT
4 [1.5 2.8] teal teal 0.537 ✓ CORRECT
5 [2.8 3.2] teal teal 0.755 ✓ CORRECT
6 [-2. 3.1] teal teal 0.296 ✓ CORRECT
7 [-1. 2.4] teal teal 0.211 ✓ CORRECT
8 [-0.5 3.8] teal teal 0.607 ✓ CORRECT
9 [2.5 1. ] teal teal 0.176 ✓ CORRECT
10 [3.5 0.5] teal teal 0.141 ✓ CORRECT
11 [0.5 2.5] teal teal 0.372 ✓ CORRECT
12 [1.5 1.2] teal teal 0.136 ✓ CORRECT
13 [2.1 2.2] teal teal 0.441 ✓ CORRECT
14 [ 3.5 -0.5] teal red -0.109 ✗ WRONG
15 [0.8 3.2] teal teal 0.794 ✓ CORRECT
16 [-0.5 2.8] teal teal 0.142 ✓ CORRECT
17 [2.2 0.2] teal teal 0.809 ✓ CORRECT
18 [-0.5 1.1] red red -0.199 ✓ CORRECT
19 [-1. 0.5] red red -0.540 ✓ CORRECT
20 [-2.5 1. ] red red -1.100 ✓ CORRECT
21 [ 0.5 -0.5] red red -0.080 ✓ CORRECT
22 [-1.5 0.2] red red -0.820 ✓ CORRECT
23 [ 0.2 -1. ] red red -0.313 ✓ CORRECT
24 [-1.5 1.5] red red -0.559 ✓ CORRECT
25 [-2.5 2.2] red red -0.859 ✓ CORRECT
26 [-1.5 -1. ] red red -1.061 ✓ CORRECT
27 [-0.5 -1.5] red red -0.721 ✓ CORRECT
28 [ 2. -1.] red teal 0.480 ✗ WRONG
29 [ 1.5 -0.25] red red -0.015 ✓ CORRECT
30 [ 0.9 -1.5] red red -0.535 ✓ CORRECT
31 [-2.5 -2. ] red red -1.502 ✓ CORRECT
32 [-3.2 -0.4] red red -1.189 ✓ CORRECT
33 [-1.5 -2.8] red red -1.502 ✓ CORRECT
34 [ 2.5 -2.8] red red -0.541 ✓ CORRECT
35 [ 0.5 -2.5] red red -0.931 ✓ CORRECT
Misclassified points (that were corrected):
--------------------------------------------------------------------------------
Point 14: [ 3.5 -0.5] -> Predicted red, but was actually teal (w^T x + b: -0.109)
Point 28: [ 2. -1.] -> Predicted teal, but was actually red (w^T x + b: 0.480)
================================================================================
EPOCH 4
================================================================================
Weight vector: w = [0.24023479 0.30060546]
Bias term: b = -0.300
Total misclassifications: 0
All Predictions:
Idx Point Actual Predicted w^T x + b Status
--------------------------------------------------------------------------------
0 [2. 3.] teal teal 1.082 ✓ CORRECT
1 [3. 2.5] teal teal 1.172 ✓ CORRECT
2 [2.5 3.5] teal teal 1.353 ✓ CORRECT
3 [3.5 2. ] teal teal 1.142 ✓ CORRECT
4 [1.5 2.8] teal teal 0.902 ✓ CORRECT
5 [2.8 3.2] teal teal 1.335 ✓ CORRECT
6 [-2. 3.1] teal teal 0.151 ✓ CORRECT
7 [-1. 2.4] teal teal 0.181 ✓ CORRECT
8 [-0.5 3.8] teal teal 0.722 ✓ CORRECT
9 [2.5 1. ] teal teal 0.601 ✓ CORRECT
10 [3.5 0.5] teal teal 0.691 ✓ CORRECT
11 [0.5 2.5] teal teal 0.572 ✓ CORRECT
12 [1.5 1.2] teal teal 0.421 ✓ CORRECT
13 [2.1 2.2] teal teal 0.866 ✓ CORRECT
14 [ 3.5 -0.5] teal teal 0.391 ✓ CORRECT
15 [0.8 3.2] teal teal 0.854 ✓ CORRECT
16 [-0.5 2.8] teal teal 0.422 ✓ CORRECT
17 [2.2 0.2] teal teal 0.289 ✓ CORRECT
18 [-0.5 1.1] red red -0.089 ✓ CORRECT
19 [-1. 0.5] red red -0.390 ✓ CORRECT
20 [-2.5 1. ] red red -0.600 ✓ CORRECT
21 [ 0.5 -0.5] red red -0.330 ✓ CORRECT
22 [-1.5 0.2] red red -0.600 ✓ CORRECT
23 [ 0.2 -1. ] red red -0.553 ✓ CORRECT
24 [-1.5 1.5] red red -0.209 ✓ CORRECT
25 [-2.5 2.2] red red -0.239 ✓ CORRECT
26 [-1.5 -1. ] red red -0.961 ✓ CORRECT
27 [-0.5 -1.5] red red -0.871 ✓ CORRECT
28 [ 2. -1.] red red -0.120 ✓ CORRECT
29 [ 1.5 -0.25] red red -0.015 ✓ CORRECT
30 [ 0.9 -1.5] red red -0.535 ✓ CORRECT
31 [-2.5 -2. ] red red -1.502 ✓ CORRECT
32 [-3.2 -0.4] red red -1.189 ✓ CORRECT
33 [-1.5 -2.8] red red -1.502 ✓ CORRECT
34 [ 2.5 -2.8] red red -0.541 ✓ CORRECT
35 [ 0.5 -2.5] red red -0.931 ✓ CORRECT
✓ NO MISCLASSIFICATIONS - CONVERGED!
Converged after 4 epochs!

================================================================================
Final weight vector: w = [0.24023479 0.30060546]
Final bias: b = -0.300
================================================================================