鸢尾花(iris)分类——基于pytorch+python实现
鸢尾花(iris)分类——基于pytorch+python实现
文章目录
简介
鸢尾花分类是一个简单的入门项目,我将该项目分成了三个项目来实现:
- 基础版,只对3个鸢尾花类别进行分类
- 基础版2,加上一个类别,用于区分不属于这三类鸢尾花
- 基础版3,实现一个GUI
下面会给出代码以及数据集的链接,并简单分析部分代码,以及本人遇到的一些问题,还有一些思考。
资源链接
数据集介绍
数据集在iris文件夹
中的iris.data
,数据集的介绍在iris.name
,数据集的前4列分别为 sepal length,sepal width, petal length, petal width。第5列为鸢尾花的类别。
下面代码中读取数据集,使用sklearn的load_iris()
,而不是直接读取iris.data文件夹
部分代码分析
下面的代码分析是对于基础版
1. 对数据进行打乱
load_iris()返回的是一个字典。
由于原数据对3个类别是有顺序的,因而需要先将数据打乱,设计随机数种子,保证下次运行可以得到相同结果。
# 读取数据集
iris = datasets.load_iris()
data = iris.data
target = iris.target
# 对数据打乱
np.random.seed(116)
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
target = target[indices]
2. 利用TensorDataset()进行数据加载
# 加载数据集
dataset_train = TensorDataset(train_data, train_target)
dataloader_train = DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
dataset_test = TensorDataset(test_data, test_target)
dataloader_test = DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=True)
3. 网络结构
这个网络及其简单,只用了2层线性结构,但是准确率已经很高了,我也不知道网络结构是怎么设计的,有没有什么方法,你可以修改这个网络,看看效果。
# 创建感知器模型
class irisClassify(nn.Module):
def __init__(self):
super().__init__()
self.module = Sequential(
Linear(4, 10),
ReLU(),
Linear(10, 3),
Softmax(dim=1)
)
def forward(self, data):
output = self.module(data)
return output
4. 网络保存
训练过程就是常规的按照pytorch方法进行即可。
由于在基础班3
中需要使用模型对输入进行预测,因而需要对模型进行保存。GUI实现的效果为:
保存思路:从上一个项目得出经验,最后一次训练出来的结果未被损失最小,因而我对每轮训练都进行了验证,保存了每次准确率>=上一次以及最后一次的训练结果。
但是由于本次数据集较小,训练速度快,同时准确率较高,所以这么做也不太有必要。
注意: 按照下面的代码,你需要创建用于保存的文件夹,并替换下面的路径
with torch.no_grad():
total_right = 0
for data in dataloader_test:
testData, testTraget = data # (6, 4), (6,)
output = iris_recognition_module(testData)
right = (output.argmax(1) == testTraget).sum()
total_right += right
print(f"第{epoch}次训练的准确率为: {1.0 * total_right / len(test_data):.3f}")
if total_right >= bestRightNum or epoch == EPOCH - 1:
bestRightNum = total_right
path = f"./4_models/iris_reiris_recognition_model_{epoch}.pth"
torch.save(iris_recognition_module.state_dict(), path)
5. 基础版2的第4个类训练
不妨称第4个类为反类。
我的思路是捏造一些数据,用来训练第4个类别。记得修改网络最后的Linear()。
关于训练第四个类遇到的问题在下面会提及。
# 反类训练数据加入
torch.manual_seed(42) # 保证运行产生随机数相同
other_data = torch.randint(low=20, high=100, size=(100, 4)).float()
other_train_data = other_data[0:80]
other_test_data = other_data[80:]
train_data = torch.vstack((train_data, other_train_data))
test_data = torch.vstack((test_data, other_test_data))
train_target = torch.cat((train_target, (3 * torch.ones(80)).long()))
test_target = torch.cat((test_target, (3 * torch.ones(20)).long()))
6. 其他
对于GUI部分的代码是由gpt编写,不解释。
遇到的问题
1. 数据类型问题
pytorch中Linear()等默认数据类型为torch.float32,标签默认的数据类型为toch.long,而使用sklearn中的load_iris
读取出来的数据类型为np.float64,故需要更改数据类型。标签读取出来的数据类型为np.int,也需要更改数据类型。
2. 在基础版2中训练次数与捏造数据问题
一开始我将捏造的数据的4个范围设置在20-1000,因而为了覆盖这个范围,生成的数据要远高于原来训练数据个数120,导致准确率极低,几乎全被分类为第4类,所以把数据范围放小到20-100,这样覆盖该范围需要的数据个数较少80个,同时为了保证效果,增加了训练轮数。
思考
1. 神经网络的建构是怎么得到的?
比如在本次项目中,只是简单的使用了一个两层的神经网络(随手写的),准确率还不错,那么对于哪些比较复杂的网络是怎么得到的呢?有没有严格的推导或者方法?如果有了解的,可以在评论区分享一下论文或者blog。
2. 训练反类这种问题?
本次项目中通过捏造数据的方法进行训练,这也只是一种直观感觉,这类问题有没有相关的研究,叫什么? 可以在评论区分享一下论文或者blog。
3. GUI编写?
GUI的编写完全是由gpt3.5完成的,只在它的基础上进行了修改。对于这种非核心部分貌似没必要花时间掌握。
代码
由于代码并不长,因而在后面贴上源码,对于使用到的图片,可以由上面链接下载,或者自己随便弄几张即可。
1. 神经网络训练模型.ipynb
import pandas as pd
import sklearn
import torch
import torch.nn as nn
import numpy as np
from sklearn import datasets
from torchvision import transforms
from torch.nn import Linear, Sequential, ReLU, Softmax
from torch.utils.data import TensorDataset, DataLoader\
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity='all'
EPOCH = 80
BATCH_SIZE = 6
LR = 0.02
# 读取数据集
iris = datasets.load_iris()
data = iris.data
target = iris.target
# 对数据打乱
np.random.seed(116)
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
target = target[indices]
# 转换为tensor, .float()转换为float32,因为Linear()默认为float32
data = torch.from_numpy(data).float() # 150 * 4
# target需要保证为long类型
target = torch.from_numpy(target).long()
train_data = data[0:120]
test_data = data[120:]
train_target = target[0:120]
test_target = target[120:]
# 展示数据
flowerName = ['Setosa', 'Versicolour', 'Virginica']
featureName = ['sepal length', 'sepal width', 'petal length', 'petal width']
data[0:5]
target[0:5]
# 加载数据集
dataset_train = TensorDataset(train_data, train_target)
dataloader_train = DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
dataset_test = TensorDataset(test_data, test_target)
dataloader_test = DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=True)
# 创建感知器模型
class irisClassify(nn.Module):
def __init__(self):
super().__init__()
self.module = Sequential(
Linear(4, 10),
ReLU(),
Linear(10, 3),
Softmax(dim=1)
)
def forward(self, data):
output = self.module(data)
return output
iris_recognition_module = irisClassify()
loss = nn.CrossEntropyLoss()
optim = torch.optim.SGD(iris_recognition_module.parameters(), lr=LR)
# 测试模型
input = torch.tensor([[1, 2, 3, 4]], dtype=torch.float32)
input.shape
output = iris_recognition_module(input)
output.dtype
iterator = 0
bestRightNum = 0
for epoch in range(EPOCH):
# 训练模型
_ = iris_recognition_module.train();
print(f"------第{epoch}次训练------")
for data in dataloader_train:
trainData, trainTarget = data
output = iris_recognition_module(trainData)
loss_out = loss(output, trainTarget)
optim.zero_grad()
loss_out.backward()
optim.step()
if iterator % 10 == 0:
print(f"第{iterator}次的损失为: {loss_out:.6f}")
iterator += 1
# 测试模型
_ = iris_recognition_module.eval();
with torch.no_grad():
total_right = 0
for data in dataloader_test:
testData, testTraget = data # (6, 4), (6,)
output = iris_recognition_module(testData)
right = (output.argmax(1) == testTraget).sum()
total_right += right
print(f"第{epoch}次训练的准确率为: {1.0 * total_right / len(test_data):.3f}")
if total_right >= bestRightNum or epoch == EPOCH - 1:
bestRightNum = total_right
path = f"./models/iris_reiris_recognition_model_{epoch}.pth"
torch.save(iris_recognition_module.state_dict(), path)
# 手动测试模型是否正确
#4.6,3.2,1.4,0.2,Iris-setosa
#5.3,3.7,1.5,0.2,Iris-setosa
#5.0,3.3,1.4,0.2,Iris-setosa
#7.0,3.2,4.7,1.4,Iris-versicolor
#6.4,3.2,4.5,1.5,Iris-versicolor
#5.1,2.5,3.0,1.1,Iris-versicolor
#5.7,2.8,4.1,1.3,Iris-versicolor
#6.3,3.3,6.0,2.5,Iris-virginica
#5.8,2.7,5.1,1.9,Iris-virginica
#7.1,3.0,5.9,2.1,Iris-virginica
with torch.no_grad():
testData1 = torch.tensor([[4.6,3.2,1.4,0.2],
[5.3,3.7,1.5,0.2],
[5.0,3.3,1.4,0.2],
[7.0,3.2,4.7,1.4],
[6.4,3.2,4.5,1.5]], dtype=torch.float32)
testTarget1 = [0, 0, 0, 1, 1]
predict1 = iris_recognition_module(testData1)
predict1 = predict1.argmax(1)
predict1
testData2 = torch.tensor([[5.1,2.5,3.0,1.1],
[5.7,2.8,4.1,1.3],
[6.3,3.3,6.0,2.5],
[5.8,2.7,5.1,1.9],
[7.1,3.0,5.9,2.1]], dtype=torch.float32)
testTarget2 = [1, 1, 2, 2, 2]
predict2 = iris_recognition_module(testData2)
predict2 = predict2.argmax(1)
predict2
testData3 = torch.tensor( [[10, 10, 100, 10],
[20, 20, 200, 20],
[5.1,2.5,3.0,1.1],
[78, 46, 90, 1],
[1200, 3400, 8900, 1200]], dtype=torch.float32)
# 这4项数据都是凭空捏造的,应该均不属于这三类
predict3 = iris_recognition_module(testData3)
predict3 = predict3.argmax(1)
predict3
2. 神经网络训练模型2.ipynb
import pandas as pd
import sklearn
import torch
import torch.nn as nn
import numpy as np
from sklearn import datasets
from torchvision import transforms
from torch.nn import Linear, Sequential, ReLU, Softmax
from torch.utils.data import TensorDataset, DataLoader
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity='all'
EPOCH = 100
BATCH_SIZE = 6
LR = 0.02
# 读取数据集
iris = datasets.load_iris()
data = iris.data
target = iris.target
# 对数据打乱
np.random.seed(116)
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
target = target[indices]
# 转换为tensor, .float()转换为float32,因为Linear()默认为float32
data = torch.from_numpy(data).float() # 150 * 4
# target需要保证为long类型
target = torch.from_numpy(target).long()
train_data = data[0:120]
test_data = data[120:]
train_target = target[0:120]
test_target = target[120:]
# 反类训练数据加入
torch.manual_seed(42) # 保证运行产生随机数相同
other_data = torch.randint(low=20, high=100, size=(100, 4)).float()
other_train_data = other_data[0:80]
other_test_data = other_data[80:]
train_data = torch.vstack((train_data, other_train_data))
test_data = torch.vstack((test_data, other_test_data))
train_target = torch.cat((train_target, (3 * torch.ones(80)).long()))
test_target = torch.cat((test_target, (3 * torch.ones(20)).long()))
# 展示数据
flowerName = ['Setosa', 'Versicolour', 'Virginica']
featureName = ['sepal length', 'sepal width', 'petal length', 'petal width']
data[0:5]
target[0:5]
# 加载数据集
dataset_train = TensorDataset(train_data, train_target)
dataloader_train = DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
dataset_test = TensorDataset(test_data, test_target)
dataloader_test = DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=True)
# 创建感知器模型
class irisClassify(nn.Module):
def __init__(self):
super().__init__()
self.module = Sequential(
Linear(4, 10),
ReLU(),
Linear(10, 4),
Softmax(dim=1)
)
def forward(self, data):
output = self.module(data)
return output
iris_recognition_module = irisClassify()
loss = nn.CrossEntropyLoss()
optim = torch.optim.SGD(iris_recognition_module.parameters(), lr=LR)
# 测试模型
input = torch.tensor([[1, 2, 3, 4]], dtype=torch.float32)
input.shape
output = iris_recognition_module(input)
output.dtype
iterator = 0
bestRightNum = 0
for epoch in range(EPOCH):
# 训练模型
_ = iris_recognition_module.train();
print(f"------第{epoch}次训练------")
for data in dataloader_train:
trainData, trainTarget = data
output = iris_recognition_module(trainData)
loss_out = loss(output, trainTarget)
optim.zero_grad()
loss_out.backward()
optim.step()
if iterator % 10 == 0:
print(f"第{iterator}次的损失为: {loss_out:.6f}")
iterator += 1
# 测试模型
_ = iris_recognition_module.eval();
with torch.no_grad():
total_right = 0
for data in dataloader_test:
testData, testTraget = data # (6, 4), (6,)
output = iris_recognition_module(testData)
right = (output.argmax(1) == testTraget).sum()
total_right += right
print(f"第{epoch}次训练的准确率为: {1.0 * total_right / len(test_data):.3f}")
if total_right >= bestRightNum or epoch == EPOCH - 1:
bestRightNum = total_right
path = f"./4_models/iris_reiris_recognition_model_{epoch}.pth"
torch.save(iris_recognition_module.state_dict(), path)
# 手动测试模型是否正确
#4.6,3.2,1.4,0.2,Iris-setosa
#5.3,3.7,1.5,0.2,Iris-setosa
#5.0,3.3,1.4,0.2,Iris-setosa
#7.0,3.2,4.7,1.4,Iris-versicolor
#6.4,3.2,4.5,1.5,Iris-versicolor
#5.1,2.5,3.0,1.1,Iris-versicolor
#5.7,2.8,4.1,1.3,Iris-versicolor
#6.3,3.3,6.0,2.5,Iris-virginica
#5.8,2.7,5.1,1.9,Iris-virginica
#7.1,3.0,5.9,2.1,Iris-virginica
with torch.no_grad():
testData1 = torch.tensor([[4.6,3.2,1.4,0.2],
[5.3,3.7,1.5,0.2],
[5.0,3.3,1.4,0.2],
[7.0,3.2,4.7,1.4],
[6.4,3.2,4.5,1.5]], dtype=torch.float32)
testTarget1 = [0, 0, 0, 1, 1]
predict1 = iris_recognition_module(testData1)
predict1 = predict1.argmax(1)
predict1
testData2 = torch.tensor([[5.1,2.5,3.0,1.1],
[5.7,2.8,4.1,1.3],
[6.3,3.3,6.0,2.5],
[5.8,2.7,5.1,1.9],
[7.1,3.0,5.9,2.1]], dtype=torch.float32)
testTarget2 = [1, 1, 2, 2, 2]
predict2 = iris_recognition_module(testData2)
predict2 = predict2.argmax(1)
predict2
testData3 = torch.tensor( [[10, 10, 100, 10],
[20, 20, 200, 20],
[5.1,2.5,3.0,1.1],
[78, 46, 90, 80],
[1200, 3400, 8900, 1200]], dtype=torch.float32)
# 这4项数据都是凭空捏造的,应该均不属于这三类
predict3 = iris_recognition_module(testData3)
predict3 = predict3.argmax(1)
predict3
3. GUI.ipynb
import tkinter as tk
import torch
import torch.nn as nn
from tkinter import messagebox
from torch.nn import Linear, Sequential, ReLU, Softmax
from PIL import Image, ImageTk
# 创建感知器模型
class irisClassify(nn.Module):
def __init__(self):
super().__init__()
self.module = Sequential(
Linear(4, 10),
ReLU(),
Linear(10, 4),
Softmax(dim=1)
)
def forward(self, data):
output = self.module(data)
return output
def load_images():
images = []
imagePath = ['./iris_image/iris_setosa.png', './iris_image/iris_versicolor.png', './iris_image/iris_virginica .png', './iris_image/iris.png']
for i in range(4):
img = Image.open(imagePath[i])
img = img.resize((276, 250), Image.Resampling.LANCZOS) # 将图片调整为200x200大小
images.append(ImageTk.PhotoImage(img))
return images
def process_inputs(input, model):
output = 0
with torch.no_grad():
input = torch.tensor(input, dtype = torch.float32).reshape(1, 4)
output = model(input).argmax(1)[0]
return output
def on_confirm(entries, result_label, images, descriptions, description_label, model):
try:
input_values = [float(entry.get()) for entry in entries]
result = process_inputs(input_values, model)
result_label.config(image=images[result])
result_label.image = images[result] # 保持对图片的引用
# 更新描述标签
description_label.config(text=descriptions[result])
except ValueError:
messagebox.showerror("输入错误", "请确保所有输入均为数字")
# 运行主循环
def load_GUI(model):
root = tk.Tk()
root.title("鸢尾花分类器")
root.geometry("600x500") # 指定窗口大小
# 创建一个框架用于布局
frame = tk.Frame(root)
frame.pack(pady=10)
# 输入框提示文本
labels_text = ["sepal length(萼片长,cm):", "sepal width(萼片宽,cm):", "petal length(花瓣长,cm):", "petal width(花瓣宽,cm):"]
# 创建输入框和对应的提示标签
entries = [] # 用于存储输入框的引用
for i, text in enumerate(labels_text):
label = tk.Label(frame, text=text)
label.grid(row=i, column=0, padx=5, pady=5, sticky="e") # 标签在输入框左侧
entry = tk.Entry(frame)
entry.grid(row=i, column=1, padx=5, pady=5) # 输入框在标签右侧
entries.append(entry)
# 确定按钮放在输入框右边
confirm_button = tk.Button(frame, text="确定", command=lambda: on_confirm(entries, result_label, images, descriptions, description_label, model))
confirm_button.grid(row=0, column=2, rowspan=4, padx=5, pady=5)
# 用于显示结果的标签和描述标签
result_label = tk.Label(root)
result_label.pack(pady=20)
description_label = tk.Label(root)
description_label.pack(pady=5)
# 图片列表和对应描述
images = []
descriptions = [
"Iris Setosa", # 替换为相应图片的描述
"Iris Versicolour",
"Iris Virginica",
"is not Iris",
]
images = load_images()
root.mainloop()
def load_model(path):
par = torch.load(path)
iris_recognition_model = irisClassify()
iris_recognition_model.load_state_dict(par)
return iris_recognition_model
def main():
model_path = './4_models/iris_reiris_recognition_model_99.pth'
iris_recognition_model = load_model(model_path)
load_GUI(iris_recognition_model)
if __name__=="__main__":
main()
作者:Febu4