# -*- coding: utf-8 -*-
import random
import queue
from time import sleep
from threading import Thread
import numpy as np
''' A binary linear classifier '''
class Perceptron(object):
def __init__(self, eta):
''' init the param vector [w1, w2, bias] and build training data,
where bias is set to '1' for all samples. '''
self.w = [1.0] * 3
# learning rate
self.eta = eta
self.data = self.buildData(False)
self.error_pattern_queue = queue.Queue()
self.optimizer = Optimizer(self.error_pattern_queue)
self.optimizer.start()
''' if sepratable is 'false', return unsepratble sample data
, sepratble otherwise. '''
def buildData(self, sepratble = True):
if sepratble:
return [(1,1,1,1),(2,3,1,1),(2,1,1,-1),(3,2,1,-1)]
else:
return [(1,1,1,-1),(2,3,1,1),(2,1,1,1),(3,2,1,-1)]
''' print the builded sample data'''
def viewData(self):
for x1, x2, bias, t in self.data:
print("%s, %s, %s, %s" % (x1, x2, bias, t))
''' train the perceptron by using gradient descend. '''
def train(self):
iter_num= 0
while True:
print('Epoch: %s, w = (%s, %s, %s)' % (iter_num, self.w[0], self.w[1], self.w[2]))
# search error patterns
for s in self.data:
if np.dot(self.w, s[0:3]) * s[3] <= 0:
self.error_pattern_queue.put(s)
self.error_pattern_queue.put('Done')
while not self.optimizer.is_compute_finish:
sleep(0.001)
if self.optimizer.delta_loss == [0.0] * 3:
break
# update 'w'
self.w = list(map(lambda x,y: x + y * self.eta, self.w, self.optimizer.delta_loss))
self.optimizer.reset()
iter_num += 1
''' A thread being responsible for updating the 'w' vector '''
class Optimizer(Thread):
def __init__(self, error_pattern_queue):
super(Optimizer, self).__init__()
self.error_pattern_queue = error_pattern_queue
self.is_compute_finish = False
self.delta_loss = [0.0] * 3
def reset(self):
self.delta_loss = [0.0] * 3
self.is_compute_finish = False
def run(self):
while True:
s = self.error_pattern_queue.get()
if s == 'Done':
self.is_compute_finish = True
continue
if s[3] == 1:
self.delta_loss = list(map(lambda x1,x2: x1 + x2, self.delta_loss, s[0:3]))
else:
self.delta_loss = list(map(lambda x1,x2: x1 - x2, self.delta_loss, s[0:3]))
def main():
pt = Perceptron(1.0)
pt.train()
if __name__ == '__main__':
main()
1、Perceptron构造函数将待求解参数进行初始化,其中将偏置bias设为1,并构造训练数据(注意: 如果sepratble设为False, 则将构造一个线性不可分数据集),最后启动梯度下降执行线程,与train()方法并行执行(注意: 此处无需将所有误分类模式找到后再进行计算梯度,而是以流式计算的方式进行,即发现一个误分类模式便开始更新梯度)。
2、实验发现,将学习率eta设置为很大的值(如1000)和 一般大小的值(如10)的迭代次数是相同的(在两个不同的线性可分数据集上均观察到此现象),但理论解释尚未弄清,希望对此有研究的大神们给出相关解释。
3、感知机仅能用于二分类,且对线性不可分数据无能无力,因为感知机本质上学习的是一个线性分类函数(参数分量与样本分量一一对应,为线性函数)。
4、感知机训练出的最终模型由参数的初始化以及学习率共同决定。