一:问题简介
1.问题描述
杂草是农业经营中不受欢迎的入侵者,它们通过窃取营养、水、土地和其他关键资源来破坏种植,这些入侵者会导致产量下降和资源部署效率低下。一种已知的方法是使用杀虫剂来清除杂草,但杀虫剂会给人类带来健康风险。我们的目标是利用计算机视觉技术可以自动检测杂草的存在,开发一种只在杂草上而不是在作物上喷洒农药的系统,并使用针对性的修复技术将其从田地中清除,从而最小化杂草对环境的负面影响。
2.预期解决方案
基于我们提供的植被数据及标签,训练一个神经网络模型,有效的处理杂草,这里推理时间和二分类准确度(F1分数)将作为评分的主要依据。
3.数据集来源
https://filerepo.idzcn.com/hack2023/Weed_Detection5a431d7.zip
4.图像展示及数据集介绍
作物
杂草
在数据集中对应给出了相应的标签用来对杂草和作物进行区分:
上图作物的标签为:0 0.478516 0.560547 0.847656 0.625000
上图杂草的标签为:1 0.508789 0.489258 0.869141 0.861328
第一个0和1是用来区分是杂草还是作物的,0为作物,1为杂草,后续为对应的坐标。
本次数据集总共有1300条数据。
二:数据处理
1.数据写入
将文件名写入txt文件中,便于我们后续利用文件名对图像和对应的标签进行读取和处理。由于标签和图像数据的文件名一致,只有后缀不同,所以我们这里就只导入图像数据集的文件名,而不导入标签的文件名和对应的后缀。
# 指定图片所在的文件夹路径
image_folder = '../zacao/data'
# 获取文件夹下所有以.jpeg结尾的文件
image_files = [f for f in os.listdir(image_folder) if f.endswith('.jpeg')]
# 提取文件名的前缀并保存到data.txt
with open('../zacao/data.txt', 'w') as file:
for filename in image_files:
# 获取文件名的前缀
prefix = os.path.splitext(filename)[0]
# 写入前缀到data.txt文件
file.write(prefix + '\n')
运行结果:
将对应的文件名写入data文件中。
2.数据预处理
1.图像处理
转换:数据格式转换为PyTorch的Tensor格式这里为tensor的float16。增强图像对比度,我们这里将对比度被设置为0.5,意味着对比度会在原始图像的基础上增加或减少50%,来使阴暗对比更明显。最后归一化。
划分:对数据集进行训练集和测试集的划分,我们这里为7-3分割
- 70%的图像会被分配给训练集(
image_train
)。 - 30%的图像会被分配给测试集(
image_test
)。
transformer = transforms.Compose([
transforms.ToTensor(),
transforms.ColorJitter(contrast=0.5), # 增强对比度
transforms.Normalize(mean=[0.5], std=[0.5]) # 归一化
])
train_images_tensor = []
with open(r'../zacao/data.txt','r') as f:
file_name_url=[i.split('\n')[0] for i in f.readlines()]
for i in range(len(file_name_url)):
image = Image.open('../zacao/data/'+file_name_url[i]+'.jpeg')
tensor = transformer(image.convert('L')).type(torch.float16)
train_images_tensor.append(tensor)
image_train = []
image_test = []
for i in range(len(train_images_tensor)):
if i <=len(train_images_tensor)*0.7:
image_train.append(train_images_tensor[i])
else:image_test.append(train_images_tensor[i])
2.标签处理
标签处理与数据处理类似,也是按照7:3的比例划分训练集和测试集并转化为tensor的float16
transformerlab = transforms.Compose([
transforms.ToTensor()
])
train_lables_tensor = []
with open(r'../zacao/data.txt','r') as f:
file_name_url=[i.split('\n')[0] for i in f.readlines()]
train_lables_tensor = []
for i in range(len(file_name_url)):
image = open('../zacao/data/' + file_name_url[i] + '.txt')
labels = image.readline()[0]
labels = float(labels)
tensor = torch.tensor(labels, dtype=torch.float16) # 使用float16数据类型
train_lables_tensor.append(tensor)
lables_train = []
lables_test = []
for i in range(len(train_lables_tensor)):
if i <=len(train_lables_tensor)*0.7:
lables_train.append(train_lables_tensor[i])
else:lables_test.append(train_lables_tensor[i])
3.构造数据集
对train数据集进行了数据增强操作,对test数据集并没有进行操作。
train_datas_tensor = torch.stack(image_train)
train_labels_tensor = torch.stack(lables_train)
test_datas_tensor = torch.stack(image_test)
test_labels_tensor = torch.stack(lables_test)
train_dataset = TensorDataset(train_labels_tensor, train_datas_tensor)
train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_dataset = TensorDataset(test_labels_tensor, test_datas_tensor)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)
三 :模型选择
1.ResNet-50模型
ResNet的主要思想是在网络中增加了直连通道,即Highway Network的思想。此前的网络结构是性能输入做一个非线性变换,而Highway Network则允许保留之前网络层的一定比例的输出。ResNet的思想和Highway Network的思想也非常类似,允许原始输入信息直接传到后面的层中,如下图所示:
其的整个流程即整体结构如下图:
ResNet有不同的网络层数,比较常用的是50-layer,101-layer,152-layer。他们都是由上述的残差模块堆叠在一起实现的,我们这里选择的是50-layer。
2.自写ResNet网络
class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_conv=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)
if use_conv:
self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
Y += X
return F.relu(Y)
b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(input_channels, num_channels, use_conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))
net = nn.Sequential(b1, b2, b3, b4, b5,
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(), nn.Linear(512, 10))
3.使用ResNet模型并训练
我们这里将输出特征修改为2,以为我们最后只有两个类别采用的是交叉熵损失函数和SGD梯度下降
net = torchvision.models.resnet50(pretrained=True)
net.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
num_features = net.fc.in_features
net.fc = nn.Linear(num_features, 2)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net.to(device).float()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
之后进行训练,这里迭代5次,代码如下:
for epoch in range(1, 6):
running_loss = 0.0
num_images = 0
loop = tqdm(enumerate(train_dataloader, 0))
for step, data in loop:
labels, inputs = data[0].to('cpu').float(), data[1].to('cpu').float()
# labels, inputs = data[0].float(), data[1].float()
optimizer.zero_grad()
inputs = inputs.float()
outputs = net(inputs)
# 创建包含相同数量的目标值的示例目标张量
target = labels # 使用实际标签作为目标
# 使用 MSE 损失函数
loss = criterion(outputs, target.long())
loss.backward()
optimizer.step()
num_images += inputs.size(0)
running_loss += loss.item()
loop.set_description(f'Epoch [{epoch}/5]')
loop.set_postfix(loss=running_loss / (step + 1))
print('Finish!!!')
输出的结果对应为每次迭代的时间,和损失率,结果如图所示,我们可以看到每次迭代的平均时间为7分钟20秒左右,损失率也是随着每次迭代而降低。
4.计算结果
我们这里计算并输出了本模型的准确率、花费的时间和F1分数,如下:F1分数为0.9693,准确率为96.67%,所用时间为72.20秒左右
四:Intel® AI Analytics Toolkits
1.使用目的
英特尔的oneAPI AI分析工具套件是一个优化的深度学习框架和高性能Python库,旨在加速端到端的机器学习和数据科学流程。这个工具套件基于oneAPI,使用优化的深度学习框架和高性能Python库,可以加速从预处理到机器学习的整个流程。这些组件都是使用oneAPI库构建的,以实现低级计算优化,从而最大化从预处理到机器学习的性能。
这个工具套件可以帮助开发人员加速开发过程,提高开发效率,使开发人员在开发过程中更加方便。它包含了功能强大且经过优化的深度学习框架,以及高性能Python库,这些组件可以帮助开发人员在机器学习和数据科学流程中提高性能。
2.基本框架
主要有如下五个部分:
- 优化的深度学习框架:这个框架是使用oneAPI库构建的,针对低级计算进行了优化,从而最大限度地提高从预处理到机器学习的性能。它提供了一组标准化的应用编程接口(API),使软件开发人员能够在不同的计算平台上使用相同的代码开发应用程序。
- 高性能Python库:这些库是为了加速机器学习和数据科学流程而设计的。它们可以帮助开发人员在处理复杂的数据科学任务时提高性能。
- 性能分析工具:这些工具可以帮助开发人员分析应用程序的性能,从而找出性能瓶颈并进行优化。
- 调试工具:这些工具可以帮助开发人员找出应用程序中的问题,比如代码错误、资源冲突等。
- 应用程序打包工具:这些工具可以帮助开发人员在不同的计算平台上调试、优化和部署应用程序。
框架如下图所示:
3. 使用ResNet50,并用OneAPI的组件进行加速
我们在最后增加了一句 net,optimizer = ipex.optimize(net,optimizer=optimizer),具体的代码如下。
net = torchvision.models.resnet50(pretrained=True)
net.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
num_features = net.fc.in_features
net.fc = nn.Linear(num_features, 2)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net.to(device).float()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
net,optimizer = ipex.optimize(net,optimizer=optimizer)
在这里我们使用了一个为Intel处理器优化的PyTorch扩展 ——ipex
模块,来优化神经网络模型和优化器。通过这种方式,可以确保模型和优化器在Intel硬件上达到最佳性能。
五:训练并保存模型
1.训练
我们首先进行训练,以均方误差作为损失函数(这里迭代5次)
for epoch in range(1, 6):
running_loss = 0.0
num_images = 0
loop = tqdm(enumerate(train_dataloader, 0))
for step, data in loop:
labels, inputs = data[0].to('cpu').float(), data[1].to('cpu').float()
# labels, inputs = data[0].float(), data[1].float()
optimizer.zero_grad()
inputs = inputs.float()
outputs = net(inputs)
# 创建包含相同数量的目标值的示例目标张量
target = labels # 使用实际标签作为目标
# 使用 MSE 损失函数
loss = criterion(outputs, target.long())
loss.backward()
optimizer.step()
num_images += inputs.size(0)
running_loss += loss.item()
loop.set_description(f'Epoch [{epoch}/5]')
loop.set_postfix(loss=running_loss / (step + 1))
print('Finish!!!')
输出的结果如下,对应为每次迭代的时间和损失率,我们可以看到单次迭代所花费的时间已经相较于之前缩减了很多了。
2.保存模型
在训练完了,我们将我们训练的模型保存为“resnet50_model.pth”,便于后续的使用
torch.save(net.state_dict(), 'resnet50_model.pth')
print("模型已保存为 resnet50_model.pth")
保存成功后的结果输出为:
并会在你的文件夹中生成对应的文件。
3.测试图片
我们随便找一张图片对我们保存的模型进行测试,测试代码如下:
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import torchvision.transforms.functional as TF
import torch
import matplotlib.image as mpimg
# 保存模型
PATH = './resnet50_model.pth'
net.load_state_dict(torch.load(PATH))
# 加载一张图片进行测试
image_path = 'data/agri_0_1009.jpeg'
image = Image.open(image_path).convert('L') # 替换为你的图片路径
image = TF.to_tensor(image) # 将图片转换为tensor
image = image.unsqueeze(0) # 添加批处理维度
image = image.to('cpu') # 转移到GPU上
# 在图片上进行预测
with torch.no_grad():
outputs = net(image) # outputs为预测结果,即各个类别的概率分布
_, predicted = torch.max(torch.abs(outputs), 1)
# 获取概率最高的类别作为预测结果
print('Predicted:', predicted.item()) # 打印预测结果
if predicted.item() == 0:
print('判断结果:作物')
elif predicted.item() == 1:
print('判断结果:杂草')
else:
print('预测结果不在0和1之间,可能存在错误')
# 读取图片
img = mpimg.imread(image_path)
# 显示图片
plt.imshow(img)
plt.show()
测试的结果如下:预测的分类为1,结果为杂草,并输出了对应的图片。
六:计算结果即准确率、花费的时间和F1分数
我们首先介绍一下F1分数:
F1分数(F1 Score),是统计学中用来衡量二分类(或多任务二分类)模型精确度的一种指标。它同时兼顾了分类模型的准确率和召回率。F1分数可以看作是模型准确率和召回率的一种加权平均,它的最大值是1,最小值是0,值越大意味着模型越好。
对应的代码为:
import time
from sklearn.metrics import f1_score
correct = 0
total = 0
all_predictions = []
all_labels = []
# 计算每个类别的精度和召回率
def calculate_f1(y_true, y_pred):
precision = f1_score(y_true, y_pred, average='binary')
return precision
# 开始推理
start_time = time.time()
with torch.no_grad():
for data in test_dataloader:
images, labels = data[1].to('cpu').float(), data[0].to('cpu').long() # 将标签转换为整数类型
net = net.float()
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
all_predictions.extend(predicted.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
# 计算推理时间
inference_time = time.time() - start_time
accuracy = 100 * correct / total
# 打印结果
f1 = f1_score(all_labels, all_predictions, average='binary') # 适用于二分类问题
print(f'F1分数为: {f1:.4f}')
print(f'Accuracy on test set: {accuracy:.2f}%')
print(f"Inference Time: {inference_time} seconds")
输出的结果为:
F1分数为:0.9739,准确率为97.18%,所花费的时间为42.907秒,相较于之前缩减了30秒左右,缩减率为41%,F1和准确率也都是有微量的增加。
七:OneAPI组件的使用
Intel Optimization for PyTorch: PyTorch优化套件支持自动混合精度,这有助于减少模型的内存占用,提高计算性能。
Intel Extension for PyTorch :提供了专门针对英特尔 CPU 和加速器的硬件优化,以充分利用英特尔处理器的性能。这些优化可以显著提高深度学习模型的推理和训练性能。
八:项目总结
本次项目是基于ResNet50模型进行的杂草检测和去除,整个项目有助于我对于卷积神经网络尤其是ResNet50模型的理解和代码的实施,并且本次项目也利用了对应的oneAPI组件进行加速,不仅了解了英特尔AI工具的基本框架,更是将ipex组件利用到了项目中。但是本项目还有一定的提升空间,由于本机并没有gpu环境,所以都是在cpu上运行的,导致时间较慢,包括其实也有一些其他比较好的模型,也希望后续我可以更加努力的学习并了解各种模型,并在后续的项目中利用到更多的oneAPI组件来加速。