【计算机视觉CV-图像分类】06 - VGGNet的鲜花分类实现:从数据预处理到模型优化的完整实战!
目录
-
引言
-
VGGNet概述
-
VGGNet的网络架构
-
基于预训练VGGNet的五类鲜花分类实现
-
4.1 数据准备与预处理
-
4.2 模型实例化与参数调整
-
4.3 模型训练与保存最优模型
-
4.4 模型导入与预测
-
4.5 训练过程的可视化
-
-
模型优化与防止过拟合
-
总结与展望
-
参考文献
引言
在计算机视觉领域,图像分类是一个基础且关键的任务。随着深度学习技术的迅猛发展,卷积神经网络(CNN)在图像分类任务中展现出了卓越的性能。VGGNet作为一种经典的深层卷积神经网络,以其简洁而深邃的网络结构,在图像分类任务中取得了显著的成果。本文将详细介绍VGGNet的架构,并通过一个五类鲜花分类的实例,演示如何利用预训练的VGGNet模型进行图像分类。本文使用的工具链为PyTorch 2.5.1与TorchVision 0.20.1,确保代码的兼容性和稳定性。
VGGNet概述
VGGNet由牛津大学视觉几何组(Visual Geometry Group, VGG)和Google DeepMind的研究人员于2014年共同开发。VGGNet在2014年ImageNet大规模视觉识别挑战赛(ILSVRC2014)中表现优异,尤其在图像分类任务中取得了显著的成绩。
VGGNet的主要特点
-
统一的卷积核尺寸:VGGNet全部采用3×3的小卷积核。这种设计不仅减少了参数数量,还能通过堆叠多个卷积层来增加网络的深度和非线性表达能力。
-
深层网络结构:VGGNet通过增加网络的深度(层数),增强了模型的特征提取能力,相较于早期的网络如AlexNet,VGGNet在图像分类任务中表现更加出色。
-
重复使用简单的模块:VGGNet通过重复使用相同的卷积和池化模块,使得网络结构简洁且易于扩展,便于理解和实现。
-
全连接层:在卷积层之后,VGGNet使用多个全连接层进行高层次的特征组合和分类任务,增强了模型的分类能力。
VGGNet的版本
VGGNet有多个不同深度的版本,最常用的是VGG-16和VGG-19,分别包含16层和19层深度。这些版本在不同的任务中表现出色,尤其是在图像特征提取方面,被广泛应用于各种计算机视觉任务中。
VGGNet的网络架构
VGGNet的网络架构以其统一的小卷积核和深层结构著称。以VGG-16为例,其详细架构如下:
图片来源:Wikipedia
VGG-16详细架构
from torchsummary import summary
# 通过net.summay()查看网络的形状
summary(model=model,input_size=(3,224,224),batch_size=1,device='cpu')
-
输入层:224×224 RGB图像。
-
卷积层:
-
Conv1: 2个3×3卷积,64个通道
-
Conv2: 2个3×3卷积,128个通道
-
Conv3: 3个3×3卷积,256个通道
-
Conv4: 3个3×3卷积,512个通道
-
Conv5: 3个3×3卷积,512个通道
-
-
池化层:每两个或三个卷积层后接一个2×2的最大池化层,进行空间降维。
-
全连接层:
-
FC1: 4096个神经元
-
FC2: 4096个神经元
-
FC3: 1000个神经元(对应ImageNet的1000类)
-
参数总数
VGG-16共有约138,357,544个参数,其中包括138,357,544个可训练参数。这一庞大的参数量赋予了VGGNet强大的特征表达能力,但也带来了计算和存储的挑战。
基于预训练VGGNet的五类鲜花分类实现
在实际应用中,训练一个深层次的卷积神经网络(如VGGNet)需要大量的计算资源和时间。为此,我们可以利用在大型数据集(如ImageNet)上预训练的模型,进行迁移学习,以便在较小的数据集上快速获得较好的性能。本文将通过一个五类鲜花分类的实例,详细讲解如何利用预训练的VGGNet模型进行图像分类任务。
4.1 数据准备与预处理
首先,我们需要准备好五类鲜花的图像数据集,并对其进行预处理,以适应VGGNet的输入要求。具体步骤包括调整图像大小、数据增强和批量加载。
数据集介绍
假设我们使用的是一个包含五类鲜花的图像数据集,每类约有100张图像。数据集分为训练集和验证集,分别存放在不同的文件夹中。
数据预处理
数据预处理是深度学习模型训练中的关键步骤。对于VGGNet,我们需要将输入图像调整为224×224的尺寸,并进行标准化处理。此外,为了提高模型的泛化能力,我们还可以应用数据增强技术,如随机水平翻转和随机旋转。
import torch
import numpy as np # 新增 numpy 导入
from torchvision import transforms
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder
import matplotlib.pyplot as plt# 指定PyTorch和TorchVision的版本兼容性
print(f"PyTorch version: {torch.__version__}")
print(f"TorchVision version: {torch.__version__}") # TorchVision 0.20.1与PyTorch 2.5.1兼容# 如果 MPS 可用,选择 "mps"。如果 CUDA 可用,选择 "cuda"。如果两者都不可用,选择 "cpu"。
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")# 将模型移到设备上
model.to(device)# 指定批次大小
batch_size = 4# 指定数据集路径
flower_train_path = '../01.图像分类/dataset/flower_datas/train/'
flower_val_path = '../01.图像分类/dataset/flower_datas/val/'# 定义数据预处理方式
train_transforms = transforms.Compose([transforms.Resize((224, 224)), # 调整图像大小transforms.RandomHorizontalFlip(), # 随机水平翻转transforms.RandomRotation(15), # 随机旋转transforms.ToTensor(), # 转换为Tensortransforms.Normalize([0.485, 0.456, 0.406], # 标准化[0.229, 0.224, 0.225])
])val_transforms = transforms.Compose([transforms.Resize((224, 224)), # 调整图像大小transforms.ToTensor(), # 转换为Tensortransforms.Normalize([0.485, 0.456, 0.406], # 标准化[0.229, 0.224, 0.225])
])# 加载训练集和验证集
flower_train = ImageFolder(root=flower_train_path, transform=train_transforms)
flower_val = ImageFolder(root=flower_val_path, transform=val_transforms)# 创建数据加载器
train_loader = DataLoader(dataset=flower_train, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(dataset=flower_val, batch_size=batch_size, shuffle=False)# 获取类别名称
classes = flower_train.classes
print(f"类别名称: {classes}")# 可视化一个批次的数据
def imshow(inp, title=None):"""显示一个Tensor图像"""inp = inp.numpy().transpose((1, 2, 0)) # 转换为 (H, W, C) 格式mean = np.array([0.485, 0.456, 0.406]) # 转换为 numpy 数组std = np.array([0.229, 0.224, 0.225]) # 转换为 numpy 数组inp = std * inp + mean # 反标准化inp = np.clip(inp, 0, 1) # 将像素值限制在 0 到 1 之间plt.imshow(inp) # 显示图像if title:plt.title(title) # 如果提供了标题,则显示类别名称作为标题plt.pause(0.001) # 暂停以确保图像显示# 展示训练集中的一个batch
inputs, classes_idx = next(iter(train_loader)) # 从训练集加载器中获取一个批次的数据
out = torchvision.utils.make_grid(inputs) # 将多个图像拼接成网格imshow(out, title=[classes[x] for x in classes_idx]) # 显示拼接后的图像,并显示对应的类别名称
plt.show() # 显示图像窗口
结果展示:
图片示例来自自定义数据集
代码解析
-
导入必要的库:包括
torch
、torchvision
及其相关模块。 -
设置设备:判断是否有GPU可用,以加速训练过程。
-
定义数据预处理:
-
训练集:包括随机水平翻转和随机旋转的数据增强,以提高模型的泛化能力。
-
验证集:仅调整图像大小和标准化,确保评估时数据的一致性。
-
-
加载数据集:使用
ImageFolder
读取指定路径下的图像数据,并应用相应的预处理。 -
创建数据加载器:使用
DataLoader
批量加载数据,设置适当的批次大小和是否打乱数据。 -
获取类别名称:从训练集中提取类别标签,便于后续的结果解读。
-
可视化数据:通过
matplotlib
展示一个批次的图像,帮助理解数据分布。
4.2 模型实例化与参数调整
接下来,我们将实例化预训练的VGG-16模型,并根据五类鲜花分类任务进行调整。具体步骤包括加载预训练权重、冻结卷积层参数、调整全连接层以适应五分类任务等。
from torchvision.models import vgg16, VGG16_Weights
import torch.nn as nn# 1. 实例化模型
# 指定 weights=VGG16_Weights.IMAGENET1K_V1 来获取官方的 ImageNet 预训练权重
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)# 查看模型结构(可选)
#print(model)# 2. 冻结卷积层的参数,以防止在训练过程中更新
for param in model.features.parameters():param.requires_grad = False# 3. 修改全连接层以适应5类分类任务
# VGG-16的classifier包含3个全连接层,最后一层输出为1000类
# 我们将其修改为5类
num_features = model.classifier[6].in_features
model.classifier[6] = nn.Linear(num_features, 5)# 将模型移动到设备(GPU或CPU)
model = model.to(device)# 4. 查看修改后的模型结构(可选)
#print(model)
代码解析
-
加载预训练模型:
-
使用
vgg16
函数,并指定weights=VGG16_Weights.IMAGENET1K_V1
来加载在ImageNet数据集上预训练的权重。
-
-
冻结卷积层参数:
-
通过设置
param.requires_grad = False
,冻结卷积层的参数,避免在训练过程中更新。这有助于减少训练时间和防止过拟合,尤其在数据集较小时效果显著。
-
-
调整全连接层:
-
VGG-16的全连接层
classifier
的最后一层(classifier[6]
)原本输出为1000类。我们将其替换为输出5类,以适应五类鲜花分类任务。
-
-
移动模型到设备:
-
将模型移动到GPU(如果可用)或CPU,确保后续的训练和推理过程在指定设备上进行。
-
4.3 模型训练与保存最优模型
在完成模型实例化和参数调整后,我们将定义训练过程,包括前向传播、损失计算、反向传播和参数更新。此外,我们还将实现保存验证集上表现最优的模型,以便在训练完成后进行加载和预测。
import torch.optim as optim
from tqdm import tqdm # 用于显示训练进度条
import copy # 用于保存模型的最佳权重# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# - **损失函数**:交叉熵损失函数,用于多分类问题,衡量预测值与真实标签的差异。# 只优化最后一层全连接层的参数
optimizer = optim.Adam(model.classifier.parameters(), lr=1e-4)
# - **优化器**:使用 Adam 优化算法,只针对模型的全连接层(`classifier`)进行优化。
# - **学习率 (lr)**:设置为 1e-4,控制每次参数更新的步幅。# 定义训练和验证的函数
def train_model(model, criterion, optimizer, train_loader, val_loader, num_epochs=25):"""训练和验证模型:param model: 要训练的神经网络模型:param criterion: 损失函数:param optimizer: 优化器:param train_loader: 训练数据加载器:param val_loader: 验证数据加载器:param num_epochs: 训练的总轮次:return: 训练完成后的模型和记录的训练历史"""best_model_wts = copy.deepcopy(model.state_dict())# 保存最佳模型的权重best_acc = 0.0 # 初始化最佳验证准确率# 用于记录每个epoch的训练和验证损失与准确率train_losses = [] # 记录训练集损失val_losses = [] # 记录验证集损失train_accuracies = [] # 记录训练集准确率val_accuracies = [] # 记录验证集准确率for epoch in range(num_epochs):print(f'Epoch {epoch+1}/{num_epochs}') # 打印当前轮次print('-' * 10) # 分隔线# 每个epoch都有训练和验证两个阶段for phase in ['train', 'val']:if phase == 'train':model.train() # 设置模型为训练模式(启用Dropout和BatchNorm)dataloader = train_loader # 使用训练集加载器else:model.eval() # 设置模型为评估模式(禁用Dropout和BatchNorm)dataloader = val_loader # 使用验证集加载器running_loss = 0.0 # 累积损失初始化running_corrects = 0 # 累积正确预测数初始化# 迭代数据for inputs, labels in tqdm(dataloader, desc=f'{phase}'):inputs = inputs.to(device) # 将输入数据移动到指定设备(GPU或CPU)labels = labels.to(device) # 将标签移动到指定设备# 前向传播with torch.set_grad_enabled(phase == 'train'):# 如果是训练阶段启用梯度计算,验证阶段关闭梯度计算以加速outputs = model(inputs) # 模型前向计算,得到输出_, preds = torch.max(outputs, 1) # 获取预测类别loss = criterion(outputs, labels) # 计算损失# 反向传播和优化(仅训练阶段)if phase == 'train':optimizer.zero_grad() # 清空梯度loss.backward() # 反向传播计算梯度optimizer.step() # 更新模型参数# 统计损失和正确预测数running_loss += loss.item() * inputs.size(0) # 累积损失,乘以批次大小running_corrects += torch.sum(preds == labels.data) # 累积正确预测数epoch_loss = running_loss / len(dataloader.dataset)# 平均损失:累积损失除以数据集大小epoch_acc = running_corrects.float() / len(dataloader.dataset)# 准确率:累积正确数除以数据集大小print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')# 输出当前阶段的损失和准确率# 记录损失和准确率if phase == 'train':train_losses.append(epoch_loss)train_accuracies.append(epoch_acc.item())else:val_losses.append(epoch_loss)val_accuracies.append(epoch_acc.item())# 深拷贝模型if epoch_acc > best_acc:best_acc = epoch_acc # 更新最佳准确率best_model_wts = copy.deepcopy(model.state_dict())# 保存当前模型的最佳权重torch.save(model.state_dict(), 'saved_models/best_vgg16_flower_lr0.001_bs32.pth')# 保存模型到文件中print('保存了最优模型')print() # 换行print(f'最佳验证准确率: {best_acc:.4f}')# 打印最佳验证准确率# 加载最佳模型权重model.load_state_dict(best_model_wts)# 恢复为最佳模型的权重# 返回训练过程的记录history = {'train_losses': train_losses,'val_losses': val_losses,'train_accuracies': train_accuracies,'val_accuracies': val_accuracies}return model, history# 开始训练
num_epochs = 25 # 训练的轮次数
model, history = train_model(model, criterion, optimizer, train_loader, val_loader, num_epochs=num_epochs)
# 调用训练函数,返回训练完成的模型和训练过程的记录
输出:
代码解析
-
定义损失函数和优化器:
-
使用交叉熵损失函数(
CrossEntropyLoss
)适用于多分类任务。 -
采用Adam优化器,仅优化全连接层的参数,学习率设为1e-4。
-
-
训练函数
train_model
:-
参数:
-
model
:待训练的模型。 -
criterion
:损失函数。 -
optimizer
:优化器。 -
train_loader
和val_loader
:训练和验证数据加载器。 -
num_epochs
:训练轮数。
-
-
过程:
-
初始化最佳模型权重和最佳准确率。
-
遍历每个epoch,包含训练和验证两个阶段。
-
在训练阶段,设置模型为训练模式,并进行前向传播、计算损失、反向传播和参数更新。
-
在验证阶段,设置模型为评估模式,仅进行前向传播和损失计算。
-
记录每个阶段的损失和准确率,并在验证准确率提升时保存模型权重。
-
-
返回:
-
训练完成后的最佳模型和训练过程中的记录(损失和准确率)。
-
-
-
保存最优模型:
-
当验证集准确率提升时,使用
torch.save
保存当前模型权重为saved_models/best_vgg16_flower_lr0.001_bs32.pth
-
-
训练过程的记录:
-
通过
history
字典记录每个epoch的训练和验证损失与准确率,便于后续的可视化分析。
-
4.4 模型导入与预测
在完成模型训练并保存最优模型后,我们将加载该模型,并对新图像进行预测。
from PIL import Image # 导入 PIL 库,用于打开和处理图像
from torchvision.models import vgg16, VGG16_Weights
import torch.nn as nn
from torchvision import transforms
import torch
# 如果 MPS 可用,选择 "mps"。如果 CUDA 可用,选择 "cuda"。如果两者都不可用,选择 "cpu"。
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
batch_size=1
# 1. 实例化模型
# 指定 weights=VGG16_Weights.IMAGENET1K_V1 来获取官方的 ImageNet 预训练权重
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
# 1. 实例化模型结构
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
# - 加载预训练的 VGG16 模型,权重为在 ImageNet 数据集上训练的版本 (IMAGENET1K_V1)num_features = model.classifier[6].in_features
# - 获取 VGG16 最后一层全连接层的输入特征数量model.classifier[6] = nn.Linear(num_features, 5)
# - 修改最后一层全连接层,将其输出节点数设置为 5,对应 5 个类别# 2. 加载最优模型权重
model.load_state_dict(torch.load('saved_models/best_vgg16_flower_lr0.001_bs32.pth', weights_only=True))
# - 从文件 'best_vgg16_flower.pth' 中加载之前训练好的模型权重model = model.to(device)
# - 将模型移动到指定设备(GPU 或 CPU)# 获取类别名称
classes = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']model.eval()
# - 设置模型为评估模式,禁用 Dropout 和 Batch Normalization 的训练行为# 3. 定义预测函数
def predict_image(image_path, model, transform, classes):"""对单张图像进行预测:param image_path: 图像文件路径:param model: 加载了权重的 VGG16 模型:param transform: 数据预处理方法(与训练一致):param classes: 类别名称列表:return: 预测的类别名称和置信度分数"""image = Image.open(image_path).convert('RGB')# - 打开图像文件,并将其转换为 RGB 模式(确保有 3 个通道)image = transform(image).unsqueeze(0)# - 应用数据预处理方法,将图像转换为 Tensor# - 调用 `unsqueeze(0)` 增加一个批次维度,形状从 (C, H, W) 变为 (1, C, H, W)image = image.to(device)# - 将图像数据移动到指定设备(GPU 或 CPU)with torch.no_grad():# - 禁用梯度计算(推理阶段不需要计算梯度,节省内存并加速)output = model(image)# - 将图像输入模型,获得预测输出_, predicted = torch.max(output, 1)# - 获取预测的类别索引,`torch.max` 返回最大值和对应的索引confidence = torch.softmax(output, 1)[0] * 100# - 对模型的输出应用 softmax 函数,将其转化为概率分布,并转换为百分比形式predicted_class = classes[predicted.item()]# - 使用类别索引从类别列表中获取对应的类别名称confidence_score = confidence[predicted.item()].item()# - 获取预测类别对应的置信度分数return predicted_class, confidence_score# - 返回预测的类别名称和置信度分数# 4. 定义与训练时相同的预处理方式
predict_transform = transforms.Compose([transforms.Resize((224, 224)),# - 调整图像大小为 224x224 像素transforms.ToTensor(),# - 将图像转换为 PyTorch 的张量格式 (C, H, W)transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])# - 使用 ImageNet 数据集的均值和标准差进行标准化
])# 5. 进行预测
test_image_path = '../01.图像分类/dataset/flower_datas/val/daisy/105806915_a9c13e2106_n.jpg'
# - 指定测试图像的路径,请替换为实际的图像文件路径predicted_class, confidence_score = predict_image(test_image_path, model, predict_transform, classes)
# - 调用预测函数,获取预测的类别名称和置信度分数print(f'预测类别: {predicted_class}, 置信度: {confidence_score:.2f}%')
# - 打印预测结果,包括类别名称和置信度百分比
输出打印:
代码解析
-
实例化模型结构:
-
重新实例化VGG-16模型,并调整全连接层以适应5类分类任务。
-
-
加载最优模型权重:
-
使用
torch.load
加载保存的最优模型权重best_vgg16_flower_lr0.001_bs32.pth
。 -
将模型移动到设备,并设置为评估模式(
model.eval()
)。
-
-
定义预测函数
predict_image
:-
接受图像路径、模型、预处理方法和类别名称作为输入。
-
读取并预处理图像,添加批次维度后移动到设备。
-
通过模型进行前向传播,获取预测结果。
-
使用
torch.softmax
计算置信度分数,并返回预测类别及其置信度。
-
-
定义预测时的预处理方式:
-
与训练时保持一致,确保图像的尺寸和标准化方式相同。
-
-
进行预测:
-
指定测试图像路径,调用预测函数,输出预测类别及其置信度。
-
4.5 训练过程的可视化
为了更直观地理解模型的训练过程,我们将可视化训练和验证阶段的损失与准确率变化趋势。这有助于判断模型是否出现过拟合或欠拟合,并指导进一步的模型优化。
import matplotlib.pyplot as plt# 绘制训练和验证损失曲线
def plot_loss(history):plt.figure(figsize=(10,5))plt.plot(history['train_losses'], label='训练损失')plt.plot(history['val_losses'], label='验证损失')plt.xlabel('Epoch')plt.ylabel('损失')plt.title('训练与验证损失曲线')plt.legend()plt.show()# 绘制训练和验证准确率曲线
def plot_accuracy(history):plt.figure(figsize=(10,5))plt.plot(history['train_accuracies'], label='训练准确率')plt.plot(history['val_accuracies'], label='验证准确率')plt.xlabel('Epoch')plt.ylabel('准确率')plt.title('训练与验证准确率曲线')plt.legend()plt.show()# 调用绘图函数
plot_loss(history)
plot_accuracy(history)
代码解析
-
定义绘图函数
plot_loss
:-
接受
history
字典,绘制训练和验证阶段的损失曲线。 -
X轴为epoch,Y轴为损失值。
-
添加图例、标题和标签以增强可读性。
-
-
定义绘图函数
plot_accuracy
:-
接受
history
字典,绘制训练和验证阶段的准确率曲线。 -
X轴为epoch,Y轴为准确率。
-
添加图例、标题和标签以增强可读性。
-
-
调用绘图函数:
-
使用训练过程中记录的损失和准确率数据,生成可视化图表。
-
结果展示:
损失曲线示例
准确率曲线示例
可视化分析
通过损失和准确率曲线,我们可以观察到模型在训练过程中的表现:
-
损失曲线:训练损失逐渐下降,验证损失在初期下降后趋于平稳,甚至可能出现轻微上升,提示模型可能开始过拟合。
-
准确率曲线:训练准确率逐渐提高,验证准确率在初期上升后趋于稳定,验证集准确率的提升放缓,进一步提示过拟合的风险。
这些观察结果有助于我们决定是否需要进一步优化模型,如引入正则化、调整学习率或增加数据量等。
模型优化与防止过拟合
在前面的训练过程中,我们可能会观察到模型在训练集上表现良好,但在验证集上表现欠佳,或验证集准确率不再提升,甚至下降。这通常是由于模型过于复杂或数据不足导致的过拟合现象。为了解决这些问题,我们可以采取以下几种优化方法:
1. 使用更深层的预训练模型
虽然VGGNet已经表现出色,但在某些任务中,使用更深层或更先进的模型(如ResNet、DenseNet等)可能会带来更好的性能。然而,考虑到本文的教学目的,我们继续使用VGGNet。
2. 数据增强
数据增强通过对训练图像进行随机变换,增加数据的多样性,提升模型的泛化能力。我们已经在数据预处理中应用了一些基本的数据增强技术,如随机水平翻转和随机旋转。根据需要,还可以添加更多的数据增强方法,如随机裁剪、颜色抖动等。
train_transforms = transforms.Compose([transforms.Resize((224, 224)),# - 调整图像大小为 224x224 像素# - 目的:统一图像输入尺寸,方便后续模型处理transforms.RandomHorizontalFlip(),# - 随机水平翻转图像,概率为 50%# - 目的:通过翻转图像增强数据集,增加模型的鲁棒性transforms.RandomRotation(15),# - 随机旋转图像,旋转角度范围在 ±15 度之间# - 目的:使模型更具泛化能力,适应旋转视角的变化transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.2),# - 颜色抖动,随机调整图像的亮度、对比度、饱和度和色调# - 参数解释:# brightness=0.2:亮度随机变化范围为 ±20%# contrast=0.2:对比度随机变化范围为 ±20%# saturation=0.2:饱和度随机变化范围为 ±20%# hue=0.2:色调随机变化范围为 ±20%(值范围 -0.5 到 0.5)# - 目的:增强数据集的多样性,让模型适应不同光线和色彩条件transforms.ToTensor(),# - 将图像转换为 PyTorch 张量(Tensor),并归一化到 [0, 1] 范围# - 转换格式为 (H, W, C) → (C, H, W),适配 PyTorch 模型输入transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])# - 按照 ImageNet 数据集的均值和标准差对图像进行标准化# - 均值:[0.485, 0.456, 0.406](分别对应 RGB 三个通道)# - 标准差:[0.229, 0.224, 0.225](分别对应 RGB 三个通道)# - 目的:让模型适应 ImageNet 预训练模型的输入要求,加速收敛并提高准确性
])
4. 引入正则化技术
Dropout 是一种有效的正则化技术,通过在训练过程中随机丢弃一部分神经元,防止模型过拟合。VGGNet的全连接层已经包含了Dropout层,但我们可以根据需要调整其概率或在卷积层中引入额外的Dropout层。
# 修改全连接层,调整Dropout概率
model.classifier = nn.Sequential(nn.Linear(25088, 4096),nn.ReLU(),nn.Dropout(0.5), # Dropout概率调整为0.5nn.Linear(4096, 4096),nn.ReLU(),nn.Dropout(0.5),nn.Linear(4096, 5)
)
model = model.to(device)
5. 早停法(Early Stopping)
早停法通过监控验证集的性能,在验证集性能不再提升时提前停止训练,防止模型过拟合。
import torch
import torch.optim as optim
from tqdm import tqdm
import copy# 定义使用早停法的模型训练函数
def train_model_with_early_stopping(model, criterion, optimizer, train_loader, val_loader, num_epochs=25, patience=5):"""使用早停法训练模型:param model: 神经网络模型:param criterion: 损失函数:param optimizer: 优化器:param train_loader: 训练集数据加载器:param val_loader: 验证集数据加载器:param num_epochs: 总训练轮次:param patience: 早停的耐心值(连续多少个 epoch 验证准确率不提升时停止训练):return: 训练完成的模型和训练记录"""best_model_wts = copy.deepcopy(model.state_dict())# 保存最佳模型的权重best_acc = 0.0 # 初始化最佳验证准确率epochs_no_improve = 0 # 连续验证准确率未提升的 epoch 数# 用于记录每个 epoch 的损失和准确率train_losses = []val_losses = []train_accuracies = []val_accuracies = []for epoch in range(num_epochs):print(f'Epoch {epoch+1}/{num_epochs}') # 打印当前 epochprint('-' * 10) # 分隔线for phase in ['train', 'val']:if phase == 'train':model.train() # 设置模型为训练模式dataloader = train_loader # 使用训练集数据加载器else:model.eval() # 设置模型为评估模式dataloader = val_loader # 使用验证集数据加载器running_loss = 0.0 # 累积损失running_corrects = 0 # 累积正确预测数# 遍历每个批次for inputs, labels in tqdm(dataloader, desc=f'{phase}'):inputs = inputs.to(device) # 将输入数据移动到指定设备(GPU 或 CPU)labels = labels.to(device) # 将标签移动到指定设备# 前向传播with torch.set_grad_enabled(phase == 'train'):outputs = model(inputs) # 计算模型输出_, preds = torch.max(outputs, 1) # 获取预测类别loss = criterion(outputs, labels) # 计算损失# 反向传播和优化(仅训练阶段)if phase == 'train':optimizer.zero_grad() # 清空梯度loss.backward() # 反向传播计算梯度optimizer.step() # 更新模型参数# 累积损失和正确预测数running_loss += loss.item() * inputs.size(0)# - `loss.item()` 是批次的平均损失,需要乘以批次大小恢复总损失running_corrects += torch.sum(preds == labels.data)# - 统计正确预测的样本数# 计算每个 epoch 的平均损失和准确率epoch_loss = running_loss / len(dataloader.dataset)# - 总损失除以数据集大小,得到平均损失epoch_acc = running_corrects.double() / len(dataloader.dataset)# - 正确预测数除以数据集大小,得到准确率print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')# 打印当前阶段的损失和准确率# 记录损失和准确率if phase == 'train':train_losses.append(epoch_loss)train_accuracies.append(epoch_acc.item())else:val_losses.append(epoch_loss)val_accuracies.append(epoch_acc.item())# 检查是否为最佳模型if epoch_acc > best_acc:best_acc = epoch_acc # 更新最佳验证准确率best_model_wts = copy.deepcopy(model.state_dict())# 保存当前模型的最佳权重torch.save(model.state_dict(), 'best_vgg16_flower.pth')print('保存了最优模型')epochs_no_improve = 0 # 重置耐心计数else:epochs_no_improve += 1 # 验证准确率未提升# 如果连续未提升的 epoch 数达到耐心值,则触发早停if epochs_no_improve >= patience:print('早停法触发,停止训练')model.load_state_dict(best_model_wts)# 加载最佳模型权重history = {'train_losses': train_losses,'val_losses': val_losses,'train_accuracies': train_accuracies,'val_accuracies': val_accuracies}return model, historyprint() # 每个 epoch 结束后换行print(f'最佳验证准确率: {best_acc:.4f}')# 打印最佳验证准确率model.load_state_dict(best_model_wts)# 加载最佳模型权重# 保存训练过程的记录history = {'train_losses': train_losses, # 每个 epoch 的训练损失'val_losses': val_losses, # 每个 epoch 的验证损失'train_accuracies': train_accuracies, # 每个 epoch 的训练准确率'val_accuracies': val_accuracies # 每个 epoch 的验证准确率}return model, history # 返回最佳模型和训练记录# 设置优化器为 AdamW
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
# - 使用 AdamW 优化器
# - 参数:
# - `lr=1e-3`:学习率设置为 0.001
# - `weight_decay=1e-2`:权重衰减因子,用于 L2 正则化,防止过拟合# 使用早停法进行训练
model, history = train_model_with_early_stopping(model, criterion, optimizer, train_loader, val_loader, num_epochs=25, patience=5
)
6. 模型优化总结
通过上述优化方法,我们可以显著提升VGGNet在五类鲜花分类任务中的性能,具体包括:
-
利用预训练模型:加速训练过程,提升初始性能。
-
数据增强:增加数据多样性,提升模型泛化能力。
-
学习率调度:动态调整学习率,优化训练过程。
-
正则化技术:防止模型过拟合,提升验证集性能。
-
早停法:避免过度训练,保存最佳模型。
这些优化方法在实际应用中相辅相成,共同提升模型的整体表现。
总结与展望
本文详细介绍了VGGNet的架构及其在图像分类任务中的应用,特别是基于预训练模型进行五类鲜花分类的实战案例。通过数据准备与预处理、模型实例化与参数调整、模型训练与保存最优模型、模型导入与预测以及训练过程的可视化,全面展示了如何在PyTorch 2.5.1与TorchVision 0.20.1环境下高效应用VGGNet。
尽管VGGNet在图像分类任务中表现出色,但其庞大的参数量和计算资源需求也带来了挑战。随着深度学习技术的不断进步,更多高效且精确的网络架构如ResNet、DenseNet等被提出,未来的研究将继续探索更为高效的网络结构和训练方法。
通过本文的学习,读者不仅掌握了VGGNet的基本知识和实现方法,还了解了如何在实际项目中优化模型,提升其性能。希望本文能为您的深度学习之路提供有力的支持和指导。
参考文献
-
Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for Large-Scale Image Recognition. arXiv:1409.1556
-
PyTorch Documentation. torchvision.models.VGG. Retrieved from Models and pre-trained weights — Torchvision 0.20 documentation
-
Wikipedia. VGGNet. Retrieved from https://en.wikipedia.org/wiki/VGG_net
-
CSDN博客. VGGNet详解与实现. Retrieved from https://blog.csdn.net
如果觉得文章有用,辛苦点赞,收藏,转发,关注一波,如果有其他不懂的可以私信我哈~
你想用深度学习解决哪个现实世界的那些问题?在评论区告诉我们
相关文章:
【计算机视觉CV-图像分类】06 - VGGNet的鲜花分类实现:从数据预处理到模型优化的完整实战!
目录 引言 VGGNet概述 VGGNet的网络架构 基于预训练VGGNet的五类鲜花分类实现 4.1 数据准备与预处理 4.2 模型实例化与参数调整 4.3 模型训练与保存最优模型 4.4 模型导入与预测 4.5 训练过程的可视化 模型优化与防止过拟合 总结与展望 参考文献 引言 在计算机视觉…...
QT 控件定义为智能指针引发的bug
问题描述: std::unique_ptr<QStackedLayout> m_stacked_layout; 如上为定义; 调用: Line13ABClient::Line13ABClient(QWidget *parent) : BaseWidget(parent) { // 成员变量初始化 m_get_ready false; m_tittle_wnd…...
@register_model 装饰器
使用 register_model 装饰器来注册模型类有以下几个优势: 自动化注册: 通过装饰器自动将模型类注册到一个全局字典中,避免了手动注册的繁琐操作,使代码更加简洁和易于维护。 易于扩展: 可以方便地添加新模型ÿ…...
图像处理-Ch7-图像金字塔和其他变换
Ch7 小波变换&其他图像变换 文章目录 Ch7 小波变换&其他图像变换背景知识(bk)图像金字塔(Image Pyramid)子带编码(Sub-band Coding)Z - 变换(线性变换)完美重建滤波器组(PCFB, Perfect Construction Filter Banks)有限脉冲响应(FIR)滤波器双正交…...
解决在vue3+vite+element-plus 中echarts在el-dialog无法正常显示问题
核心:在dom加载完成后调用echarts实例 的resize()方法 这里是一个例子 这里封装一个echarts <template><div class"container" ref"container"></div> </template> <script lang"ts" setup> import {…...
SQL进阶技巧:如何计算摆动的序列?| LeetCode 376.-摆动序列
目录 0 摆动序列 1 数据准备 2 问题分析 3 小结 0 摆动序列 原题链接:376. 摆动序列 - 力扣(LeetCode) 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的…...
告别卡顿:探索 Java FGC 的根源与高效解决方案
Java Full Garbage Collection (FGC) 的产生原因及解决办法 引言 在Java应用程序的生命周期中,垃圾回收(Garbage Collection, GC)是确保内存有效管理的关键机制。然而,当发生Full Garbage Collection(FGC)…...
MySQL索引为什么是B+树
MySQL索引为什么是B树 索引是帮助MySQL高效获取数据的数据结构,在数据之外,数据库还维护着满足特定查找算法的数据结构B树,这些数据结果以某种特定的方式引用数据,这样就可以在这些数据结构上实现高级查找算法,提升数据…...
准备考试:解决大学入学考试问题
引言 在编程竞赛和算法挑战中,我们经常会遇到各种类型的组合问题。这些问题不仅考验我们的逻辑思维能力,还要求我们熟练掌握数据结构和算法。在这篇文章中,我们将探讨一个有趣的问题——“准备考试”,这个问题来自于一个虚构的情…...
vue3中如何自定义插件
英译汉插件 i18n.ts export default {install: (app: any, options: any) > {// 注入一个全局可用的$translate()方法app.config.globalProperties.$translate (key: string) > {// 获取options对象的深层属性// 使用key作为索引return key.split(".").redu…...
Linux应用软件编程-多任务处理(进程)
多任务:让系统具备同时处理多个事件的能力。让系统具备并发性能。方法:进程和线程。这里先讲进程。 进程(process):正在执行的程序,执行过程中需要消耗内存和CPU。 进程的创建:操作系统在进程创…...
PyCharm专项训练5 最短路径算法
一、实验目的 本文的实验目的是通过编程实践,掌握并应用Dijkstra(迪杰斯特拉)算法和Floyd(弗洛伊德)算法来解决图论中的最短路径问题。 二、实验内容 数据准备: 使用邻接表的形式定义两个图graph_dijkstra…...
“AI+Security”系列第4期(一)之“洞” 见未来:AI 驱动的漏洞挖掘新范式
在数字化浪潮下,安全漏洞问题日益严峻,成为各行业发展的重大挑战。近日,“AISecurity” 系列第 4 期线下活动于北京成功举办,聚焦 “洞” 见未来:AI 驱动的漏洞挖掘新范式,汇聚了安全领域的众多专家。 本次…...
安卓蓝牙扫描流程
目录 系统广播 流程图 源码跟踪 系统广播 扫描开启广播:BluetoothAdapter.ACTION_DISCOVERY_STARTED "android.bluetooth.adapter.action.DISCOVERY_STARTED";扫描关闭广播:BluetoothAdapter.ACTION_DISCOVERY_FINISHED "android.b…...
【视觉惯性SLAM:对极几何】
对极几何(Epipolar Geometry)介绍 对极几何是立体视觉中的核心内容之一,它描述了两个相机在观察同一个三维场景时,成像平面之间的几何关系。对极几何能够约束图像中对应点的位置关系,是双目立体匹配、三维重建、以及位…...
Stream `Collectors.toList()` 和 `Stream.toList()` 的区别(Java)
Stream Collectors.toList() 和 Stream.toList() 的区别 问题背景 在以下代码中: Test void test() {JSONArray nodes new JSONArray();String[] names {"df1", "df2", "df3"};for (String name : names) {JSONObject obj new …...
【Python知识】Python面向对象编程知识
Python面向对象编程知识 概述1. 类(Class)2. 对象(Object)3. 封装(Encapsulation)4. 继承(Inheritance)5. 多态(Polymorphism)6. 抽象(Abstractio…...
安卓帧率获取
背景 性能优化,经常用到一些指标,诸如帧率、功耗等。对于普通app来讲, 之前一直使用gfxinfo指令获取丢帧率。但是这个指令无法获取游戏的帧率,查阅资料,发现SurfaceFlinger可以获取游戏帧率。 帧率获取原理 获取当前f…...
shell脚本(全)
shell脚本概述 第一个shell脚本 shell注释 shell变量 shell位置参数 shell字符串 shell内置命令 shell命令替换 输出 流程控制IF export命令 退出脚本 运行Shell脚本 实例导航 shell脚本概述 在说什么是shell脚本之前,先说说什么是shell。 从程序员的…...
Flask-----SQLAlchemy教程
存session session[username] username # 存储数据到 session 取session username session.get(username) render_template return render_template(index.html, usernameAlice),渲染一个包含 username 变量的模板。 redirect return redirect(url_for(profil…...
【C++11】可变模板参数
目录 可变模板的定义方式 参数包的展开方式 递归的方式展开参数包 STL中的emplace相关接口函数 STL容器中emplace相关插入接口函数 编辑 模拟实现:emplace接口 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比 C9…...
.NET开发人员学习书籍推荐
作为一名.NET开发人员,掌握相关技术是提升开发能力和拓展职业发展的关键。无论你是刚入门的新人,还是希望精进技术的资深开发者,选择合适的学习资源至关重要。下面是一些经典且实用的学习书籍推荐,帮助你在C#、SQL、前端开发等方面…...
jupyter切换内核方法配置问题总结
下面这个博客总结了3种不同的方法,很有调理,推荐尝试 【最全指南】如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境? !!! 注意使用上面介绍的ipykernel方法2, 要在每一个希望被jupyter识别到的环境内【分别】安装ipykernel以及添加配置 …...
SVM理论推导
本文介绍支持向量机(SVM)的理论推导。 一、SVM 的基本思想 SVM 的目标是找到一个最优超平面,将样本分为不同的类别,并最大化类别间的间隔。 1. 线性可分情况下: 在特征空间中找到一个超平面,使得&#…...
如何永久解决Apache Struts文件上传漏洞
Apache Struts又双叒叕爆文件上传漏洞了。 自Apache Struts框架发布以来,就存在多个版本的漏洞,其中一些漏洞涉及到文件上传功能。这些漏洞可能允许攻击者通过构造特定的请求来绕过安全限制,从而上传恶意文件。虽然每次官方都发布补丁进行修…...
【Java数据结构与算法】第10-14章
第10章 树结构的基础部分 10.1 二叉树 10.1.1 为什么需要树这种数据结构 10.1.2 树示意图 10.1.3 二叉树的概念 10.1.4 二叉树遍历的说明 10.1.5 二叉树遍历应用实例(前序,中序,后序) 10.1.6 二叉树-查找指定节点 思路图解 10.1.7 二叉树-删除节点 package com.atguigu.tree;…...
MacOS M3源代码编译Qt6.8.1
编译时间过长,如果不想自己编译,可以通过如果网盘进行下载: 链接: https://pan.baidu.com/s/17lvF5jQ-vR6vE-KEchzrVA?pwdts26 提取码: ts26 在macOS上编译Qt 6需要一些前置步骤和工具。以下是编译Qt 6的基本步骤: 安装Xcode和…...
3.银河麒麟V10 离线安装Nginx
1. 下载nginx离线安装包 前往官网下载离线压缩包 2. 下载3个依赖 openssl依赖,前往 官网下载 pcre2依赖下载,前往Git下载 zlib依赖下载,前往Git下载 下载完成后完整的包如下: 如果网速下载不到请使用网盘下载 通过网盘分享的文件…...
实现 QTreeWidget 中子节点勾选状态的递归更新功能只影响跟节点的状态父节点状态不受影响
在 Qt 开发中,QTreeWidget 提供了树形结构的显示和交互功能。为了实现某个子节点勾选或取消勾选时,只影响当前节点及其子节点的状态,同时递归更新父节点的状态以正确显示 Qt::PartiallyChecked 或 Qt::Checked,我们可以借助 Qt 的…...
ubuntu24.04使用opencv4
ubuntu24.04LTS自带opencv4.5代码实例 //opencv_example.cpp #include <opencv2/opencv.hpp> #include <iostream>int main() {// 读取图像cv::Mat img cv::imread("image.jpg", cv::IMREAD_COLOR);if (img.empty()) {std::cerr << "无法读…...
R语言数据分析案例46-不同区域教育情况回归分析和探索
一、研究背景 教育是社会发展的基石,对国家和地区的经济、文化以及社会进步起着至关重要的作用。在全球一体化进程加速的今天,不同区域的教育发展水平呈现出多样化的态势。这种差异不仅体现在教育资源的分配上,还表现在教育成果、教育投入与…...
flink sink doris
接上文:一文说清flink从编码到部署上线 网上关于flink sink drois的例子较多,大部分不太全面,故本文详细说明,且提供完整代码。 flink doris版本对照表 1.添加依赖 <!--doris cdc--><!-- 参考:"https…...
《探索 Apache Spark MLlib 与 Java 结合的卓越之道》
在当今大数据与人工智能蓬勃发展的时代,Apache Spark MLlib 作为强大的机器学习库,与广泛应用的 Java 语言相结合,为数据科学家和开发者们提供了丰富的可能性。那么,Apache Spark MLlib 与 Java 结合的最佳实践究竟是什么呢&#…...
Net9解决Spire.Pdf替换文字后,文件格式乱掉解决方法
官方文档 https://www.e-iceblue.com/Tutorials/Spire.PDF/Program-Guide/Text/Find-and-replace-text-on-PDF-document-in-C.html C# 在 PDF 中查找替换文本 原文件如下图,替换第一行的新编码,把41230441044替换为41230441000 替换代码如下ÿ…...
Kafka可视化工具 Offset Explorer (以前叫Kafka Tool)
数据的存储是基于 主题(Topic) 和 分区(Partition) 的 Kafka是一个高可靠性的分布式消息系统,广泛应用于大规模数据处理和实时, 为了更方便地管理和监控Kafka集群,开发人员和运维人员经常需要使用可视化工具…...
青少年编程与数学 02-004 Go语言Web编程 21课题、应用部署
青少年编程与数学 02-004 Go语言Web编程 21课题、应用部署 一、应用部署二、GoWeb部署到WINDOWS系统中1. 安装Go环境2. 创建并编写Go Web应用3. 初始化Go模块4. 编译Go Web应用5. 配置和运行Nginx6. 运行Go Web应用7. 访问应用总结 三、GoWeb部署到LINUX系统中1. 准备Linux服务…...
009-spring-bean的实例化流程
1 spring容器初始化时,将xml配置的bean 信息封装在 beandefinition对象 2 所有的beandefinition存储在 beandefinitionMap的map集合中 3 spring对map进行遍历,使用反射创建bean实例对象 4 创建好的bean存在名为singletonObjects的map集合中 5 调用ge…...
Timsort算法
Timsort算法是一种混合、稳定且高效的排序算法,源自归并排序和插入排序。它通过将已识别的子序列(称为“run”)与现有run合并直到满足某些条件来完成排序。以下是对Timsort算法的详细解释及举例说明: Timsort算法概述 混合性&…...
uniapp+vue 前端防多次点击表单,防误触多次请求方法。
最近项目需求写了个uniappvue前端H5,有个页面提交表单的时候发现会有用户乱点导致数据库多条重复脏数据。故需要优化,多次点击表单只请求一次。 思路: 直接调用uni.showToast,点完按钮跳一个提交成功的提示。然后把防触摸穿透mask设置成true就行&#…...
八、Hbase
Hbase 一、NoSQL非关系型数据库简介1.NoSQL 的起因2.NoSQL 的特点3.NoSQL 面临的挑战4.NoSQL 的分类 二、HBase数据库概述1.HBase数据库简介2.HBase数据模型简介3.HBase数据模型基本概念4.Hbase概念视图(逻辑视图)5.Hbase物理视图6.Hbase主要组件7.Hbase安装8.Hbase的数据读写流…...
ubuntu安装sublime安装与免费使用
1. ubuntu安装sublime 参考官网: Linux Package Manager Repositories 2. 破解过程 打开如下网址,打开/opt/sublime_text/sublime_text https://hexed.it/ 3. 替换在hexed打开的文件中查找并替换: 4180激活方法 使用二进制编辑器 8079 0500 0f94 c2替换为 c641 05…...
Onedrive精神分裂怎么办(有变更却不同步)
Onedrive有时候会分裂,你在本地删除文件,并没有同步到云端,但是本地却显示同步成功。 比如删掉了一个目录,在本地看已经删掉,onedrive显示已同步,但是别的电脑并不会同步到这个删除操作,在网页版…...
图像裁剪与批量推理:解决分割和变化检测中的大图处理问题
引言 在分割、变化检测等任务中,我们经常会遇到一个问题:模型的输入尺寸是固定且较小的(如256256或512512)。当需要处理分辨率较高的大图时,直接输入到模型中显然是不切实际的。那么,如何高效地解决这个问…...
第4章 函数
2024年12月25日一稿 4.1 函数的定义 4.1.1 函数和像 4.1.2 函数的性质 4.1.3 常用函数 4.2 复合函数和反函数 4.2.1 复合函数 4.2.2 反函数 4.3 特征函数与模糊子集 4.4 基数的概念 4.4.1 后继与归纳集 4.4.2 自然数,有穷集,无穷集 4.4.3 基数 4.5 可数…...
【JavaEE进阶】Spring传递请求参数
目录 🎍序言 🌴传递单个参数 🍀传递多个参数 🎄传递对象 🌳后端参数重命名(后端参数映射) 🚩ReuqestParam注解 🎍序言 访问不同的路径,就是发送不同的请求.在发送…...
在跨平台开发环境中构建高效的C++项目:从基础到最佳实践20241225
在跨平台开发环境中构建高效的C项目:从基础到最佳实践 引言 在现代软件开发中,跨平台兼容性和高效开发流程是每个工程师追求的目标。尤其是对于 C 开发者,管理代码的跨平台构建以及调试流程可能成为一项棘手的挑战。在本文中,我…...
无人零售及开源 AI 智能名片 S2B2C 商城小程序的深度剖析
摘要:本文聚焦无人零售这一新兴零售模式及其发展浪潮中崛起的开源 AI 智能名片 S2B2C 商城小程序。深入阐述无人零售的发展态势,细致剖析其驱动因素、现存问题,全面详细介绍小程序的功能特性、应用优势以及对无人零售的潜在价值,旨…...
PCL点云库入门——PCL库点云滤波算法之直通滤波(PassThrough)和条件滤波(ConditionalRemoval)
0、滤波算法概述 PCL点云库中的滤波算法是处理点云数据不可或缺的一部分,它们能够有效地去除噪声、提取特征或进行数据降维。例如,使用体素网格滤波(VoxelGrid)可以减少点云数据量,同时保留重要的形状特征。此外&#…...
v语言介绍
V 语言是一种多用途的编程语言,可以用于前端开发、后端开发、系统编程、游戏开发等多个领域。它的设计哲学是提供接近 C 语言的性能,同时简化开发过程并提高代码的安全性和可读性。接下来我会详细介绍 V 在前后端开发中的应用,并给出一个具体…...
GPT-O3:简单介绍
GPT-O3:人工智能领域的重大突破 近日,OpenAI发布了其最新的AI模型GPT-O3,这一模型在AGI评估中取得了惊人的成绩,展现出强大的能力和潜力。GPT-O3的出现标志着人工智能领域的重大进步,预计将在2025年实现更大的突破。 …...