Pytorch实现鸢尾花分类问题

这篇文章是笔者在开始学习 Pytorch 时所做的一次小实验。

边学边练,在实践中学习 Pytorch 的使用方法,我觉得是最吼的。

鸢尾花分类问题

先简要介绍一下我们此次实验的背景。

给出一个 .csv 的数据集文件,文件中的数据一共有 150行,5列,也就是 150x5 的规模。其中,前四列为鸢尾花的特征,最后一列为鸢尾花的种类(数据文件中,鸢尾花一共有 3 个种类)。

给大家看一下数据文件的大概样子:

Iris_data.csv

我们的任务就是利用 Pytorch 来实现这三个品种种鸢尾花的分类。

闲话少说,直接上代码!


代码部分

笔者也是一位机器学习的初学者,下面的代码都是我自己一行一行敲出来的。因为是一边学一边敲,所以会有大量的注释。

我会把整个源程序的代码分成一段一段的,逐段解释。如果有需要复现的话,请按照我的分段顺序把代码重新放到一个源文件中。

导入所需的包
1
2
3
4
5
6
7
8
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import math
import matplotlib.pyplot as plt
%matplotlib notebook # 不加这行代码的话,画图时也许有点小 bug,可能不显示图像
from torch.utils.data import DataLoader, Dataset
设置随机数种子

这里设置随机数种子的目的是让程序每次跑出来的结果都是一样的,便于复现。

1
2
np.random.seed(10)
torch.manual_seed(10)

数据处理部分

下面这段代码中的 DatasetDataLoader 是用来帮助我们加载和使用数据集的。

Dataset 的主要功能是构建数据集。

DataLoader 的主要功能是加载数据集,它有一个参数叫 batch_size,意思是我们每次从数据集中拿出几组样本来进行训练。

这样做的好处就是,我们在面对规模较大的数据集时,可以一边加载一小部分数据一边训练,而不用先把整个数据集都读进内存,然后再进行训练。

这里还有个小知识点:我们把一整个数据集叫做 Batch,而把其中的一小部分数据集叫做 Mini-Batch,把 Batch 除以 Mini-Batch 的结果叫做 Iteration,也就是需要迭代的次数。

此外,Batch 并不一定需要被 Mini-Batch 整除。

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
class MyDataset(Dataset):
def __init__(self, filepath):

# 用 pandas 读取 csv 格式文件,如果不加 header=None 的话,它会把数据集的第一行默认作为列标签
xy = pd.read_csv('./iris_data/iris.csv', header=None)
names_copy = xy.iloc[:, 4].copy() # 记住 .loc[] 与 .iloc 的区别

names_copy_length = len(names_copy)

# 对鸢尾花的类别进行编号
for i in range(names_copy_length):
if names_copy[i] == 'Iris-setosa':
names_copy[i] = 0
elif names_copy[i] == 'Iris-versicolor':
names_copy[i] = 1
else:
names_copy[i] = 2

xy.iloc[:, 4] = names_copy

# 选取前 4 列作为 feature,最后一列是 label
self.x_data = np.array(xy.iloc[:, :4])
self.y_data = np.array(xy.iloc[:, 4])
self.len = xy.shape[0]

# 这个 getitem 是一个 magin method, 它使得我们能够以下标的形式 dataset[index],访问 dataset 中的元素
def __getitem__(self, index):
return self.x_data[index], self.y_data[index]

# maginc method,使得我们能够用 len(dataset) 来获取 dataset 的长度
def __len__(self):
return self.len

# 构建 dataset 和 dataloader
dataset = MyDataset('./iris_data/iris.csv')
train_loader = DataLoader(dataset=dataset, batch_size=1, shuffle=True)
网络结构部分

网络模型一共有四层:一个有 4 个神经元的输入层、一个有 3 个神经元输出层、两个都有 5 个神经元的隐藏层。

两个隐藏层的激活函数用的都是 sigmoid() 函数,最后的输出层用的是 softmax() 函数。

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
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear_1 = nn.Linear(4, 5)
self.linear_2 = nn.Linear(5, 4)
self.linear_3 = nn.Linear(4, 3)
self.sigmoid = nn.Sigmoid() # 这里用的是 Sigmoid 函数模块,只用普通的 sigmoid 函数也是可以的
self.loss_down = []

self.criterion = nn.CrossEntropyLoss() # CrossEntropyLoss 中就包括了 softmax 和 NLLLoss
self.optimizer = torch.optim.SGD(self.parameters(), lr=0.007, momentum=0.05)
# 这里使用了带冲量的优化,可以加速梯度下降的过程

def forward(self, x):
x = self.sigmoid(self.linear_1(x))
x = self.sigmoid(self.linear_2(x))

# 这里返回的是未经过激活的 linear_3(x),因为我们待会还要用交叉熵函数对其处理
return self.linear_3(x)

# loss 曲线下降展示
def show(self):
plt.xlabel("epoch")
plt.ylabel("loss")
plt.plot(self.loss_down, label="dataset", color='coral')
plt.legend(loc=0,)
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
def train(model, epoch):
running_loss = 0

# 这里使用 enumerate 的目的只是为了知道当前是第多少次迭代(iteration)
for index, data in enumerate(train_loader, 0):
inputs, labels = data
truetrue
# 前向传播
outputs = model(inputs.float())

# 计算误差
loss = model.criterion(outputs, labels)

# 反向传播
model.optimizer.zero_grad() # 优化器清零
loss.backward()
model.optimizer.step() # 优化

# 计算累计误差
running_loss += loss.item()

# 每累计 150 个误差,在曲线图上画一个点
if index % 150 == 149:
model.loss_down.append(running_loss/100)
running_loss = 0

# 实例化模型并训练
model = Model()
for epoch in range(500):
train(model, 100)
model.show()

这里给大家看一下我在训练集上的 loss 曲线图。

训练集上loss曲线图

实现多分类问题

这里为了演示,我就直接把原来的训练集拿来作为 测试集,这在实际应用中是不可行的,请大家牢记。由于这段预测的输出结果比较长,我就不在这里展示了,大家可以跑一下程序看看(我这个的预测准确率大概在 96%)。

演示代码如下:

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
def predict():
correct = 0
total = 0

# 测试的时候不需要再计算梯度了,因为模型已经训练好了,不用再优化了
with torch.no_grad():

# 以 train_loader 代替 test_loader,因为 test_loader 我没搞,哈哈
for data in train_loader:
inputs, labels = data
outputs = model(inputs.float()) # 不转为 float 就会报错

# torch.max() 返回一个元组 (values, indices)
# 下面这行代码的操作就类似于 one-hot 编码
_, predicted = torch.max(outputs.data, dim=1) # dim=1,按行来进行

# 获取测试集样本总数
total += labels.size(0)

# 统计预测正确的数量
correct += (predicted == labels).sum().item()
length = len(labels)

# 打印预测值和目标值
for i in range(length):
if predicted[i] == labels[i]:
print("target = %d, predicted = %d" % (labels[i], predicted[i]))
print("Right!")
else:
print("target = %d, predicted = %d" % (labels[i], predicted[i]))
print("Wrong!")

print("TrainDatasets Accuracy: %d %%" % (100 * correct/total))

predict()

------本文结束感谢您的阅读 ------
0%