20231220_HNUST_计算智能(实训二)课设_总结

🆘如有侵权,请联系删除;如有写得不对的地方,请帮忙指出;如您有更好的想法、建议和资源分享,欢迎找我交流!
🔔🔔🔔

  • 部分知识参考网络上的博客。
  • 需要结合指导书自行进行学习。
  • 以下内容不一定是正确的,请自行甄别。
  • 请勿抄袭,仅供学习参考。

实验一 基于感知机的鸢尾花分类

一、实验题目

基于感知机的鸢尾花分类

二、实验目的

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

三、实验内容

(1)鸢尾花

此处使用鸢尾花数据集,scikit-learn 是基于 Python 的机器学习库,其默认安装包含了几个小型的数据集, 并提供了读取这些数据集的接口。 其中 sklearn.datasets.load_iris()用于读取鸢尾花数据集

鸢尾花有三个主要类型(种属): Setosa 山鸢尾、 Versicolour 变色鸢尾 和 Virginica 维吉尼亚鸢尾,其主要区别是萼片长度、萼片宽度、花瓣长度和花瓣宽度。 简单测试代码如下:

def datasets_demo():
  iris = load_iris()
  print("鸢尾花数据集:\n", iris)
  print("查看数据集描述: \n", iris["DESCR"])
  print("查看特征值的名字: \n", iris.feature_names)
  print("查看特征值: \n", iris.data, iris.data.shape)

(2)感知机

感知机(perceptron)是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取+1和-1二值。感知机模型损失函数是误分类点到超平面的总距离,利用梯度下降法对损失函数进行极小化,求出将训练数据进行线性划分的分离超平面,从而得到感知机模型。感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式。

下图为一个有三个输入的感知器的结构图。其中 x1、x2、x3 为输入, w1、w2、w3 为相应的权值,b 为偏置,f 为激活函数,y 为神经元的输出。偏置 b 也可视为输入恒为 1 的边的权值,记为 w0。

在这里插入图片描述

(3)相关测试
在这里插入图片描述

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

在这里插入图片描述

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

未训练前的结果

在这里插入图片描述

题目给的原始数据,epoch = 8 的时候结束。

(4)关键代码

def train():
    global W
    Y = np.sign(np.dot(X, W))
    E = T - Y
    delta_W = lr * (X.T.dot(E)) / X.shape[0]
    W = W + delta_W
    k = - W[1] / W[2]
    d = -W[0] / W[2]
    xdata = (0, 6)
    plt.plot(xdata, xdata * k + d, 'black', linewidth=3)

四、实验结果与分析

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

解:如图所示:

在这里插入图片描述
lr = 1, epoch = 8
在这里插入图片描述
lr = 0.5, epoch = 9
在这里插入图片描述
lr = 0.1, epoch = 181

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

解:如图所示:

在这里插入图片描述
lr = 1,w = [-1,1,1], epoch = 4
在这里插入图片描述
lr = 1,w = [1,-1,-1], epoch = 11
在这里插入图片描述
lr = 1,w = [1,-1,1], epoch = 7
在这里插入图片描述
lr = 1,w = [-1,1,-1], epoch = 5

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

解:

离散感知机,由代码可知,此处使用的是sign()函数,属于离散型。

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

解:

因为如果不除以X.shape[0]的话,得到的delta_W是100个数据相加后的,而不是对单个数据的权值,所以应该除以X.shape[0]。

批量下降,不属于随机下降。属于梯度下降。
【批量梯度下降(Batch Gradient Descent)
delta_W = lr * (X.T.dot(E)) / X.shape[0]】

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

解:

lr = 0.1,W =[-1,1,-1]时,感知机判断该鸢尾花属于Setosa山鸢尾,其他都属于Versicolour 变色鸢尾。

不一致,受 lr, W 等影响。

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

解:

修改代码:在这里插入图片描述

# 改: 拼接数据 沿着行的方向

init_data = np.concatenate((iris.data[:50, 2:4], iris.data[100:150, 2:4]), axis=0)
X = np.c_[np.ones(100), init_data]
plt.plot(X[50:100, 1], X[50:100, 2], 'o', color='blue', label='Virginica 维吉尼亚鸢尾')
在这里插入图片描述
lr=1,W=[1,1,1],epoch=7

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

解:
X = np.c_[np.ones(100), iris.data[:100, 1:4]]

W = np.array([[1],[1],[1],[1]])

在这里插入图片描述

五、小结与心得体会

① 认识到了机器学习算法在实际问题中的应用价值。通过使用感知机模型,对鸢尾花的数据集进行了分类,并分析了相关性质;

② 了解到了感知机模型的原理和实现过程,通过编写代码,实现了感知机模型的学习和预测过程,对感知机模型的原理有了更深入的理解;

③ 发现了数据预处理的重要性,在本次实验中,对鸢尾花的数据先做了预处理,为后续模型的训练提供了便利;

④ 认识到了超参数对实验结果的影响,同时也可以通过优化方法来改进感知模型和好坏,比如可以改用随机梯度下降方法等。

实验二 以人事招聘为例的误差反向传播算法

一、实验题目

以人事招聘为例的误差反向传播算法

二、实验目的

理解多层神经网络的结构和原理,掌握反向传播算法对神经元的训练过程,了解反向传播公式。通过构建 BP 网络实例,熟悉前馈网络的原理及结构。

三、实验内容

(1) BP算法

BP算法是一种反向传播算法,它由信号的正向传播和误差的反向传播两个过程组成。正向传播时,输入样本从输入层进入网络,经隐层逐层传递至输出层,如果输出层的实际输出与期望输出(导师信号)不同,则转至误差反向传播;如果输出层的实际输出与期望输出(导师信号)相同,结束学习算法。反向传播时,将输出误差(期望输出与实际输出之差)按原通路反传计算,通过隐层反向,直至输入层,在反传过程中将误差分摊给各层的各个单元,获得各层各单元的误差信号,并将其作为修正各单元权值的根据。这一计算过程使用梯度下降法完成,在不停地调整各层神经元的权值和阈值后,使误差信号减小到最低限度。权值和阈值不断调整的过程,就是网络的学习与训练过程。

