ProCreations's picture
Claim 6: run the real-data SVM comparison on Breast Cancer and Banknote (was not attempted); DoWS/T-DoWS reach 0.98 accuracy vs 0.56-0.69 for primal-dual, feasibility reported as unresolved
25055a4 verified
Raw
History Blame Contribute Delete
5.55 kB
"""Claim 6: evaluate Algorithms 3 (DoWS) and 4 (T-DoWS) against a primal-dual
baseline on SVM classification with real datasets.
The logbook stated it does not reproduce the real-data SVM comparison and ran no
SVM experiment. Two of the three datasets are run here; MNIST 3-vs-5 could not be
retrieved (OpenML returned HTTP 504).
SVM as constrained optimisation, which is the form the algorithms address:
minimise f(w,b) = 0.5||w||^2
subject to y_i (w.x_i + b) >= 1 (one constraint per sample)
Randomised feasibility: sample a violated constraint and project onto its
halfspace, rather than forming the full constraint set.
"""
import json, pickle, numpy as np
RES = {}
def load():
D = pickle.load(open("svm_data.pkl", "rb"))
out = {}
for k, (X, y) in D.items():
X = (X-X.mean(0))/(X.std(0)+1e-9)
out[k] = (X, y.astype(float))
return out
C_SLACK = 1.0
def obj(w, b, xi=None):
"""SOFT-margin objective. Hard margin is INFEASIBLE on banknote (min margin
-0.767 under an exact LinearSVC fit), so violations there could never reach 0
and the feasibility comparison was measuring an infeasible program."""
v = 0.5*float(w @ w)
return v+C_SLACK*float(np.sum(xi)) if xi is not None else v
def viol(X, y, w, b, xi):
return float(np.maximum(0.0, 1.0-xi-y*(X @ w+b)).max())
def project_one(X, y, w, b, xi, i):
"""Project (w,b,xi_i) onto y_i(w.x_i+b) + xi_i >= 1, xi_i >= 0."""
a = y[i]*X[i]; c = y[i]
g = a @ w+c*b+xi[i]-1.0
if g < 0:
n2 = a @ a+c*c+1.0
lam = -g/max(n2, 1e-12)
w = w+lam*a; b = b+lam*c; xi[i] = xi[i]+lam
xi[i] = max(0.0, xi[i])
return w, b
def run(X, y, alg, T=4000, seed=0, nproj=None):
"""nproj scales with the CONSTRAINT COUNT. Claim 4 of this same paper (already
verified) says infeasibility decays geometrically in the NUMBER of feasibility
updates; with a fixed 5 projections/step over 569-1372 constraints most
constraints are almost never visited and violation plateaus."""
rng = np.random.default_rng(seed)
n, d = X.shape
if nproj is None: nproj = max(5, n//4)
w = np.zeros(d); b = 0.0; w0 = w.copy(); xi = np.ones(n)
G2 = 1e-12; rmax = 1e-8
lam_d = 0.0
hist = []
for t in range(1, T+1):
g = w.copy() # grad of 0.5||w||^2
if alg == "primal_dual":
i = int(rng.integers(n))
s = 1.0-y[i]*(X[i] @ w+b)
gv = -y[i]*X[i] if s > 0 else np.zeros(d)
gb = -y[i] if s > 0 else 0.0
eta = 0.5/np.sqrt(t)
w = w-eta*(g+lam_d*gv); b = b-eta*(lam_d*gb)
xi[i] = max(0.0, xi[i]-eta*(C_SLACK-lam_d))
lam_d = max(0.0, lam_d+eta*(s-xi[i]))
else:
G2 += g @ g
rmax = max(rmax, float(np.linalg.norm(w-w0)))
if alg == "dows":
eta = rmax/np.sqrt(G2)
else: # t_dows: tamed, no bounded-Y assumption
eta = rmax/np.sqrt(G2*np.log(np.e+t))
w = w-eta*g
xi = np.maximum(0.0, xi-eta*C_SLACK)
for _ in range(nproj): # randomised feasibility updates
i = int(rng.integers(n))
w, b = project_one(X, y, w, b, xi, i)
if t % 200 == 0:
hist.append({"t": t, "obj": obj(w, b, xi), "max_violation": viol(X, y, w, b, xi)})
acc = float(np.mean(np.sign(X @ w+b) == y))
return w, b, hist, acc
def main():
data = load()
rows = []
for name, (X, y) in data.items():
for alg in ("dows", "t_dows", "primal_dual"):
o, v, a = [], [], []
for s in range(3):
w, b, h, acc = run(X, y, alg, seed=s)
o.append(h[-1]["obj"]); v.append(h[-1]["max_violation"]); a.append(acc)
rows.append({"dataset": name, "n": int(X.shape[0]), "d": int(X.shape[1]),
"algorithm": alg, "final_objective": round(float(np.mean(o)), 5),
"final_max_violation": round(float(np.mean(v)), 6),
"train_accuracy": round(float(np.mean(a)), 4)})
print(" %-14s %-12s objective=%9.4f max violation=%9.5f accuracy=%.4f"
% (name, alg, np.mean(o), np.mean(v), np.mean(a)), flush=True)
RES["claim6_svm"] = {"rows": rows, "seeds": 3, "iterations": 4000,
"datasets_run": sorted(data.keys()),
"mnist_note": "MNIST 3-vs-5 unavailable: OpenML returned HTTP 504"}
# comparison summary
summ = []
for name in data:
sub = {r["algorithm"]: r for r in rows if r["dataset"] == name}
summ.append({"dataset": name,
"dows_beats_pd_on_violation": bool(sub["dows"]["final_max_violation"] <= sub["primal_dual"]["final_max_violation"]),
"t_dows_beats_pd_on_violation": bool(sub["t_dows"]["final_max_violation"] <= sub["primal_dual"]["final_max_violation"]),
"dows_acc_minus_pd": round(sub["dows"]["train_accuracy"]-sub["primal_dual"]["train_accuracy"], 4)})
print(" [%s] DoWS beats PD on feasibility: %s | T-DoWS: %s | accuracy delta %+.4f"
% (name, summ[-1]["dows_beats_pd_on_violation"], summ[-1]["t_dows_beats_pd_on_violation"],
summ[-1]["dows_acc_minus_pd"]), flush=True)
RES["claim6_svm"]["summary"] = summ
json.dump(RES, open("svm_results.json", "w"), indent=1)
if __name__ == "__main__":
main(); print("DONE")