任务
- 了解感知机的原理并能够实现
- 感知机是一个二元分类模型,通过其完成三分类任务
- 从数据集四个特征中选择两个合适的特征来实现分类,并评估模型的准确性和F1分数
- 将结果可视化:绘制数据和感知机超平面的散点图
感知机原理
感知机(Perceptron)由两层神经元组成,如图所示,输入层接收外界输入信号后传递给输出层,输出层是M-P神经元,亦称“阈值逻辑单元”(threshold logic unit)。
感知机的学习规则非常简单,对训练样例
(
x
,
y
)
(x,y)
(x,y),若当前感知机的输出为
y
^
\hat{y}
y^,则感知机权重将这样调整:
ω
i
←
ω
i
+
Δ
ω
i
Δ
ω
i
=
η
(
y
−
y
^
)
x
i
\omega _i\leftarrow \omega _i + \Delta \omega _i\\ \Delta\omega _i=\eta(y-\hat{y})x_i
ωi←ωi+ΔωiΔωi=η(y−y^)xi
其中
η
∈
(
0
,
1
)
\eta\in(0,1)
η∈(0,1)称为学习率(learning rate)。可以看出,若感知机对训练样例
(
x
,
y
)
(x,y)
(x,y)预测正确,即
y
^
=
y
\hat{y}=y
y^=y则感知机不发生变化,否则将根据错误的程度进行权重调整。
需要注意的是,感知机只有输出层神经元进行激活函数处理,即只拥有一层功能神经元,其学习能力非常有限。若两类模式是线性可分的,即存在一个线性超平面能够将它们分开,则感知机的学习过程一定会收敛(converge)而求得适当的权向量 ω = ( ω 1 ; ω 2 , ⋯ , ω n + 1 ) \omega=(\omega_1;\omega_2,\cdots,\omega_{n+1}) ω=(ω1;ω2,⋯,ωn+1);否则感知机学习过程将会发生振荡, ω \omega ω难以稳定下来,不能求得合适解。
多分类学习
考虑 N N N个类别 C 1 , C 2 , … , C N C_1,C_2,…,C_N C1,C2,…,CN,多分类学习的基本思路是“拆解法”,即将多分类任务拆为若干个二分类任务求解。
具体来说,先对问题进行拆分,然后为拆分的每个二分类任务训练一个分类器;在测试时,对这些分类器的预测结果进行集成以获得最终的多分类结果。
最经典的拆分策略有三种:“一对一”(One vs. One,简称OvO)、“一对其余”(One vs. Rest,简称OvR)和“多对多”(Many vs. Many,简称MvM)。
给定数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x m , y m ) } , y i ∈ { C 1 , C 2 , … C N } D=\{(x_1,y_1),(x_2,y_2),…,(x_m,y_m)\},y_i\in\{C_1,C_2,…C_N\} D={(x1,y1),(x2,y2),…,(xm,ym)},yi∈{C1,C2,…CN}
- OvO将这 N N N个类别两两配对,从而产生 N ( N − 1 ) / 2 N(N-1)/2 N(N−1)/2个二分类任务。在测试阶段,新样本将同时提交给所有分类器,于是将得到 N ( N − 1 ) / 2 N(N-1)/2 N(N−1)/2个分类结果,最终结果可通过投票产生。
- OvR则是每次将一个类的样例作为正例、所有其他类的样例作为反例来训练 N N N个分类器。在测试时若仅有一个分类器预测为正类,则对应的类别标记作为最终分类结果。若有多个分类器预测为正类,则通常考虑各分类器的预测置信度,选择置信度最大的类别标记为分类结果。
- MvM是每次将若干个类别作为正类,若干个其他类作为反类。显然OvO和OvR是MvM的特例。MvM的正、反类构造必须有特殊的设计,不能随意选取,最常用的MvM技术:“纠错输出码”(Error
Correcting Output Codes,简称ECOC)
三分类代码实现
导入模块
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
数据处理
数据归一化(normalization):将数据映射到0~1范围之内。该方法实现对原始数据等比例缩放,通过利用变量取值的最大值和最小值将原始数据转换为介于某一特定范围的数据,从而消除量纲和数量级影响:
X
i
−
X
m
i
n
X
m
a
x
−
X
m
i
n
\frac{X_i-X_{min}}{X_{max}-X_{min}}
Xmax−XminXi−Xmin
def normalization(data):
_range = np.max(data) - np.min(data)
return (data - np.min(data)) / _range
将csv格式文件导入python转换为ndarray格式
def get_data(path_1, path_2):
df_train = pd.read_csv(path_1) # 打开csv文件(训练集)
df_test = pd.read_csv(path_2) # 打开csv文件(测试集)
X_train = df_train.iloc[0:90, [2, 3]].values # 取第三个和第四个特征作为输入
X_test = df_test.iloc[0:60, [2, 3]].values # 取第三个和第四个特征作为输入
X_train = normalization(X_train) # 训练集归一化
X_test = normalization(X_test) # 测试集归一化
X_train_1 = X_train # 一号感知机的训练集
X_train_2 = X_train[30:90, :] # 二号感知机的训练集
# 一号感知机的训练集标签
Y_train_1 = df_train.iloc[0:90, [-1]].values
Y_train_1[0:30, [0]], Y_train_1[30:90, [0]] = -1, 1
# 二号感知机的训练集标签
Y_train_2 = df_train.iloc[30:90, [-1]].values
Y_train_2[0:30, [0]], Y_train_2[30:60, [0]] = -1, 1
# 将两个感知机的X,Y数据水平拼接
train_data_1 = np.hstack((X_train_1, Y_train_1))
train_data_2 = np.hstack((X_train_2, Y_train_2))
# 测试集的标签
Y_test = df_test.iloc[0:60, [-1]].values
Y_test[0:20, [0]], Y_test[20:40, [0]], Y_test[40:60, [0]] = 0, 1, 2
# 测试集的X,Y数据水平拼接
test_data = np.hstack((X_test, Y_test))
return train_data_1, train_data_2, test_data
自定义感知机算法
class Perceptron():
"""自定义感知机算法"""
def __init__(self, learning_rate=0.01, num_iter=50, random_state=1):
self.learning_rate = learning_rate # 学习率
self.num_iter = num_iter # 迭代次数
self.random_state = random_state # 随机数种子
self.error_list = [] # 分类错误个数统计列表
# 通过标准差为0.01的正态分布初始化权重
rgen = np.random.RandomState(self.random_state)
self.theta = rgen.normal(loc=0.0, scale=0.01, size=3)
def fit(self, data):
"""更新权重"""
# 循环遍历更新权重
for _ in range(self.num_iter):
error_count = 0 # 分类错误统计
grad_1, grad_2, grad_3 = 0, 0, 0 # 梯度初始化
for i, d in enumerate(data):
x_i = d[0:2] # 数据中的x
target = d[2] # 数据中的y
# 分类正确不更新,分类错误更新权重
y_i = self.predict(x_i) # 预测值
if y_i * target <= 0:
# 对所有分类错误点的梯度进行求和
grad_1 += data[i, -1] * data[i, 0]
grad_2 += data[i, -1] * data[i, 1]
grad_3 += data[i, -1]
error_count += 1
# 更新参数
self.theta[0] += self.learning_rate * grad_1
self.theta[1] += self.learning_rate * grad_2
self.theta[2] += self.learning_rate * grad_3
# 将该迭代回合中的分类错误个数添加进列表
self.error_list.append(error_count)
return self
def predict_input(self, X):
"""计算预测值"""
return np.dot(X, self.theta[0:2]) + self.theta[-1]
def predict(self, X):
"""得出sign(预测值)即分类结果"""
return np.where(self.predict_input(X) >= 0.0, 1, -1)
可视化
def show_result_1(data_1, data_2, theta_1, theta_2 , error_list_1, error_list_2):
x = np.linspace(-10, 10, 1000)
plt.rcParams['figure.figsize'] = (12.0, 4.0) # 设置图形尺寸
plt.subplot(121) # 将整个图像窗口分为1行2列,当前为位置为1
plt.xlim(xmax=1.25, xmin=0) # 设置x轴的数值显示范围
plt.ylim(ymax=0.5, ymin=-0.2) # 设置y轴的数值显示范围
for i, p_x in enumerate(data_1): # 遍历数据集
if p_x[-1] == -1: # 若标签为1
plt.scatter(p_x[0], p_x[1], c='r', alpha=0.3) # 生成红色散点
# else:
# plt.scatter(p_x[0], p_x[1], c='b', alpha=0.3) # 生成蓝色散点
w_est = - theta_1[0] / theta_1[1] # 斜率
b_est = - theta_1[2] / theta_1[1] # 截距
y_est = w_est * x + b_est # 回归直线
plt.plot(x, y_est, 'g', linewidth=4) # 画出直线
for i, p_x in enumerate(data_2): # 遍历数据集
if p_x[-1] == -1: # 若标签为1
plt.scatter(p_x[0], p_x[1], c='g', alpha=0.3) # 生成红色散点
else:
plt.scatter(p_x[0], p_x[1], c='b', alpha=0.3) # 生成蓝色散点
w_est = - theta_2[0] / theta_2[1] # 斜率
b_est = - theta_2[2] / theta_2[1] # 截距
y_est = w_est * x + b_est # 回归直线
plt.plot(x, y_est, 'g', linewidth=4) # 画出直线
plt.subplot(122) # 将整个图像窗口分为1行2列,当前为位置为2
plt.plot(np.arange(len(error_list_1)), error_list_1, 'g+-')
plt.plot(np.arange(len(error_list_2)), error_list_2, 'r+-')
plt.show()
测试
def test(data, theta_1, theta_2):
right_count = 0
error_count = 0
for i, d in enumerate(data):
x_i = d[0:2]
target = d[2]
y_1_i = theta_1[0] * x_i[0] + theta_1[1] * x_i[1] + theta_1[2]
if y_1_i < 0: # 预测为’setosa‘
if target == 0:
right_count += 1
else:
error_count += 1
else: # 进入第二个感知机
y_2_i = theta_2[0] * x_i[0] + theta_2[1] * x_i[1] + theta_2[2]
if y_2_i < 0:
if target == 2:
right_count += 1
else:
error_count += 1
else:
if target == 1:
right_count += 1
else:
error_count += 1
print("正确率为:", right_count/(right_count + error_count))