(2) 梯度下降方法

  • 批量梯度下降:这种方法在每个训练步骤中,使用整个训练集来计算梯度并更新模型参数。它的主要缺点是计算量大,收敛速度慢,但在每一步都使用全部数据可以保证模型参数向着总体最优化方向收敛。
  • 随机梯度下降:随机梯度下降算法每次只使用一个样本来计算梯度并更新模型参数,这使得它能够更快地收敛,特别是在大数据集上。然而,由于只使用一个样本进行更新,它可能会在优化过程中产生一些波动,导致在某些情况下无法找到全局最优解。
  • 小批量梯度下降:小批量梯度下降是对随机梯度下降和批量梯度下降的一种折中。这种方法每次使用一小部分(例如10-30个)样本来计算梯度并更新模型参数。这种方法在保持收敛速度的同时,减少了优化过程中的波动。对于大数据集,小批量梯度下降通常比批量梯度下降更快,但比随机梯度下降更稳定。

(3) 以人事招聘为例

在这里插入图片描述

其中张三、李四等人是应聘者,他们向该部门投递简历,简历包括两类数据:学习成绩和社会实践得分,人事部门有三个层级,一科长根据应聘者的学习成绩和实践得分评估其智商,二科长根据同样的资料评估其情商;一处长根据两个科长提供的智商、情商评分,评估应聘者的工作能力,二处长评估工作态度;最后由总裁汇总两位处长的意见,得出最终结论,即是否招收该应聘者。

等价于一个形状为(2,2,2,1)的前馈神经网络,如下图所示:

在这里插入图片描述
在这里插入图片描述

(4) 关键代码

def update():
  global batch_X, batch_T, W1, W2, W3, lr, b1, b2, b3

  Z1 = np.dot(batch_X, W1) + b1
  A1 = sigmoid(Z1)
  Z2 = (np.dot(A1, W2) + b2)
  A2 = sigmoid(Z2)
  Z3 = (np.dot(A2, W3) + b3)
  A3 = sigmoid(Z3)
  delta_A3 = (batch_T - A3)
  delta_Z3 = delta_A3 * dsigmoid(A3)
  delta_W3 = A2.T.dot(delta_Z3) / batch_X.shape[0]
  delta_B3 = np.sum(delta_Z3, axis=0) / batch_X.shape[0]
  delta_A2 = delta_Z3.dot(W3.T)
  delta_Z2 = delta_A2 * dsigmoid(A2)
  delta_W2 = A1.T.dot(delta_Z2) / batch_X.shape[0]
  delta_B2 = np.sum(delta_Z2, axis=0) / batch_X.shape[0]
  delta_A1 = delta_Z2.dot(W2.T)
  delta_Z1 = delta_A1 * dsigmoid(A1)
  delta_W1 = batch_X.T.dot(delta_Z1) / batch_X.shape[0]
  delta_B1 = np.sum(delta_Z1, axis=0) / batch_X.shape[0]

  W3 = W3 + lr * delta_W3
  W2 = W2 + lr * delta_W2
  W1 = W1 + lr * delta_W1
  b3 = b3 + lr * delta_B3
  b2 = b2 + lr * delta_B2
  b1 = b1 + lr * delta_B1

def sigmoid(x):
  return 1 / (1 + np.exp(-x))

def dsigmoid(x):
  return x * (1 - x)

四、实验结果与分析

(1) 如果去掉总裁这一层,相应张三的样本修改为(1.0,0.1,1.0,1.0),分别对应张三的学习成绩、张三的实践成绩、张三的工作能力真值、张三的工作态度真值,代码应该如何修改?

解:

删去了含 W3 和 b3 的所有内容,修改

X = np.array([[1, 0.1]])
T = np.array([[1],[1]])
delta_A2 = (batch_T - A2)
for i in map(predict, A2)
def predict(x):
  if x[0] >= 0.5 and x[1] >= 0.5:
    return [1, 1]
  elif x[0] >= 0.5 and x[1] <= 0.5:
    return [1, 0]
  elif x[0] <= 0.5 and x[1] >= 0.5:
    return [0, 1]
  elif x[0] <= 0.5 and x[1] <= 0.5:
    return [0, 0]

在这里插入图片描述

(2) 如果增加一个样本,李四(0.1,1.0,0),分别对应李四的学习成绩,李四的实践成绩,李四被招聘可能性的真值,代码应该如何修改?此时是一个样本计算一次偏导、更新一次权值,还是两个样本一起计算一次偏导、更新一次权值?(提示:注意 batch_size 的作用)

解:

修改如下:

X = np.array([[1, 0.1], [0.1, 1]])
T = np.array([[1], [0]])

batch_size = 1【以下是epochs和loss的图,不代表update()迭代次数的关系】

此时是一个样本计算一次偏导、更新一次权值

在这里插入图片描述在这里插入图片描述

(3) 样本为张三[1,0.1,1]、李四[0.1,1,0]、王五[0.1,0.1,0]、赵六[1,1,1],请利用batch_size 实现。教材279 页提到的“批量梯度下降”、“随机梯度下降”和“小批量梯度下降”,请注意“随机梯度下降”和“小批量梯度下降”要体现随机性。

解:

修改输入数据为:

X = np.array([[1, 0.1],
       [0.1, 1],
       [0.1, 0.1],
       [1, 1]])
T = np.array([[1],
       [0],
       [0],
       [1]])

A. 批量梯度法:

需要修改输入数据,batch_size = 4

在这里插入图片描述在这里插入图片描述

B. 随机梯度下降:

