[关闭]
@ArrowLLL 2017-08-14T14:20:53.000000Z 字数 6046 阅读 2111

感知机学习算法实践


Elon Lin


概述

李航《统计学习方法》学习的第二篇博文。内容为书籍第二章感知机的算法实践。感知机的学习笔记 —— 感知机学习笔记

本篇使用kaggle网站提供的关于“数字识别”的数据。经过随机选取获得训练集和测试集,使用训练集训练感知机,对测试集测试,然后通过计算误判率来验证算法的有效性和正确性。。

初始时文件结构为 :

.
├── data.py
├── main.py
├── oldTrain.csv
├── perceptron.py
├── test.py
└── tree.txt

0 directories, 6 files

运行main.py后会新增文件,改变项目结构为如下:

.
├── ans.csv
├── data.py
├── main.py
├── oldTrain.csv
├── perceptron.py
├── pycache
│     ├── data.cpython-35.pyc
│     ├── perceptron.cpython-35.pyc
│     └── test.cpython-35.pyc
├── test.csv
├── test.py
├── train.csv
└── tree.txt

1 directory, 12 files

热如何产生这些文件的在后面的部分会讲到。

数据来源与处理

数据为kaggle网站提供的“数字识别”,可在 下载地址 中看到如下图所示的链接文件:
traincsv.PNG-28.2kB
由于于感知机的限制性,该次实践只需要train.csv,不需要test.csv,原因下面会讲到。因此点击train.csv 然后 Download 即可。

因为感知机用于解决二分类问题,不能用于将10个数字分类。所以我们要做的,是将train.csv 中不必要的数据去除掉。我选择只留下 0 和 1 的数据,将 2~9 的数据全部删除,当然读者们可以选择留下任意两个数字,可以用于二分类即可。

另外,因为我们只选取其中 0 和 1 两类数据,而kaggle提供的test.csv 是包含所有 0~9 数据的10个数据的,并且没有说明类别。这也是为什么我们不需要test.csv的原因。由于没有测试数据,因此,我们需要将处理后的 train.csv 分解为两部分,分别作为训练集和测试集,按照惯例,是8:2的比例,80%作为训练集,20%作为测试集。

以下为处理数据的python代码 data.py :

  1. #!usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import sys
  4. import random
  5. def get01(fileName) :
  6. '''
  7. fileName中含有的是带有标签0-9的数据,该函数用于将fileName中的2-9的数据
  8. 去除,只留下0和1的数据
  9. '''
  10. data = []
  11. with open(fileName) as f:
  12. lines = f.read().split()
  13. tag = lines[0]
  14. for line in lines[1:] :
  15. if line[0] in ('1', '0') :
  16. data.append(line)
  17. return tag, data
  18. def getData(args) :
  19. '''
  20. 用于获取args文件中的0和1的数据,并将数据按照2:8分为数据集和测试集,以及测试集对应的
  21. 答案放入相应的文件 "train.csv", "test.csv", "ans.csv"
  22. '''
  23. # 获取只包含0和1的数据集
  24. tags, lines = get01(args)
  25. lineNum = len(lines)
  26. # 随机取8:2的数据
  27. testDataSet = set(random.sample(range(lineNum), (int)(lineNum / 5)))
  28. # 创建相应的文件
  29. train = open('train.csv', 'w+')
  30. train.write(tags + '\n')
  31. test = open('test.csv', 'w+')
  32. tags.replace('label', 'case')
  33. test.write(tags + '\n')
  34. answer = open('ans.csv', 'w+')
  35. answer.write("case,label\n")
  36. # 写入文件
  37. c = 0
  38. for i, line in zip(range(lineNum), lines) :
  39. label = '+1' if line[0] == '1' else '-1'
  40. if i in testDataSet :
  41. answer.write("%s,%s\n" % (c, label))
  42. test.write(str(c) + line[1:] + '\n')
  43. c += 1
  44. else :
  45. line = label + line[1:]
  46. train.write(line + '\n')
  47. # 在终端输出分配结果
  48. print ('data num : ' + str(lineNum))
  49. print ('train data : ' + str(lineNum - len(testDataSet)))
  50. print ('test data : ' + str(len(testDataSet)))
  51. train.close()
  52. test.close()
  53. return 'train.csv', 'test.csv', 'ans.csv'
  54. if __name__ == '__main__' :
  55. if len(sys.argv) == 2 :
  56. oldFile = sys.argv[1]
  57. else :
  58. oldFile = 'oldTrain.csv'
  59. print (getData(oldFile))

