机器学习第一次编程作业:

分别生成线性可分数据集和非线性可分数据集,对每个数据集均采用PLA算法及Pocket算法进行分类。比较两种算法的各自优劣性。

1.生成数据集

  1. import random
  2. import pandas
  3. from pandas import DataFrame
  4. data1=DataFrame(columns=['X','Y','label'])#数据集1
  5. data2=DataFrame(columns=['X','Y','label'])#数据集2
  6. for i in range(0,50):
  7. X1=random.randint(0,50)
  8. Y1=random.randint(0,50)
  9. if(X1+Y1-50>0):
  10. lable=1
  11. else:
  12. lable=-1
  13. data1 = data1.append(DataFrame({"X":X1, "Y":Y1, "label":lable},columns = ["X", "Y", "label"],index=[0]),ignore_index=True)
  14. #append函数在每列元素下增加一个新生成的元素
  15. data1.to_csv('data1.csv',index=False, header=False)#生成数据集1
  16. for i in range(0,50):
  17. X2=random.randint(0,50)
  18. Y2=random.randint(0,50)
  19. if(X2+Y2-50>0 and i<45):
  20. lable=1
  21. elif(X2+Y2-50>0 and i>=45):
  22. lable=-1
  23. elif(X2+Y2-50<=0 and i<45):
  24. lable=-1
  25. elif(X2+Y2-50<=0 and i>=45):
  26. lable=1
  27. data2 = data2.append(DataFrame({"X":X2, "Y":Y2, "label":lable},columns = ["X", "Y", "label"],index=[0]),ignore_index=True)
  28. #append函数在每列元素下增加一个新生成的元素
  29. data2.to_csv('data2.csv',index=False, header=False)#生成数据集2

关于pandas库的使用说明,利用pandas库生成两组数据集,每组数据集有50个数据。其中第一组数据是由直线x+y=50分成两部分,直线上方的数据标记为1,直线下方的数据标记为-1,改组数据为线性可分数据集;第二组数据生成过程基本相同,但对于最后5个数据点,将直线上方数据标记为-1,直线下方数据标记为1.(存在着一定的随机性,并不能保证数据一定线性不可分)

2.PLA算法

  1. import numpy as np
  2. import pandas as pd
  3. import time
  4. import matplotlib.pyplot as plt
  5. # 1、导入数据集
  6. def loaddata():
  7. data1 = pd.read_csv("data1.csv", header=None)
  8. samples = data1.iloc[:, :2].values # 取所有行,前两列:2实际上是0:2
  9. labels = data1.iloc[:, 2].values # 取所有行,第三列
  10. return samples, labels
  11. # 2、训练感知机模型
  12. # 定义一个PLA算法类
  13. class PLA:
  14. def __init__(self, x, y): # 初始化函数,x={x1,x2}为点集,y={-1,1}为标签集,a为学习率系数
  15. self.x = x
  16. self.y = y
  17. self.a = 0.1
  18. self.b = 0
  19. self.w = np.zeros((x.shape[1], 1)) # 初始化权重
  20. self.sumexamples = self.x.shape[0]
  21. self.sumxdims = self.x.shape[1]
  22. def sign(self, w, b, x): # y的函数关系
  23. y = np.dot(x, w) + b
  24. return int(y)
  25. def update(self, ylable, xdata): # 更新W,b
  26. deltaw = ylable * self.a * xdata
  27. deltaw = deltaw.reshape(self.w.shape)
  28. self.w = self.w + deltaw
  29. self.b = self.b + ylable * self.a
  30. def train(self): # 训练数据集
  31. isFind = False # 开始前没有找到合适分区
  32. num = 0
  33. while not isFind: # 定义标志位isFind,当while真,则进行下面操作,赋予标志位真
  34. count = 0
  35. for i in range(self.sumexamples):
  36. yt = self.sign(self.w, self.b, self.x[i, :])
  37. if (yt * self.y[i] <=0): # 错误分类的情况,如果不设置等于零的话,初始化后就不能迭代了
  38. print('第', num, '次错误分类的点为:', self.x[i, :], '此时的w和b为:', self.w, self.b)
  39. count += 1
  40. num += 1
  41. self.update(self.y[i], self.x[i, :])
  42. if count == 0:
  43. print('最终训练得到的w和b为:', self.w, self.b)
  44. isFind = True
  45. return self.w, self.b