batch_size = 1
# 如果想要保证案例代码中的画图代码不进行修改,此处for循环就需要删去,因为案例代码中是以epoch的次数为横坐标。【以下也是】
  index = random.randint(0, max_batch - 1)
  batch_X = X[index: index + 1, :]
  batch_T = T[index: index + 1, :]
  update()
在这里插入图片描述在这里插入图片描述

C. 小批量梯度下降:

batch_size = 2
len = X.shape[0]
index1 = random.randint(0, len - 1)
index2 = random.randint(0, len - 1)
while index1 == index2:
  index2 = random.randint(0, len - 1)
batch_X = np.r_[X[index1:(index1 + 1), :], X[index2:(index2 + 1), :]]
batch_T = np.r_[T[index1:(index1 + 1), :], T[index2:(index2 + 1), :]]
update()
在这里插入图片描述在这里插入图片描述
batch_size = 3

len = X.shape[0]
index1 = random.randint(0, len - 1)
index2 = random.randint(0, len - 1)
index3 = random.randint(0, len - 1)
while index1 == index2:
  index2 = random.randint(0, len - 1)
while index3 == index1 or index3 == index2:
  index3 = random.randint(0, len - 1)
batch_X = np.r_[X[index1:(index1 + 1), :], X[index2:(index2 + 1), :]]
batch_T = np.r_[T[index1:(index1 + 1), :], T[index2:(index2 + 1), :]]

batch_X = np.r_[batch_X, X[index3:(index3 + 1), :]]
batch_T = np.r_[batch_T, T[index3:(index3 + 1), :]]

batch_X = X[index * batch_size:(index + 1) * batch_size, :]
batch_T = T[index * batch_size:(index + 1) * batch_size, :]
在这里插入图片描述在这里插入图片描述

分析:

从理论上而言,梯度下降的速度:随机梯度下降 > 小批量梯度下降> 批量梯度下降。此处样本数据量较小,所以观察的不是很明显。

现在大多会采用随机梯度下降方法,此方法的效果很好,训练速度非常快,迭代变化大,但是准确率低一点。

(4) 【选做】本例中输入向量、真值都是行向量 ,请将它们修改为 列向量,如X = np.array([[1,0.1]])改为X = np.array([[1],[0.1]]),请合理修改其它部分以使程序得到与行向量时相同的结果。(不允许直接使用 X 的转置进行全局替换)

解:

此处采用的问题三中的设定,且采用的是批量梯度下降法,修改的地方简述如下:

X和T的初始化数据,W和b进行转置,代码中形如dot(A,B)的部分改成dot(B,A),X.shape[0]通通改成X.shape[1]等。

法一:

X = np.array([[1, 0.1, 0.1, 1],
       [0.1, 1, 0.1, 1]])
T = np.array([[1, 0, 0, 1]])

W1 = W1.T
W2 = W2.T
W3 = W3.T
b1 = b1.T
b2 = b2.T
batch_size = 4

def update():
  global batch_X, batch_T, W1, W2, W3, lr, b1, b2, b3
  Z1 = np.dot(W1, batch_X) + b1
  A1 = sigmoid(Z1)
  Z2 = (np.dot(W2, A1) + b2)
  A2 = sigmoid(Z2)
  Z3 = (np.dot(W3, A2) + b3)
  A3 = sigmoid(Z3)
  delta_A3 = (batch_T - A3)
  delta_Z3 = delta_A3 * dsigmoid(A3)
  delta_W3 = delta_Z3.dot(A2.T) / batch_X.shape[1]
  delta_B3 = np.sum(delta_Z3, axis=1) / batch_X.shape[1]
  delta_A2 = W3.T.dot(delta_Z3)
  delta_Z2 = delta_A2 * dsigmoid(A2)  
  delta_W2 = delta_Z2.dot(A1.T) / batch_X.shape[1]
  delta_B2 = np.sum(delta_Z2, axis=1) / batch_X.shape[1]
  delta_A1 = W2.T.dot(delta_Z2)
  delta_Z1 = delta_A1 * dsigmoid(A1)
  delta_W1 = delta_Z1.dot(batch_X.T) / batch_X.shape[1]
  delta_B1 = np.sum(delta_Z1, axis=1) / batch_X.shape[1]
  # 更新权值
  W3 = W3 + lr * delta_W3
  W2 = W2 + lr * delta_W2
  W1 = W1 + lr * delta_W1
  # 改变偏置值
  b3 = (b3.T + lr * delta_B3).T
  b2 = (b2.T + lr * delta_B2).T
  b1 = (b1.T + lr * delta_B1).T

# 更新权值
batch_X = X[:, idx_batch * batch_size:(idx_batch + 1) * batch_size]
batch_T = T[:, idx_batch * batch_size:(idx_batch + 1) * batch_size]

# 隐藏层输出
A1 = sigmoid(np.dot(W1, X) + b1)
A2 = sigmoid(np.dot(W2, A1) + b2)
A3 = sigmoid(np.dot(W3, A2) + b3)

for i in map(predict, A3.T):
  print(i)

法二:

实现思路大题一致,在代码上进行了调整。代码如下:

# 列向量变化
X = X.T
T = T.T
b1 = b1.T
b2 = b2.T
b3 = b3.T
W1 = W1.T
W2 = W2.T
W3 = W3.T

max_batch = X.shape[1] // batch_size

