0%

神经网络实验

实验目的

  1. 熟悉和掌握感知机神经网络
  2. 熟悉和掌握随机梯度下降算法
  3. 了解和掌握第三方机器学习库Scikit-learn中的模型调用

实验要求

  1. 采用Python、Matlab等高级语言进行编程,推荐优先选用Python语言
  2. 核心模型和算法需自主编程实现,不得直接调用Scikit-learn、PyTorch等成熟框架的第三方实现(除非实验内容明确指定调用)
  3. 代码可读性强:变量、函数、类等命名可读性强,包含必要的注释

实验原理

感知机(perceptron)是二分类的线性分类模型,属于监督学习算法。输入为实例的特征向量,输出为实例的类别(取+1和-1)。感知机旨在求出将输入空间中的实例划分为两类的分离超平面。为求得超平面,感知机导入了基于误分类的损失函数,利用梯度下降法对损失函数进行最优化求解。

如果训练数据集是线性可分的,则感知机一定能求得分离超平面。如果是非线性可分的数据,则无法获得超平面。

感知机具有简单而易于实现的优点,分为原始形式和对偶形式。感知机预测是用学习得到的感知机模型对新的实例进行预测的,因此属于判别模型。感知机是神经网络和支持向量机的基础。

二分类模型: $f(x)=\operatorname{sign}(w * x+b)$

损失函数: $L(w, b)=-\Sigma y_i(w *+b)$
算法:

随即梯度下降法 Stochastic Gradient Descent

随机抽取一个误分类点使其梯度下降。

$w=w+\eta y i x i$

$b=b+\eta y i$

当实例点被误分类, 即位于分离超平面的错误侧, 则调整 $\mathrm{w}, \mathrm{b}$ 的值, 使分离超平面向该无分类点的一一侧移动,直 至误分类点被正确分类拿出 iris 数据集中两个分类的数据和[sepal length, sepal width]作为特征

实验内容

感知机神经网络分类

  1. 从iris数据集中取出[sepal length,sepal width]两个属性作为样本特征,保持类别标记不变,训练单隐层感知机网络进行二分类实验。注意取前100个样本作为训练集,剩余的50个样本作为测试集。
  2. 借助matplotlib 画出原始训练数据分布的散点图(x=“sepal length”,y=“sepal width”,点的颜色代表不同类别)
  3. 按照下述模型和优化目标, 构造感知机模型和损失函数:
    • 模型: $f(x)=\operatorname{sign}(w \cdot x+b)$
    • 优化目标: $\min {w, b} L(w, b)=-\sum{x_i \in M} y_i\left(w \cdot x_i+b\right)$
  4. 编写适用于感知机的随机梯度下降算法(Stochastic Gradient Descent,SGD),对单隐层的感知机进行梯度下降
    • 此数据集线性可分,可以设置迭代的停止条件为“直到训练集内没有误分类样本为止”
    • 学习率设置为0.1, 偏置初始化0,权重均初始化为1
    • SGD 更新:
      $$
      \begin{aligned}
      & w=w+\eta y_i x_i \\
      & b=b+\eta y_i
      \end{aligned}
      $$
      结果展示
  • 将模型拟合的分类边界与上述原始数据点画到同一图中,观察训练效果
  • 用训练的模型对测试数据进行分类,得到测试错误率
  • 将模型拟合的分类边界与测试数据点画到同一图中,观察效果

神经网络调参

调整学习率参数取值分别为[0.01, 0.05, 0.1, 0.5]运行模型,比较模型最后的测试正确率以及模型收敛所需要的训练轮数(epoch)数。

Sciki-learn机器学习库的调用

  1. 直接调用机器学习库Scikit-learn中感知机模型(https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Perceptron.html ),用上述训练和测试集训练进行训练, 采用上述类似的方法可视化训练集和验证集上模型的分类结果。(调用时保持模型超参数tol的取值为默认值0.001)
  2. 比较自己实现的模型和调用的Scikit-learn中模型在训练集上的可视化结果图,观察有何不同,分析产生不同的原因。

实验代码和结果

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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
class Model:
def __init__(self):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32)
self.b = 0 # 初始w/b的值
self.epoch=0;

def sign(self, x, w, b):
y = np.dot(x, w) + b # 求w,b的值
return y

