Classifying documents with Bayes' theorem and the assumption that words are independent given the class: training by counting, smoothing, prediction by an argmax over class scores.
Chain:Bayes’ Theorem turns Pr(c∣d) into Pr(d∣c)Pr(c)/Pr(d); Pr(d) is constant over classes and dropped; conditional independence of the words factorizes Pr(d∣c) into the pwc.
Where things go wrong: a word never seen in class c zeroes its score. Laplace Smoothing repairs this; the log-space implementation needs it, and avoids underflow of long products.
Implementation patterns in the exams:
Variant
Idea
Exam
for-loops over word_ids
multiply pc and pwc, argmax, tie by larger id
WS 17/18
matrix-vector product
row c of M is logpwc plus logpc, vector is counts plus a 1
WS 19/20
word to id map, return class and probability
product per class, track best
WS 22/23
Naive Bayes is linear. With two classes the prediction is w⋅x>b for word counts x (Linear Classifier); this links the topic to Linear Classifiers and Logistic Regression, where the same decision rule is learned directly instead of via counting.
Prior vs. evidence: repeating a document multiplies the word evidence but not the prior, so only with equal priors is the prediction unchanged.
SuccessSolution (not in the exam note; written by Claude)
defnaive_bayes_predict(pc, pwc, word_ids):
best, best_p =0, -1for c in range(len(pc)):
p = pc[c]
for w in word_ids:
p *= pwc[c][w]
if p >= best_p: # >= : ties go to the larger id best, best_p = c, p
return best
SuccessSolution (written by Claude)
Let sc(d1)=p⋅Qc with the common prior p and Qc=∏w∈d1pwc. Then sc(d2)=p⋅Qc2. For Qc≥0, Qc↦Qc2 is strictly increasing, so Qc≤Qc′⟺Qc2≤Qc′2, and the ordering of the classes (also the tie-breaking) is the same.
SuccessSolution (written by Claude)
No. Two classes, pA=0.9, pB=0.1, one word w with pwA=0.2, pwB=0.5. For d1=(w): 0.9⋅0.2=0.18>0.1⋅0.5=0.05, predicts A. For d2=(w,w): 0.9⋅0.04=0.036<0.1⋅0.25=0.025? No, 0.036>0.025, so adjust: take pwA=0.1, pwB=0.5 and pA=0.6, pB=0.4. d1: 0.06<0.2 predicts B … use instead pwA=0.5, pwB=0.4, pA=0.4, pB=0.6: d1: 0.2 vs 0.24 gives B; d2: 0.1 vs 0.096 gives A. The prediction changes, because the prior is not squared.
SuccessSolution (written by Claude)
4.3: see the second example in Naive Bayes Classifier > Example: pX=1/3, pY=2/3, p1X=3/4, p2X=1/4, p1Y=1/4, p2Y=3/4, so w=(ln3,−ln3), b=ln2.
4.4: need b=0 and w1=−w2>0. Take equal class sizes (pX=pY) and a point (1,0) labeled X and a point (0,1) labeled Y: p1X=1, p2X=0, p1Y=0, p2Y=1 (smooth with the same ε if zero probabilities are a concern, giving w=(lnr,−lnr) with r>1 and b=0).
SuccessSolution
defsmoothing(pwc, eps):
for pw in pwc:
for i in range(len(pw)):
pw[i] += eps
sum_pw = sum(pw)
for i in range(len(pw)):
pw[i] /= sum_pw
Non-zero: adding eps > 0 makes every entry strictly positive; dividing by a positive sum keeps it so.
Order: pa<pb⇒pa+ε<pb+ε, and dividing by the same positive constant preserves the strict inequality.
Distribution: after dividing by sum_pw=∑i(pi+ε) the entries of each class sum to 1.
SuccessSolution
defpredict(doc, pwc, pc):
doc.append(1)
for i in range(len(pwc)):
pwc[i].append(pc[i])
for j in range(len(pwc[i])):
pwc[i][j] = math.log(pwc[i][j])
predictions = pwc * doc
return argmax(predictions)
Appending 1 to the document and pc[i] to row i puts the prior into the dot product. The logarithm turns ∏jp(wj∣c)nj⋅p(c) into ∑jnjlogp(wj∣c)+logp(c), which is the dot product of the log row with the document vector. argmax returns the class with the highest log-probability.
SuccessSolution
8 numbers are even, 8 odd: pA=pB=1/2.
All documents have 4 words, so nA=nB=8⋅4=32.
Class A (even): the last bit is always 0; each of the other three positions has four 0s and four 1s. Count of word 0: 8+4+4+4=20; of word 1: 0+4+4+4=12.
Class B (odd): the last bit is always 1. Count of word 0: 12; of word 1: 20.
p0∣A=20/32=5/8, p1∣A=3/8, p0∣B=3/8, p1∣B=5/8.
For “0 0 0 1”: Pr(B∣0001)Pr(A∣0001)=pBp0∣B3p1∣BpAp0∣A3p1∣A=(3/8)3(5/8)(5/8)3(3/8)=3252=925>1, so class A is predicted. ■
SuccessSolution
defpredict(doc, wordIds, pwc, pc):
c_best, p_best =-1, -1for c in range(0, len(pc)):
p = pc[c]
for w in doc:
wid = wordIds[w]
p = p * pwc[c][wid]
if p > p_best:
c_best, p_best = c, p
return c_best, p_best
For each class, P(c)⋅∏w∈docP(w∣c) is computed using the conditional-independence assumption; the best class is tracked and returned with its score. (Tasks 4.2 and 4.3 of this exam are about a web page that calls this function, see Web Applications.)