# 针对列向量的更新
def update_column():
  global batch_X, batch_T, W1, W2, W3, lr, b1, b2, b3
  Z1 = np.dot(W1, batch_X) + b1  # 横着,0行是节点1 1行是节点2
  A1 = sigmoid(Z1)  # (2,1)
  Z2 = (np.dot(W2, A1) + b2)
  A2 = sigmoid(Z2)  # (2,1)
  Z3 = (np.dot(W3, A2) + b3)
  A3 = sigmoid(Z3)
  batch_T = batch_T.T
  delta_A3 = (batch_T - A3)  # 这里是损失函数1/2*(T-A3)^2的导数
  delta_Z3 = delta_A3 * dsigmoid(A3)  # 激活函数反向求导
  delta_W3 = np.dot(delta_Z3, A2.T) / batch_X.shape[1]  # (1,2)
  delta_B3 = np.sum(delta_Z3, axis=1) / batch_X.shape[1]  # (1,1)
  delta_A2 = np.dot(W3.T, delta_Z3)
  delta_Z2 = delta_A2 * dsigmoid(A2)
  delta_W2 = np.dot(delta_Z2, A1.T) / batch_X.shape[1]
  delta_B2 = np.sum(delta_Z2, axis=1) / batch_X.shape[1]
  delta_A1 = np.dot(W2.T, delta_Z2)
  delta_Z1 = delta_A1 * dsigmoid(A1)
  delta_W1 = np.dot(delta_Z1, batch_X.T) / batch_X.shape[1]
  delta_B1 = np.sum(delta_Z1, axis=1) / batch_X.shape[1]
  W3 = W3 + lr * delta_W3
  W2 = W2 + lr * delta_W2
  W1 = W1 + lr * delta_W1
  b3 = b3 + lr * delta_B3.reshape(1, 1)
  b2 = b2 + lr * delta_B2.reshape(2, 1)
  b1 = b1 + lr * delta_B1.reshape(2, 1)

实验结果如下:

在这里插入图片描述在这里插入图片描述

可看出与问题三中的实验结果一致,效果达到,此处如果采用其他两种梯度下降方法的话,无法看到与其之前一样的实验结果,因为他们具有随机性,数据每次跑的结果会不一致。

五、小结与心得体会

① 理解了多层神经网络的结构和原理,掌握反向传播算法对神经元的训练过程,对反向传播公式有了深入的了解;

② 以人事招聘为例,把整体的训练过程和每一步的代码理解的相对很透彻,代码通俗易懂,自己也能在此代码基础上进行修改;

③ 难点:此实验本人一直在纠结 batch_size 的大小问题,做到问题三的时候才理解了它的作用;问题4也有点难度,主要搞清楚维度的问题,深入理解原理。

实验三 基于神经网络的手写数字识别

一、实验题目

基于神经网络的手写数字识别

二、实验目的

数字分类的训练和使用,实现一个三层全连接神经网络模型。具体包括:

  • 实现三层神经网络模型来进行手写数字分类,建立一个简单而完整的神经网络工程。通过本实验理解神经网络中基本模块的作用和模块间的关系,为后续建立更复杂的神经网络实验奠定基础。
  • 利用Python实现神经网络基本单元的前向传播(正向传播)和反向传播,加深对神经网络中基本单元的理解,包括全连接层、激活函数、损失函数等基本单元。
  • 利用Python实现神经网络的构建和训练,实现神经网络所使用的梯度下降算法,加深对神经网络训练过程的理解。

三、实验内容

(1)神经网络

神经网络主要由以下几部分组成:

  • 输入层:负责接收外部环境的信息,并由输入单元组成,这些输入单元可以接收样本中的各种特征信息。

  • 隐藏层:介于输入层和输出层之间,这些层完全用于分析,其函数联系输入层变量和输出层变量,使其更配适数据。

  • 输出层:生成最终结果,每个输出单元会对应到某一种特定的分类,为网络送给外部系统的结果值。

在实验中:

本实验中的三层全连接神经网络由三个全连接层构成,在每两个全连接层之间插入 ReLU 激活函数以引入非线性变换,最后使用 Softmax 层计算交叉熵损失。

(2)相关知识【详情可以看指导书,此处只简单描述】

  • 全连接层

全连接层的权重 W 是二维矩阵,维度为 m×n,偏置 b 是 n 维列向量。前向传播时,全连接层的输出的计算公式为(注意偏置可以是向量,计算每一个输出使用不同的值;偏置也可以是一个标量,计算同一层的输出使用同一个值)

在这里插入图片描述

  • ReLU 激活函数

ReLU 激活函数是按元素运算操作,输出向量 y 的维度与输入向量 x 的维度相同。在前向传播中,如果输入 x 中的元素小于 0,输出为 0,否则输出等于输入。因此 ReLU 的计算公式为:

在这里插入图片描述

  • Softmax 损失层

Softmax损失层是一种在神经网络中用于多分类任务的损失函数层。Softmax函数将神经网络的输出转换为概率分布,使得每个类别的预测概率之和为1。

  • 两层神经网络示例

在这里插入图片描述

(3)总体设计

设计一个三层神经网络实现手写数字图像分类。该网络包含两个隐层和一个输出层,其中输入神经元个数由输入数据维度决定,输出层的神经元个数由数据集包含的类别决定,两个隐层的神经元个数可以作为超参数自行设置。

为了便于迭代开发,工程实现时采用模块化的方式来实现整个神经网络的处理,共划分为5大模块:

1)数据加载模块:从文件中读取数据,并进行预处理,其中预处理包括归一化、维度变换等处理。如果需要人为对数据进行随机数据扩增,则数据扩增处理也在数据加载模块中实现。

2)基本单元模块:实现神经网络中不同类型的网络层的定义、前向传播、反向传播等功能。

3)网络结构模块:利用基本单元模块建一个完整的神经网络。

4)网络训练模块:用训练集对神经网络进行训练。对建立的神经网络结构,实现神经网络的前向传播、神经网络的反向传播、对神经网络进行参数更新、保存神经网络参数等基本操作,以及训练函数主体。

5)网络推断模块:使用训练得到的网络模型,对测试样本进行预测(也称为测试或推断)。具体操作包括加载训练得到的模型参数、神经网络的前向传播等。

四、实验结果与分析

(1)请在代码中有TODO的地方填空,将程序补充完整,在报告中写出相应代码,并给出自己的理解。

解: 相关修改如下:

