Python机器学习算法实现
Author:louwill
前面两讲我们对感知机和神经网络进行了介绍。感知机作为一种线性分类模型,很难处理非线性问题。为了处理非线性的情况,在感知机模型的基础上有了两个方向,一个就是上一讲说到的神经网络,大家也看到了,现在深度学习大放异彩,各种网络功能强大。但实际上在神经网络兴起之前,基于感知机的另一种模型——支持向量机,同样可以解决非线性问题。
支持向量机一般来说有三种任务类型:线性可分情况,近似线性可分情况以及线性不可分情况。针对这三种分别线性可分支持向量机、线性支持向量机和线性不可分支持向量机。笔者将分三次对这三种支持向量机进行介绍。
线性可分支持向量机
和感知机一样,线性可分支持向量机的训练目标也是寻找一个分离超平面,能将数据分成不同的类。通过感知机的学习我们知道,当训练数据线性可分时,一般存在不止一个线性超平面可以将数据分类,可能有无数多个线性超平面。而线性可分支持向量机则是利用间隔最大化求得一个最优的分离超平面。
关于函数间隔、几何间隔和支持向量等相关概念,笔者这里不过多阐述,详细细节可参考统计学习方法一书。总之,线性可分支持向量机可被形式化一个凸二次规划问题:
这里多说一句,感知机、最大熵和支持向量机等模型的优化算法都是一些经典的优化问题,对其中涉及的凸优化、拉格朗日对偶性、KKT条件、二次规划等概念,建议各位找到相关材料和教材认真研读,笔者这里不多做表述。
一般来说,我们可以直接对上述凸二次规划进行求解,但有时候该原始问题并不容易求解,这时候需要引入拉格朗日对偶性,将原始问题转化为对偶问题进行求解。原始二次规划的一般形式为:
引入拉格朗日函数:
定义该拉式函数的最大化函数:
通过证明可得原始问题等价于该拉式函数的极小极大化问题:
原始问题为极小极大化问题,根据拉格朗日对偶性,对偶问题即为极大极小化问题:
为计算该对偶问题的解,我们需要对L(w,b, α)求极小,再对α求极大。具体推导如下。第一步:
第二步:
第三步:根据KKT条件可得:
最后可根据对偶问题求得原始问题的解为:
以上便是线性可分支持向量机的对偶问题推导过程,详细过程可参考相关教材,笔者不做过多展开(主要是打公式太费时间)。
线性可分支持向量机的简单实现
这里我们基采取和之前感知机模型一样的优化思想来求解线性可分支持向量机的原始问题。
先准备示例训练数据:
data_dict = {-1:np.array([[1,7],
[2,8],
[3,8],]),
1:np.array([[5,1],
[6,-1],
[7,3],])}
导入相关package并绘图展示:
import numpy as np
import matplotlib.pyplot as plt
colors = {1:'r',-1:'g'}
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
[[ax.scatter(x[0],x[1],s=100,color=colors[i])
for x in data_dict[i]] for i in data_dict]
plt.show()
接下来定义线性可分支持向量机的模型主体和训练部分:
def train(data):
# 参数字典 { ||w||: [w,b] }
opt_dict = {}
# 数据转换列表
transforms = [[1,1],
[-1,1],
[-1,-1],
[1,-1]]
# 从字典中获取所有数据
all_data = []
for yi in data:
for featureset in data[yi]:
for feature in featureset:
all_data.append(feature)
# 获取数据最大最小值
max_feature_value = max(all_data)
min_feature_value = min(all_data)
all_data = None
# 定义一个步长列表
step_sizes = [max_feature_value * 0.1,
max_feature_value * 0.01,
max_feature_value * 0.001
]
# 参数b的范围设置
b_range_multiple = 2
b_multiple = 5
latest_optimum = max_feature_value*10
# 基于不同步长训练优化
for step in step_sizes:
w = np.array([latest_optimum,latest_optimum])
# 凸优化
optimized = False
while not optimized:
for b in np.arange(-1*(max_feature_value*b_range_multiple),
max_feature_value*b_range_multiple,
step*b_multiple):
for transformation in transforms:
w_t = w*transformation
found_option = True
for i in data:
for xi in data[i]:
yi=i
if not yi*(np.dot(w_t,xi)+b) >= 1:
found_option = False
if found_option:
opt_dict[np.linalg.norm(w_t)] = [w_t,b]
if w[0] < 0:
optimized = True
print('Optimized a step!')
else:
w = w - step
norms = sorted([n for n in opt_dict])
#||w|| : [w,b]
opt_choice = opt_dict[norms[0]]
w = opt_choice[0]
b = opt_choice[1]
latest_optimum = opt_choice[0][0]+step*2
for i in data:
for xi in data[i]:
yi=i
print(xi,':',yi*(np.dot(w,xi)+b))
return w, b
基于示例数据的训练结果如下:
然后定义预测函数:
# 定义预测函数
def predict(features):
# sign( x.w+b )
classification = np.sign(np.dot(np.array(features),w)+b)
if classification !=0:
ax.scatter(features[0], features[1], s=200, marker='^', c=colors[classification])
print(classification)
return classification
基于示例数据的预测结果:
整合成线性可分支持向量机类
整理上述代码并添加结果可视化:
class Hard_Margin_SVM:
def __init__(self, visualization=True):
self.visualization = visualization
self.colors = {1:'r',-1:'g'}
if self.visualization:
self.fig = plt.figure()
self.ax = self.fig.add_subplot(1,1,1)
# 定义训练函数
def train(self, data):
self.data = data
# 参数字典 { ||w||: [w,b] }
opt_dict = {}
# 数据转换列表
transforms = [[1,1],
[-1,1],
[-1,-1],
[1,-1]]
# 从字典中获取所有数据
all_data = []
for yi in self.data:
for featureset in self.data[yi]:
for feature in featureset:
all_data.append(feature)
# 获取数据最大最小值
self.max_feature_value = max(all_data)
self.min_feature_value = min(all_data)
all_data = None
# 定义一个学习率(步长)列表
step_sizes = [self.max_feature_value * 0.1,
self.max_feature_value * 0.01,
self.max_feature_value * 0.001
]
# 参数b的范围设置
b_range_multiple = 2
b_multiple = 5
latest_optimum = self.max_feature_value*10
# 基于不同步长训练优化
for step in step_sizes:
w = np.array([latest_optimum,latest_optimum])
# 凸优化
optimized = False
while not optimized:
for b in np.arange(-1*(self.max_feature_value*b_range_multiple),
self.max_feature_value*b_range_multiple,
step*b_multiple):
for transformation in transforms:
w_t = w*transformation
found_option = True
for i in self.data:
for xi in self.data[i]:
yi=i
if not yi*(np.dot(w_t,xi)+b) >= 1:
found_option = False
# print(xi,':',yi*(np.dot(w_t,xi)+b))
if found_option:
opt_dict[np.linalg.norm(w_t)] = [w_t,b]
if w[0] < 0:
optimized = True
print('Optimized a step!')
else:
w = w - step
norms = sorted([n for n in opt_dict])
#||w|| : [w,b]
opt_choice = opt_dict[norms[0]]
self.w = opt_choice[0]
self.b = opt_choice[1]
latest_optimum = opt_choice[0][0]+step*2
for i in self.data:
for xi in self.data[i]:
yi=i
print(xi,':',yi*(np.dot(self.w,xi)+self.b))
# 定义预测函数
def predict(self,features):
# sign( x.w+b )
classification = np.sign(np.dot(np.array(features),self.w)+self.b)
if classification !=0 and self.visualization:
self.ax.scatter(features[0], features[1], s=200, marker='^', c=self.colors[classification])
return classification
# 定义结果绘图函数
def visualize(self):
[[self.ax.scatter(x[0],x[1],s=100,color=self.colors[i]) for x in data_dict[i]] for i in data_dict]
# hyperplane = x.w+b
# v = x.w+b
# psv = 1
# nsv = -1
# dec = 0
# 定义线性超平面
def hyperplane(x,w,b,v):
return (-w[0]*x-b+v) / w[1]
datarange = (self.min_feature_value*0.9,self.max_feature_value*1.1)
hyp_x_min = datarange[0]
hyp_x_max = datarange[1]
# (w.x+b) = 1
# 正支持向量
psv1 = hyperplane(hyp_x_min, self.w, self.b, 1)
psv2 = hyperplane(hyp_x_max, self.w, self.b, 1)
self.ax.plot([hyp_x_min,hyp_x_max],[psv1,psv2], 'k')
# (w.x+b) = -1
# 负支持向量
nsv1 = hyperplane(hyp_x_min, self.w, self.b, -1)
nsv2 = hyperplane(hyp_x_max, self.w, self.b, -1)
self.ax.plot([hyp_x_min,hyp_x_max],[nsv1,nsv2], 'k')
# (w.x+b) = 0
# 线性分隔超平面
db1 = hyperplane(hyp_x_min, self.w, self.b, 0)
db2 = hyperplane(hyp_x_max, self.w, self.b, 0)
self.ax.plot([hyp_x_min,hyp_x_max],[db1,db2], 'y--')
plt.show()
测试效果如下:
data_dict = {-1:np.array([[1,7],
[2,8],
[3,8],]),
1:np.array([[5,1],
[6,-1],
[7,3],])}
svm = Hard_Margin_SVM()
svm.train(data=data_dict)
predict_us = [[0,10],
[1,3],
[3,4],
[3,5],
[5,5],
[5,6],
[6,-5],
[5,8],
[2,5],
[8,-3]]
for p in predict_us:
svm.predict(p)
svm.visualize()
以上就是本节内容,关于近似线性可分以及软间隔最大化问题,笔者将在下一篇推文中介绍。完整代码文件和数据可参考笔者GitHub地址:
https://github.com/luwill/machine-learning-code-writing
参考资料:
https://pythonprogramming.net/
https://github.com/SmirkCao/Lihang/tree/master/CH07
往期精彩:
一个数据科学从业者的学习历程
长按二维码.关注机器学习实验室