介绍最先以算法方式描述的分类机器学习算法:感知器(perceptron)和自适应线性神经元(adaptive linear neuron).咱们将使用python按部就班地实现一个感知器,而且经过训练使其具有对鸢尾花数据集中数据进行分类的能力python
主要知识点:算法
为了了解大脑的工做原理以设计人工智能系统,沃伦.麦卡洛可(Warren McCullock)与沃尔特.皮茨(Walter Pitts)在1943年提出来第一个脑神经元的抽象模型,也称为麦卡洛可–皮茨神经元(MCP),神经元是大脑相互链接的神经细胞,它能够处理和传递化学信号和电信号编程
from IPython.display import Image
麦卡洛可和皮茨将神经细胞描述为一个具有二进制输出的逻辑门.树突接收多个输入信号,若是累加的信号超过某一阈值,经细胞体的整合就会生成一个输出信号,并经过轴突进行传递网络
几年后,弗兰克.罗森布拉特(Frank Rossenblatt)基于MCP神经元模型提出了第一个感知器学习法则.在此感知器规则中,罗森布拉特提出了一个自学习算法,此算法能够本身经过优化获得权重系数,此系数与输入值的乘积决定了神经元是否被激活app
关于线性代数的知识可经过以下连接免费获取:http://www.cs.cmu.edu/~zkolter/course/linalg/linalg_notes.pdf
linalg_notes.pdf
下图中,左图说明了感知器模型的激励函数如何将输入z=w^Tx转换到二值输出(-1或1),右图说明了感知器模型如何将两个可区分类别进行线性区分dom
MCP神经元和罗森布拉特阈值感知器的理念就是,经过模拟的方式还原大脑中单个神经元的工做方式:它是否被激活.这样罗森布拉特感知器最初的规则很是简单,可总结为以下几步:机器学习
须要注意的是:感知器收敛的前提是两个类别必须是线性可分的,且学习速率足够小.若是两个类别没法经过一个线性决策边界进行划分,能够为模型在训练数据集上的学习迭代次数设置一个最大值,或者设置一个容许错误分类样本数量的阈值—不然,感知器训练算法将永远不停地更新权值函数
总结一下感知器的基本概念:学习
上图说明了感知器如何接收样本x的输入,并将其与权值w进行加权以计算净输入(net input).进而净输入被传递到激励函数(在此为单位阶跃函数),而后生成值为+1或-1的二值输出,并以其做为样本的预测类标.在学习阶段,此输出用来计算预测的偏差并更新权重测试
经过使用面向对象编程的方式在一个python类中定义感知器的接口,使得咱们能够初始化新的感知器对象,并使用类中定义的fit方法从数据中进行学习,使用predict方法进行预测.按照python开发的惯例,对于那些并不是在初始化对象时建立可是又被对象中其余方法调用的属性,能够在后面添加一个下划线,例如:self.w_
import numpy as np class Perceptron(object): def __init__(self,eta=0.01,n_iter=10): """ Parameters ---------- eta:float Learning rate(between 0.0 and 1.0) n_iter:int Passes over the training dataset Attributes ---------- w_:1d-array Weights after fitting errors_:list Number of misclassifications in every epoch """ self.eta=eta self.n_iter=n_iter def fit(self,X,y): """ Parameters ---------- X:{array-like},shape=[n_samples,n_features] y:array-like,shape=[n_samples] Target values """ self.w_=np.zeros(1+X.shape[1]) self.errors_=[] for _ in range(self.n_iter): errors=0 for xi,target in zip(X,y): update=self.eta*(target-self.predict(xi)) self.w_[1:]+=update*xi self.w_[0]+=update errors+=int(update!=0.0) self.errors_.append(errors) return self def net_input(self,X): """Calculate net input""" return np.dot(X,self.w_[1:])+self.w_[0] def predict(self,X): """Return class label after unit step""" return np.where(self.net_input(X)>=0.0,1,-1)
在感知器实现过程当中,咱们实例化一个Perceptron对象时,给出了一个学习速率eta和在训练数据集上进行迭代的次数n_iter.经过fit方法,咱们将self.w_中的权值初始化为一个零向量R^m+1,其中m是数据集中维度(特征)的数量,咱们在此基础上增长了一个0权重列(也就是阈值)
挑选鸢尾花数据集中山鸢尾(Setosa)和变色鸢尾(Versicolor)两种花的信息做为测试数据.虽然感知器并不将数据样本特征的数量限定为两个,但出于可视化方面的缘由,咱们只考虑数据集中萼片长度(sepal length)和花瓣长度(petal-length)这两个特征.不过,感知器算法能够扩展到多类别的分类器应用中,好比经过一对多(One-vs.-all,OvA)
首先咱们使用pandas库直接从UCI机器学习库中将鸢尾花数据集转换为DataFrame对象并加载到内存中,并使用tail方法显示数据的最后5行以确保数据正确加载
import pandas as pd df=pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data",header=None) df.tail()
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
145 | 6.7 | 3.0 | 5.2 | 2.3 | Iris-virginica |
146 | 6.3 | 2.5 | 5.0 | 1.9 | Iris-virginica |
147 | 6.5 | 3.0 | 5.2 | 2.0 | Iris-virginica |
148 | 6.2 | 3.4 | 5.4 | 2.3 | Iris-virginica |
149 | 5.9 | 3.0 | 5.1 | 1.8 | Iris-virginica |
接下来,咱们从中提取前100个类标,其中分别包含50个山鸢尾类标和50个变色鸢尾类标,并将这些类标用两个整数值来替代:1代替变色鸢尾,-1表明山鸢尾.同时把pandas DataFrame产生的对应的整数类标赋给Numpy的向量y,提取这100个训练样本的第一个特征列(萼片长度)和第三个特征(花瓣长度),并赋给属性矩阵X
import matplotlib.pyplot as plt import numpy as np y=df.iloc[0:100,4].values y=np.where(y=='Iris-setosa',-1,1) X=df.iloc[0:100,[0,2]].values plt.scatter(X[:50,0],X[:50,1],color='red',marker='o',label='setosa') plt.scatter(X[50:100,0],X[50:100,1],color='blue',marker='x',label='versicolor') plt.xlabel('petal length') plt.ylabel('sepal length') plt.legend(loc='upper left') plt.show()
如今,咱们可使用抽取出的鸢尾化数据集来训练感知器.同时将绘制每次迭代的错误分类数量的折线图,以检验算法是否收敛并找到能够分开两种类型鸢尾花的决策边界
ppn=Perceptron(eta=0.1,n_iter=10) ppn.fit(X,y) plt.plot(range(1,len(ppn.errors_)+1),ppn.errors_,marker='o') plt.xlabel('Epochs') plt.ylabel('Number of misclassifications') plt.show()
经过一个简单的函数来实现队二维数据集决策边界的可视化
from matplotlib.colors import ListedColormap def plot_decision_regions(X,y,classifier,resolution=0.02): markers=('s','x','o','^','v') colors=('red','blue','lightgreen','gray','cyan') cmap=ListedColormap(colors[:len(np.unique(y))]) x1_min,x1_max=X[:,0].min()-1,X[:,0].max()+1 x2_min,x2_max=X[:,1].min()-1,X[:,1].max()+1 xx1,xx2=np.meshgrid(np.arange(x1_min,x1_max,resolution),np.arange(x2_min,x2_max,resolution)) Z=classifier.predict(np.array([xx1.ravel(),xx2.ravel()]).T) Z=Z.reshape(xx1.shape) plt.contourf(xx1,xx2,Z,alpha=0.4,cmap=cmap) plt.xlim(xx1.min(),xx1.max()) plt.ylim(xx2.min(),xx2.max()) for idx,cl in enumerate(np.unique(y)): plt.scatter(x=X[y==cl,0],y=X[y==cl,1],alpha=0.8,c=cmap(idx),marker=markers[idx],label=cl)
plot_decision_regions(X,y,classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show()
注意:感知器所面临的最大问题是算法的收敛.Frank Rosenblatt从数学上证实了若是两个类别能够经过线性超平面进行划分,则感知器算法必定会收敛.可是若是两个类别没法经过线性断定边界彻底正确地划分,则权重会不断更新.为防止发生此类事件,一般事先设置权重更新的最大迭代次数
自适应线性神经网络(Adaptive Linear Neuron,Adaline)算法至关有趣,它阐明了代价函数的核心概念,而且对其作了最小化优化,这是理解逻辑斯谛回归(logistic regression),支持向量机(support vector machine)的高级机器学习分类算法的基础
基于Adeline规则的权重更新是经过一个连续的线性激励函数来完成的,而不像Rosenblatt感知器那样使用单位阶跃函数,这是两者的主要区别
线性激励函数在更新权重的同时,咱们使用量化器(quantizer)对类标进行预测,量化器与前面提到的单单位阶跃函数相似
机器学习中监督学习算法的一个核心组成在于:在学习阶段定义一个待优化的目标函数.这个目标函数一般是须要咱们作最小化处理的代价函数
咱们将在前面实现的感知器代码的基础上修改fit方法,将其权重的更新改成经过梯度降低最小化代价函数来实现Adaline算法
class AdalineGD(object): def __init__(self,eta=0.01,n_iter=50): self.eta=eta self.n_iter=n_iter def fit(self,X,y): self.w_=np.zeros(1+X.shape[1]) self.cost_=[] for i in range(self.n_iter): output=self.net_input(X) errors=(y-output) self.w_[1:]+=self.eta*X.T.dot(errors) self.w_[0]+=self.eta*errors.sum() cost=(errors**2).sum()/2.0 self.cost_.append(cost) return self def net_input(self,X): return np.dot(X,self.w_[1:])+self.w_[0] def activation(self,X): return self.net_input(X) def predict(self,X): return np.where(self.activation(X)>=0.0,1,-1)
咱们分别使用eta=0.01和eta=0.0001两个学习速率来描绘迭代次数与代价函数的图像
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(8,4)) ada1=AdalineGD(n_iter=10,eta=0.01).fit(X,y) ax[0].plot(range(1,len(ada1.cost_)+1),np.log10(ada1.cost_),marker='o') ax[0].set_xlabel('Epochs') ax[0].set_ylabel('log(Sum-squared-error)') ax[0].set_title('Adaline-learning rate 0.01') ada2=AdalineGD(n_iter=10,eta=0.0001).fit(X,y) ax[1].plot(range(1,len(ada2.cost_)+1),np.log10(ada2.cost_),marker='o') ax[1].set_xlabel('Epochs') ax[1].set_ylabel('log(Sum-squared-error)') ax[1].set_title('Adaline-learning rate 0.0001') plt.show()
左边的图像显示了学习速率过大可能会出现的问题—并无使代价函数的值尽量的低,反而由于算法跳过了全局最优解,致使偏差随着迭代次数增长而增大
右边的图像代价函数逐渐减少,可是选择的学习速率eta=0.0001的值过小,以至为了达到算法收敛的目标,须要更多的迭代次数
下图说明了咱们如何经过更改特定的权重参数值来最小化代价函数J(左子图).右子图展现了若是学习速率选择过大会发生什么状况:算法跳过全局最优解(全局最小值)
梯度降低就是经过特征缩放而受益的众多算法之一.在此,采用一种称做标准化的特征缩放方法,此方法可使数据具有标准正态分布的特性:各特征值的均值为0,标准差为1
标准化能够简单地经过Numpy的mean和std方法来完成
X_std=np.copy(X) X_std[:,0]=(X[:,0]-X[:,0].mean())/X[:,0].std() X_std[:,1]=(X[:,1]-X[:,1].mean())/X[:,1].std()
在进行标准化操做后,咱们以学习速率eta=0.01再次对Adaline进行训练,看看它是不是收敛的
ada=AdalineGD(n_iter=15,eta=0.01) ada.fit(X_std,y) plot_decision_regions(X_std,y,classifier=ada) plt.title('Adaline-Gradient Descent') plt.xlabel('sepal length [standardized]') plt.ylabel('petal length [standardized]') plt.legend(loc='upper left') plt.show() plt.plot(range(1,len(ada.cost_)+1),ada.cost_,marker='o') plt.xlabel('Epochs') plt.ylabel('Sum-squared-error') plt.show()
在上一节中,咱们学习了如何使用整个训练数据集沿着梯度相反的方向进行优化,以最小化代价函数;这也是此方法有时称做批量梯度降低的缘由.假定如今有一个包含几百万条数据的巨大数据集,这个量非同寻常的.因为向全局最优势移动的每一步都须要使用整个数据集来进行评估,所以这种状况下使用批量梯度降低的计算成本很是高
一个经常使用的替代批量梯度降低的优化算法是随机梯度降低(stochastic gradient descent),有时也称做迭代梯度降低(iterative gradient descent)或者在线梯度降低(on-line gradient descent)
为了经过随机梯度降低获得更加准确的结果,让数据以随机的方式提供给算法是很是重要的,这也是咱们每次迭代都要打乱训练集以防止进入循环的缘由
因为咱们已经使用梯度降低实现了Adaline学习规则,所以只需在此基础上将学习算法中的权重更新改成经过随机梯度降低来实现便可
from numpy.random import seed class AdalineSGD(object): def __init__(self,eta=0.01,n_iter=10,shuffle=True,random_state=None): self.eta=eta self.n_iter=n_iter self.w_initialized=False self.shuffle=shuffle if random_state: seed(random_state) def fit(self,X,y): self._initialize_weights(X.shape[1]) self.cost_=[] for i in range(self.n_iter): if self.shuffle: X,y=self._shuffle(X,y) cost=[] for xi,target in zip(X,y): cost.append(self._update_weights(xi,target)) avg_cost=sum(cost)/len(y) self.cost_.append(avg_cost) return self def partial_fit(self,X,y): if not self.w_initialized: self._initialize_weights(X.shape[1]) if y.ravel().shape[0]>1: for xi,target in zip(X,y): self._update_weights(xi.target) else: self._update_weights(X,y) return self def _shuffle(self,X,y): r=np.random.permutation(len(y)) return X[r],y[r] def _initialize_weights(self,m): self.w_=np.zeros(1+m) self.w_initialized=True def _update_weights(self,xi,target): output=self.net_input(xi) error=(target-output) self.w_[1:]+=self.eta*xi.dot(error) self.w_[0]+=self.eta*error cost=0.5*error**2 return cost def net_input(self,X): return np.dot(X,self.w_[1:]+self.w_[0]) def activation(self,X): return self.net_input(X) def predict(self,X): return np.where(self.activation(X)>=0.0,1,-1)
分类器AdalineSGD中_shuffle方法的工做原理以下:经过numpy.random的permutation函数,咱们生成一个包含0-100的不重复的随机序列.这些数字能够做为索引帮助打乱咱们的特征矩阵和类标向量
接下来,咱们就能够经过fit方法训练AdalineSGD分类器,并应用plot_decision_regions方法绘制训练结果
ada=AdalineSGD(n_iter=15,eta=0.01,random_state=1) ada.fit(X_std,y) plot_decision_regions(X_std,y,classifier=ada) plt.title('Adaline-Stochastic Gradient Descent') plt.xlabel('sepal length [standardized]') plt.ylabel('petal length [standardized]') plt.legend(loc='upper left') plt.show() plt.plot(range(1,len(ada.cost_)+1),ada.cost_,marker='o') plt.xlabel('Epochs') plt.ylabel('Average Cost') plt.show()