1. PLA算法python实现
为便于在二维空间中进行画图分析,构建二维感知机模型,即w和x均为二维向量,根据学习算法的原始形式,可以得出PLA算法的代码如下:
def PLA():
W = np.zeros(2)
b = 0
count = 0
while True:
count += 1
cease = True
for i in range(0,len(dataset)):
x = dataset[i][:-1]
X = np.array(x)
Y = np.dot(W,X) + b
if sign(Y) == sign(dataset[i][-1]):
continue
else:
cease = False
W = W + (dataset[i][-1]) * X
b = b + dataset[i][-1]
if cease:
break
print("W:",W)
print("count:",count)
Display(dataset,W[0],W[1],b)
return W
观察PLA可知,在数据集线性可分的情况下,PLA一定能停机;
在数据集线性不可分的情况下,对任意由w和b决定的线性划分,总存在某一点x使得wx+b != sign(y),故PLA将不会停机。
2. POCKET_PLA算法python实现
为使PLA算法可用于线性不可分的情况,引入容忍噪声的PLA算法,即POCKET_PLA算法,其主要思想如下:
初始化最优划分[w_best,b_best]
对整个数据集:
1 (随机)找到一个被分错的点(x , y)
2 尝试使用该点来修正当前划分
wt+1 = wt + y*x
bt+1 = bt + y
3 如果修正后的划分 [wt+1,bt+1] 优于 [w_best,b_best] ,则将 [w_best,b_best] 更新为 [wt+1,bt+1]
循环直至 限定的循环次数(POCKET_PLA算法不会自行停机)
最终得到的[wt+1,bt+1] 即为最好的线性分类器
一言以蔽之,在POCKET_PLA算法中,保证每次 w_best 中存放的都是最优划分。
由以上思想可得 POCKET_PLA 的代码实现如下:
def POCKET_PLA():
count = 0 #count记录继上次找到一个更好的划分开始,已经走过的循环次数
W = np.ones(2)
b = 0
best_W = W
best_b = b #best_W和best_b共同记录当前最好的划分
min_num = 15 #min_num记录最好划分错误点的个数,初始化为全部点的个数
while True:
count += 1
cease = True
faultset = []
for i in range(0,len(dataset)):
x = dataset[i][:-1]
X = np.array(x)
Y = np.dot(W,X) + b
if sign(Y) == sign(dataset[i][-1]):
continue
else:
cease = False
faultset.append(dataset[i]) #该点分错,加入错误点集
if cease == False:
j = random.randint(0,len(faultset)-1) #从被分错的点中随机选取一个并尝试修正
W = W + (faultset[j][-1]) * faultset[j][:-1]
b = b + faultset[j][-1]
num = num_fault(W,b,dataset)
if num < min_num: #找到一个j更好的划分,记录下来
count = 0 #将count归零
min_num = num
best_W = W
best_b = b
if (cease or count==100): #限制迭代次数,上限100次
break
print("best_W:",best_W)
print("best_b:",best_b)
print("count:",count)
print("min_fault_point:",min_num)
return best_W
容易看出,每修正一次划分,POCKET_PLA 算法都会遍历一次所有的点来判断新划分的好坏(num_fault函数见文末代码),因此在计算同一线性可分的数据集时,POCKET_PLA 算法应该慢于PLA算法。
3. 数据集选取
自行构建一个简单的线性可分数据集:
dataset = [[0.10 ,-0.10, 1],
[0.30 , 0.60, 1],
[0.50 ,-0.20, 1],
[0.60 ,-0.25, 1],
[-0.10,-0.25, 1],
[-0.42,-0.30, 1],
[-0.50,-0.15,-1],
[-0.55,-0.12,-1],
[-0.70,-0.28,-1],
[-0.51, 0.22,-1],
[-0.48, 0.48,-1],
[-0.52, 0.47,-1],
[ 0.15, 0.63,-1],
[ 0.09, 0.81,-1],
[-0.68, 0.58,-1]]
使用matplotlib库绘制出其散点图:
自行构建一个简单的线性不可分数据集:
dataset = [[ 0.10,-0.10, 1],
[ 0.00, 0.75, 1],
[ 0.50,-0.20, 1],
[ 0.60,-0.25, 1],
[-0.10,-0.25, 1],
[-0.55, 0.30, 1],
[-0.50,-0.15,-1],
[-0.55,-0.12,-1],
[ 0.53,-0.28,-1],
[-0.51, 0.22,-1],
[-0.48, 0.48,-1],
[-0.52, 0.47,-1],
[ 0.15, 0.63,-1],
[ 0.09, 0.81,-1],
[-0.68, 0.58,-1]]
4. 验证算法执行的时间和效果
4.1 比较同一线性可分数据集下PLA和POCKET_PLA算法的执行时间和效果
(1)执行时间比较
计算程序执行时间的代码:
import time
starttime = time.time()
endtime = time.time()
dtime = endtime - starttime
在程序主体中加入以上代码,使用构建的线性可分数据集分别执行10次PLA和POCKET_PLA算法并记录执行次数和执行时间(精确到小数点后6位)
NO. | PLA执行时间(s) | PLA循环次数 | POCKET_PLA执行时间(s) | P_P循环次数 |
1 | 0.001993 | 22 | 0.005988 | 36 |
2 | 0.001995 | 22 | 0.005018 | 30 |
3 | 0.002030 | 22 | 0.006949 | 42 |
4 | 0.001962 | 22 | 0.006981 | 34 |
5 | 0.001991 | 22 | 0.003988 | 28 |
6 | 0.001994 | 22 | 0.004986 | 28 |
7 | 0.001993 | 22 | 0.004987 | 34 |
8 | 0.001981 | 22 | 0.007949 | 42 |
9 | 0.001993 | 22 | 0.005984 | 36 |
10 | 0.002000 | 22 | 0.006980 | 48 |
平均 | 0.001993 | 22 | 0.005482 | 36 |
可见PLA算法在执行速度上明显比POCKET_PLA算法要快。
(2)执行效果比较
由于POCKET_PLA每次从错误点中随机选取一个进行修正,而PLA中无随机选取的过程,故 POCKET_PLA算法每次运行结果不同,而PLA算法每次运行结果相同。
PLA运行结果:
POCKET_PLA运行结果:
第一次运行
第二次运行
第三次运行
4.2 验证线性不可分情况下POCKET_PLA算法的执行效果
同理,每次运行的执行效果不相同:
第一次运行:
第二次运行:
第三次运行:
5. 附完整代码:POCKET_PLA(Divisible and Indivisible)
import numpy as np
import time
import matplotlib.pyplot as plt
from numpy import *
import random
"""dataset = [[0.10 ,-0.10, 1],
[0.30 , 0.60, 1],
[0.50 ,-0.20, 1],
[0.60 ,-0.25, 1],
[-0.10,-0.25, 1],
[-0.42,-0.30, 1],
[-0.50,-0.15,-1],
[-0.55,-0.12,-1],
[-0.70,-0.28,-1],
[-0.51, 0.22,-1],
[-0.48, 0.48,-1],
[-0.52, 0.47,-1],
[ 0.15, 0.63,-1],
[ 0.09, 0.81,-1],
[-0.68, 0.58,-1]]"""
dataset = [[ 0.10,-0.10, 1],
[ 0.00, 0.75, 1],
[ 0.50,-0.20, 1],
[ 0.60,-0.25, 1],
[-0.10,-0.25, 1],
[-0.55, 0.30, 1],
[-0.50,-0.15,-1],
[-0.55,-0.12,-1],
[ 0.53,-0.28,-1],
[-0.51, 0.22,-1],
[-0.48, 0.48,-1],
[-0.52, 0.47,-1],
[ 0.15, 0.63,-1],
[ 0.09, 0.81,-1],
[-0.68, 0.58,-1]]
dataset = np.array(dataset) #变为数组,不然无法进行分片操作
def Display(dataset,w0,w1,b): #结果图
plt.scatter(dataset[0:6,0], dataset[0:6,1], color='blue', marker='o', label='Positive')
plt.scatter(dataset[6:,0], dataset[6:,1], color='red', marker='x', label='Negative')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(loc='upper left')
plt.title('Scatter')
plt.plot([-1,1],[(w0-b)/w1,-1*(w0+b)/w1],'g')#画直线
plt.show()
def num_fault(W,b,dataset): #当前划分下错误点的个数
count = 0
for i in range(0,len(dataset)):
X = np.array(dataset[i][:-1])
Y = np.dot(W,X) + b
if sign(Y) == sign(dataset[i][-1]):
continue
else:
count += 1
return count
def POCKET_PLA():
starttime = time.time()
count = 0 #count记录继上次找到一个更好的划分开始,已经走过的循环次数
W = np.ones(2)
b = 0
best_W = W
best_b = b #best_W和best_b共同记录当前最好的划分
min_num = 15 #min_num记录最好划分错误点的个数,初始化为全部点的个数
while True:
count += 1
cease = True
faultset = [] #错误点集
for i in range(0,len(dataset)):
x = dataset[i][:-1]
X = np.array(x)
Y = np.dot(W,X) + b
if sign(Y) == sign(dataset[i][-1]):
continue
else:
cease = False
faultset.append(dataset[i]) #该点分错,加入错误点集
if cease == False:
j = random.randint(0,len(faultset)-1) #从被分错的点中随机选取一个并尝试修正
W = W + (faultset[j][-1]) * faultset[j][:-1]
b = b + faultset[j][-1]
num = num_fault(W,b,dataset)
if num < min_num: #找到一个更好的划分,记录下来
count = 0 #将count归零
min_num = num
best_W = W
best_b = b
if (cease or count==100): #限制迭代次数,上限100次
break
endtime = time.time()
dtime = endtime - starttime
print("time: %.8s s" % dtime)
print("best_W:",best_W)
print("best_b:",best_b)
print("count:",count)
print("min_fault_point:",min_num)
Display(dataset,best_W[0],best_W[1],best_b)
return best_W
def main():
W = POCKET_PLA()
if __name__ == '__main__':
main()