import logging import random import numpy as np from cvxopt import matrix, solvers import copy import time import pickle class BinarySVM: def __init__(self, kernel='linear', alg='SMO', gamma=None, degree=None, C=1.0, verbose=False): self.eps = 1e-6 self.alg = alg self.C, self.w, self.b, self.ksi = C, [], 0.0, [] self.n_sv = -1 self.sv_x, self.sv_y, self.alphas = np.zeros(0), np.zeros(0), np.zeros(0) self.kernel = kernel if self.kernel == 'poly': self.degree = degree if degree is not None else 2.0 elif self.kernel == 'rbf': self.gamma = gamma if verbose: logging.basicConfig(level=logging.INFO) else: logging.basicConfig(level=logging.WARNING) def _kernel(self, x, z=None): if z is None: z = x if self.kernel == 'linear': return np.dot(x, z.T) elif self.kernel == 'poly': return (np.dot(x, z.T) + 1.0) ** self.degree elif self.kernel == 'rbf': xx, zz = np.sum(x*x, axis=1), np.sum(z*z, axis=1) res = -2.0 * np.dot(x, z.T) + xx.reshape((-1, 1)) + zz.reshape((1, -1)) # del xx, zz return np.exp(-self.gamma * res) # elif self.kernel == 'sigmoid': # pass else: print 'Unknown kernel' exit(3) def _SMO(self, K, y): # def select_pairs(): # # return -1, -1 if not proper alphas are found # return 1, 1 print 'Begin SMO...' tol, m = 1e-4, len(y) # m: number of training samples self.alphas, self.b = np.zeros(m), 0.0 niter = 0 while True: # i, j = select_pairs() num_against_kkt_alphas = 0 unbounded = [i for i in xrange(m) if self.eps < self.alphas[i] < self.C - self.eps] bounded = [i for i in xrange(m) if self.alphas[i] <= self.eps or self.alphas[i] >= self.C - self.eps] # for i in xrange(m): for i in unbounded+bounded: yi, ai_old = y[i], self.alphas[i] Ei = np.sum(self.alphas * y * K[i]) + self.b - yi logging.debug('Ei = ' + str(Ei)) if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): num_against_kkt_alphas += 1 for j in xrange(m): if j==i: continue eta = K[i, i] + K[j, j] - 2.0 * K[i, j] logging.debug('eta = ' + str(eta)) if np.abs(eta) <= self.eps: # eta == 0.0 continue # print 'Ei', Ei # print 'Ej_list', Ej_list # print '|Ej-Ei|', np.abs(Ei - Ej_list) print 'Choose', i, 'and', j yi, yj, ai_old, aj_old = y[i], y[j], self.alphas[i], self.alphas[j] s = yi * yj Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # logging.debug('Ei = ' + str(Ei)) # input('*******\n') if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: continue print 'delta_j:', yj, Ei - Ej, eta aj_new = aj_old + yj * (Ei - Ej) / eta logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) logging.debug('aj_new = ' + str(aj_new)) if aj_new > H: aj_new = H elif aj_new < L: aj_new = L delta_j = aj_new - aj_old print 'alphas:', self.alphas print aj_new, aj_old if abs(delta_j) < 1e-4: print "j = %d, is not moving enough" % j continue ai_new = ai_old - s * delta_j delta_i = ai_new - ai_old self.alphas[i], self.alphas[j] = ai_new, aj_new print 'New alpha_i and alpha_j:', ai_new, aj_new bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] # self.b = (bi + bj) / 2.0 if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 break if num_against_kkt_alphas == 0: break else: niter += 1 print 'In iteration', niter, ',', num_against_kkt_alphas, 'alphas are against KKT conditions' print 'Finish SMO...' return self.alphas, self.b def _SMO1(self, K, y): print 'Begin SMO...' m = len(y) self.alphas, self.b = np.zeros(m), 0.0 tol = self.eps passes, max_passes = 0, 10 logging.debug('m = ' + str(m)) n_iter = 0 while passes < max_passes: n_iter += 1 logging.info('Iteration ' + str(n_iter)) logging.debug('passes = ' + str(passes)) num_changed_alphas = 0 # run over pair (i, j). But for some alpha_i, only choose one alpha_j in an iteration epoch. for i in xrange(m): # Ei = self.f(x[i]) - y[i] yi, ai_old = y[i], self.alphas[i] Ei = np.sum(self.alphas * y * K[i]) + self.b - yi logging.debug('Ei = ' + str(Ei)) if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): # logging.debug('Now select j!') # select j!=i randomly # while True: # j = random.randint(0, m-1) # yj = y[j] # Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # if abs(Ej - Ei) > self.eps: # break for j in xrange(m): if j == i: continue # need to update Ei, because changes in self.alphas may change Ei # Ei = np.sum(self.alphas * y * K[i]) + self.b - yi yj = y[j] Ej = np.sum(self.alphas * y * K[j]) + self.b - yj if abs(Ej - Ei) <= self.eps: continue aj_old = self.alphas[j] if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: continue eta = 2.0 * K[i, j] - K[i, i] - K[j, j] logging.debug('eta = ' + str(eta)) if eta >= -self.eps: # eta >= 0.0 continue aj_new = aj_old - yj * (Ei - Ej) / eta logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) logging.debug('aj_new = ' + str(aj_new)) if aj_new > H: aj_new = H elif aj_new < L: aj_new = L delta_j = aj_new - aj_old if abs(delta_j) < 1e-4: print "j = %d, is not moving enough" % j continue ai_new = ai_old + yi * yj * (aj_old - aj_new) # ai_new = ai_old + yi * yj * delta_j delta_i = ai_new - ai_old self.alphas[i], self.alphas[j] = ai_new, aj_new bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 num_changed_alphas += 1 break if num_changed_alphas == 0: passes += 1 else: passes = 0 print 'Finish SMO...' return self.alphas, self.b def _SMO2(self, K, y): def take_step(j, i, Ei, Ej=None): if j == i: return False yi, yj, ai_old, aj_old = y[i], y[j], self.alphas[i], self.alphas[j] if Ej is None: Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # s = yj * yi if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: return False eta = 2.0 * K[i, j] - K[i, i] - K[j, j] if eta < -self.eps: ai_new = ai_old - yi * (Ej - Ei) / eta if ai_new < L: ai_new = L elif ai_new > H: ai_new = H else: return False # Lobj, Hobj = W(ai=L), W(ai=H) # if Lobj > Hobj + self.eps: # ai_new = L # elif Lobj < Hobj - self.eps: # ai_new = H # else: # ai_new = ai_old if ai_new < self.eps: ai_new = 0.0 elif ai_new > self.C - self.eps: ai_new = self.C if abs(ai_new - ai_old) < self.eps * (ai_new + ai_old + self.eps): return False aj_new = aj_old + yj * yi * (ai_old - ai_new) # update bias self.alphas[i], self.alphas[j] = ai_new, aj_new delta_j = aj_new - aj_old delta_i = ai_new - ai_old bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 # update w, if linear SVM # update error cache print 'Choosing (', i, ',', j, ')' return True def examine_example(i): yi, ai_old = y[i], self.alphas[i] Ei = np.sum(self.alphas * y * K[i]) + self.b - yi if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): # calculate ids of non-zero and non-C alphas mid_alpha_ids = filter(lambda j: 0.0 < self.alphas[j] < self.C, xrange(m)) if len(mid_alpha_ids) > 1: # todo: second heuristic for j in xrange(m): Ej = np.sum(self.alphas * y * K[j]) + self.b - y[j] if abs(Ei - Ej) < self.eps: continue if take_step(j, i, Ei, Ej): return True else: break for j in mid_alpha_ids: if take_step(j, i, Ei): return True for j in xrange(m): if take_step(j, i, Ei): return True return False print 'Begin SMO...' m = len(y) self.alphas, self.b = np.zeros(m), 0.0 tol = self.eps num_changed_alphas, examine_all = 0, True # examine_all=True means needing to examine all examples while num_changed_alphas > 0 or examine_all: num_changed_alphas = 0 if examine_all: for i in xrange(m): num_changed_alphas += examine_example(i) else: for i in xrange(m): if 0 < self.alphas[i] < self.C: num_changed_alphas += examine_example(i) if examine_all: examine_all = False elif num_changed_alphas == 0: examine_all = 1 # passes, max_passes = 0, 10 # logging.debug('m = ' + str(m)) # n_iter = 0 # while passes < max_passes: # n_iter += 1 # logging.info('Iteration ' + str(n_iter)) # logging.debug('passes = ' + str(passes)) # num_changed_alphas = 0 # # run over pair (i, j). But for some alpha_i, only choose one alpha_j in an iteration epoch. # for i in xrange(m): # # Ei = self.f(x[i]) - y[i] # yi, ai_old = y[i], self.alphas[i] # Ei = np.sum(self.alphas * y * K[i]) + self.b - yi # logging.debug('Ei = ' + str(Ei)) # if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): # # logging.debug('Now select j!') # # select j!=i randomly # # while True: # # j = random.randint(0, m-1) # # yj = y[j] # # Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # # if abs(Ej - Ei) > self.eps: # # break # for j in xrange(m): # if j == i: # continue # # need to update Ei, because changes in self.alphas may change Ei # # Ei = np.sum(self.alphas * y * K[i]) + self.b - yi # yj = y[j] # Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # if abs(Ej - Ei) <= self.eps: # continue # aj_old = self.alphas[j] # if yi != yj: # L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) # else: # L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) # logging.debug('L = ' + str(L) + ', H = ' + str(H)) # if H - L < self.eps: # continue # eta = 2.0 * K[i, j] - K[i, i] - K[j, j] # logging.debug('eta = ' + str(eta)) # if eta >= -self.eps: # eta >= 0.0 # continue # aj_new = aj_old - yj * (Ei - Ej) / eta # logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) # logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) # logging.debug('aj_new = ' + str(aj_new)) # if aj_new > H: # aj_new = H # elif aj_new < L: # aj_new = L # delta_j = aj_new - aj_old # if abs(delta_j) < 1e-4: # print "j = %d, is not moving enough" % j # continue # ai_new = ai_old + yi * yj * (aj_old - aj_new) # delta_i = ai_new - ai_old # self.alphas[i], self.alphas[j] = ai_new, aj_new # bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] # bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] # if 0 < ai_new < self.C: # self.b = bi # elif 0 < aj_new < self.C: # self.b = bj # else: # self.b = (bi + bj) / 2.0 # num_changed_alphas += 1 # break # if num_changed_alphas == 0: # passes += 1 # else: # passes = 0 print 'Finish SMO...' return self.alphas, self.b def _SMO3(self, K, y): def choose_alphas(): # choose alpha heuristically yield -2, -2, 0.0, 0.0 # initiate passes, max_passes, changed = 0, 1, False while passes < max_passes: num_changed_alphas = 0 # unbounded = [i for i in xrange(m) if self.eps < self.alphas[i] < self.C - self.eps] # bounded = [i for i in xrange(m) if self.alphas[i] <= self.eps or self.alphas[i] >= self.C - self.eps] range_i = np.argsort((self.alphas - self.eps) * (self.eps - self.C + self.alphas)) # print 'range_i:', range_i for i in range_i: # print 'Try i:', i yi, ai_old = y[i], self.alphas[i] Ei = np.sum(self.alphas * y * K[i]) + self.b - yi logging.debug('Ei = ' + str(Ei)) if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): # print i, 'is against KKT conditions' range_j = range(m) random.shuffle(range_j) # print 'range j:', range_j # for j in xrange(m): for j in range_j: if j == i: continue yj = y[j] Ej = np.sum(self.alphas * y * K[j]) + self.b - yj if abs(Ej - Ei) <= self.eps: continue yield (i, j, Ei, Ej) changed = yield (i, j, Ei, Ej) # receive return value from the main function if changed: # if (i, j) pair changed in the latest iteration num_changed_alphas += 1 break else: # print i, 'satisfies KKT conditions' pass if num_changed_alphas == 0: passes += 1 else: passes = 0 yield -1, -1, 0.0, 0.0 print 'Begin SMO3...' m = len(y) self.alphas, self.b = np.zeros(m), 0.0 tol = self.eps logging.debug('m = ' + str(m)) gen = choose_alphas() n_iter = 0 updated = False gen.next() while True: n_iter += 1 logging.info('Iteration ' + str(n_iter)) # logging.debug('passes = ' + str(passes)) # run over pair (i, j). But for some alpha_i, only choose one alpha_j in an iteration epoch. try: gen.send(updated) i, j, Ei, Ej = gen.next() except StopIteration, e: break # print '(i, j)', i, j # input('********\n') if i == -1: # no more (i, j) pairs against KKT condition break updated = False yi, yj, ai_old, aj_old = y[i], y[j], self.alphas[i], self.alphas[j] if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: continue eta = 2.0 * K[i, j] - K[i, i] - K[j, j] logging.debug('eta = ' + str(eta)) if eta >= 0: # eta >= 0.0 continue aj_new = aj_old - yj * (Ei - Ej) / eta logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) logging.debug('aj_new = ' + str(aj_new)) if aj_new > H: aj_new = H elif aj_new < L: aj_new = L delta_j = aj_new - aj_old if abs(delta_j) < 1e-4: # print "j = %d, is not moving enough" % j continue ai_new = ai_old + yi * yj * (aj_old - aj_new) # ai_new = ai_old + yi * yj * delta_j delta_i = ai_new - ai_old self.alphas[i], self.alphas[j] = ai_new, aj_new bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 updated = True print 'Updated through', i, j print 'alphas:', self.alphas print 'Finish SMO3...' return self.alphas, self.b def _SMO4(self, K, y): def getE(i): return np.sum(self.alphas * y * K[i]) + self.b - y[i] # assert np.linalg.norm(K - K.T, 'F') print 'Begin SMO...' # print 'K = ', K tol, m = 1e-4, len(y) # m: number of training samples self.alphas, self.b = np.zeros(m), 0.0 niter = 0 while True: num_against_kkt_alphas = 0 for i in xrange(m): yi, ai_old = y[i], self.alphas[i] # Ei = np.sum(self.alphas * y * K[i]) + self.b - yi Ei = getE(i) logging.debug('Ei = ' + str(Ei)) if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): num_against_kkt_alphas += 1 for j in xrange(m): if j == i: continue eta = K[i, i] + K[j, j] - 2.0 * K[i, j] logging.debug('eta = ' + str(eta)) if eta <= 0: # eta == 0.0 continue # print 'Ei', Ei # print 'Ej_list', Ej_list # print '|Ej-Ei|', np.abs(Ei - Ej_list) # print 'Choose', i, 'and', j yj, aj_old = y[j], self.alphas[j] s = yi * yj # Ej = np.sum(self.alphas * y * K[j]) + self.b - yj Ej = getE(j) if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: continue # print 'delta_j:', yj, Ei - Ej, eta aj_new = aj_old + yj * (Ei - Ej) / eta logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) logging.debug('aj_new = ' + str(aj_new)) if aj_new > H: aj_new = H elif aj_new < L: aj_new = L delta_j = aj_new - aj_old # print 'alphas:', self.alphas # print aj_new, aj_old if abs(delta_j) < 1e-4: # print "j = %d, is not moving enough" % j continue ai_new = ai_old - s * delta_j delta_i = ai_new - ai_old # print i, j # print 'delta = ', delta_i, delta_j # print 's = ', s # print 'Before:', self.alphas self.alphas[i], self.alphas[j] = ai_new, aj_new # print 'New alpha_i and alpha_j:', ai_new, aj_new bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] # self.b = (bi + bj) / 2.0 if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 # print 'After: alpha:', self.alphas # print 'sum(alpha * y):', np.dot(self.alphas, y) break # try next alpha_i if num_against_kkt_alphas == 0: break else: niter += 1 print 'In iteration', niter, ',', num_against_kkt_alphas, 'alphas are against KKT conditions' num_against_kkt_alphas = 0 print 'Finish SMO...' return self.alphas, self.b def _QP(self, x, y): # In QP formulation (dual): m variables, 2m+1 constraints (1 equation, 2m inequations) m = len(y) print x.shape, y.shape P = self._kernel(x) * np.outer(y, y) P, q = matrix(P, tc='d'), matrix(-np.ones((m, 1)), tc='d') G = matrix(np.r_[-np.eye(m), np.eye(m)], tc='d') h = matrix(np.r_[np.zeros((m,1)), np.zeros((m,1)) + self.C], tc='d') A, b = matrix(y.reshape((1,-1)), tc='d'), matrix([0.0]) # print "P, q:" # print P, q # print "G, h" # print G, h # print "A, b" # print A, b solution = solvers.qp(P, q, G, h, A, b) if solution['status'] == 'unknown': print 'Not PSD!' exit(2) else: self.alphas = np.array(solution['x']).squeeze() def _SMO5(self, K, y): def choose_alphas(): # choose alpha heuristically check_all_examples = False # whether or not need to check all examples # passes, max_passes = 0, 2 # while passes < max_passes: while True: num_changed_alphas = 0 if check_all_examples: # in range_i, unbounded alphas rank first # range_i = np.argsort((self.alphas - self.eps) * (self.alphas + self.eps - self.C)) range_i = xrange(m) else: # check unbounded examples only range_i = [i for i in xrange(m) if self.eps < self.alphas[i] < self.C - self.eps] # print 'range_i:', range_i for i in range_i: # print 'Try i:', i yi, ai_old = y[i], self.alphas[i] Ei = np.sum(self.alphas * y * K[i]) + self.b - yi logging.debug('Ei = ' + str(Ei)) if (yi*Ei < -tol and ai_old < self.C) or (yi*Ei > tol and ai_old > 0): # print i, 'is against KKT conditions' range_j = range(m) random.shuffle(range_j) # print 'range j:', range_j # for j in xrange(m): for j in range_j: if j == i: continue yj = y[j] Ej = np.sum(self.alphas * y * K[j]) + self.b - yj # if abs(Ej - Ei) <= self.eps: # continue yield (i, j, Ei, Ej) if updated[0]: # if (i, j) pair changed in the latest iteration num_changed_alphas += 1 break if num_changed_alphas == 0: if check_all_examples: # if have checked all examples and no alpha violates KKT condition break else: check_all_examples = True # check all examples in the next iteration as a safeguard else: check_all_examples = False yield -1, -1, 0.0, 0.0 print 'Begin SMO5...' m = len(y) self.alphas, self.b = np.zeros(m), 0.0 tol = self.eps logging.debug('m = ' + str(m)) gen = choose_alphas() n_iter = 0 updated = [False] # use mutable object to pass message between functions while True: n_iter += 1 logging.info('Iteration ' + str(n_iter)) # logging.debug('passes = ' + str(passes)) # run over pair (i, j). But for some alpha_i, only choose one alpha_j in an iteration epoch. try: # gen.send(updated) i, j, Ei, Ej = gen.next() except StopIteration, e: break if i == -1: # no more (i, j) pairs against KKT condition break updated[0] = False yi, yj, ai_old, aj_old = y[i], y[j], self.alphas[i], self.alphas[j] if yi != yj: L, H = max(0.0, aj_old - ai_old), min(self.C, self.C + aj_old - ai_old) else: L, H = max(0.0, ai_old + aj_old - self.C), min(self.C, ai_old + aj_old) logging.debug('L = ' + str(L) + ', H = ' + str(H)) if H - L < self.eps: continue eta = K[i, i] + K[j, j] - 2.0 * K[i, j] logging.debug('eta = ' + str(eta)) if eta <= 0: # This should not be happen, because gram matrix should be PSD if eta == 0.0: logging.warning('eta = 0, possibly identical examples encountered!') else: logging.error('GRAM MATRIX IS NOT PSD!') input('*****************') continue aj_new = aj_old + yj * (Ei - Ej) / eta logging.debug('i, j, yi, yj = ' + ' '.join(map(str, [i, j, yi, yj]))) logging.debug('Ei = ' + str(Ei) + ', Ej = ' + str(Ej)) logging.debug('aj_new = ' + str(aj_new)) if aj_new > H: aj_new = H elif aj_new < L: aj_new = L delta_j = aj_new - aj_old if abs(delta_j) < 1e-5: # print "j = %d, is not moving enough" % j continue ai_new = ai_old + yi * yj * (aj_old - aj_new) # ai_new = ai_old - yi * yj * delta_j delta_i = ai_new - ai_old self.alphas[i], self.alphas[j] = ai_new, aj_new bi = self.b - Ei - yi * delta_i * K[i, i] - yj * delta_j * K[i, j] bj = self.b - Ej - yi * delta_i * K[i, j] - yj * delta_j * K[j, j] if 0 < ai_new < self.C: self.b = bi elif 0 < aj_new < self.C: self.b = bj else: self.b = (bi + bj) / 2.0 updated[0] = True logging.info('Updated through' + str(i) + str(j)) logging.debug('alphas:' + str(self.alphas)) print 'Finish SMO5...' return self.alphas, self.b def fit(self, x, y): # x, y: np.ndarray # x.shape: (m, n), where m = # samples, n = # features. # y.shape: (m,), m labels which range from {-1.0, +1.0}. assert type(x) == np.ndarray # print type(x), type(y) print x.shape, y.shape # In the design matrix x: m examples, n features # In QP formulation (dual): m variables, 2m+1 constraints (1 equation, 2m inequations) # print 'x = ', x # print 'y = ', y if self.kernel == 'rbf' and self.gamma is None: self.gamma = 1.0 / x.shape[1] print 'gamma = ', self.gamma if self.alg == 'dual': self._QP(x, y) else: assert self.alg == 'SMO' K = self._kernel(x) self._SMO5(K, y) logging.info('self.alphas = ' + str(self.alphas)) sv_indices = filter(lambda i:self.alphas[i] > self.eps, xrange(len(y))) self.sv_x, self.sv_y, self.alphas = x[sv_indices], y[sv_indices], self.alphas[sv_indices] self.n_sv = len(sv_indices) logging.info('sv_indices:' + str(sv_indices)) print self.n_sv, 'SVs!' logging.info(str(np.c_[self.sv_x, self.sv_y])) if self.kernel == 'linear': self.w = np.dot(self.alphas * self.sv_y, self.sv_x) if self.alg == 'dual': # calculate b: average over all support vectors sv_boundary = self.alphas < self.C - self.eps self.b = np.mean(self.sv_y[sv_boundary] - np.dot(self.alphas * self.sv_y, self._kernel(self.sv_x, self.sv_x[sv_boundary]))) def predict_score(self, x): return np.dot(self.alphas * self.sv_y, self._kernel(self.sv_x, x)) + self.b def show(self): if (self.alg == 'dual') or (self.alg == 'SMO'): print '\nFitted parameters:' print 'n_sv = ', self.n_sv print 'sv_x = ', self.sv_x print 'sv_y = ', self.sv_y print 'alphas = ', self.alphas if self.kernel == 'linear': print 'w = ', self.w print 'b = ', self.b else: print 'No known optimization method!' def predict(self, x): return np.sign(self.predict_score(x)) def save(self, file_name='BinarySVM1.pkl'): fh = open('../model/' + file_name, 'wb') pickle.dump(self, fh) fh.close() class MultiSVM: def __init__(self, kernel='linear', alg='SMO', decision_function='ovo', gamma=None, degree=None, C=1.0): self.degree, self.gamma, self.decision_function = degree, gamma, decision_function self.alg, self.C = alg, C self.kernel = kernel self.n_class, self.classifiers = 0, [] def fit(self, x, y): labels = np.unique(y) self.n_class = len(labels) print labels if self.decision_function == 'ovr': # one-vs-rest method for label in labels: y1 = np.array(y) y1[y1 != label] = -1.0 y1[y1 == label] = 1.0 print 'Begin training for label', label, 'at', \ time.strftime('%Y-%m-%d, %H:%M:%S', time.localtime(time.time())) t1 = time.time() clf = BinarySVM(self.kernel, self.alg, self.gamma, self.degree, self.C) clf.fit(x, y1) t2 = time.time() print 'Training time for ' + str(label) + '-vs-rest:', t2 - t1, 'seconds' self.classifiers.append(copy.deepcopy(clf)) else: # use one-vs-one method assert self.decision_function == 'ovo' n_labels = len(labels) for i in xrange(n_labels): for j in xrange(i+1, n_labels): neg_id, pos_id = y == labels[i], y == labels[j] x1, y1 = np.r_[x[neg_id], x[pos_id]], np.r_[y[neg_id], y[pos_id]] y1[y1 == labels[i]] = -1.0 y1[y1 == labels[j]] = 1.0 # print 'y1 = ', y1 print 'Begin training classifier for label', labels[i], 'and label', labels[j], 'at', \ time.strftime('%Y-%m-%d, %H:%M:%S', time.localtime(time.time())) t1 = time.time() clf = BinarySVM(self.kernel, self.alg, self.gamma, self.degree, self.C) clf.fit(x1, y1) t2 = time.time() print 'Training time for ' + str(labels[i]) + '-vs-' + str(labels[j]) + ':', t2 - t1, 'seconds' self.classifiers.append(copy.deepcopy(clf)) def predict(self, test_data): n_samples = test_data.shape[0] if self.decision_function == 'ovr': score = np.zeros((n_samples, self.n_class)) for i in xrange(self.n_class): clf = self.classifiers[i] score[:, i] = clf.predict_score(test_data) return np.argmax(score, axis=1) else: assert self.decision_function == 'ovo' assert len(self.classifiers) == self.n_class * (self.n_class - 1) / 2 vote = np.zeros((n_samples, self.n_class)) clf_id = 0 for i in xrange(self.n_class): for j in xrange(i+1, self.n_class): res = self.classifiers[clf_id].predict(test_data) vote[res < 0, i] += 1.0 # negative sample: class i vote[res > 0, j] += 1.0 # positive sample: class j # print i, j # print 'res = ', res # print 'vote = ', vote clf_id += 1 return np.argmax(vote, axis=1) def save(self, file_name='MultiSVM1.pkl'): fh = open('../model/' + file_name, 'wb') pickle.dump(self, fh) fh.close() def show(self): for clf in self.classifiers: clf.show()