Pytorch实现非线性分类

在平面上,包括了3组不同类别的训练数据,分别使用红色、蓝色和绿色表示。
它们呈非线性的分布方式。

训练数据

基于Pytorch深度学习框架,训练一个神经网络模型,将这三组数据分开。
并且,我们要将模型产生的分类决策边界,使用橙色进行标记。

神经网络模型

安装并导入Pytorch相关的库

需要安装 scikit-learn、matplotlib、numpy、torch 库。

1
2
3
4
pip install scikit-learn
pip install matplotlib
pip install numpy
pip install torch torchvision torchaudio

在代码中导入上述安装好的库

1
2
3
4
5
from sklearn.datasets import make_blobs,make_circles
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch

分类数据的生成

  • 定义函数make_data
    函数传入num,代表每种类别的数据个数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def make_data(num):
    # 设定随机数生成器的种子
    # 使随机数序列,每次运行时都是确定的
    np.random.seed(0)
    # 红色数据使用make_blobs生成
    # 以(0, 0)为中心的正太分布数据
    red, _ = make_blobs(
    n_samples=num,centers=[[0, 0]],cluster_std=0.15
    )
    # 绿色数据用make_circles生成,分布在红色数据的周围
    green, _ = make_circles(
    n_samples=num,noise=0.02,factor=0.7
    )
    # 蓝色数据分布在四个角落
    blue, _ = make_blobs(
    n_samples=num,
    centers=[[-1.2, -1.2],[-1.2, 1.2],[1.2, -1.2],[1.2, 1.2]],
    cluster_std=0.2
    )
    # 函数返回三种数据
    return red, green, blue
  • 定义函数draw_data
    函数传入make_data生成的绿色、蓝色和红色三种数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def draw_data(red, green, blue):
    # 创建-4到4的平面画板
    board = plt.figure()
    axis = board.add_subplot(1,1,1)
    axis.set(
    xlim=[-1.5, 1.5],
    ylim=[-1.5, 1.5],
    title='Neural Network',
    xlabel='x1',
    ylabel='x2'
    )
    # 使用plt.scatter绘制出绿色、蓝色和红色三种数据
    plt.scatter(green[:, 0], green[:, 1], color='green')
    plt.scatter(blue[:, 0], blue[:, 1], color='blue')
    plt.scatter(red[:, 0], red[:, 1], color='red')

神经网络模型的定义

为了解决该分类问题,我们要定义一个三层神经网络。

输入层包括x1和x2两个特征,它们表示平面上,数据点的横坐标和纵坐标。

隐藏层有5个神经元,它们是解决该分类问题的高级特征。
输出层有3个神经元,对应3种不同的类别。

输出层输出的y1、y2、y3,会输入至softmax函数,转换为三种类别的概率,p1、p2和p3。

三层神经网络

  • 定义神经网络类Network
    函数传入参数n_in, n_hidden, n_out,代表输入层、隐藏层和输出层中的神经元数量。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 定义神经网络类Network,它继承nn.Module类
    class Network(nn.Module):
    def __init__(self, n_in, n_hidden, n_out):
    super(Network, self).__init__()
    # layer1是输入层与隐藏层之间的线性层
    self.layer1 = nn.Linear(n_in, n_hidden)
    # layer2是隐藏层与输出层之间的线性层。
    self.layer2 = nn.Linear(n_hidden, n_out)
    # 实现神经网络的前向传播
    def forward(self, x):
    x = self.layer1(x) # 计算layer1的结果
    x = torch.relu(x) # 进行relu激活
    # 计算layer2的结果,并返回
    return self.layer2(x)

n_in, n_hidden, n_out:输入层、隐藏层和输出层的关系
输入层、隐藏层和输出层

神经网络模型的训练

完成模型的定义后,训练神经网络模型。

  • 定义函数train_network
    函数传入make_data生成的绿色、蓝色和红色三种数据。
    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
    def train_network(red, green, blue):
    n_features = 2 # 特征数
    n_hidden = 5 # 隐藏层神经元个数
    n_classes = 3 # 类别数
    n_epochs = 10000 # 迭代次数
    learning_rate = 0.001 # 学习速率

    # 将绿色、蓝色、红色三种样本,从numpy数组转换为张量形式
    green = torch.FloatTensor(green)
    blue = torch.FloatTensor(blue)
    red = torch.FloatTensor(red)
    # 一起组成训练数据data
    data = torch.cat((red, green, blue), dim=0)
    # 设置label保存三种样本的标签
    label = torch.LongTensor(
    [0] * len(green) + [1] * len(blue) + [2] * len(red)
    )

    # 创建神经网络模型实例model
    model = Network(n_features, n_hidden, n_classes)
    # 创建交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 创建Adam优化器optimizer
    optimizer = torch.optim.Adam(
    model.parameters(), lr=learning_rate
    )

    # 进入softmax回归模型的循环迭代
    for epoch in range(n_epochs):
    # 使用当前的模型,预测训练数据data,结果保存在output中
    # 这里即为前向传播
    output = model(data)
    # 计算预测值output与真实值label之间的损失loss
    loss = criterion(output, label)
    # 通过自动微分计算损失函数关于模型参数的梯度
    loss.backward()
    # 更新模型参数,使得损失函数减小
    optimizer.step()
    # 将梯度清零,以便于下一次迭代
    # 这实际上就是反向传播
    optimizer.zero_grad()

    # 模型的每一轮迭代,有前向传播和反向传播共同组成
    if epoch % 1000 == 0:
    # 每1000次迭代,打印一次当前的损失
    # loss.item对应损失的标量值
    print(f'{epoch} iterations: loss = {loss.item()}')
    # 返回训练后的模型
    return model