首先需要将下载的 train.csv 更名为 oldTrain.csv. 从python代码中可以看出,生成的训练数据文件也叫 train.csv, 更改源文件名字可以防止原数据被覆盖,从而保护原本的数据,多次利用。

data.py 的作用就是将原本的 oldTrain.csv 中的数据按照 8:2 的比例分为 train.csvtest.csv,以及 test.csv 对应的label的答案 ans.csv 。由于是随机取数据,故该代码可以重用。每运行一次可以由原来的数据生成不同的训练集和测试集。

这个代码很好理解,如果有兴趣的话,可以0和1的二分类改为任何两个数字的二分类问题。例如3和4, 2和8, 9和7等等的。

感知机模型训练

感知机模型的训练代码保存为 perceptron.py. 这里采用的是原始形式的感知机模型。学习率为 (eta),在代码中可以自行调整,默认值为1。

  1. #!usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. w, b, eta = [], 0, 1
  4. def L(x) :
  5. '''
  6. 函数 L(x) = w .* x + b
  7. '''
  8. return sum(wv * xv for (wv, xv) in zip(w, x)) + b
  9. def findWrong(dataSet) :
  10. '''
  11. 找到数据集中的误分类点,如果没有找到返回 None,找到则返回
  12. 一个元组 (误分类结果 y, 该误分类点的特征向量 x)
  13. '''
  14. t = 0
  15. for (y,x) in dataSet :
  16. t += 1
  17. if L(x) * y <= 0 :
  18. return (y, x)
  19. return None
  20. def sgd(data) :
  21. '''
  22. 利用传入的误分类点使用梯度下降法极小化目标函数 :
  23. w = w + (eta * y) .* x
  24. b = b + eta * y
  25. '''
  26. global w, b, eta
  27. y, x = data
  28. w = [dw + eta * y * dx for (dw, dx) in zip(w, x)]
  29. b = b + eta * y
  30. def train(fileName, _eta = 1) :
  31. '''
  32. 训练过程函数,感知机要求训练数据保证线性可分,采用的是原始形式的感知机学习算法
  33. '''
  34. # 取出对应csv按文件中的数据,并存入列表dataSet
  35. # 列表中的单个数据以二元组形式存储,(标签, 特征向量)
  36. f = open(fileName)
  37. data = f.read().split()
  38. column_names = data[0].split(',')
  39. f.close()
  40. dataSet = []
  41. for singleData in data[1:] :
  42. singleData = list(map(int, singleData.split(',')))
  43. dataSet.append((singleData[0], singleData[1:]))
  44. # 初始化全局变量 :权值向量w, 偏置b, 学习率eta
  45. global w, b, eta
  46. eta = _eta
  47. w = [0 for i in range(len(dataSet[0][4]))]
  48. trainTime = 0
  49. # 训练过程,先找到一个误分类点,然后用sgd函数进行修正,
  50. while True :
  51. selectData = findWrong(dataSet)
  52. if selectData == None : break
  53. sgd(selectData)
  54. trainTime += 1
  55. if trainTime % 10 == 0 :
  56. print ("----- train Time :", trainTime, " -----")
  57. # 输出训练次数,即误分类次数
  58. print ("----- total train time :", trainTime, " -----")
  59. return w, b
  60. if __name__ == '__main__' :
  61. w, b = train('train.csv')
  62. print ("w :", w)
  63. print ("b = ", b)

perceptron.py 即为训练模型的代码实现。采用的是感知机学习算法的原始形式,为了查看训练过程,每误分类10次就输出一次训练次数。

train() 函数中使用 findWrong() 函数找到误分类点,然后用 sgd() 函数利用梯度下降法极小化目标函数;findWrong() 函数中又调用 L(data) 函数得到目标函数值。

代码并不复杂,加上注释中给出的过程以及参考《统计学习方法》对应的模型很容易理解。

对测试集进行测试

