写在前面

岁月匆匆,不知不觉已经步入了大三学年。专业课的学习也变得更加深入,今天要做的是计算智能的课程设计,基于感知机的鸢尾花分类。2021.11.29.

基于感知机的鸢尾花分类

实验目的

利用感知机算法对鸢尾花种类进行分类,要求熟悉感知机算法,掌握利用Python实现机器学习算法的一般流程,了解scikit-learn机器学习库的使用。

背景知识

植物的分类与识别是植物学研究和农林业生产经营中的重要基础工作,对于区分植物种类、探索植物间的亲缘关系、阐明植物系统的进化规律具有重要意义。传统识别植物的方法主要依靠人工,需要丰富的专业知识,工作量大,效率不高,而且难以保证分类的客观性和精确性。随着信息技术飞速发展,将计算机视觉、模式识别、数据库等技术应用于植物种类识别,使得识别更加简单、准确、易行。相对于植物的其它部分,其花朵图像更容易获取,花朵的颜色和形状等都可作为分类依据。本案例在提取花朵形态特征的基础上,利用感知机算法进行分类与识别。鸢(读音同”愿”)尾花,英文为 Iris,多年生草本植物,花大而美丽,叶片青翠碧绿,观赏价值很高,是一种重要的观赏用庭园植物。 其外形作为独特的徽章,在欧洲的宗教、皇室建筑上被广泛应用。特别是在法国,印在法国军旗上的三朵鸢尾花图案标志着法兰西王国的王权。鸢尾花有三个主要类型(种属): Setosa 山鸢尾、 Versicolour 变色鸢尾 和 Virginica 维吉尼亚鸢尾,其主要区别是萼片长度、萼片宽度、花瓣长度和花瓣宽度。

鸢尾花

鸢尾花数据集:scikit-learn 是基于 Python 的机器学习库,其默认安装包含了几个小型的数据集,并提供了读取这些数据集的接口。 其中 sklearn.datasets.load_iris()用于读取鸢尾花数据集,该数据集有 150 组 3 种类型鸢尾花的 4 种属性:萼片长度 sepal length、萼片宽度 sepal width、花瓣长度 petallength 和花瓣宽度 petal width,样本编号与类型的关系是:样本编号 0 至 49 为 Setosa ,50 至 99 为Versicolour ,100 至 149 为 Virginica。

示例代码

1 ) 加载用到的库

1
2
3
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris #仅用于加载数据集

2 )加载鸢尾花数据集

1
iris = load_iris()

如前所述,scikit-learn 提供了读取鸢尾花数据集的接口 sklearn.datasets.load_iris(),该数据集有 150 组 3 种类型鸢尾花的 4 种属性:萼片长度 sepal length、萼片宽度 sepal width、花瓣长度 petal length 和花瓣宽度 petal width,样本编号与类型的关系是:样本编号 0 至 49 为 Setosa ,50 至 99 为Versicolour ,100 至 149 为 Virginica。代码 iris = load_iris()中得到的 iris 是一个字典,包含七个“key-value”对:

key-value对

其中每一个 key 的意义和 value 说明如下:

意义说明

3)通过画图了解三种鸢尾花的分布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plt.clf()
plt.xlim(0, 7)#x 轴上的最小值和最大值
plt.ylim(0, 4)
plt.title(u'iris 数据集 萼片', fontsize=15)
X=iris.data[:,0:2]

plt.xlabel('petal length 萼片长度', fontsize=13)
plt.ylabel('petal width 萼片宽度', fontsize=13)
plt.plot(X[:50, 0], X[:50, 1], 'bo', color='blue', label='Setosa 山鸢尾')
plt.plot(X[50:100, 0], X[50:100, 1], 'bo', color='orange', label='Versicolour 变色鸢尾')
plt.plot(X[100:150, 0], X[100:150, 1], 'bo', color='red', label='Virginica 维吉尼亚鸢尾')
plt.legend()

plt.show()

生成图片如下:

生成图片

从图中大致可以看出,萼片长度和萼片宽度与鸢尾花类型间呈现出非线性关系。

生成图片

从图中大致可以看出,花瓣长度和花瓣宽度与鸢尾花类型间有较好的线性关系,使用花瓣数据来划分鸢尾花类型效果更好。

4 )算法初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
X=np.c_[np.ones(100),iris.data[:100,2:4]]

