感知机是二类分类的线性分类模型,旨在求出将训练数据进行线性划分的分离超平面,所以导入基于误分类的损失函数,利用梯度降低法对损失函数进行极小化,求得感知机模型。html
假设输入空间是 ,输出空间是 。输入 表示实例的特征向量,对应于输入空间的点;输出 表示实例的类别。由输入空间到输出空间的以下函数python
web
称为感知机。其中, 叫作权值(weight)或权值向量(weight vector), 叫作偏置(bias)。 是符号函数,即算法
express
线性方程app
dom
对于特征空间 中的一个超平面 S ,其中 是超平面的法向量, 是超平面的截距。这个超平面将特征空间划分为两个部分。位于两部分的点(特征向量)分别被分为正、负两类。所以超平面 称为分离超平面,如图所示svg
感知机经过训练训练集数据求得感知机模型,即求得模型参数 , 。经过学习获得的感知机模型,对于新的输入实例给出其对应的输出类别。函数
假设训练数据集是线性可分的,咱们须要找到一个前面所说的分离超平面,即肯定感知机模型参数 , 。这须要制定一个学习策略,即定义损失函数并将损失函数极小化。学习
首先考虑误分点总数,但因其不是参数 , 的连续可导函数,不易优化,所以选择误分点到超平面 的总距离。咱们知道一个点 到平面 的距离 为 ,对于误分点 来讲,当 时, ;当 时, 。因此有:
因而误分点到超平面 的距离为:
假设超平面 的误分点集合为 ,那么误分点到超平面 的总距离为:
由于 的大小 不会影响极小化的结果,所以,忽略 就获得感知及学习的损失函数:
感知机学习算法是误分类驱动的,采用随机梯度降低(SGD)法极小化损失函数。首先任意选取一个超平面 (即初始化模型参数 , ),而后随机选取一个误分类点(遍历数据集找到误分类的点)使其梯度降低(见CH12_2):
对于误分类点
,对
,
进行更新:
重复执行上述步骤直到一次更新后再也不有误分类点。并且因为初值和误分类点的选择顺序不一样,最终结果能够有无穷多个。其实这很容易理解,考虑平面上线性可分的数据集,固然存在无穷多个超平面能够将其分开。另外能够证实误分类的次数是有上届的,通过有限次搜索能够找到将训练数据集彻底分开的超平面,也就是说,当训练数据集线性可分时,感知机学习算法原始形式迭代是收敛的。算法的伪代码以下:
输入:训练数据集 ,其中 , , ,学习速率为 ( )
输出: , ;感知机模型
(1) 选取初值 , ,权值能够初始化为0或一个很小的随机数
(2) 在训练数据集中选取
(3) 若是 ,即该点是误分类点:
(4) 转至(2),直至训练集中没有误分类点
原始形式中,对于每一个误分类点 ,其须要通过 次迭代才能被正确分类, 越大说明该点距离分离超平面越近,也就越难正确分类。原始形式中,参数的更新为:
咱们令 ,并初始化参数 , 。那么最后学习到的 , 为:
这样每次迭代时,仅更新 和 便可:
相比原始形式,对偶形式固定了初始权值,实例仅以 和 出现,因此能够提早计算内积 并以矩阵形式存储(即Gram矩阵),这能够节省空间并提升计算速度。算法的伪代码以下:
输入:训练数据集 ,其中 , , ,学习速率为 ( )
输出: , ;感知机模型 ,其中
(1) 选取初值 ,
(2) 在训练数据集中选取
(3) 若是 ,即该点是误分类点:
(4) 转至(2),直至训练集中没有误分类点
前者最大程度追求正确划分,最小化错误,容易发生过拟合;后者尽可能同时避免过拟合
前者的学习策略是最小化损失函数并使用梯度降低法;后者采用的是利用不等式的约束条件构造拉格朗日函数并求极值
前者无最优解,或者说解不惟一
已知正实例点 , ,负实例点 ,试用感知机学习算法求感知机模型 ,其中 ,
import numpy as np import matplotlib.pyplot as plt class showPicture: ''' 超平面可视化 ''' def __init__(self, x, y, w, b): self.b = b self.w = w plt.figure() plt.title('') plt.xlabel('$x^{(1)}$', size=14) plt.ylabel('$x^{(2)}$', size=14, rotation = 0) # 绘制分离超平面 xData = np.linspace(0, 5, 100) yData = self.expression(xData) plt.plot(xData, yData, color='r', label='y1 data') # 绘制数据点 nums = x.shape[0] for i in range(nums): plt.scatter(x[i][0],x[i][1], c = 'r' if y[i] == 1 else 'b', marker = '+' if y[i] == 1 else '_', s = 150) plt.savefig('img/perceptron/incomplement01.png',dpi=75) def expression(self,x): ''' 根据模型参数预测新的数据点的分类结果 ''' y = (-self.b - self.w[0]*x)/self.w[1] return y def show(self): plt.show() class perceptron: ''' 感知机模型 ''' def __init__(self,x,y,a=1): ''' 训练数据集X,Y 学习率a设置为1 ''' self.x = x self.y = y self.w = np.zeros((x.shape[1],1)) # 选取初值w0,b0 self.b = 0 self.a = 1 def sign(self,w,b,x): ''' 定义符号函数 ''' y = np.dot(x,w)+b return int(y) def train(self, logprint = False): ''' 训练感知机模型 logprint:是否打印每次迭代结果,默认不打印 ''' flag = True length = len(self.x) while flag: count = 0 # 迭代控制 for i in range(length): tmpY = self.sign(self.w,self.b,self.x[i,:]) if tmpY*self.y[i]<=0: # 若数据被误分类 tmp = self.y[i]*self.a*self.x[i,:] tmp = tmp.reshape(self.w.shape) # 梯度降低ayi_x_i存储为列向量 self.w = tmp +self.w # 权值更新 self.b = self.b + self.y[i] # 偏置更新 count +=1 if logprint == True: # 打印日志 print('第%d次迭代:\n更新后参数 w 为%f,b 为%f' %(count, self.w, self.b)) if count == 0: flag = False # 无误分退出迭代 return self.w,self.b x = np.array([3, 3, 4, 3, 1, 1]).reshape(3, 2) y = np.array([1, 1, -1]) testp = perceptron(x, y) w, b = testp.train() tests = showPicture(x, y, w, b)
已知正实例点 , ,负实例点 ,试用感知机学习算法求感知机模型 ,其中 ,
import numpy as np import matplotlib.pyplot as plt import mpl_toolkits.axisartist as AA class myperceptron2: def __init__(self, x, y, eta = 1): self.x = x self.y = y self.n = np.zeros(x.shape[0]) self.eta = 1 # 求Gram矩阵 def gram(self): g = [] for i in self.x: for j in self.x: g.append(np.dot(i, j)) # 这里浪费存储空间 return np.array(g).reshape(self.x.shape[0], self.x.shape[0]) def train(self): flag = True while flag: count = 0 for i in range(len(self.x)): if self.y[i]*(np.sum([self.n[j]*self.eta*self.y[j]*(self.gram()[i][j]+1) for j in range(len(self.x))])) <= 0: self.n[i] += 1 count += 1 if count==0: flag = False w = np.add.reduce([self.n[i]*self.eta*self.y[i]*self.x[i] for i in range(len(self.x))]).reshape((self.x.shape[1], 1)) b = np.sum([self.n[i]*self.eta*self.y[i] for i in range(len(self.y))]) return w, b def showPicture2(x, y, w, b): fig = plt.figure() # 坐标轴设置 ax = AA.Subplot(fig, 111) # 使用axisartist.Subplot方法建立一个绘图区对象ax fig.add_axes(ax) # 将绘图区对象添加到画布中 ax.axis[:].set_visible(False) # 经过set_visible方法设置绘图区全部坐标轴隐藏 ax.axis["x"] = ax.new_floating_axis(0,0) # 添加x轴 ax.axis["x"].set_axisline_style("-|>", size = 1.0) # 给x坐标轴加上箭头 ax.axis["x"].set_axis_direction("bottom") # 设置x轴数字标签在轴下面 ax.axis["x"].label.set_text("$x^{(1)}$") ax.axis["x"].label.set_fontsize(14) ax.set_xlim(-0.1, 5.5) ax.axis["y"] = ax.new_floating_axis(1,0) ax.axis["y"].set_axisline_style("-|>", size = 1.0) ax.axis["y"].set_axis_direction("left") ax.axis["y"].label.set_text("$x^{(2)}$") ax.axis["y"].label.set_rotation(90) ax.axis["y"].label.set_fontsize(14) ax.set_ylim(-1.1, 3.5) # 添加公式 w1, w2 = w[0][0], w[1][0] if (w1==1) & (w2 == 1): if b != 0: ax.text(3, 0.5, "%s$x^{(1)}$+%s$x^{(2)}$%+i = 0" %('', '', b), color = 'black', fontsize = 14) else: ax.text(3, 0.5, "%s$x^{(1)}$+%s$x^{(2)}$ = 0" %('', ''), color = 'black', fontsize = 14) elif w1==-1 & w2 == 1: if b != 0: ax.text(3, 0.5, "-%s$x^{(1)}$+%s$x^{(2)}$%+i = 0" %('', '', b), color = 'black', fontsize = 14) else: ax.text(3, 0.5, "-%s$x^{(1)}$+%s$x^{(2)}$ = 0" %('', ''), color = 'black', fontsize = 14) elif w1==-1 & w2 == -1: if b != 0: ax.text(3, 0.5, "-%s$x^{(1)}$-%s$x^{(2)}$%+i = 0" %('', '', b), color = 'black', fontsize = 14) else: ax.text(3, 0.5, "-%s$x^{(1)}$-%s$x^{(2)}$ = 0" %('', ''), color = 'black', fontsize = 14) elif w1==1 & w2 == 1: if b != 0: ax.text(3, 0.5, "%s$x^{(1)}$+%s$x^{(2)}$%+i = 0" %('', '', b), color = 'black', fontsize = 14) else: ax.text(3, 0.5, "%s$x^{(1)}$+%s$x^{(2)}$ = 0" %('', ''), color = 'black', fontsize = 14) else: if b != 0: ax.text(3, 0.5, "%i$x^{(1)}$%+i$x^{(2)}$%+i = 0" %(w1, w2, b), color = 'black', fontsize = 14) else: ax.text(3, 0.5, "%i$x^{(1)}$%+i$x^{(2)}$ = 0" %(w1, w2), color = 'black', fontsize = 14) # 绘制分离超平面 lx = np.arange(0, 5, 0.05) ly = (-b - w[0][0]*lx)/w[1][0] plt.plot(lx, ly, c='black') # 添加数据点 for i in range(len(x)): m = '+' if y[i] == 1 else '_' plt.scatter(x[i][0], x[i][1], marker = m, s = 150) ax.text(x[i][0], x[i][1]+0.2, "$x_{0}$".format(str(i+1)), color = 'black', fontsize = 14) plt.savefig('img/perceptron/imcomplement02.png',dpi=75) x = np.array([3, 3, 4, 3, 1, 1]).reshape(3, 2) y = np.array([1, 1, -1]) mytestp = myperceptron2(x, y) w, b = mytestp.train() showPicture2(x, y, w, b)
sklearn.linear_model.Perceptron参数列表:
参数 | 参数类型 | 参数解释 |
---|---|---|
penalty | None(默认不加惩罚), ‘l2’(L2正则) or ‘l1’(L1正则) or ‘elasticnet’(混合正则) | 惩罚项 |
alpha | float(默认0.0001) | 正则化参数 |
fit_intercept | bool(默认True) | 是否对参数 进行估计,若为False则数据应是中心化的 |
max_iter | int(默认1000) | 最大迭代次数 |
tol | float or None(默认1e-3) | 中止标准,若不指定,训练会在两次迭代损失小于tol时中止 |
shuffle | bool(默认True) | 每轮训练后是否打乱数据 |
verbose | integer | verbose = 0 为不在标准输出流输出日志信息; verbose = 1 为输出进度条记录; verbose = 2 为每一个epoch输出一行记录 |
eta0 | double(默认为1) | 学习率 |
n_jobs | int or None(默认为None) | 在多分类时使用的CPU数量,默认为None(或1),若为-1则使用全部CPU |
random_state | int, RandomState instance or None(默认None) | 使用shuffle时的随机种子 |
early_stopping | bool(默认False) | 当验证得分再也不提升时是否设置提早中止来终止训练。若设置此项,当验证得分在n_iter_no_change轮内没有提高时提早中止训练 |
validation_fraction | float(默认0.1) | 验证集比例,只有设置了early_stopping时才有用 |
n_iter_no_change | int(默认5) | 在提早中止前模型再也不提高的次数 |
class_weight | dict, {class_label: weight} or “balanced” or None | 类别的权重,默认等权重,“balanced”会自动根据数据中的y的各种别数量的反比来调整权重:n_samples / (n_classes * np.bincount(y)) |
warm_start | bool | 若为True则调用前一次设置的参数,使用新设置的参数 |
属性 | 解释 | 类型 |
---|---|---|
coef_ | 权值w参数 | array, shape = [1, n_features] if n_classes == 2 else [n_classes, n_features] |
intercept_ | 偏置b参数 | array, shape = [1] if n_classes == 2 else [n_classes] |
n_iter_ | 迭代次数 | int |
import numpy as np from matplotlib import pyplot as plt from sklearn.datasets import make_classification from sklearn.linear_model import Perceptron # 导入感知机模型 # 生成分类数据 x,y = make_classification(n_samples=1000, n_features=2, n_redundant=0, n_informative=1, n_clusters_per_class=1) x_data_train = x[:800] x_data_test = x[800:] y_data_train = y[:800] y_data_test = y[800:] # 训练 clf = Perceptron(fit_intercept = False, max_iter = 100, tol = 0.001, shuffle = False, eta0 = 0.1) clf.fit(x_data_train,y_data_train) #print(clf.coef_) # w参数 #print(clf.intercept_) # b参数 acc = clf.score(x_data_test,y_data_test) # 使用测试集进行验证 print(acc) positive_x1 = [x[i,0] for i in range(1000) if y[i] == 1] # 正实例点 positive_x2 = [x[i,1] for i in range(1000) if y[i] == 1] negetive_x1 = [x[i,0] for i in range(1000) if y[i] == 0] # 负实例点 negetive_x2 = [x[i,1] for i in range(1000) if y[i] == 0] #画出正例和反例的散点图 fig = plt.figure(dpi = 100) plt.scatter(positive_x1, positive_x2, c = 'r') plt.scatter(negetive_x1, negetive_x2, c = 'b') #画出超平面(在本例中便是一条直线) line_x = np.arange(-4,4) line_y = line_x * (-clf.coef_[0][0] / clf.coef_[0][1]) - clf.intercept_ plt.plot(line_x,line_y) plt.show()
0.985
参考:AI圈终身学习的博客
鸢尾花数据集中有三类数据,分别是山鸢尾,变色鸢尾和维吉尼亚鸢尾,各有50个,总共有150个。数据集的特征数据为:
import pandas as pd from sklearn.datasets import load_iris iris = load_iris() df = pd.DataFrame(iris.data, columns=iris.feature_names) # iris.data包含一个(150, 4)的数据,设置列名为iris.feature_names df['label'] = iris.target # iris.target为类别标签(150, 1) df.head()
在这个数据集里,特征单位都是厘米(cm),label为0的是山鸢尾, label为1的是变色鸢尾,label为2的是维吉尼亚鸢尾。
因为感知机的线性局限性,他只能作二分类任务。
因此如今咱们对数据集进行数据预处理,把label为2的维吉尼亚鸢尾的数据去掉,变成一个识别图片是山鸢尾仍是变色鸢尾的二分类任务。
数据预处理特别简单,由于数据集的数据是顺序存放的,前50条是label为0的山鸢尾,中间50条是label为1的变色鸢尾,因此咱们直接取前100条就行了。
咱们在选取以前先对特征进行选择,这里咱们只选萼片组[‘sepal length’, ‘sepal width’]做为特征,先看下萼片组[‘sepal length’, ‘sepal width’]和label的相关性:
# 萼片组['sepal length','sepal width']特征分布查看 fig = plt.figure(dpi = 100) plt.scatter(df[:50]['sepal length (cm)'], df[:50]['sepal width (cm)'], label='0') plt.scatter(df[50:100]['sepal length (cm)'], df[50:100]['sepal width (cm)'], label='1') plt.xlabel('sepal length') plt.ylabel('sepal width') plt.legend() plt.show()
能够看到sepal length大概分布在4~7之间,sepal width大概分布在2~5之间
咱们先把萼片组特征[‘sepal length’, ‘sepal width’]和前100条只包含label=0和label=1的数据取出来:
# 取前100行,第一、二、5列数据为训练集 data = np.array(df.iloc[:100, [0, 1, -1]]) X, y = data[:,:-1], data[:,-1]
class Perceptron(object): def __init__(self, input_feature_num, activation=None): self.activation = activation if activation else self.sign self.w = [0.0] * input_feature_num self.b = 0.0 def sign(self, z): # 阶跃激活函数: # sign(z) = 1 if z > 0 # sign(z) = 0 otherwise return int(z>0) def predict(self, x): # 预测输出函数 # y_hat = f(wx + b) return self.activation( np.dot(self.w, x) + self.b) def fit(self, x_train, y_train, iteration=10, learning_rate=0.1): # 训练函数 for _ in range(iteration): for x, y in zip(x_train, y_train): y_hat = self.predict(x) self._update_weights(x, y, y_hat, learning_rate) print(self) def _update_weights(self, x, y, y_hat, learning_rate): # 权重更新, 对照公式查看 delta = y - y_hat self.w = np.add(self.w, np.multiply(learning_rate * delta, x)) self.b += learning_rate * delta def __str__(self): return 'weights: {}\tbias: {}'.format(self.w, self.b)
perceptron = Perceptron(input_feature_num=X.shape[1]) perceptron.fit(X, y, iteration=1000, learning_rate=0.1)
weights: [ 7.9 -10.07] bias: -12.399999999999972
x_points = np.linspace(4, 7, 10) y_ = -(perceptron.w[0]*x_points + perceptron.b)/perceptron.w[1] fig = plt.figure(dpi = 100) plt.plot(x_points, y_) # 超平面 plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0') plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1') plt.xlabel('sepal length') plt.ylabel('sepal width') plt.legend() plt.show()
from sklearn.linear_model import Perceptron clf = Perceptron(fit_intercept=False, max_iter=1000, tol = 0.0001, shuffle=False, eta0 = 0.1) clf.fit(X, y)
Perceptron(alpha=0.0001, class_weight=None, early_stopping=False, eta0=0.1,
fit_intercept=False, max_iter=1000, n_iter=None, n_iter_no_change=5,
n_jobs=None, penalty=None, random_state=0, shuffle=False, tol=0.0001,
validation_fraction=0.1, verbose=0, warm_start=False)
x_points = np.linspace(4, 7, 10) y_ = -(clf.coef_[0][0]*x_points + clf.intercept_)/clf.coef_[0][1] fig = plt.figure(dpi = 100) plt.plot(x_points, y_) plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0') plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1') plt.xlabel('sepal length') plt.ylabel('sepal width') plt.legend() plt.show()
clf.n_iter_ # 实际迭代次数
27
由于sklearn的Perceptron咱们设置了迭代的中止标准,因此实际的迭代次数不多使得模型表现很差
《统计学习方法》李航