class SVM:
def __init__(self, C=1, toler=0.001, maxIter=500, kernel_option=('', 1)):
self.C = C # 正则化参数
self.toler = toler # 容错率
self.maxIter = maxIter # 最大迭代次数
self.kernel_opt = kernel_option # 选择核函数
def cal_kernel_value(self, X, X_i, kernel_option):
'''
计算样本之间的核函数的值
:param X: 特征集 m*n
:param X_i: 特征集的第i个样本, 一个行向量 1*n
:param kernel_option: 选择的核函数与参数值
:return: 样本之间的核函数值, m*1
'''
m = X.shape[0]
kernel_value = np.mat(np.zeros((m, 1)))
if kernel_option[0] == 'rbf': # 径向基函数(高斯核函数)
sigma = kernel_option[1]
for i in range(m):
diff = X[i, :] - X_i
kernel_value[i] = np.exp(np.dot(diff, diff.T) / (-2 * sigma**2))
else:
kernel_value = np.dot(X, X_i.T)
return kernel_value
def cal_kernel(self, X, kernel_option):
'''
计算核函数的矩阵
:param X: 训练集m*n
:param kernel_option: 选择的核函数
:return: 核函数的矩阵m*m
'''
m = X.shape[0]
kernel_matrix = np.mat(np.zeros((m, m)))
for i in range(m):
kernel_matrix[:, i] = self.cal_kernel_value(X, X[i, :], kernel_option)
return kernel_matrix
def SVM_training(self, X, y):
self.X = np.mat(X) # 转换为矩阵 m*n
self.y = np.mat(y) # m*1
self.m = len(X)
self.alpha = np.mat(np.zeros((self.m, 1))) # 拉格朗日乘子
self.b = 0 # 偏差b
self.Ecache = np.mat(np.zeros((self.m, 2))) # 存放误差E
self.kernel_mat = self.cal_kernel(self.X, self.kernel_opt) # 载入核函数矩阵
entireSet = True
alpha_changed = 0
iter = 0
# 当迭代轮次超过最大值或者 遍历全集后alpha值无变化, 则跳出外循环,训练结束
while iter < self.maxIter and (alpha_changed > 0 or entireSet):
print('Iter:', iter)
alpha_changed = 0
if entireSet: # 第一次 遍历全集
for i in range(self.m):
alpha_changed += self.innerL(i)
iter += 1
else: # 第二次 遍历非边界值, 直到其中所有的alpha都不能再优化(即alpha_changed==0), 再遍历全集
bound_samples = []
for i in range(self.m): # 找出所有非边界值的索引
if self.alpha[i, 0] > 0 and self.alpha[i, 0] < self.C:
bound_samples.append(i)
# bound_samples = [i for i, a in enumerate(self.alpha[:, 0]) if (a > 0 and a < self.C)]
# print(bound_samples)
for i in bound_samples:
alpha_changed += self.innerL(i)
iter += 1
if entireSet:
entireSet = False
elif alpha_changed == 0:
entireSet = True
return
# 获取不等于i的j值
def Jrand(self, i, m):
j = i
while j == i:
j = np.random.randint(0, m)
return j
# 裁剪alpha,使满足KKT条件
def clipAlpha(self, alphaj, L, H):
if alphaj < L:
alphaj = L
if alphaj > H:
alphaj = H
return alphaj
# 计算误差E
def cal_E(self, k):
fxk = float(np.dot(np.multiply(self.alpha, self.y).T, self.kernel_mat[:, k]) + self.b)
Ek = fxk - float(self.y[k])
return Ek
# 更新alpha 和 b 后,更新Ecache
def update_E(self, k):
Ek = self.cal_E(k)
self.Ecache[k] = [1, Ek]
# 寻找第二个乘子。 满足abs(Ei-Ej)最大,即 步伐最大。
def select_second_alpha(self, i, Ei):
j, Ej, maxstep = 0, 0, 0
self.Ecache[i] = [1, Ei]
validE = np.nonzero(self.Ecache[:, 0].A)[0] # 找出所有已经更新进Ecache的E值
# validE = [i for i, a in enumerate(self.Epache[:, 0]) if a > 0]
if len(validE) > 1:
for k in validE:
if k == i:
continue
Ek = self.cal_E(k)
deltaE = abs(Ei - Ek)
if deltaE > maxstep:
j = k
Ej = Ek
maxstep = deltaE
else: # 第一次遍历,找一个不等于i的随机数j
j = self.Jrand(i, self.m)
Ej = self.cal_E(j)
return j, Ej
# 内循环,找出违反KTT条件的alpha,并更新alpha,b,Ecache。 如果没有更新值,返回0,否则返回1
def innerL(self, i):
Ei = self.cal_E(i)
r = self.y[i] * Ei # 拆开来,等价于 y(wx+b)-1
if (r < -self.toler and self.alpha[i] < self.C) or (r > self.toler and self.alpha[i] > 0): # 如果没有容错率,则分别是 r<0 和 r>0. 容错率的意思是,在(-toler,toler)之间的点,就当做是满足KKT条件了,而放过不做优化
j, Ej = self.select_second_alpha(i, Ei)
alphaIold = self.alpha[i].copy()
alphaJold = self.alpha[j].copy()
if self.y[i] == self.y[j]: # 计算出上下边界
L = max(0, alphaIold + alphaJold - self.C)
H = min(self.C, alphaIold + alphaJold)
else:
L = max(0, alphaJold - alphaIold)
H = min(self.C, self.C + alphaJold - alphaIold)
if L == H:
# print('L == H')
return 0
eta = self.kernel_mat[i, i] + self.kernel_mat[j, j] - 2 * self.kernel_mat[i, j]
if eta <= 0: # eta是alphaj的二阶导数。 根据二阶导数性质,只有当二阶导数>0是,原函数才能取到最小值。
# print('eta <= 0')
return 0
alphaJnew = alphaJold + self.y[j] * (Ei - Ej) / eta
alphaJnew = self.clipAlpha(alphaJnew, L, H)
if abs(alphaJnew - alphaJold) < 0.00001: # 改变太小的也认为是没有改变
# print('alpha NOT moving enough')
return 0
self.alpha[j] = alphaJnew
alphaInew = alphaIold + self.y[i] * self.y[j] * (alphaJold - alphaJnew)
self.alpha[i] = alphaInew
bi = float(-Ei + self.y[i] * self.kernel_mat[i, i] * (alphaIold - alphaInew) + self.y[j] * self.kernel_mat[i, j] * (alphaJold - alphaJnew) + self.b)
bj = float(-Ej + self.y[i] * self.kernel_mat[i, j] * (alphaIold - alphaInew) + self.y[j] * self.kernel_mat[j, j] * (alphaJold - alphaJnew) + self.b)
if alphaInew < self.C and alphaInew > 0: # 如果alphaJnew 和 alphaInew 都是非边界点, 那他们代表的是支持向量。bi=bj
self.b = bi
elif alphaJnew < self.C and alphaJnew > 0:
self.b = bj
else:
self.b = (bi + bj) / 2
self.update_E(i) # 更新Ecache
self.update_E(j)
return 1
return 0
# 根据求出来的alpha,计算权重
def cal_w(self):
w = np.dot(np.multiply(self.alpha, self.y).T, self.X)
return w
# 输出预测结果
def predict(self, X):
w = self.cal_w()
pred = np.dot(X, w.T) + self.b
return [1 if p >= 0 else -1 for p in pred]
# 计算准确率
def accuracy(self, X, y):
predictions = self.predict(X)
correct = [1 if a == b else 0 for a, b in zip(predictions, y)]
accuracy = correct.count(1) / len(correct)
return accuracy
【机器学习】SVM(基于SMO算法)—— phthon3 实现方案
猜你喜欢
转载自blog.csdn.net/zhenghaitian/article/details/81043161
今日推荐
周排行