# 得到真值T,从数据集中得到真值
T=iris.target[:100].reshape(100,1)

# 将T中所有不等于1的元素赋值为-1,以契合sign函数
T[T!=1] = -1


# 权值初始化,3行1列,即w0 w1 w2
# 直接赋初值,不使用随机数
W = np.array([[1],
[1],
[1]])

# 学习率设置
lr = 1
# 神经网络输出
Y = 0

np.ones(100) 生成 100 行 1 列的 1,作为偏置的输入。
iris.data[:100,2:4] 得到前 100 行,2 列和 3 列数据,作为两个特征(花瓣长度、花瓣宽度)的输入。
np.c_是按行连接两个矩阵,就是把两矩阵左右相连形成一个新矩阵,要求行数相等。
组合两个特征和偏置,形成最终的输入 X。
X=(x0 x1 x2),即偏置、花瓣长度、花瓣宽度。
真值 T 直接从数据集中得到,然后将 T 中所有不等于 1 的元素赋值为-1,以契合接下来将要使用到的 sign 函数。

5 )学习算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# # 学习算法
#训练感知机模型
# 更新一次权值
def train():
# 使用全局变量W
global W
# 同时计算100个数据的预测值
# Y的形状为(100,1)-100行1列
Y = np.sign(np.dot(X,W))
# T - Y得到100个的标签值与预测值的误差E。形状为(100,1)
E = T - Y
# X的形状为(100,3)
# X.T表示X的转置矩阵,形状为(3,100)
# 我们一共有100个数据,每个数据3个特征的值。定义第i个数据的第j个特征值为xij
# 如第1个数据,第2个值为x12
# X.T.dot(E)为一个3行1列的数据:
# 第1行等于:x0_0×e0+x1_0×e1+x2_0×e2+x3_0×e3+...+x99_0×e99,它会调整权值W0
# 第2行等于:x0_1×e0+x1_1×e1+x2_1×e2+x3_1×e3+...+x99_1×e99,它会调整权值W1
# 第3行等于:x0_2×e0+x1_2×e1+x2_2×e2+x3_2×e3+...+x99_2×e99,它会调整权值W2
# X.shape表示X的形状X.shape[0]得到X的行数,表示有多少个数据
# X.shape[1]得到列数,表示每个数据有多少个特征值。
delta_W = lr * (X.T.dot(E)) / X.shape[0]
W = W + delta_W

6)画图函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# # 画图函数
def draw():
plt.clf()
plt.xlim(0, 6)#x轴上的最小值和最大值
plt.ylim(0, 2)#y轴上的最小值和最大值
plt.title(u'Perceptron感知器 epoch:%d\n W0:%f W1:%f W2:%f' %(i+1,W[0],W[1],W[2]), fontsize=15)

plt.xlabel('petal length 花瓣长度', fontsize=13)
plt.ylabel('petal width 花瓣宽度', fontsize=13)
plt.plot(X[:50, 1], X[:50, 2], 'bo', color='red', label='Setosa山鸢尾')
# 用黄色的点来画出负样本
plt.plot(X[50:100, 1], X[50:100, 2], 'bo', color='blue', label='Versicolour变色鸢尾')
plt.plot(2.5, 1, 'b+', color='black', label='待预测点')

k = - W[1] / W[2]
d = -W[0] / W[2]
# 设定两个点
xdata = (0,6)
# 通过两个点来确定一条直线,用红色的线来画出分界线
plt.plot(xdata,xdata * k + d,'black', linewidth=3)
plt.legend()
######################################################以下绘制决策面两边的颜色,不要求掌握
# 生成决策面
from matplotlib.colors import ListedColormap #绘制决策面两边的颜色,不要求掌握
# 生成x,y的数据
n = 256
xx = np.linspace(0, 6, n)
yy = np.linspace(0, 2, n)
# 把x,y数据生成mesh网格状的数据,因为等高线的显示是在网格的基础上添加上高度值
XX, YY = np.meshgrid(xx, yy)
# 填充等高线
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(np.sign(W[0]+W[1]*XX+W[2]*YY)))])
plt.contourf(XX, YY, np.sign(W[0]+W[1]*XX+W[2]*YY),8, alpha = 0.5, cmap=cmap)
#######################################################以上绘制决策面两边的颜色,不要求掌握
plt.pause(0.1)
plt.show()