def load_data(self):
  # TODO: 调用函数 load_mnist 读取和预处理 MNIST 中训练数据和测试数据的图像和标记
  print('Loading MNIST data from files...')
  train_images = self.load_mnist(os.path.join(MNIST_DIR, TRAIN_DATA), True)
  # 改:
  train_labels = self.load_mnist(os.path.join(MNIST_DIR, TRAIN_LABEL), False)
  test_images = self.load_mnist(os.path.join(MNIST_DIR, TEST_DATA), True)
  test_labels = self.load_mnist(os.path.join(MNIST_DIR, TEST_LABEL), False)

def forward(self, input):  # 神经网络的前向传播
  # TODO:神经网络的前向传播
  h1 = self.fc1.forward(input)
  h1 = self.relu1.forward(h1)
  # add
  h2 = self.fc2.forward(h1)
  h2 = self.relu2.forward(h2)
  h3 = self.fc3.forward(h2)
  prob = self.softmax.forward(h3)
  return prob

def backward(self):  # 神经网络的反向传播
  # TODO:神经网络的反向传播
  dloss = self.softmax.backward()
  dh3 = self.fc3.backward(dloss)
  dh2 = self.relu2.backward(dh3)
  dh2 = self.fc2.backward(dh2)
  dh1 = self.relu1.backward(dh2)
  dh1 = self.fc1.backward(dh1)

def build_model(self):  # 建立网络结构
  # TODO:建立三层神经网络结构
  print('Building multi-layer perception model...')
  self.fc1 = FullyConnectedLayer(self.input_size, self.hidden1)
  self.relu1 = ReLULayer()
  self.fc2 = FullyConnectedLayer(self.hidden1, self.hidden2)
  self.relu2 = ReLULayer()
  self.fc3 = FullyConnectedLayer(self.hidden2, self.out_classes)
  self.softmax = SoftmaxLossLayer()
  self.update_layer_list = [self.fc1, self.fc2, self.fc3]

def forward(self, input):  # 前向传播计算
  start_time = time.time()
  self.input = input
  # TODO:全连接层的前向传播,计算输出结果 公式 3.3
  self.output = np.matmul(input, self.weight) + self.bias
  return self.output

def backward(self, top_diff):  # 反向传播的计算
  # TODO:全连接层的反向传播,计算参数梯度和本层损失
  self.d_weight = np.matmul(self.input.T, top_diff)
  self.d_bias = np.sum(top_diff, axis=0)
  bottom_diff = np.matmul(top_diff, self.weight.T)
  return bottom_diff

def update_param(self, lr):  # 参数更新
  # TODO:对全连接层参数利用参数进行更新 3.14
  self.weight = self.weight - lr * self.d_weight
  self.bias = self.bias - lr * self.d_bias

def forward(self, input):  # 前向传播的计算
  start_time = time.time()
  self.input = input
  # TODO:ReLU层的前向传播,计算输出结果 3.5
  output = input.copy()  # output 只是一个副本,input 不会随着 output 的改变而改变
  output[input < 0] = 0
  return output

def backward(self, top_diff):  # 反向传播的计算
  # TODO:ReLU层的反向传播,计算本层损失 3.6
  bottom_diff = top_diff.copy()
  bottom_diff[self.input < 0] = 0
  return bottom_diff

def forward(self, input):  # 前向传播的计算
  # TODO:softmax 损失层的前向传播,计算输出结果 3.11
  input_max = np.max(input, axis=1, keepdims=True)
  input_exp = np.exp(input - input_max)
  self.prob = input_exp / np.sum(input_exp, axis=1, keepdims=True)
  return self.prob

def backward(self):  # 反向传播的计算
  # TODO:softmax 损失层的反向传播,计算本层损失 3.13
  bottom_diff = (self.prob - self.label_onehot) / self.batch_size
  return bottom_diff

以上代码补全难度不大,但是需要很细心,要认真的读指导书,按照指导书一步步地写,我在最开始写的时候忽略了一个细节,导致程序一直在报错,折腾了很久才发现自己的想法在代码实现时候出现了偏差,改了之后,成功运行。

(2)mlp.load_data()执行到最后时,train_images、train_labels、test_images、test_labels 的维度是多少?即多少行多少列,用(x,y)来表示。self.train_data 和 self.test_data 的维度是多少?

解:由程序结果可知:

train_images.shape = (60000, 784)

train_labels.shape = (60000, 1)

test_images.shape = (10000, 784)

test_labels.shape = (10000, 1)

self.train_data.shape = (60000, 785)

self.test_data.shape = (10000, 785)

在这里插入图片描述

(3) 本案例中的神经网络一共有几层?每层有多少个神经元?如果要增加或减少层数,应该怎么做(简单描述即可不用编程)?如果要增加或减少某一层的节点,应该怎么做(简单描述)?如果要把 softmax 换成 sigmoid,应该怎么做(简单描述)?这种替换合理么?

解:

  • 本案例中的神经网络一共有三层。input_size=784, hidden1=32, hidden2=16, out_classes=10。

  • 如果要增加或减少层数,首先要更改MNIST_MLP函数的相关参数,比如增加一层的话,加个hidden3等,里面的函数,比如前向传播反向传播等都需要对应增加或者减少层次的影响,load_data()函数不需要更改,build_model()/ init_model()/ save_model()/ load_model()都需要对应地进行更改。【第五问做了增加一层的实验】

  • 如果要增加或减少某一层的节点,在初始化模型时候,修改参数设置。

  • 新建一个sigmoid函数,输出层神经元个数改为1,传播过程公式也要相对应地进行调整。这种替换在某些情况下可能是合理的,但也存在一些限制。使用Sigmoid函数可以使得每个输出神经元独立地预测属于特定数字类别的概率。这在某些情况下可能更灵活,因为不再强制要求输出总和为1。然而,需要注意的是,使用Sigmoid函数可能会导致类别之间的竞争性下降,因为每个类别都是独立预测的。

(4) 在 train()函数中,max_batch = self.train_data.shape[0] // self.batch_size 这一句的意义是什么?self.shuffle_data()的意义是什么?

