安装环境:
WINDOWS+Anaconda3 套 件 22.9.0 ( Anaconda3-2022.10-Windowsx86_64.exe)
含Python编译环境及相关的扩展库,包括Python 3.9.13、SciPy 1.9.1、NumPy 1.21.5、Matplotlib 3.5.2。Anaconda3套件4.10.3默认安装了编译开发环境Spyder 5.1.5、jupyter notebook 6.4.8和jupyterlab 3.3.2,这些都可以作为开发工具,当然也可以自行选择其它开发工具如Pycharm、Eclipse、Sublime Text、VSCODE、IDLE等。
安装库:
pip install numpy
pip install matplotlib
pip install scipy
pip install sklearn
pip install scikit-learn
人工神经网络是对人脑功能的一种模拟,它在一定程度上揭示了生物神经网络运行的基本规则。
生物神经网络一般是指生物的大脑神经元、细胞等组成的网络,用于产生生物的意识,帮助生物进行思考和行动。
神经元细胞的全体又连接成一个大型复杂的神经网络
每个神经元伸出的突起分 2 种,树突和轴突。树突分支比较多,每个分支还可以再分支, 长度一般比较短,作用是接受信号。轴突只有一个,从细胞体的一个凸出部分伸出,长度一般比较长,作用是把从树突和细胞表面传入细胞体的神经信号传出到其他神经元。轴突的末端分为许多小支,连接到其他神经元的树突上。
功能实现:传入的神经元冲动经整合使细胞膜的电位升高,当电位升高到超过动作电位的阈值时,神经元为兴奋状态,产生神经冲动由轴突经神经末梢传出;传入神经元的冲动经整合使细胞膜电位降低,当电位降低到低于动作电位阈值时,神经元为抑制状态,不产生神经冲动。
(1)生物神经元之间相互连接,其连接强度决定了信号传递的强弱
(2)神经元之间的连接强度是可以随着训练改变的
(3)信号可以起刺激作用,也可以起抑制作用
(4)一个神经元接收信号的累积效果决定了该神经元的状态
(5)每个神经元有一个动作阈值
1957年,计算机专家Rosenblatt 提出了感知器模型。这是一种具有连续可调权值矢量的神经网络模型,经过训练可以达到对一定的输入矢量模式进行分类和识别的目的。然比较简单,却是第一个真正意义上的神经网络。它可以被视为一种最简单形式的前馈式人工神经网络,是一种二元线性分类器。深度学习-感知机
单输入感知器模型如图4-12所示,它仅有一个输入和一个输出,功能也比较简单,能对单输入信号进行一定变换后再次输出。模型用公式可表达为a=f(wp+b),其中a为神经元的输出,f为传输函数,w为权值,p为输入, b为偏置。
其中f就是激活函数。激活函数是感知器的核心部分,引入激活函数增加了神经网络的非线性特征,从而使得神经网络能够实现各种复杂功能。
如果没有激活函数,无论叠加多少层神经网络,其计算过程都只是线性计算
根据激活函数是否连续,来分类
激活函数离散:则对应的感知器是离散感知器
激活函数连续:则对应的感知器是连续感知器
哔哩哔哩-梯度下降视频讲解
批量梯度下降法是最原始的形式,它是指在每一次迭代时使用所有样本来进行梯度的更新
优点:
(1)一次迭代是对所有样本进行计算,此时利用矩阵进行操作,实现了并行。
(2)由全数据集确定的方向能够更好地代表样本总体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD一定能够得到 全局最优。
缺点:
(1)当样本数目 m 很大时,每迭代一步都需要对所有样本计算,训练过程会很慢。
随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快
优点:
(1)由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数的更新速度大大加快。
缺点:
(1)准确度下降。由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。
(2)可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。
(3)不易于并行实现。
解释一下为什么SGD收敛速度比BGD要快:
答:这里我们假设有30W个样本,对于BGD而言,每次迭代需要计算30W个样本才能对参数进行一次更新,需要求得最小值可能需 要多次迭代(假设这里是10);而对于SGD,每次更新参数只需要一个样本,因此若使用这30W个样本进行参数更新,则参数会 被更新(迭代)30W次,而这期间,SGD就能保证能够收敛到一个合适的最小值上了。也就是说,在收敛时,BGD计算了 10×30W10×30W 次,而SGD只计算了 1×30W1×30W 次。
小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代 使用 batch_size个样本来对参数进行更新
优点:
(1)通过矩阵运算,每次在一个batch上优化神经网络参数并不会比单个数据慢太多。
(2)每次 使用一个batch可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。(比如上例中的 30W,设置batch_size=100时,需要迭代3000次,远小于SGD的30W次)
(3)可实现并行化。
缺点:
(1)batch_size的不当选择可能会带来一些问题。
batcha_size的选择带来的影响:
(1)在合理地范围内,增大batch_size的好处:
a. 内存利用率提高了,大矩阵乘法的并行化效率提高。
b. 跑完一次 epoch(全数据集)所需的迭代次数减少,对于相同数据量的处理速度进一步加快。
c. 在一定范围内,一般来说 Batch_Size 越大,其确定的下降方向越准,引起训练震荡越小。
(2)盲目增大batch_size的坏处:
a. 内存利用率提高了,但是内存容量可能撑不住了。
b. 跑完一次 epoch(全数据集)所需的迭代次数减少,要想达到相同的精度,其所花费的时间大大增加了,从而对参数的修正也 就显得更加缓慢。
c. Batch_Size 增大到一定程度,其确定的下降方向已经基本不再变化。
# 加载用到的库
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], 'o', color='blue', label='Setosa山鸢尾')
plt.plot(X[50:100, 0], X[50:100, 1], 'o', color='orange', label='Versicolour变色鸢尾')
plt.plot(X[100:150, 0], X[100:150, 1], 'o', 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], 'o', color='blue', label='Setosa山鸢尾')
plt.plot(X[50:100, 0], X[50:100, 1], 'o', color='orange', label='Versicolour变色鸢尾')
plt.plot(X[100:150, 0], X[100:150, 1], 'o', color='red', label='Virginica维吉尼亚鸢尾')
plt.legend()
plt.show()
plt.pause(3)
# 从图中大致可以看出,花瓣长度和花瓣宽度与鸢尾花类型间有较好的线性关系,使用花瓣数据来划分鸢尾花类型效果更好。
# 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 函数
# 数据dataset1为 Setosa 山鸢尾+Versicolour 变色鸢尾
dataset1 = np.c_[np.ones(100), iris.data[:100, 2:4]]
# 数据dataset2为 Setosa 山鸢尾+Virginica 维吉尼亚鸢尾
dataset2 = np.r_[np.c_[np.ones(50), iris.data[:50, 2:4]], np.c_[np.ones(50), iris.data[100:, 2:4]]]
# 数据dataset3为 Setosa 山鸢尾+Virginica 变色鸢尾 ,增加萼片长度特征
dataset3 = np.c_[np.ones(100), iris.data[:100, 2:4], iris.data[:100, 0:1]]
X = dataset1
# 得到真值T,从数据集中得到真值
T1 = iris.target[:100].reshape(100, 1)
T = T1
# 将T中所有不等于1的元素赋值为-1,以契合sign函数
T[T != 1] = -1
# 权值初始化,3行1列,即w0 w1 w2
# 直接赋初值,不使用随机数
W = np.array([[1],
[+1],
[1]])
# 学习率设置
lr = 0.1
# 神经网络输出
Y = 0
# # 学习算法
# 训练感知机模型
# 更新一次权值
def train():
# 使用全局变量W
global W
# 同时计算100个数据的预测值
# Y的形状为(100,1)-100行1列
# Rosenblatt 在其感知机论文中使用的激活函数是 sign(x),该函数用来描述一个实数的符号,当
# x>0 时,输出值为 +1;当 x= 0 时,输出值为 0;当 x<0 时,输出值为-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], 'o', color='red', label='Setosa山鸢尾')
# 用黄色的点来画出负样本
plt.plot(X[50:100, 1], X[50:100, 2], 'o', color='blue', label='Versicolour变色鸢尾')
plt.plot(2.5, 1, '+', 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.05)
# plt.show()
# # 训练100次
for i in range(1000):
if (i == 0): # 特地画出未经训练的初始图像,以方便理解
draw()
plt.pause(1) # 停留两秒,这是分类直线最初的位置,取决于W的初始值,是人为决定的超参数
train() # 更新一次权值
draw() # 画出更新一次权值后的图像
Y = np.sign(np.dot(X, W))
# .all()表示Y中的所有值跟T中所有值都对应相等,结果才为真
if (Y == T).all():
plt.show()
print('epoch=' + str(i + 1))
print('Finished')
# 跳出循环
break
def train():
# 全局变量W
global W
# np.sign()是取数字的符号函数,这里作为激活函数
# np.dot()是向量点积和矩阵乘法
Y = np.sign(np.dot(X,W))
E = T - Y
delta_W = lr * (X.T.dot(E)) / X.shape[0]
W = W + delta_W
考虑学习率的作用。修改示例代码,程序在 epoch 等于多少时实现分类?
学习率 | 权值 | epoch | 待预测点 |
---|---|---|---|
1 | (1,1,1) | 8 | 1 |
0.5 | (1,1,1) | 9 | 1 |
0.1 | (1,1,1) | 181 | 1 |
0.1 | (-1,1,1) | 13 | 1 |
0.1 | (+1,-1,-1) | 33 | 1 |
0.1 | (1,-1,+1) | 322 | 1 |
0.1 | (-1,+1,-1) | 147 | 0 |
示例程序使用的是离散感知机还是连续感知机?如何判断?
离散感知机,根据激活函数是否连续,来分类
激活函数离散:则对应的感知器是离散感知器
激活函数连续:则对应的感知器是连续感知器
这里使用的是sign()函数,是离散感知器
为什么在学习算法中要除以 X.shape[0] ?示例程序采用的是批量下降还是逐一下降?是否属于随机下降?是否属于梯度下降?
在学习算法中 delta_W = lr * (X.T.dot(E)) / X.shape[0] ,delta_W 表示的是要调整的权值误差,X.T.dot(E)计算过程如下:
# 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.T.dot(E)是delta_W是所有样本的在3个权值上的误差之和,是一个3行1列矩阵,其中每一行都是所有样本在该权值上的误差之和,所以我们要求出单个样本在当前权值上的平均误差就要除以样本数量即X.shape(0),这样计算出来的就是每个样本的在3个权值上的平均误差,后续修改权值W=W+detla_W 就可以直接矩阵相加,得到最终更新后的权值。
假设你在自然界找到了一朵鸢尾花,并测得它的花瓣长度为 2.5cm,花瓣宽度为 1cm,它属于哪一类?在 draw()中已用 plt.plot 画出这个’待预测点’。请观察 1~7 这 7 种组合中,感知机的判断始终一致么?这说明它受到什么因素的影响?
感知机判断不一致,受初始权值影响
修改示例代码,将变色鸢尾的数据替换为维吉尼亚鸢尾,再进行分类。即横轴为花瓣长度,纵轴为花瓣宽度,数据为 Setosa 山鸢尾+Virginica 维吉尼亚鸢尾。
# 修改数据集,np.r_()是按行拼接,np.c_()是按列拼接,其他不用变
X = np.r_[np.c_[np.ones(50), iris.data[:50, 2:4]], np.c_[np.ones(50), iris.data[100:, 2:4]]]
目前感知机只有两个输入+偏置,如果有三个输入(比如增加萼片长度作为输入),程序应如何修改?
# 数据dataset3为 Setosa 山鸢尾+Virginica 变色鸢尾 ,增加萼片长度特征
X = np.c_[np.ones(100), iris.data[:100, 2:4], iris.data[:100, 0:1]]
引用及参考:
[1] https://www.cnblogs.com/lliuye/p/9451903.html