7)主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
# # 训练100次
for i in range(1000):
if(i==0): #特地画出未经训练的初始图像,以方便理解
draw()
plt.pause(5) #停留两秒,这是分类直线最初的位置,取决于W的初始值,是人为决定的超参数
train() #更新一次权值
draw() #画出更新一次权值后的图像
Y = np.sign(np.dot(X,W))
# .all()表示Y中的所有值跟T中所有值都对应相等,结果才为真
if(Y == T).all():
print('Finished')
# 跳出循环
break

实验内容

请回答下列问题:

第一题

1.考虑学习率的作用。修改示例代码,固定初始权值=(1,1,1),将学习率分别设定为 1、0.5、0.1(组合 1~3),程序在 epoch 等于多少时实现分类?

  • 当修改学习率为1时:

当修改学习率为0.5时:

当修改学习率为0.1时:

第二题

2.考虑初始权值的作用。修改示例代码,固定学习率=0.1,将初始权值分别设定为(-1,1,1)、(+1,-1,-1)、(1,-1,+1) 、(-1,+1,-1) (组合 4~7),程序在 epoch 等于多少时实现分类?

当初始权值设定为(-1,1,1)时:

当初始权值设定为(+1,-1,-1)时:

当初始权值设定为(+1,-1,+1) 时:

当初始权值设定为(-1,+1,-1) 时:

跑程序出现的问题

出问题的前提是在未联网的情况下,跑了感知机实验。

当初始权值设定为(+1,-1,+1) 时,感知机失效,epoch在176-190之间徘徊。与同学对照,发现绝大多数定格在了322次。

  • 我尝试重启pycharm,多次尝试后均未奏效。
  • 尝试修改程序,先修改学习率跑一次,再把学习率改回来跑,失效。

第三题

3.示例程序使用的是离散感知机还是连续感知机?如何判断?

使用的是离散感知机,因为得到的值不是连续的。

第四题

4.为什么在学习算法中要除以 X.shape[0] ?示例程序采用的是批量下降还是逐一下降?是否属于随机下降?是否属于梯度下降?

  1. 由于相加了多次所以要取平均值,保持偏执和其余两项权值大小正常;

  2. 属于批量下降

  3. 不属于随机下降,不属于梯度下降

X.shape[0]=100

如果不除以X.shape[0]:

[[-100.]

[-146.2]

[-24.6]]

如果除以X.shape[0]:

[[-1]

[-1.426]

[-0.246]]

第五题

5.假设你在自然界找到了一朵鸢尾花,并测得它的花瓣长度为 2.5cm,花瓣宽度为 1cm,它属于哪一类?在 draw()中已用 plt.plot 画出这个’待预测点’。请观察 1~7 这 7 种组合中,感知机的判断始终一致么?这说明它受到什么因素的影响?

  1. 属于Versicolour 变色鸢尾。
  2. 暂时空着
  3. 感知机的判断并不一致,它受到学习率和权值的影响。

第六题

6.修改示例代码,将变色鸢尾的数据替换为维吉尼亚鸢尾,再进行分类。即横轴为花瓣长度,纵轴为花瓣宽度,数据为 Setosa 山鸢尾+Virginica 维吉尼亚鸢尾。

更改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
Xb=np.c_[np.ones(50),iris.data[100:150,2:4]]
Xa=np.c_[np.ones(50),iris.data[:50,2:4]]
X=np.r_[Xa,Xb]
# 得到真值T,从数据集中得到真值
T1=iris.target[:50]
T2=iris.target[100:150]
T=np.r_[T1,T2].reshape(100,1)

# 将T中等于0的元素都变为-1,等于2的元素都变成1,以契合sign函数
T[T==0] = -1
T[T==2] = 1;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
plt.clf()
plt.xlim(0, 7)#x轴上的最小值和最大值
plt.ylim(0, 6)#y轴上的最小值和最大值
plt.title(u'Perceptron感知器 epoch:%d\n W0:%f W1:%f W2:%f' %(i+1,W[0],W[1],W[2]), fontsize=15)