2.1PLA算法伪代码:

1.(二维情况下)设实现PLA算法和Pocket算法 - 图1为数据集,实现PLA算法和Pocket算法 - 图2为横坐标,实现PLA算法和Pocket算法 - 图3为纵坐标,权重系数为实现PLA算法和Pocket算法 - 图4.初始化参数,设实现PLA算法和Pocket算法 - 图5.从实现PLA算法和Pocket算法 - 图6开始循环,直至实现PLA算法和Pocket算法 - 图7

2.判断是否有实现PLA算法和Pocket算法 - 图8,若等式成立则进行第3步,否则进行第四步。
3.实现PLA算法和Pocket算法 - 图9,回到第2步;实现PLA算法和Pocket算法 - 图10时,break.
4.更新实现PLA算法和Pocket算法 - 图11:实现PLA算法和Pocket算法 - 图12,实现PLA算法和Pocket算法 - 图13,回到第二步;
附:1.class类的init函数使用方法

2.2算法可视化表示:

  1. #3.画图显示
  2. class picture:
  3. def __init__(self,data,w,b,l):
  4. self.b=b
  5. self.w=w
  6. plt.figure(1)
  7. plt.title('PLA算法',size=15)
  8. plt.xlabel('x',size=10)
  9. plt.ylabel('y',size=10)
  10. xData = np.linspace(0, 50, 100, endpoint=True) # 生成横坐标
  11. yData = self.expression(xData)
  12. plt.plot(xData, yData, color='r', label='分类线')
  13. plt.scatter(data[5][0], data[5][1], color='b', s=50, label='+1')
  14. plt.scatter(data[0][0], data[0][1], color='g', s=50, marker='*', label='-1')
  15. for i in range(0, len(l)):
  16. if (l[i] > 0):
  17. plt.scatter(data[i][0], data[i][1], color='b', s=50)
  18. else:
  19. plt.scatter(data[i][0], data[i][1], color='g', s=50, marker='*')
  20. plt.savefig('PLA.png', dpi=75)
  21. plt.legend(loc='upper left')#标签位置
  22. def expression(self, x):
  23. y = (-self.b - self.w[0] * x) / self.w[1] # 注意在此,把x0,x1当做两个坐标轴,把x1当做自变量,x2为因变量,由此生成直线
  24. return y
  25. def Show(self):
  26. plt.show()
  27. plt.rcParams['font.sans-serif']=['SimHei'] #显示中文标签
  28. plt.rcParams['axes.unicode_minus']=False #这两行需要手动设置

附:2.Python 绘图包 Matplotlib Pyplot 教程

2.3算法计算耗时:

  1. if __name__ == '__main__':
  2. start = time.time()
  3. samples, labels = loaddata()
  4. mypla = PLA(x=samples, y=labels)
  5. weights, bias = mypla.train()
  6. costtime = time.time() - start
  7. print("Time used:", costtime)
  8. picture = picture(samples, weights, bias,labels)
  9. picture.Show()