测试代码 test.py,用于对 test.csv 文件内的数据进行分类 :

  1. #!usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. w, b = [], 0
  4. def test(x) :
  5. '''
  6. 按照模型 y = w .* x + b 计算对应数据的类别
  7. y值非负返回为 +1
  8. y值为负则返回 -1
  9. '''
  10. global w, b
  11. L = sum(wv * xv for (wv, xv) in zip(w, x)) + b
  12. return 1 if L >= 0 else -1
  13. def testFile(fileName, dw, db) :
  14. '''
  15. 从测试文件fileName中读取测试集的特征,利用dw和db构成的感知机模型对
  16. fileName中的测试数据进行分类
  17. '''
  18. global w, b
  19. w, b = dw, db
  20. # 读取数据
  21. with open(fileName) as f :
  22. testData = f.read().split()
  23. # 使用 test()函数对数据进行分类,并将结果存入testResult列表
  24. testResult, column_names = {}, testData[0]
  25. for data in testData[1:] :
  26. data = data.split(',')
  27. label = data[0]
  28. result = test(map(int, data[1:]))
  29. testResult[label] = result
  30. # 返回结果,为字典类型{label : result}
  31. return testResult
  32. if __name__ == '__main__' :
  33. dw = [0 for i in range(28 * 28)]
  34. testResult = testFile('test.csv', dw, 0)
  35. for label, result in testResult.items() :
  36. if(result == -1) :
  37. print (label, ":", result)

需要注意的是, 在 '__main__' 中将 dwdb 分别标记为零向量和零值,单独运行时并不能对测试文件正确分类。需要利用 main.py 将上面三个代码结合起来才能得到正确的分类结果。

过程就是读取,然后分类,根据代码注释,也并不难理解。

主函数main

main.py 文件将上述3份代码结合起来,将原来的 oldTrain.csv 进行剔除选择测试集和训练集,然后训练,然后测试,最终输出结果。

  1. #!usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import data
  4. import perceptron
  5. import test
  6. import os, sys
  7. def dealAns(ansFile) :
  8. '''
  9. 读取csv文件ansFile,并进行处理获得结果字典{case : resultLabel}
  10. '''
  11. with open(ansFile) as f :
  12. ansSet = f.read().split()
  13. answer, column_names = {}, ansSet[0].split(',')
  14. for ans in ansSet[1:] :
  15. ans = ans.split(',')
  16. answer[ans[0]] = int(ans[1])
  17. return answer
  18. def main(dataFileName, eta) :
  19. '''
  20. 传入参数包括原本包含0-9这10个数字的文件集合dataFileName, 以及预先传入的学习率eta值
  21. 在终端输出最终的测试结果,包括正确分类和错误分类的数量以及分类的正确率
  22. '''
  23. # 调用 data.getData 对数据集清洗并按照8 :2分集
  24. trainFile, testFile, ansFile = data.getData(dataFileName)
  25. # 使用perceptron.train利用训练集训练感知机模型
  26. w, b = perceptron.train(trainFile, eta)
  27. # 调用 test.testFile 对测试文件中的数据进行测试,传入相应的w值和b值
  28. testResult = test.testFile(testFile, w, b)
  29. # 读取ans.csv文件返回一个字典 {case : label}
  30. answer = dealAns(ansFile)
  31. # 输出测试结果
  32. positive, negative = 0, 0
  33. for label, result in testResult.items() :
  34. if answer[label] == result :
  35. positive += 1
  36. else :
  37. negative += 1
  38. print ("PN = ", positive, negative)
  39. print ("accurity =", positive / (positive + negative))
  40. if __name__ == '__main__' :
  41. argv = sys.argv
  42. if(len(argv) == 3) :
  43. main(argv[1], int(argv[2]))
  44. else :
  45. 'parame error !!'

代码如上,过程也已在代码中注释标明, 不做过多的解释。

调用的命令需要加入两个参数,分别是原始文件的名字和学习率eta值。例如本人将原始数据文件保存为 oldTrain.csv,学习率选定为 1,因此在终端输入的命令为 python3 main.py oldTrain.csv 1

运行过程与结果

如下图 :
perceptron.png-47.8kB

因为每一次挑选的测试集和训练集胡有所差别,所以测试结果也会有不同,但总体的分类正确率均在0.99以上。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注