-
Notifications
You must be signed in to change notification settings - Fork 1
/
BinaryClassificationSVM.py
157 lines (132 loc) · 5.81 KB
/
BinaryClassificationSVM.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import numpy as np
class BinaryClassificationSVM(object):
"""
This class implements binary classification SVM.
Args:
C(float): Regularization factor. Default 0.
kernel(str): Kernel function. {"linear": linear kernel, "poly": polynomial kernel,
"rbf": radial basis kernel, "sigmoid": sigmoid kernel.}
gamma: Parameter in poly/rbf/sigmoid kernel.
degree: Parameter in poly kernel.
coef: Constant parameter in poly/sigmoid kernel.
Attributes:
alpha: coefficient of each vector in classifier.
b: constant coefficient in classifier.
Methods:
fit(x, y, max_iteration=100): Fit the SVM model according to the given training data.
predict(x): Perform classification on samples in X.
"""
def __init__(self, C=0.0, kernel="linear", gamma=None, degree=3, coef=1):
"""
Constructor method.
"""
self.C = C
self.kernel = kernel
self.gamma = gamma
self.degree = degree
self.coef = coef
self.alpha = None
self.b = 0
self.x = None
self.y = None
self.kernel_matrix = None
def fit(self, x, y, max_iteration=100):
"""
Fit the SVM model according to the given training data.
Args:
x: Array of data. Shape {amount_of_data, number_of_dimension_of_each_data}
y: Labels of data. Class should be represented by -1/1. Shape {amount_of_data, }
max_iteration(int): Maximum iteration times of SMO algorithms. Default 100.
Return:
object: SVM model
"""
self.x = x
self.y = y
self.alpha = np.zeros([y.shape[0]])
self.kernel_matrix = self.__kernel(self.x, self.x)
for i in range(max_iteration):
if self.__iteration() == -1:
break
return self
def predict(self, x):
"""
Perform classification on samples in X.
Return:
Labels of data. Shape {amount_of_data, }
"""
kernel = self.__kernel(x, self.x)
gx = np.sum(kernel * self.alpha * self.y, axis=1) + self.b
return np.sign(gx).astype(np.int)
def __iteration(self):
"""
Find alpha1 alpha2 and update the value.
"""
new_index_1 = None
gx = np.sum(self.kernel_matrix * self.alpha * self.y, axis=1) + self.b
ex = gx - self.y
for i in range(self.alpha.shape[0]):
if 0 < self.alpha[i] < self.C and gx[i] * self.y[i] != 1:
new_index_1 = i
break
if new_index_1 is None:
for i in range(self.alpha.shape[0]):
if self.alpha[i] == 0 and gx[i] * self.y[i] < 1:
new_index_1 = i
break
if self.alpha[i] == self.C and gx[i] * self.y[i] > 1:
new_index_1 = i
break
if new_index_1 is None:
return -1
_ex = np.abs(ex.copy() - ex[new_index_1])
_ex[new_index_1] = -np.inf
new_index_2 = np.argmax(_ex)
eta = self.kernel_matrix[new_index_1, new_index_1] + \
self.kernel_matrix[new_index_2, new_index_2] - \
self.kernel_matrix[new_index_1, new_index_2] * 2
new_value_2 = self.y[new_index_2] * (ex[new_index_1] - ex[new_index_2]) / eta + self.alpha[new_index_2]
if self.y[new_index_1] != self.y[new_index_2]:
_l = max(0, self.alpha[new_index_2] - self.alpha[new_index_1])
_h = min(self.C, self.C + self.alpha[new_index_2] - self.alpha[new_index_1])
else:
_l = max(0, self.alpha[new_index_2] + self.alpha[new_index_1] - self.C)
_h = min(self.C, self.alpha[new_index_2] + self.alpha[new_index_1])
if new_value_2 > _h:
new_value_2 = _h
if new_value_2 < _l:
new_value_2 = _l
new_value_1 = self.y[new_index_1] * self.y[new_index_2] * (self.alpha[new_index_2] - new_value_2) + \
self.alpha[new_index_1]
b1 = -ex[new_index_1] - \
self.y[new_index_1] * self.kernel_matrix[new_index_1, new_index_1] * (new_value_1 - self.alpha[new_index_1]) - \
self.y[new_index_2] * self.kernel_matrix[new_index_2, new_index_1] * (new_value_2 - self.alpha[new_index_2]) + \
self.b
b2 = -ex[new_index_2] - \
self.y[new_index_1] * self.kernel_matrix[new_index_1, new_index_2] * (new_value_1 - self.alpha[new_index_1]) - \
self.y[new_index_2] * self.kernel_matrix[new_index_2, new_index_2] * (new_value_2 - self.alpha[new_index_2]) + \
self.b
self.b = (b1 + b2) / 2
self.alpha[new_index_1] = new_value_1
self.alpha[new_index_2] = new_value_2
return 0
def __kernel(self, x, y):
"""
Calculate kernel value between data in x and data in y.
Return:
Kernel , shape {amount_of_data_in_x, amount_of_data_in_y}
"""
res = None
if self.kernel == "linear":
res = np.dot(x, y.T)
if self.gamma is None:
self.gamma = 1 / self.x.shape[1]
if self.kernel == "poly":
res = np.dot(x, y.T) * self.gamma + self.coef
res = res ** self.degree
if self.kernel == "rbf":
res = np.array([np.sum((y - x[i]) ** 2, axis=1) for i in range(x.shape[0])])
res = np.exp(-self.gamma * res[1:])
if self.kernel == "sigmoid":
res = np.dot(x, y.T) * self.gamma - self.coef
res = np.tanh(res)
return np.reshape(res, [x.shape[0], y.shape[0]]) # in case that x/y contains only one data.