# 随机梯度下降法
# 随机梯度下降法(SGD),随机抽取一个误分类点使其梯度下降。根据损失函数的梯度,对w,b进行更新
def fit(self, X_train, y_train,lr): # 将参数拟合 X_train数据集矩阵 y_train特征向量
is_wrong = False
# 误分类点的意思就是开始的时候,超平面并没有正确划分,做了错误分类的数据。
while not is_wrong:
self.epoch +=1;
wrong_count = 0 # 误分为0,就不用循环,得到w,b
for d in range(len(X_train)):
X = X_train[d]
y = y_train[d]
if y * self.sign(X, self.w, self.b) <= 0:
# 如果某个样本出现分类错误,即位于分离超平面的错误侧,则调整参数,使分离超平面开始移动,直至误分类点被正确分类。
self.w = self.w + lr * np.dot(y, X) # 调整w和b
self.b = self.b + lr * y
wrong_count += 1
if wrong_count == 0:
is_wrong = True
return 'Perceptron Model!'
def test(self,X_test,y_test):
all=len(X_test)
error=0
for d in range(all):
X=X_test[d]
y=y_test[d]
if y * self.sign(X, self.w, self.b) <= 0:
error+=1;
return(error/all)
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
first_40_rows = df.head(40)
rows_50_to_90 = df[50:90]
dt = pd.concat([first_40_rows, rows_50_to_90])
data = np.array(dt.iloc[:90, [0, 1, -1]])
test1=df[40:50]
test2=df[90:100]
test=pd.concat([test1,test1])
test=np.array(test.iloc[:100, [0, 1, -1]])
TX, Ty = test[:,:-1], test[:,-1]
Ty = np.array([1 if i == 1 else -1 for i in Ty])
#print(len(Ty))
#print(data)
plt.scatter(df[:40]['sepal length'], df[:40]['sepal width'], label='0')
plt.scatter(df[50:90]['sepal length'], df[50:90]['sepal width'], label='1')
plt.xlabel('sepal length')#给x坐标命名
plt.ylabel('sepal width')#给y坐标命名
plt.legend()
plt.title("train")

X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])

perceptron = Model()
perceptron.fit(X, y,0.1)
x_points = np.linspace(4, 7,10)
y_ = -(perceptron.w[0]*x_points + perceptron.b)/perceptron.w[1]
plt.plot(x_points, y_)
plt.show()
#print(perceptron.epoch)
er=perceptron.test(TX,Ty)
plt.scatter(df[40:50]['sepal length'], df[40:50]['sepal width'], label='0')
plt.scatter(df[90:100]['sepal length'], df[90:100]['sepal width'], label='1')
plt.legend()
plt.title("test")
plt.plot(x_points, y_)
#plt.show()
print(er)

alt text
alt text

学习率为0.1时,训练了13次,错误率为0.1

alt text

学习率为0.01时,训练了62次,错误率为0.1

alt text

学习率为0.05时,训练了16次,错误率为0.1

alt text

学习率为0. 5时,训练了13次,错误率为0.1

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
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8, random_state=42)

# 使用Scikit-learn中的Perceptron模型进行训练
perceptron = Perceptron(tol=0.001)
perceptron.fit(X_train, y_train)

# 预测测试集
y_pred = perceptron.predict(X_test)

# 计算测试集准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"测试集的准确率: {accuracy}")


plt.scatter(X_train[y_train == 1][:, 0], X_train[y_train == 1][:, 1], label='0')
plt.scatter(X_train[y_train == -1][:, 0], X_train[y_train == -1][:, 1], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.title('Training Set')
x_min, x_max = X_train[:, 0].min() - 1, X_train[:, 0].max() + 1
x_points = np.linspace(x_min, x_max, 10)
y_ = -(perceptron.coef_[0][0] * x_points + perceptron.intercept_) / perceptron.coef_[0][1]
plt.plot(x_points, y_, color='blue')
plt.legend()
plt.show()

alt text
准确率为0.9875

结果不同可能是由于学习率设置不同,以及随机种子的差异

小结或讨论

感知器是人工神经网络中的一种典型结构, 它的主要的特点是结构简单。它是一种分类学习器,是很多复杂算法的基础。其“赏罚概念”在机器学习算法在中广为应用。在分类正确时,对正确的权重向量w奖赏,即w不变;当分类错误时,对权重向量惩罚,即将权重向量w向着争取的方向转变

Perceptron 由 Frank Rosenblatt 升发, 并于 1962 年出版的“神经动力学原理:感知器和脑机制理论”一文中所提出。当时, Rosenblatt 的文章被 Marvin Minksy 和 Seymour Papert 反对,认为神经网络有缺陷,只能解决线性分类问题。然而, 这种限制仅发生在单层神经网络中。Perceptron 可用于解决二分类问题。在传统线性可分的二分类情况下, 可以使 $w^T x>=0$ 时分类为正样本, $w^T x<0$ 分类为负样本。

算法步骤为对所有负样本乘以 -1 以方便算法流程, 即使 $w^T x>=0$ 时判断为分类正确。

随机生成初始权重向量 $w 0$, 在每轮迭代中, 若样本 $i$ 分类正确即 $w^T x_i>=0$ 时,不对 $w$ 进行修改; 当本轮迭代中, 针对样本 $i$ 出现分类错误, 即 $w^T xi<0$ 时, 对权重向量 $w$ 惩罚, 使之朝着正确的趋势改进。 $\eta$ 为学习率。如果无错误分类,则迭代结束