3.Pocket算法

  1. import numpy as np
  2. import pandas as pd
  3. import time
  4. import matplotlib.pyplot as plt
  5. import random
  6. # 1、导入数据集
  7. def loaddata():
  8. data1 = pd.read_csv("data2.csv", header=None)
  9. samples = data1.iloc[:, :2].values # 取所有行,前两列:2实际上是0:2
  10. labels = data1.iloc[:, 2].values # 取所有行,第三列
  11. return samples, labels
  12. # 2、训练感知机模型
  13. # 定义一个Pocket算法类
  14. class Pocket:
  15. def __init__(self, x, y): # 初始化函数,x={x1,x2}为点集,y={-1,1}为标签集,a为学习率系数
  16. self.x = x
  17. self.y = y
  18. self.a = 1
  19. self.b = 0
  20. self.w = np.zeros((x.shape[1], 1)) # 初始化权重
  21. self.best_w = np.zeros((x.shape[1], 1)) # 设置一个误分类点最少的最佳权重用于比较
  22. self.best_b = 0
  23. self.sumexamples = self.x.shape[0]
  24. self.sumxdims = self.x.shape[1]
  25. def sign(self, w, b, x): # y的函数关系
  26. y = np.dot(x, w) + b
  27. return int(y)
  28. def update(self, ylable, xdata): # 更新W,b
  29. deltaw = ylable * self.a * xdata
  30. deltaw = deltaw.reshape(self.w.shape)
  31. wc = self.w + deltaw#用于比较的中间值
  32. bc= self.b + ylable * self.a
  33. if (len(self.countnum(self.best_w, self.best_b)) >= (len(self.countnum(wc, bc)))):
  34. self.best_w=self.w + deltaw
  35. self.best_b=self.b + ylable * self.a
  36. self.w=self.w+deltaw
  37. self.b=self.b+ ylable * self.a
  38. def countnum(self,w,b):
  39. mistakes=[]
  40. for i in range(self.sumexamples):
  41. Y=self.sign(w, b, self.x[i, :])
  42. if (Y*self.y[i] <=0):#误分类点
  43. mistakes.append(i)
  44. return mistakes
  45. def train(self,number): # 训练数据集
  46. isFind = False # 开始前没有找到合适分区
  47. num = 0#循环次数
  48. while not isFind: # 定义标志位isFind,当while真,则进行下面操作,赋予标志位真
  49. mistakes=self.countnum(self.w,self.b)
  50. if (len(mistakes)==0):
  51. print('最终训练得到的w和b为:', self.best_w, self.best_b)
  52. break
  53. n = mistakes[random.randint(0, len(mistakes) - 1)]
  54. self.update(self.y[n], self.x[n, :])
  55. print('第', num, '次错误分类的点为:', self.x[n, :], '此时的w和b为:', self.w, self.b)
  56. num+=1
  57. print('此时误分类点数为',len(mistakes))
  58. if num == number:
  59. print('最终训练得到的w和b为:', self.best_w, self.best_b)
  60. print('最终训练得到的误分类点个数为', len(self.countnum(self.best_w, self.best_b))+1)
  61. isFind = True
  62. return self.best_w, self.best_b
  63. #3.画图显示
  64. class picture:
  65. def __init__(self,data,w,b,l):
  66. self.b=b
  67. self.w=w
  68. plt.figure(1)
  69. plt.title('Pocket算法',size=15)
  70. plt.xlabel('x',size=10)
  71. plt.ylabel('y',size=10)
  72. xData = np.linspace(0, 50, 100, endpoint=True) # 生成横坐标
  73. yData = self.expression(xData)
  74. plt.plot(xData, yData, color='r', label='分类线')
  75. plt.scatter(data[2][0], data[5][1], color='b', s=50, label='+1')
  76. plt.scatter(data[0][0], data[0][1], color='g', s=50, marker='*', label='-1')
  77. for i in range(0, len(l)):
  78. if (l[i] > 0):
  79. plt.scatter(data[i][0], data[i][1], color='b', s=50)
  80. else:
  81. plt.scatter(data[i][0], data[i][1], color='g', s=50, marker='*')
  82. plt.savefig('PLA.png', dpi=75)
  83. plt.legend(loc='upper left')#标签位置
  84. def expression(self, x):
  85. y = (-self.b - self.w[0] * x) / self.w[1] # 注意在此,把x0,x1当做两个坐标轴,把x1当做自变量,x2为因变量,由此生成直线
  86. return y
  87. def Show(self):
  88. plt.show()
  89. plt.rcParams['font.sans-serif']=['SimHei'] #显示中文标签
  90. plt.rcParams['axes.unicode_minus']=False #这两行需要手动设置
  91. if __name__ == '__main__':
  92. start = time.time()
  93. samples, labels = loaddata()
  94. mypocket = Pocket(x=samples, y=labels)
  95. weights, bias = mypocket.train(10000)
  96. costtime = time.time() - start
  97. print("Time used:", costtime)
  98. picture = picture(samples, weights, bias,labels)
  99. picture.Show()

