设计实验(包括生成数据集、设计收敛衡量指标,如MSE),验证其在线性可分二分类问题上的性能(验证该感知器原理是否有收敛性),并作可视化展示。
算法的收敛性证明称为感知器收敛定理。
==完整代码在最后!!==
实现原理
算法:(更新的算法)
1.这是一种错误驱动的在线学习算法 (在线学习算法:来一条样本进行一次学习)
(错误驱动:就是第三点,即分错一个进行更新w)
2.先初始化一个权重向量W = 0 (通常是一个全零向量)
3.每分错一个样本(X,Y)时,即yWT X < 0
4.用当前样本来更新权重
我们可以看第三点 :其中y是一个标量,W X都是向量 去掉y后面相当于进行的点乘运算,得到的即是y_hat 预测值。那么对于w更新的公式:y*x 因为当前是一个分错的样本,那么可以得到yx是异号,所以可以类比于之前学的(逻辑回归?更新是,w减去学习率×梯度?)也可以看成w-一个值
从而推出感知器的损失函数为
对于yWT X
当分错时可知这是一个负值,可得到损失就是yWT X ;
当分类正确时这是正值,取最大值就是0,当前没有损失。
这就是大概的流程,下面以一个例子来可视化一下过程。
设计实验
生成数据集
造一个十行二列的数据D
再将特征增加一维,新的一维为1,对应神经元中的偏置
划分训练集,第二列作为横坐标X1,第三列为纵坐标X2,并随机进行一个划分,用来得到相应的y标签 (例如我的例子是y = (1.5-x)/2 分为两类)
可视化我当前的数据集
初始化W (我这里将偏置值b放入w)并将Y中布尔值转化为int类型
准备模型
Rosenblatt函数 即得到y_hat
训练模型
取最大更新上限为T,k记录更新次数
用MSE数组记录了所有更新的w 来计算MSE指标(均分误差)观察是否收敛
收敛衡量指标 MSE
可视化展示
MSE指标减小,且可视化过程中更新过的W可以看出分类效果逐渐变好,且最后分类正确。
完整代码
import numpy as np import matplotlib.pyplot as plt D = np.random.random((10,2)) #D D_new = np.ones((10,3)) #D_new D_new[:,1:] = D #将特征增加一维,新的一维为1,对应神经元中的偏置 #D_new ''' 参考我的数据: array([[1. , 0.16814338, 0.66474844], [1. , 0.83121908, 0.45326107], [1. , 0.71082789, 0.62126183], [1. , 0.77474789, 0.96576895], [1. , 0.08396405, 0.33528545], [1. , 0.78157961, 0.57065742], [1. , 0.36439544, 0.94370923], [1. , 0.31868589, 0.39326507], [1. , 0.5699746 , 0.09303593], [1. , 0.61981254, 0.25513333]]) ''' # X1 = D[:,0] # X2 = D[:,1] X1 = D_new[:,1] #取后两列作为样本值 X2 = D_new[:,2] #X1,X2 Y = np.array([(x1+2*x2)>1.5 for (x1,x2) in zip(X1,X2)]) #Y , X1 , X2 #print(X1[Y],X2[Y]) Y_0 = ((Y-1)*(-1)).astype(bool) #print(Y,Y_0) #print(X1[Y_0],X2[Y_0]) plt.scatter(X1[Y],X2[Y],c='r') plt.scatter(X1[Y_0],X2[Y_0],c='g') x = np.linspace(0,1,100) y = (1.5-x)/2 plt.plot(x,y) plt.show() w = np.zeros(D_new.shape[1]) #将b归入w y = Y.astype(int) y = y*2-1 #D_new,w,y def Rosenblatt(w_,x): #增加一个参数,w,便于后续计算收敛衡量指标MSE y = np.dot(w_,x) # print(y) if y>0: return 1 else: return -1 T = 250 t = 0 k = 0 N = len(D_new) # for n in range(N): MSE = list() #初始化一个列表 用于保存中间更新的w while(t<T): for i in range(N): xi = D_new[i] yi = y[i] print(i,xi,yi) y_hat = Rosenblatt(w,xi) #用y_hat表示一下预测 if yi * y_hat < 0: w = w + yi * xi k = k + 1 print('更新:',k,w) MSE.append(w) #对于更新的w进行保存 t = t + 1 print("last:",w) #MSE #用于计算均方误差 ''' 参考我的W [array([-1. , 2.15707843, 1.12887558]), array([-2. , 1.83839253, 0.7356105 ]), array([-1. , 2.66961161, 1.18887158]), array([-2. , 2.35092571, 0.79560651]), array([-1. , 2.71532116, 1.73931574]), array([-2. , 2.39663526, 1.34605066])] ''' # yi_hat = np.zeros(y.shape) MSE_ans = [0] * len(MSE) #用于记录MSE指标的变化 # print(MSE_ans) for i in range(len(MSE)): yi_hat = np.zeros(y.shape) #用于保存每次更新w所对应的预测值y_hat # print(yi_hat) for j in range(N): xj = D_new[j] yj = y[j] # print(MSE_ans) yi_hat[j] = Rosenblatt(MSE[i],xj) print(yi_hat) print(y) MSE_ans[i] += np.sum((y-yi_hat) ** 2)/len(y) #公式计算MSE # break #MSE_ans #均方误差最后结果 plt.scatter(X1[Y],X2[Y],c='r') plt.scatter(X1[Y_0],X2[Y_0],c='g') x = np.linspace(0,1,100) # y = (1.5-x)/2 for i in MSE: y = -(i[0] + i[1] * x )/i[2] #对于每个w画图 看图像的变化 逐渐把两个类别分开 plt.plot(x,y) plt.legend(MSE) #从上到下的例表示w的变化 plt.show() plt.scatter(X1[Y],X2[Y],c='r') plt.scatter(X1[Y_0],X2[Y_0],c='g') H = np.array([[-1.5 , 1, 2], #上面是真实的w [-2. , 2.39663526, 1.34605066]]) #下面是最后更新的w x = np.linspace(0,1,100) # y = (1.5-x)/2 for i in H: y = -(i[0] + i[1] * x )/i[2] plt.plot(x,y) plt.legend(['True','Prediction']) #观察真实和预测的区别 都将两个类别分开 plt.show()
换衡量收敛指标
当然可以使用损失函数可视化来判断收敛效果。
参考大佬的代码及可视化
大佬的代码是封装成类的,日常膜大佬!
import numpy as np from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt class Model: def __init__(self,sample_nums,T): #样本数 self.sample_nums=sample_nums #迭代次数 self.T=T #生成数据 self.X,self.Y=self.make_data(sample_nums) #划分测试集训练集 self.X_train,self.X_test, self.y_train, self.y_test=self.split_data(self.X,self.Y) def make_data(self,sample_nums): # 生成样本吧,有两个特征 np.random.seed(1) X = np.random.random((sample_nums, 2)) X1 = X[:, 0] X2 = X[:, 1] Y = np.array([(2 * x1 + x2) > 1.5 for (x1, x2) in zip(X1, X2)]) return X, Y def split_data(self,X,Y): X_train,X_test, y_train, y_test=train_test_split(X,Y,test_size=0.4,random_state=0) return X_train,X_test, y_train, y_test #获得所有样本的损失 def get_loss(self,X,Y): loss=0 for x,y in zip(X,Y): loss+=max(0,-y*np.dot(self.w,x)) return loss def train(self): # w需要多加一个分量,表示偏置值b self.w = np.zeros(self.X_train.shape[1] + 1) y = self.y_train.astype(int) y = y * 2 - 1 one = np.ones(len(self.X_train)) #需要给原始数据添加一列1,用于乘以偏置值 X1 = np.c_[one, self.X_train] t = 0 k = 0 losses=[] while (t < self.T): for i in range(len(self.X_train)): # xi 是原始数据 xi = self.X_train[i] # xi_是添加一列1的数据 xi_ = X1[i] yi = y[i] print(i, xi, yi) if yi * self.predict(xi_) < 0: self.w = self.w + yi * xi_ k = k + 1 print('更新:', k, self.w) t = t + 1 losses.append(self.get_loss(X1,y)) self.losses=losses def predict(self,x): y=np.dot(self.w,x) if y>0: return 1 else: return -1 def visualsize(self): #该直线为 w[1] * x1 + w[2] * x2 + w[0] = 0 #变换一下得 x2=(-w[0]-w[1]*x1)/w[2] X=self.X_test Y=self.y_test Y_0 = ((Y - 1) * (-1)).astype(bool) #横坐标 X1=X[:,0] #纵坐标 X2=X[:,1] plt.scatter(X1[Y], X2[Y], c='r') plt.scatter(X1[Y_0], X2[Y_0], c='g') x1 = np.linspace(0, 1, 100) x2 = (-self.w[0] - self.w[1] * x1) / self.w[2] y_true = 1.5 - 2 * x1 plt.plot(x1, x2) plt.plot(x1,y_true) plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 plt.title('分类情况测试集可视化') plt.legend(['predict','true']) plt.show() #可视化损失 plt.plot(np.arange(len(self.losses)),self.losses) plt.title("可视化损失") plt.show() def score(self): hit=0 y_test=np.array(self.y_test,dtype=int) #将0,1 映射为1 -1 y_test=2*y_test-1 for x,y in zip(self.X_test,y_test): predict=self.predict(np.insert(x,0,1)) if predict==y: hit+=1 return hit/len(self.X_test) if __name__ == '__main__': model=Model(100,1000) model.train() model.visualsize() print("正确率为:",model.score())