plt.xlabel('petal length 花瓣长度', fontsize=13)
plt.ylabel('petal width 花瓣宽度', fontsize=13)
plt.plot(X[:50, 1], X[:50, 2], 'bo', color='red', label='Setosa山鸢尾')
# 用黄色的点来画出负样本
plt.plot(X[50:100, 1], X[50:100, 2], 'bo', color='blue', label='Virginica维吉尼亚鸢尾')
plt.plot(2.5, 1, 'b+', color='black', label='待预测点')

k = - W[1] / W[2]
d = -W[0] / W[2]
# 设定两个点
xdata = (0,6)
# 通过两个点来确定一条直线,用红色的线来画出分界线
plt.plot(xdata,xdata * k + d,'black', linewidth=3)
plt.legend()

效果:

第七题

7.【可选】目前感知机只有两个输入+偏置,如果有三个输入(比如增加萼片长度作为输入),程序应如何修改(可以不画图)?

1
2
3
4
5
6
7
8
9
10
11
12
# 从图中大致可以看出,花瓣长度和花瓣宽度与鸢尾花类型间有较好的线性关系,使用花瓣数据来划分鸢尾花类型效果更好。
Xb=np.c_[np.ones(50),iris.data[100:150,2:4],iris.data[100:150,0:1]]
Xa=np.c_[np.ones(50),iris.data[:50,2:4],iris.data[100:150,0:1]]
X=np.r_[Xa,Xb]
# 得到真值T,从数据集中得到真值
T1=iris.target[:50]
T2=iris.target[100:150]
T=np.r_[T1,T2].reshape(100,1)

# 将T中所有不等于1的元素赋值为-1,以契合sign函数
T[T==0] = -1
T[T==2] = 1;
1
2
3
4
5
6
7
8
9
10
11
# 权值初始化,4行1列,即w0 w1 w2 w3 
# 直接赋初值,不使用随机数
W = np.array([[+1],
[-1],
[+1],
[+1]])

# 学习率设置
lr = 0.1
# 神经网络输出
Y = 0

完整实验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 加载用到的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris #仅用于加载数据集


# 画图时的中文支持
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号

#加载数据集
iris = load_iris()

# # 鸢尾花数据集

# scikit-learn是基于Python的机器学习库,其默认安装包含了几个小型的数据集,并提供了读取这些数据集的接口。
# 其中sklearn.datasets.load_iris()用于读取鸢尾花数据集,该数据集有150组3种类型鸢尾花的4种属性:萼片长度sepal length、萼片宽度sepal width、花瓣长度petal length和花瓣宽度petal width,样本编号与类型的关系是:样本编号0至49为 Setosa ,50至99为 Versicolour ,100至149为 Virginica。


# 通过画图了解三种鸢尾花的分布



plt.clf()
plt.xlim(0, 7)#x轴上的最小值和最大值
plt.ylim(0, 4)
plt.title(u'iris数据集 萼片', fontsize=15)
X=iris.data[:,0:2]

plt.xlabel('petal length 萼片长度', fontsize=13)
plt.ylabel('petal width 萼片宽度', fontsize=13)
plt.plot(X[:50, 0], X[:50, 1], 'bo', color='blue', label='Setosa山鸢尾')
plt.plot(X[50:100, 0], X[50:100, 1], 'bo', color='orange', label='Versicolour变色鸢尾')
plt.plot(X[100:150, 0], X[100:150, 1], 'bo', color='red', label='Virginica维吉尼亚鸢尾')
plt.legend()

plt.show()
plt.pause(3)

# 从图中大致可以看出,萼片长度和萼片宽度与鸢尾花类型间呈现出非线性关系。



plt.clf()
plt.xlim(0, 7)#x轴上的最小值和最大值
plt.ylim(0, 3)
plt.title(u'iris数据集 花瓣', fontsize=15)
X=iris.data[:,2:4]

plt.xlabel('petal length 花瓣长度', fontsize=13)
plt.ylabel('petal width 花瓣宽度', fontsize=13)
plt.plot(X[:50, 0], X[:50, 1], 'bo', color='blue', label='Setosa山鸢尾')
plt.plot(X[50:100, 0], X[50:100, 1], 'bo', color='orange', label='Versicolour变色鸢尾')
plt.plot(X[100:150, 0], X[100:150, 1], 'bo', color='red', label='Virginica维吉尼亚鸢尾')
plt.legend()

plt.show()
plt.pause(3)