绘制决策边界

生成用于绘制决策边界的等高线数据。
根据已经训练的model,计算对应类别结果。
不同类别的结果会对应不同的高度。
基于数据点的坐标与高度数据,绘制等高线。

  • 定义函数draw_decision_boundary
    函数传入min-x1到max-x1是画板的横轴范围,min-x2到max-x2是画板的纵轴范围,model是训练好的模型。
    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
    def draw_decision_boundary(minx1, maxx1, minx2, maxx2, model):
    # 调用mesh-grid生成网格数据点
    # 每个点的距离是0.02,这样生成的点,可以覆盖平面的全部范围。
    xx1, xx2 = np.meshgrid(
    np.arange(minx1, maxx1, 0.02),
    np.arange(minx2, maxx2, 0.02),
    )
    # 设置x1s、x2s和z分别表示数据点的横坐标、纵坐标和类别的预测结果
    x1s = xx1.ravel()
    x2s = xx2.ravel()
    z = []
    # 遍历全部样本
    for x1, x2 in zip(x1s, x2s):
    # 将样本转为张量
    test_point = torch.FloatTensor([[x1, x2]])
    # 使用model预测结果
    output = model(test_point)
    # 选择概率最大的类别
    _, predicted = torch.max(output, 1)
    # 添加到高度z中
    z.append(predicted.item())
    # 将z重新设置为和xx1相同的形式
    z = np.array(z).reshape(xx1.shape)
    # 返回xx1、xx2和z
    return xx1, xx2, z

生成每个点的距离是0.02的网格数据点
网格数据点

使用model预测结果,选择概率最大的类别,添加到高度z中。
将平面上的黑色点,标记为红色、绿色、蓝色三种颜色。
标记高度的网格数据点

main函数绘制决策边界

调用make_data,draw_data,train_network,draw_decision_boundary四个函数。
最后调用plt.contour绘制多分类的决策边界。运行程序,在结果中可以看到橙色的决策边界。

1
2
3
4
5
6
7
8
9
10
if __name__ == '__main__':
red, green, blue = make_data(100)

draw_data(red, green, blue)

model = train_network(red, green, blue)

xx1, xx2, z = draw_decision_boundary(-4, 4, -4, 4, model)
plt.contour(xx1, xx2, z, colors=['orange'])
plt.show()

非线性决策边界

完整代码示例

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
from sklearn.datasets import make_blobs,make_circles
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch

def make_data(num):
np.random.seed(0)
red, _ = make_blobs(
n_samples=num,
centers=[[0, 0]],
cluster_std=0.15
)

green, _ = make_circles(
n_samples=num,
noise=0.02,
factor=0.7
)

blue, _ = make_blobs(
n_samples=num,
centers=[
[-1.2, -1.2],
[-1.2, 1.2],
[1.2, -1.2],
[1.2, 1.2]
],
cluster_std=0.2
)

return red, green, blue

def draw_data(red, green, blue):
board = plt.figure()
axis = board.add_subplot(1,1,1)
axis.set(
xlim=[-1.5, 1.5],
ylim=[-1.5, 1.5],
title='Neural Network',
xlabel='x1',
ylabel='x2'
)

plt.scatter(green[:, 0], green[:, 1], color='green')
plt.scatter(blue[:, 0], blue[:, 1], color='blue')
plt.scatter(red[:, 0], red[:, 1], color='red')
#plt.show()

class Network(nn.Module):
def __init__(self, n_in, n_hidden, n_out):
super(Network, self).__init__()
self.layer1 = nn.Linear(n_in, n_hidden)
self.layer2 = nn.Linear(n_hidden, n_out)

def forward(self, x):
x = self.layer1(x)
x = torch.relu(x)
return self.layer2(x)

def train_network(red, green, blue):
n_features = 2
n_hidden = 5
n_classes = 3
n_epochs = 10000
learning_rate = 0.001

green = torch.FloatTensor(green)
blue = torch.FloatTensor(blue)
red = torch.FloatTensor(red)
data = torch.cat((red, green, blue), dim=0)
label = torch.LongTensor(
[0] * len(green) + [1] * len(blue) + [2] * len(red)
)

model = Network(n_features, n_hidden, n_classes)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(
model.parameters(), lr=learning_rate
)
for epoch in range(n_epochs):
output = model(data)
loss = criterion(output, label)
loss.backward()
optimizer.step()
optimizer.zero_grad()

if epoch % 1000 == 0:
print(f'{epoch} iterations: loss = {loss.item()}')
return model

def draw_decision_boundary(minx1, maxx1, minx2, maxx2, model):
xx1, xx2 = np.meshgrid(
np.arange(minx1, maxx1, 0.02),
np.arange(minx2, maxx2, 0.02),
)

x1s = xx1.ravel()
x2s = xx2.ravel()
z = []
for x1, x2 in zip(x1s, x2s):
test_point = torch.FloatTensor([[x1, x2]])
output = model(test_point)
_, predicted = torch.max(output, 1)
z.append(predicted.item())

z = np.array(z).reshape(xx1.shape)
return xx1, xx2, z

if __name__ == '__main__':
red, green, blue = make_data(100)

draw_data(red, green, blue)

model = train_network(red, green, blue)

xx1, xx2, z = draw_decision_boundary(-4, 4, -4, 4, model)
plt.contour(xx1, xx2, z, colors=['orange'])
plt.show()