解:

self.train_data.shape[0] 代表的是训练数据的行数,self.batch_ size 代表的是每次参与迭代的数据组数,max_batch代表的是最大的分组组数,也是避免数据越界。

self.shuffle_data() 用于随机打乱数据集,保证随机性。

(5) 最终evaluate()函数输出的Accuracy in test set是多少?请想办法提高该数值。

解:

结果如下:0.940400【具有随机性】

在这里插入图片描述

提高:

  • 改为 e = 100,结果如下:0.965200【具有随机性】

在这里插入图片描述

  • 改为 h1, h2, e = 64, 32, 100,结果如下:0.9785【具有随机性】

在这里插入图片描述

  • 增加一层,修改相关代码如下:
h1, h2, h3, e = 128, 64, 64, 1
mlp = MNIST_MLP(hidden1=h1, hidden2=h2, hidden3=h3, max_epoch=e)

def __init__(self, batch_size=100, input_size=784, hidden1=64, hidden2=32, hidden3=16, out_classes=10, lr=0.01, max_epoch=2, print_iter=100):
  # add
  self.hidden3 = hidden3

self.fc3 = FullyConnectedLayer(self.hidden3, self.hidden3)
self.relu3 = ReLULayer()
self.fc4 = FullyConnectedLayer(self.hidden3, self.out_classes)
self.softmax = SoftmaxLossLayer()
self.update_layer_list = [self.fc1, self.fc2, self.fc3, self.fc4]

# add
self.fc4.load_param(params['w4'], params['b4'])

# add
params['w4'], params['b4'] = self.fc4.save_param()

# add
h3 = self.relu3.forward(h3)
h4 = self.fc4.forward(h3)
prob = self.softmax.forward(h4)
dh4 = self.fc4.backward(dloss)
dh3 = self.relu3.backward(dh4)
dh3 = self.fc3.backward(dh3)

# add
cot = 1
while mlp.evaluate() <= 0.98:
  mlp.train()
  cot += 1
print("执行" , cot, "次,Accuracy >= 0.98")
print("All train time: %f" % (time.time() - start_time))
mlp.save_model('mlp-%d-%d-%d-%depoch.npy' % (h1, h2, h3, e))
mlp.load_model('mlp-%d-%d-%d-%depoch.npy' % (h1, h2, h3, e))


结果如下:0.981100【具有随机性】

在这里插入图片描述

五、小结与心得体会

① 易错点

  • output = input

这行代码只是创建了一个新的引用,使得output和input指向同一个对象。这意味着,如果你修改output,input也会被修改,因为它们实际上是同一个对象。

  • output = input.copy()

这行代码创建了input的一个副本,并将这个副本赋值给output。这样,output和input指向不同的对象,所以修改output不会影响到 input。

② 通过本实验理解了神经网络中基本模块的作用和模块间的关系,对神经网络的前向传播和反向传播过程有了更深入的了解;

③ 也通过这次实验,认识到了神经网络中超参数的重要性,不同的超参数设置可能会对实验结果产生非常大的影响。

实验四 基于传递闭包的模糊聚类

一、实验题目

基于传递闭包的模糊聚类

二、实验目的

掌握建立模糊等价矩阵的方法,会求传递闭包矩阵;掌握利用传递闭包进行模糊聚类的一般方法;会使用Python进行模糊矩阵的有关运算。

三、实验内容

(1) 模糊聚类

模糊聚类是一种采用模糊数学语言对事物按一定的要求进行描述和分类的数学方法。它根据研究对象本身的属性来构造模糊矩阵,并在此基础上根据一定的隶属度来确定聚类关系。聚类就是将数据集分成多个类或簇,使得各个类之间的数据差别应尽可能大,类内之间的数据差别应尽可能小。在模糊聚类中,使用隶属度表示一个样本属于某一类的程度。

(2)实验步骤

① 得到特征指标矩阵

② 采用最大值规格化法将数据规格化

③ 采用最大最小法构造得到模糊相似矩阵

④ 采用平方法合成传递闭包

⑤ 计算截集,得到模糊聚类结果

(3)实验案例

数据源:本案例所用数据来自教材《计算智能导论》,原文如下:

在对教师的课堂教学质量进行评价时,考虑以下五项指标:师德师表、教学过程、教学方法、教学内容以及基本功。每项满分 20,总分 100。现在有{1, 2, 3, 4 }四位老师进行参评,参评成绩如下矩阵,试对老师的成绩进行分类。

在这里插入图片描述

四、实验结果与分析

(1)为什么按最大最小法得到的一定是一个方阵?且一定是自反方阵?且一定是对称方阵?

解:

  • 相似系数rij表示两个样本xi与xj之间的相似程度,样本数目为n,所以组成方阵R=(rij)n*n

  • 自反方阵要满足,由从最大最小法的公式 在这里插入图片描述
    中我们可以看出,当i=j时,此时rij=1, 等价于rii=1,可知该矩阵对角线上的数全为1,所以该矩阵一定是自反方阵。

  • 由最大最小法的公式可以得知,rij=rji,所以该矩阵一定是对称方阵。

(2) 为什么可以根据水平截集对数据进行分类?(提示:一个等价关系唯一确定一个划分)

解:

由于模糊相似矩阵R本身已经是自反的、对称的,其传递闭包t®又是传递的,则t®一定是模糊等价矩阵,可以决定一个划分。

一个等价关系唯一确定一个划分,当我们使用某个等价关系对数据集进行划分时,每个数据点都会被分配到一个或多个聚类中,并且每个聚类的隶属度是不同的。通过观察水平截集,我们可以了解每个数据点属于哪个聚类,从而实现对数据的分类,且a水平截集是指隶属度大于等于a的元素组成的集合,可以对数据进行分类。

(3) 请解释代码 72 行中两个-1 的含义。

解:

代码: return np.sort(np.unique(a).reshape(-1))[::-1]