# 从图中大致可以看出,花瓣长度和花瓣宽度与鸢尾花类型间有较好的线性关系,使用花瓣数据来划分鸢尾花类型效果更好。
X=np.c_[np.ones(100),iris.data[:100,2:4]]

# 得到真值T,从数据集中得到真值
T=iris.target[:100].reshape(100,1)

# 将T中所有不等于1的元素赋值为-1,以契合sign函数
T[T!=1] = -1


# 权值初始化,3行1列,即w0 w1 w2
# 直接赋初值,不使用随机数
W = np.array([[1],
[1],
[1]])

# 学习率设置
lr = 1
# 神经网络输出
Y = 0




# # 学习算法


#训练感知机模型
# 更新一次权值
def train():
# 使用全局变量W
global W
# 同时计算100个数据的预测值
# Y的形状为(100,1)-100行1列
Y = np.sign(np.dot(X,W))
# T - Y得到100个的标签值与预测值的误差E。形状为(100,1)
E = T - Y
# X的形状为(100,3)
# X.T表示X的转置矩阵,形状为(3,100)
# 我们一共有100个数据,每个数据3个特征的值。定义第i个数据的第j个特征值为xij
# 如第1个数据,第2个值为x12
# X.T.dot(E)为一个3行1列的数据:
# 第1行等于:x0_0×e0+x1_0×e1+x2_0×e2+x3_0×e3+...+x99_0×e99,它会调整权值W0
# 第2行等于:x0_1×e0+x1_1×e1+x2_1×e2+x3_1×e3+...+x99_1×e99,它会调整权值W1
# 第3行等于:x0_2×e0+x1_2×e1+x2_2×e2+x3_2×e3+...+x99_2×e99,它会调整权值W2
# X.shape表示X的形状X.shape[0]得到X的行数,表示有多少个数据
# X.shape[1]得到列数,表示每个数据有多少个特征值。
delta_W = lr * (X.T.dot(E)) / X.shape[0]
W = W + delta_W


# # 画图函数
def draw():
plt.clf()
plt.xlim(0, 6)#x轴上的最小值和最大值
plt.ylim(0, 2)#y轴上的最小值和最大值
plt.title(u'Perceptron感知器 epoch:%d\n W0:%f W1:%f W2:%f' %(i+1,W[0],W[1],W[2]), fontsize=15)

plt.xlabel('petal length 花瓣长度', fontsize=13)
plt.ylabel('petal width 花瓣宽度', fontsize=13)
plt.plot(X[:50, 1], X[:50, 2], 'bo', color='red', label='Setosa山鸢尾')
# 用黄色的点来画出负样本
plt.plot(X[50:100, 1], X[50:100, 2], 'bo', color='blue', label='Versicolour变色鸢尾')
plt.plot(2.5, 1, 'b+', color='black', label='待预测点')

k = - W[1] / W[2]
d = -W[0] / W[2]
# 设定两个点
xdata = (0,6)
# 通过两个点来确定一条直线,用红色的线来画出分界线
plt.plot(xdata,xdata * k + d,'black', linewidth=3)
plt.legend()
######################################################以下绘制决策面两边的颜色,不要求掌握
# 生成决策面
from matplotlib.colors import ListedColormap #绘制决策面两边的颜色,不要求掌握
# 生成x,y的数据
n = 256
xx = np.linspace(0, 6, n)
yy = np.linspace(0, 2, n)
# 把x,y数据生成mesh网格状的数据,因为等高线的显示是在网格的基础上添加上高度值
XX, YY = np.meshgrid(xx, yy)
# 填充等高线
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(np.sign(W[0]+W[1]*XX+W[2]*YY)))])
plt.contourf(XX, YY, np.sign(W[0]+W[1]*XX+W[2]*YY),8, alpha = 0.5, cmap=cmap)
#######################################################以上绘制决策面两边的颜色,不要求掌握
plt.pause(0.1)
plt.show()


# # 训练1000次
for i in range(1000):
if(i==0): #特地画出未经训练的初始图像,以方便理解
draw()
plt.pause(5) #停留两秒,这是分类直线最初的位置,取决于W的初始值,是人为决定的超参数
train() #更新一次权值
draw() #画出更新一次权值后的图像
Y = np.sign(np.dot(X,W))
# .all()表示Y中的所有值跟T中所有值都对应相等,结果才为真
if(Y == T).all():
print('Finished')
# 跳出循环
break