3.1Pocket算法伪代码:

Pocket算法与PLA算法基本相同,只是由于某些数据不一定是线性可分的,对于这种情况,利用PLA算法将会无限迭代下去,此时就需要在允许一定误差存在的情况下,找到此时最佳的分类直线,由此提出了Pocket算法。其中,Pocket算法与PLA算法最大的区别在于权重系数的更新。
1.(二维情况下)设实现PLA算法和Pocket算法 - 图14为数据集,实现PLA算法和Pocket算法 - 图15为横坐标,实现PLA算法和Pocket算法 - 图16为纵坐标,权重系数为实现PLA算法和Pocket算法 - 图17.初始化参数,设实现PLA算法和Pocket算法 - 图18.从实现PLA算法和Pocket算法 - 图19开始循环,直至实现PLA算法和Pocket算法 - 图20
2.判断是否有实现PLA算法和Pocket算法 - 图21,若等式成立则进行第3步,否则进行第四步。
3.实现PLA算法和Pocket算法 - 图22,回到第二步;实现PLA算法和Pocket算法 - 图23时,break.
4.更新实现PLA算法和Pocket算法 - 图24:实现PLA算法和Pocket算法 - 图25,进行第五步;
5.判断是否有实现PLA算法和Pocket算法 - 图26分类下最佳错误分类点的个数小于实现PLA算法和Pocket算法 - 图27分类下最佳错误分类点的个数,若实现PLA算法和Pocket算法 - 图28误分类点更少,则取其作为最佳权重实现PLA算法和Pocket算法 - 图29实现PLA算法和Pocket算法 - 图30;否则不改变最佳权重,取实现PLA算法和Pocket算法 - 图31。回到第二步。
最后,为了让程序停下来,我们可以设置一个迭代次数N,迭代N次后算法停止。

4.算法比较

4.1线性可分数据集

首先,两种算法对于线性可分数据集均可找到合适的实现PLA算法和Pocket算法 - 图32对数据进行正确的划分,因此可以在设置初始权重为0,学习率实现PLA算法和Pocket算法 - 图33的情况下,对data1进行分类:

算法 运行时间 迭代次数
PLA算法 Time used: 1.3461673259735107 9137
Pocket算法 Time used: 5.010123252868652 7318

在初始权重为0,学习率实现PLA算法和Pocket算法 - 图34的情况下,对data1进行分类:

算法 运行时间 迭代次数
PLA算法 Time used: 0.9483866691589355 6646
Pocket算法 Time used: 2.678621292114258 3662

可以发现,两个算法相比,Pocket算法的迭代次数要低于PLA算法,但运行时间要比PLA算法长。迭代次数的不同主要是因为数据点选取的随机性,前期对一些影响小的数据进行了迭代;但是Pocket算法运行时间长是由于,每次进行完权重更新后,需要选出此时的参数与之前最优参数相比下,谁能得到更少的误分类点。两个算法运行结果如下:
image.pngimage.png
可以发现两个算法都能较好的实现线性可分数据集的分类。

4.2线性不可分数据集

对于线性不可分数据集,PLA算法会无限迭代下去,且每次迭代对会有较强的随机性,基本无法处理此类数据集。可以利用Pocket算法进行分类,求得误分类点最少的情况。
当初始权重为0,学习率实现PLA算法和Pocket算法 - 图37的情况下,设置不同迭代上限,对data2进行分类:

Pocket算法迭代上限 运行时间 最少误分类点个数
50000 Time used: 33.37401080131531 5
10000 Time used: 6.838443040847778 6
5000 Time used: 3.323680877685547 7

通过对最优权重的保存,当得到相应迭代次数或者完全正确分类的时候停止,可以发现Pocket算法解决了PLA算法对于存在少数噪点的线性不可分数据集无法停止的情况。对于数据集data2,迭代上限设置在10000到50000次之间时可以得到算法误分类点最少的情况。
image.png