语义:该语句返回一个无重复元素且逆序的结果

如: [1,1,2,6,3,6,5] -->[6,5,3,2,1]

第一个 -1 :表示自动计算维度的大小,这里是要转化成一维数组。

第二个 -1 :表示将元素进行逆序排序。

(4) 在平方法的代码实现中,如何判断平方后的矩阵是否满足传递性?为什么可以这么判断?

解:判断代码如下:

a = FuzzySimilarMatrix(a)  # 用模糊相似矩阵
c = a
while True:
  m = c
  c = MatrixComposition(MatrixComposition(a, c), MatrixComposition(a, c))
  if (c == m).all():  # 闭包条件
    return np.around(c, decimals=2)  # 返回传递闭包,四舍五入,保留两位小数
    break
  else:
    continue

(c == m).all() 通过这个条件,等价于R2=R,从而判断平方后的矩阵满足传递性。因为这个条件就是传递性需要满足的条件。

(5) 请修改代码,将最大最小法替换为算术平均最小法。这会改变最终的聚类结果么?

解: 修改代码如下:

mmin.extend([(a[i, :] + a[j, :]) / 2]) # 取i行和j行的和

在这里插入图片描述
在这里插入图片描述

由上面结果可以看出,聚类结果被改变了。

五、小结与心得体会

① 纠结点:

在这里插入图片描述

被这个bug困住了半天,我先是全部改成了一维,确实解决了这个bug,但是发现最后的结果自己看不懂了,就去找了指导书编写者石林老师讨论了一下,最后发现就是不能变化原数据的维度,最后自己改成了手动去重,代码如下:

# 跑的时候存在bug, 所以人为做了去重操作
unique_list = []
[unique_list.append(x) for x in lists if x not in unique_list]
# set 去重法
result = [list(item) for item in set(tuple(sublist) for sublist in lists)]
return list(unique_list)
# return list(result)

最后得以解决此bug,顺利完成了实验。

② 掌握了如何去建立模糊等价矩阵的方法,且会运用Python进行模糊矩阵的有关计算,总的来说,实验四难度不大,只要好好读懂原理理解代码即可。

实验五 遗传算法求解无约束单目标优化问题

一、实验题目

遗传算法求解无约束单目标优化问题

二、实验目的

理解遗传算法原理,掌握遗传算法的基本求解步骤,包括选择、交叉、变异等,学会运用遗传算法求解无约束单目标优化问题。

三、实验内容

(1)遗传算法

遗传算法是一种启发式优化算法,基本思想是源于达尔文的进化论,通过模拟生物遗传进化过程,对问题进行求解。他通过对候选解进行遗传操作(如交叉、变异、选择等)来生成新的解,并根据适应度函数对这些解进行评估和筛选,最终得到最优解或较优解。

基本流程:

在这里插入图片描述

主要步骤:

① 初始化种群:在优化问题中,我们需要将问题转化为遗传算法可以处理的形式,即将问题表示成染色体和基因的形式。然后,我们需要初始化一个初始种群,即随机生成一些符合问题要求的染色体。

② 计算适应度:在遗传算法中,适应度函数用于评估染色体的优劣程度。我们需要计算每个染色体的适应度,以便后续进行选择操作。

③ 选择操作:选择操作用于选择适应度高的染色体作为下一代的父代,这样可以保留好的基因,并逐步优化种群。

④ 遗传操作:在遗传操作中,我们使用交叉和变异来产生新的染色体。

⑤ 重复选择和遗传操作,直到达到停止条件【达到一定的迭代次数,达到一定的适应度阈,种群中的染色体相对稳定等】

(2)无约束单目标优化问题

本案例意在说明如何使用遗传算法求解无约束单目标优化问题,即求一元函数在区间[-1, 2]上的最大值。该函数图像如下:

在这里插入图片描述

由图像可知该函数在在区间[-1, 2]上有很多极大值和极小值,对于求其最大值或最小值的问题,很多单点优化的方法(梯度下降等)就不适合,这种情况下可以考虑使用遗传算法。

(3)关键代码

def encode(population, _min=-1, _max=2, scale=2**18, binary_len=18):  
  normalized_data = (population-_min) / (_max-_min) * scale
  binary_data = np.array([np.binary_repr(x, width=binary_len)
              for x in normalized_data.astype(int)])
  return binary_data

def decode(popular_gene, _min=-1, _max=2, scale=2**18):
  return np.array([(int(x, base=2)/scale*3)+_min for x in popular_gene])

四、实验结果与分析

(1) 代码第64行的语义是什么?两个[0]各自代表什么?最后 newX 有几个元素?

解:

代码:newX = np.array([chroms[np.where(probs_cum > rand)[0][0]] for rand in each_rand])

语义:

轮盘赌,根据随机概率选择出新的基因编码,对于each_rand中的每个随机数,找到被轮盘赌中的那个染色体。

两个[0]各自代表:

第一个[0]就是取行索引,第二个[0]是取所有行索引列表的第一个元素,而chroms[np.where(probs_cum > rand)[0][0]即取第一个大于rand值的值,遍历each_rand,最终得到一个数组。

newX:

初始化种群有多少元素,此处len(newX)就等于多少,这里 len(newX) = 100。

(2) 代码第70行的语义是什么?为什么要除以 2 再乘以 2?reshape 中的-1 表示什么?

解:

代码:pairs = np.random.permutation(int(len(newX)*prob//2*2)).reshape(-1, 2)

语义:

在种群数目等于newX的情况下,根据交叉概率prob,选择int(len(newX)*prob//2*2))个染色体,然后生成一个二维数组,列为2。

reshape(-1, 2):这部分代码将上述随机排列的整数数组重新塑造为一个二维数组,其中每一行包含两个元素。

原因:

第一个 “//2” 是因为len(newX)不一定是偶数,如果奇数的情况,我们就会省去。那么两两交叉变异,所以再“*2”得到总数。

-1:

-1 是一个特殊的值,表示根据其他维度自动计算该维度(行)的大小。

(3) 请结合Mutate函数的内容,详述变异是如何实现的。

解:

大体流程描述如下:

① 这是先是设置变异概率为 prob = 0.1,然后得到单个染色体的长度(clen,这里也代表二进制数位数),再利用map存储key-value情况。

② 随机生成每个染色体的随机变异概率。

③ 遍历所有染色体,如果当前染色体的随机变异概率 < prob,那么发生变异。变异过程为:从clen中随机找一个位置进行翻转,其他不变。

④ 无论是否发生变异,都要在新种群中添加该染色体。

⑤ 最后返回变异后的种群。

(4) 将代码第145行修改为newchroms = Select_Crossover(chroms, fitness),即不再执行变异,执行结果有什么不同,为什么会出现这种变化?

解:

未修改前:

在这里插入图片描述在这里插入图片描述

修改后:

在这里插入图片描述
在这里插入图片描述

由图可以发现:

  • 如果不进行变异,交叉前后的值没有什么改变,因为染色体的突变概率降低,变化小,下一代再次根据相同规则选的时候,与前一代种群差别不大,所以,点都聚集在一个地方。

  • 也可以看到,最后的X1和 Max 值并不相等,执行变异的情况会更加接近真实值。

(5) 轮盘让个体按概率被选择,对于适应度最高的个体而言,虽然被选择的概率高,但仍有可能被淘汰,从而在进化过程中失去当前最优秀的个体。一种改进方案是,让适应度最高的那个个体不参与选择,而是直接进入下一轮(直接晋级),这种方案被称为精英选择(elitist selection)。请修改Select部分的代码,实现这一思路。

解:

修改如下:

def Select_Crossover(chroms, fitness, prob=0.6):  # 选择和交叉
  probs = fitness/np.sum(fitness) 
  # 改: 找最大的
  max_prob_index = np.argmax(probs)
  max_chrom = chroms[max_prob_index]
  # 删去
  np.delete(probs, max_prob_index)
  np.delete(chroms, max_prob_index)
  probs_cum = np.cumsum(probs)  
  # 改 len - 1
  each_rand = np.random.uniform(size=len(fitness)-1)  

  newX = np.array([chroms[np.where(probs_cum > rand)[0][0]]
          for rand in each_rand])
  pairs = np.random.permutation(
  int(len(newX)*prob//2*2)).reshape(-1, 2)  # 产生6个随机数,乱排一下,分成二列

center = len(newX[0])//2  # 交叉方法采用最简单的,中心交叉法
for i, j in pairs:
# 在中间位置交叉

  x, y = newX[i], newX[j]
  newX[i] = x[:center] + y[center:]  # newX的元素都是字符串,可以直接用+号拼接
  newX[j] = y[:center] + x[center:]

return newX

由下图可看出:

实验结果更具说服力,更加接近真实值。

在这里插入图片描述
在这里插入图片描述

(6) 【选做】请借鉴示例代码,实现教材P57的例 2.6.1,即用遗传算法求解下列二元函数的最大值。

在这里插入图片描述

解:

相关代码如截屏:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

对比书上给出的结果,可验证以上代码跑出来的结果是正确的,不过图形变成了三维,此处没有去实现可视化,只正确地得出了最后的结果。

五、小结与心得体会

① 掌握了遗传算法的基本求解步骤,包括选择、交叉、变异等,对其理论知识有了深入的了解;

② 学会了运用遗传算法求解无约束单目标优化问题,成功跑通案例代码,并能够基于案例代码成功解出问题六;

③ 难点:对问题六研究了很久,其根本还是要理解代码的本质,对种群初始化、编码、解码、交叉、变异等过程进行修改,这里有两个变量,要注意逻辑问题,报告中所贴的代码还是不具备统一性,只能解决单一性的问题。

全篇总结

① 认真完成了全部的实验,理解了相关算法和模型的原理,并将所学理论运用到了实践中,测试了相关案例,完成了实验内容;

② 总结了课设内容,代码和相关文件已上传至Gitee;

③ 提高了自己的代码能力和文档编写能力,也让自己对计算智能这门学习有了更深入的了解,对人工智能领域的相关研究有了基本的认识。

致谢

  • 感谢文一凭老师的指导,感谢石林老师的教学与指导,感谢学姐学长的帮助,感谢朋友们和同学们的支持与鼓励,感谢相关博客的作者。

课程设计报告日志

时间(第15周)设计内容简要记录
星期一(12.11)通读实验指导书,配置好本机环境,做实验一的实验内容,并完成了实验一的课设报告。
星期二(12.12)通读实验二的实验内容,第4问未能解决,其他内容完成,且完成了实验二的课设报告。
星期三(12.13)解决了实验二的实验内容的第4问,其他内容完成,完善实验二的实验报告。
星期四(12.14)重新理了一遍实验二第4问的思路,并编写了plus版本的代码,完善了实验报告,且开始做实验五。
星期五(12.15)解决了实验五的第1-5问,深入理解了遗传算法的本质,并完成了相关实验报告内容,第6问有点难,未能解决。
时间(第16周)设计内容简要记录
周末(12.16)(12.17)解决了实验五的第6问,编写了正确的代码,其结果与书本提供的结果一致,完善了实验报告。
星期一(12.18)对实验一、二和五进行复习,重新理解原理,跑代码,调参数,验证其结果的准确性,完善报告。
星期二(12.19)研究实验四,成功解决实验内容,完成了实验报告的编写,并复习了实验五,整理了相关代码。
星期三(12.20)研究实验三,成功写出了实验代码,完整地测试了实验给的案例数据。
星期四(12.21)完成了实验三的相关内容,理解了神经网络的本质,完成了相关的实验报告内容。
星期五(12.22)完善并打印实验报告,总结代码和相关资料上传至Gitee,并找老师进行测试。

初步完成于20231220

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值