PyTorch深度学习实战 | 基于神经网络的水质分类

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 代码实现# 定义模型x = x.view(x.size(0), -1) # 展平成 (batch_size, 3072)x = self.fc3(x) # 输出return xprint("--- network.py 测试结果 ---")print(f"模型输出尺寸 (Batch, Classes): {output.shape}")语言描述步骤操作 / 组件输入数据形状输出数据形状核心作用1输入数据 (x)原始图像数据(B 为batch_size2展平 (Flatten)

 实战内容

                本文主要介绍使用神经网络实现对水质的检测,最后我们设计了一个UI页面。

说明1:这个项目训练集和测试集使用是的相同的图片,因为我的数据太少了,所以这里我这样做

了。实际是不允许的

说明2:这个项目和之前我写的一个项目是相同的,只是下面的代码用的pytorch是2.8的所以,有

些代码做了些改动。

说明3:我还对最后的UI页面做了一些美化,具体的内容上也做了更加详细的介绍。下面一起来看

一下这个项目吧。

提取数据集

      链接:https://pan.baidu.com/s/1DSDl5uKF0qaoyVs3f-L7iQ?pwd=wy46 

      提取码:wy46

数据集介绍

image.gif

思路:

定义网络结构(network.py):定义神经网络结构

训练(train.py):数据集的读取,训练

评估(evaluation.py):对模型评估

可视化界面(UI.py):可视化的UI页面

image.gif


定义网络结构

image.gif

代码实现

import torch
# 定义模型
class WaterQualityNet(torch.nn.Module):
    def __init__(self, input_size=32*32*3, hidden_size=128, num_classes=2):
        super(WaterQualityNet, self).__init__()
        self.fc1 = torch.nn.Linear(input_size, hidden_size)
        self.fc2 = torch.nn.Linear(hidden_size, hidden_size)
        self.fc3 = torch.nn.Linear(hidden_size, num_classes)
        self.relu = torch.nn.ReLU()
    def forward(self, x):
        x = x.view(x.size(0), -1)  # 展平成 (batch_size, 3072)
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)  # 输出
        return x
if __name__ == '__main__':
    model = WaterQualityNet()
    test_input = torch.randn(2, 3, 32, 32)
    output = model(test_input)
    print("--- network.py 测试结果 ---")
    print(f"模型输出尺寸 (Batch, Classes): {output.shape}")

image.gif

语言描述

步骤 操作 / 组件 输入数据形状 输出数据形状 核心作用
1 输入数据 (x) (B,3,32,32) (B,3,32,32) 原始图像数据(B 为 batch_size)。
2 展平 (Flatten) (B,3,32,32) (B,3072) 将图像从空间结构展平,适配 MLP 的全连接层。
3 第一层全连接 (fc1) (B,3072) (B,128) 将 3072 个输入特征线性映射到 128 个隐藏特征。
4 ReLU 激活 (relu) (B,128) (B,128) 引入非线性,激活神经元。
5 第二层全连接 (fc2) (B,128) (B,128) 在隐藏空间中进一步精炼特征。
6 ReLU 激活 (relu) (B,128) (B,128) 第二次引入非线性。
7 第三层全连接 (fc3) (B,128) (B,2) 将隐藏特征映射到最终的类别空间。
8 输出 (B,2) (B,2) 最终输出 2 个类别的 Logits(原始分数)。

训练

Dataset 和 DataLoader

   假设我现在生成一个房价的数据集:10个样本,特征X是面积和房龄,标签Y是房价

X_features = np.random.rand(10, 2) * 100 # 面积和房龄
Y_prices = 2 * X_features[:, 0] + 5 * X_features[:, 1] + 10 + np.random.randn(10) * 5 # 价格 (带噪声)
Y_labels = Y_prices.reshape(-1, 1) # 确保 Y 是 (10, 1) 的形状
print(f"原始总样本数: {len(X_features)}")
print(f"原始特征矩阵形状: {X_features.shape}")
print(X_features)
print(Y_labels)

image.gif

image.gif

也就是说housing_data_warehouse 是 HousingDataset(X_features, Y_labels)类的一个实例对象,

这个对象有两个属性,两个方法。

import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
# --- 1. 定义自定义 Dataset 类 (加入打印) ---
class HousingDataset(Dataset):
    """
    一个简单的房价数据集类,用于存储特征 (面积, 房龄) 和标签 (价格)。
    """
    def __init__(self, X_data, Y_data):
        self.X = torch.tensor(X_data, dtype=torch.float32)
        self.Y = torch.tensor(Y_data, dtype=torch.float32)
    def __len__(self):
        """
        **__len__ 的作用体现**:当外部程序(如 len() 函数或 DataLoader)需要知道总样本数时,它会被调用。
        """
        # --- 强调 __len__ 被调用 ---
        print("\n[内部调用]:__len__ 方法被调用,返回数据集大小。")
        # ---------------------------
        return len(self.X)
    def __getitem__(self, idx):
        """
        **__getitem__ 的作用体现**:当外部程序(如 dataset[i] 或 DataLoader)需要获取特定样本时,它会被调用。
        """
        # --- 强调 __getitem__ 被调用 ---
        print(f"\n[内部调用]:__getitem__({idx}) 方法被调用,正在准备样本。")
        # -----------------------------
        # 返回第 idx 个特征和对应的标签
        return self.X[idx], self.Y[idx]
# --- 2. 模拟数据集 (10个样本) ---
X_features = np.random.rand(10, 2) * 100 # 面积和房龄
Y_prices = 2 * X_features[:, 0] + 5 * X_features[:, 1] + 10 + np.random.randn(10) * 5 # 价格 (带噪声)
Y_labels = Y_prices.reshape(-1, 1) # 确保 Y 是 (10, 1) 的形状
# --- 3. 实例化 Dataset ---
housing_data_warehouse = HousingDataset(X_features, Y_labels)
print("\n---housing_data_warehouse的实例对象的两个属性 ")
print(housing_data_warehouse.X)
print((housing_data_warehouse.X))
# ----------------------------------------------------
# A. 演示 __len__ 的作用:
# ----------------------------------------------------
print("\n--- 演示 __len__ 的作用 ---")
# 当我们调用 Python 内置的 len() 函数时,它就会调用 Dataset 对象的 __len__ 方法。
dataset_size = len(housing_data_warehouse) 
print(f"外部程序调用 len(dataset) 得到的结果是: {dataset_size}")
print("结论:len() 方法告诉 DataLoader 等程序,数据集有多少行数据。")
print("-" * 30)
# ----------------------------------------------------
# B. 演示 __getitem__ 的作用:
# ----------------------------------------------------
print("\n--- 演示 __getitem__ 的作用 ---")
sample_index = 5
# 当我们使用方括号 [] 索引访问 Dataset 对象时,Python 就会调用 __getitem__(index) 方法。
feature_5, label_5 = housing_data_warehouse[sample_index]
print(f"外部程序调用 dataset[{sample_index}] 得到的结果:")
print(f"  特征 (X): {feature_5.numpy().round(2)}")
print(f"  标签 (Y): {label_5.item():.2f}")
print("结论:__getitem__ 负责数据的实际加载和返回。")
print("-" * 30)

image.gif

image.gif

# --- 4. 实例化 DataLoader ---
# 设置 DataLoader 的核心参数:
batch_size = 4  # 批量大小
shuffle = True  # 每个 Epoch 都要打乱数据顺序
num_workers = 0 # 简单示例设置为 0 (单进程加载), 实际项目中应设为 > 0 加速
data_loader_pipe = DataLoader(
    dataset=housing_data_warehouse,  # 指定数据源(我们的 Dataset 仓库)
    batch_size=batch_size,           # 指定每次加载多少样本
    shuffle=shuffle,                 # 指定是否打乱
    num_workers=num_workers          # 指定并行加载的进程数
)
# --- 5. 演示 DataLoader 在训练循环中的作用 ---
print(f"使用 DataLoader 迭代数据 (批量大小: {batch_size}):")
total_batches = len(data_loader_pipe)
print(f"总共有 {total_batches} 个 Mini-batch (10 / 4 = 2 批,最后剩 2 个样本是第 3 批)。")
for i, (batch_X, batch_Y) in enumerate(data_loader_pipe):
    # 模拟这正是模型训练循环中接收到的数据
    print(f"\n--- 批次 {i + 1}/{total_batches} ---")
    print(f"批次 X 形状: {batch_X.shape}") 
    print(f"批次 Y 形状: {batch_Y.shape}")
    print(f"X (前两个样本): {batch_X[:2].numpy().round(2)}")
    print(f"Y (前两个标签): {batch_Y[:2].numpy().round(2)}")

image.gif

image.gif

数据读取和预处理

class WaterQualityDataset(Dataset):
    def __init__(self, root, transform=None):
        self.dataset = ImageFolder(root, transform=transform)
    def __getitem__(self, index):
        return self.dataset[index]
    def __len__(self):
        return len(self.dataset)

image.gif

 ImageFolder 的作用: 这是一个极其方便的工具,专门设计用来处理按文件夹结构组织的图像

数据集。它会自动完成以下工作:遍历 root 目录下所有的子文件夹。将每个子文件夹的名字识别

为一个类别标签。将子文件夹内的所有图片文件路径收集起来。在内部创建一个从类别名到数字标

签(如 0,1,2...)的映射。因此,self.dataset 实际上已经是一个可以工作的 PyTorch Dataset 对

象,包含了所有的图片路径和对应的标签。

  imageFolder 的 __getitem__ 会自动执行以下步骤:找到 index 对应的图片文件路径。读取该

图片文件。应用在 __init__ 中传入的 transform 转换。返回一个元组:(图像的 Tensor, 对应的数字

标签)。

 len:返回数据集大小

image.gif

# 数据预处理和加载
transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
])

image.gif

Resize((32, 32)):将所有图片缩放到 32×32 统一尺寸,便于模型处理。

ToTensor():将图片转换为 PyTorch 张量,并将像素值归一化到 [0,1] 区间。

train_dataset = WaterQualityDataset('D:/dataset', transform=transform)
test_dataset = WaterQualityDataset('D:/dataset', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=1, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False, num_workers=0)

image.gif

train_dataset 和 test_dataset 从 D:/dataset加载数据,使用 DataLoader 进行批量加载。

batch_size=1:每次加载 1 张图片(可以适当增大 batch_size 提高训练效率)。

shuffle=True:打乱训练集顺序,提升泛化能力。

num_workers=0:数据加载的 线程数,如果在 GPU 训练时,可以设大一点加速加载。

定义模型、损失函数、优化器

# 初始化WaterQualityNet模型
model = WaterQualityNet()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

image.gif

指定设备开始训练

num_epochs = 10
loss_history = []  # 记录每个 epoch 的损失
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in tqdm(train_loader, desc=f'Epoch {epoch + 1}/{num_epochs}'):
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()   # 清空梯度
        outputs = model(images) # 前向传播
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数
        running_loss += loss.item()
    epoch_loss = running_loss / len(train_loader)  # 计算平均损失
    loss_history.append(epoch_loss)
    print(f'Training Loss: {epoch_loss}')

image.gif

model.train():进入训练模式(启用 Dropout、BatchNorm 等)。

遍历 train_loader:

数据转移到 GPU (images.to(device), labels.to(device))

前向传播 (outputs = model(images))

计算损失 (loss = criterion(outputs, labels))

反向传播 (loss.backward())

更新参数 (optimizer.step())

记录每个 epoch 的平均损失,存入 loss_history。

可视化

plt.figure(figsize=(8, 5))
plt.plot(range(1, num_epochs + 1), loss_history, marker='o', linestyle='-', color='b')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Over Epochs')
plt.show()

image.gif

plt.plot() 画出 损失值随 epoch 变化的趋势

横轴是 训练轮数(epoch)

纵轴是 损失值(Loss)

如果损失曲线不断下降,说明模型在收敛

如果损失曲线震荡或者上升,说明可能存在学习率过大、数据不稳定等问题

完整代码

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader, Dataset
from torchvision.datasets import ImageFolder
from tqdm import tqdm
from network import WaterQualityNet
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
# 定义水质数据集
class WaterQualityDataset(Dataset):
    def __init__(self, root, transform=None):
        self.dataset = ImageFolder(root, transform=transform)
    def __getitem__(self, index):
        return self.dataset[index]
    def __len__(self):
        return len(self.dataset)
# 数据预处理和加载
transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
])
train_dataset = WaterQualityDataset('D:/dataset', transform=transform)
test_dataset = WaterQualityDataset('D:/dataset', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=1, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False, num_workers=0)
# 初始化WaterQualityNet模型
model = WaterQualityNet()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
num_epochs = 10
loss_history = []  # 存储损失值
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in tqdm(train_loader, desc=f'Epoch {epoch + 1}/{num_epochs}'):
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    epoch_loss = running_loss / len(train_loader)
    loss_history.append(epoch_loss)
    print(f'Training Loss: {epoch_loss}')
torch.save(model, 'water_quality_full_model.pth')
print('模型已经保存为 water_quality_full_model.pth')
# 可视化损失曲线
plt.figure(figsize=(8, 5))
plt.plot(range(1, num_epochs + 1), loss_history, marker='o', linestyle='-', color='b')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Over Epochs')
plt.show()

image.gif

image.gif


评估

import torch
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
from tqdm import tqdm
# 数据加载
test_loader = DataLoader(
    datasets.ImageFolder('D:/dataset1', transform=transforms.Compose([
        transforms.Resize((32, 32)),
        transforms.ToTensor(),
    ])),
    batch_size=32, shuffle=False, num_workers=0
)
# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 加载模型
model = torch.load('water_quality_full_model.pth')
model.to(device)
model.eval()
# 计算 Top-1 Accuracy
correct, total = 0, 0
with torch.no_grad():
    for images, labels in tqdm(test_loader, desc='Evaluating'):
        outputs = model(images.to(device))
        correct += (outputs.argmax(1) == labels.to(device)).sum().item()
        total += labels.size(0)
# 输出准确率
print(f"✅ Top-1 Accuracy: {correct / total * 100:.2f}%")

image.gif

correct 用于累加预测正确的样本数;total 用于累加总样本数,这里我也想解释一下为啥不用

softmax,对原始分数(Logits)取最大值,得到的预测类别对 Softmax 概率取最大值得到的预测

类别是完全一样的。既然结果一样,我们当然选择计算量最小的方式:跳过 Softmax 这一步。


UI页面

import sys
import torch
import torchvision.transforms as transforms
from PIL import Image
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QFileDialog, QVBoxLayout, QMessageBox
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import Qt
# --- 1. 模型导入与加载 ---
# 导入自定义模型类 WaterQualityNet
try:
    # 假设模型类在 network.py 中
    # 注意:在实际运行环境中,您需要确保 network.py 存在
    # from network import WaterQualityNet
    
    # 占位符类,用于通过检查,如果实际项目中没有 network.py
    class WaterQualityNet(torch.nn.Module):
        def __init__(self):
            super().__init__()
            # 这是一个虚拟的初始化,确保 torch.load 不会失败(如果模型路径正确)
            print("注意:WaterQualityNet 类已定义占位符。如果模型加载失败,请提供实际的模型定义。")
        def forward(self, x):
            # 虚拟前向传播
            return torch.rand(x.size(0), 2) 
except ImportError:
    # 错误处理:如果无法找到模型定义文件
    print("错误:未找到 WaterQualityNet 类,请检查 network.py 导入路径是否正确!")
    sys.exit(1)
# PyTorch 设备设置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 模型文件名
model_path = "water_quality_full_model.pth"
model = None
try:
    # 核心修复:加载完整模型对象(含自定义类)。
    # 必须设置 weights_only=False,以兼容自定义类加载。
    # 警告: 实际运行时,torch.load 要求 WaterQualityNet 类定义必须完整且可用
    model = torch.load(model_path, map_location=device, weights_only=False)
    model.to(device)
    model.eval()  # 切换到评估模式
    print(f"模型成功加载到 {device} 设备")
except Exception as e:
    # 在 GUI 环境下使用 QMessageBox 更好
    # QMessageBox.critical(None, "模型加载错误", f"无法加载模型文件:{model_path}\n错误详情:{str(e)}")
    print(f"模型加载失败,使用 WaterQualityNet 占位符可能导致加载失败。请确保模型文件和类定义匹配。错误详情:{str(e)}")
    # 如果模型加载失败,为了演示 UI,我们使用一个虚拟模型
    model = WaterQualityNet().to(device)
    model.eval()
# 图像预处理(与训练时保持一致)
transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
])
# 预测结果映射
QUALITY_MAPPING = {
    0: "💧 干净水 (一类)",
    1: "⚠️ 轻微污染水 (二类)"
    # 请根据您的实际训练标签进行调整
}
# --- 2. PyQt5 UI 设计与样式 ---
class WaterQualityApp(QWidget):
    def __init__(self):
        super().__init__()
        self.image_path = None
        self.init_ui()
        self.apply_style()
    def init_ui(self):
        # 布局
        layout = QVBoxLayout()
        layout.setSpacing(15)
        # 标题
        self.title_label = QLabel("智能水质图像检测系统")
        self.title_label.setObjectName("TitleLabel")
        layout.addWidget(self.title_label, alignment=Qt.AlignCenter)
        # 图片展示区
        self.image_label = QLabel("等待上传图片...")
        self.image_label.setObjectName("ImageLabel")
        layout.addWidget(self.image_label, stretch=1)
        # 按钮:上传图片
        self.upload_button = QPushButton("📷 上传水质图片")
        self.upload_button.setObjectName("UploadButton")
        self.upload_button.clicked.connect(self.load_image)
        layout.addWidget(self.upload_button)
        # 按钮:检测水质
        self.detect_button = QPushButton("🚀 检测水质类别")
        self.detect_button.setObjectName("DetectButton")
        self.detect_button.clicked.connect(self.detect_water_quality)
        layout.addWidget(self.detect_button)
        # 结果标签
        self.result_label = QLabel("结果将在这里显示...")
        self.result_label.setObjectName("ResultLabel")
        layout.addWidget(self.result_label, alignment=Qt.AlignCenter)
        self.setLayout(layout)
        self.setWindowTitle("智能水质检测系统")
        self.setGeometry(100, 100, 700, 700)
    
    def apply_style(self):
        # 统一的 CSS 样式表,更易于维护和美化
        self.setStyleSheet("""
            QWidget {
                background-color: #f8f9fa; /* 浅灰色背景 */
                font-family: 'Inter', sans-serif;
            }
            #TitleLabel {
                font-size: 28px;
                font-weight: bold;
                color: #007bff; /* 蓝色主色调 */
                padding: 15px;
            }
            #ImageLabel {
                border: 3px dashed #ced4da; /* 虚线边框 */
                border-radius: 10px;
                background-color: #ffffff; /* 白色内容背景 */
                min-height: 300px;
                font-size: 16px;
                color: #6c757d;
            }
            QPushButton {
                font-size: 16px;
                padding: 12px;
                border-radius: 8px;
                border: none;
                font-weight: 500;
                color: white;
            }
            #UploadButton {
                background-color: #28a745; /* 绿色 */
            }
            #UploadButton:hover {
                background-color: #218838;
            }
            #DetectButton {
                background-color: #007bff; /* 蓝色 */
            }
            #DetectButton:hover {
                background-color: #0069d9;
            }
            #ResultLabel {
                font-size: 22px;
                padding: 10px;
                border-radius: 8px;
                font-weight: bold;
                background-color: #e9ecef; /* 结果区域背景 */
                color: #495057;
                margin-top: 15px;
            }
        """)
    def load_image(self):
        """打开文件对话框,选择水质图像并显示"""
        file_dialog = QFileDialog()
        # 限制只选择图片文件
        image_path, _ = file_dialog.getOpenFileName(
            self, 
            "选择水质图片", 
            "", 
            "图片文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        if image_path:
            # 尝试加载图片
            try:
                pixmap = QPixmap(image_path)
                # 根据 image_label 的大小缩放图片,保持纵横比
                scaled_pixmap = pixmap.scaled(
                    self.image_label.size(), 
                    Qt.KeepAspectRatio, 
                    Qt.SmoothTransformation
                )
                self.image_label.setPixmap(scaled_pixmap)
                self.image_label.setAlignment(Qt.AlignCenter)
                self.image_label.setText("") # 清除 '等待上传图片...' 文本
                self.image_path = image_path 
                
                # 重置结果标签
                self.result_label.setStyleSheet("font-size: 22px; padding: 10px; border-radius: 8px; font-weight: bold; background-color: #e9ecef; color: #495057; margin-top: 15px;")
                self.result_label.setText("图片已上传,请点击 '检测水质类别'...")
            except Exception as e:
                 QMessageBox.warning(self, "文件错误", f"无法加载图片文件: {str(e)}")
    def detect_water_quality(self):
        """执行模型推理,预测水质类别"""
        if self.image_path and model is not None:
            try:
                # 1. 预处理
                image = Image.open(self.image_path).convert("RGB")
                image_tensor = transform(image).unsqueeze(0).to(device)
                # 2. 模型推理
                with torch.no_grad():
                    output = model(image_tensor)
                    probabilities = torch.softmax(output, dim=1)
                    _, predicted_class_idx = torch.max(output, 1)
                # --- 详细输出模型转换过程 (新增内容) ---
                print("\n" + "="*50)
                print("--- 模型输出到最终预测结果的转换 ---")
                
                # 1. 原始 Logits (模型输出)
                # output.squeeze().tolist() 将 [1, N] 维度的 Tensor 转换为 Python 列表
                print(f"1. 原始 Logits (模型输出,未归一化分数): {output.squeeze().tolist()}")
                
                # 2. Softmax 概率
                print(f"2. Softmax 概率 (置信度分布): {probabilities.squeeze().tolist()}")
                
                # 3. 预测索引
                predicted_index = predicted_class_idx.item()
                print(f"3. 预测索引 (最大概率对应的类别编号): {predicted_index}")
                
                # ---------------------------------------------
                # 3. 结果映射
                water_quality = QUALITY_MAPPING.get(predicted_index, "未知类别")
                confidence = probabilities[0, predicted_index].item()
                
                print(f"4. 映射结果: {water_quality} (置信度: {confidence*100:.2f}%)")
                print("="*50)
                # 4. 显示结果(美化样式)
                
                if predicted_index == 0: # 假设 0 是干净水(合格)
                    color = "#28a745" # 绿色
                    bg_color = "#d4edda" # 浅绿色背景
                else:
                    color = "#dc3545" # 红色
                    bg_color = "#f8d7da" # 浅红色背景
                
                self.result_label.setStyleSheet(f"color: {color}; font-size: 24px; margin-top: 20px; font-weight: bold; background-color: {bg_color}; border: 1px solid {color}; padding: 15px; border-radius: 8px;")
                self.result_label.setText(f"{water_quality} | 置信度: {confidence*100:.2f}%")
            except Exception as e:
                # 错误处理
                self.result_label.setStyleSheet("color: #6c757d; font-size: 18px; margin-top: 20px; background-color: #fff3cd;")
                self.result_label.setText(f"❌ 检测失败,请检查模型或输入:{str(e)[:50]}...")
                print(f"预测错误详情:{e}")
        else:
              QMessageBox.warning(self, "操作提示", "请先上传一张图片!")
# 运行 PyQt5 应用
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WaterQualityApp()
    window.show()
    sys.exit(app.exec_())

image.gif

image.gif

image.gif


目录
相关文章
|
28天前
|
机器学习/深度学习 PyTorch 测试技术
PyTorch深度学习实战 |多头注意力机制
摘要:本文详细介绍了Transformer中的多头自注意力机制,从整体结构到实现细节,包括四部分内容:(1)多头自注意力的基本架构;(2)内部计算流程解析;(3)注意力计算公式详解;(4)代码实现。重点阐述了多头并行的计算方式、缩放点积注意力的计算步骤(QK转置、缩放、softmax和加权求和),以及残差连接和层归一化的作用。通过"Are you OK?"示例展示了输入张量如何经过8个64维注意力头处理后拼接成512维输出。文章最后提供了完整的PyTorch实现代码,并附测试用例验证模型
165 0
|
28天前
|
机器学习/深度学习 PyTorch BI
PyTorch深度学习实战 |损失函数
本文介绍了PyTorch深度学习中四种核心损失函数及其应用场景。交叉熵损失(CrossEntropyLoss)适用于多分类任务,二进制交叉熵损失(BCELoss)专用于二分类问题,KL散度损失(KLDivLoss)用于衡量概率分布差异(如VAE模型),均方误差(MSELoss)则是回归任务的基础损失函数。通过具体代码示例(如图像分类、广告点击预测、房价预测等),文章展示了每种损失函数的PyTorch实现方式,并比较了不同损失函数的适用场景和计算特点,帮助读者根据任务需求选择合适的损失函数。
184 0
|
28天前
|
机器学习/深度学习 数据挖掘 PyTorch
PyTorch深度学习实战 |手算​​FCN全卷积神经网络
本文介绍了FCN-8s语义分割网络的实现细节。首先解释了语义分割的概念及其与图像分类的区别,重点分析了FCN网络结构中的全卷积化、上采样和跳跃连接三个关键技术。全卷积化将传统CNN的全连接层改为卷积层,实现像素级分类;上采样通过双线性插值恢复特征图尺寸;跳跃连接则融合高低层特征以提升细节表现。文章详细推导了损失函数的计算过程,并提供了完整的PyTorch实现代码,包括双线性插值权重初始化、VGG16骨干网络和FCN-8s主体结构。最后通过测试验证了模型能正确输出与输入尺寸匹配的预测结果。
252 3
|
28天前
|
机器学习/深度学习 编解码 算法
PyTorch深度学习实战 |手算​​U-net
本文详细解析了U-Net网络架构及其在医学图像分割中的应用。重点对比了U-Net与FCN的核心区别:U-Net采用特征拼接(Concat)保留所有层级信息,而FCN使用特征相加(Add)进行融合。文章深入剖析了U-Net的编码器-瓶颈-解码器结构,解释了其独特的裁剪拼接机制和Overlap-tile策略,并提供了完整的PyTorch实现代码。现代U-Net通过SamePadding实现了输入输出尺寸一致,显著提升了分割精度。文章还探讨了弹性形变数据增强和带空间权重的损失函数设计,为医学图像分析提供了实用解决
170 2
|
28天前
|
机器学习/深度学习 并行计算 PyTorch
PyTorch深度学习实战 |Alexnet网络花分类任务项目版本
本文介绍了基于PyTorch的花卉分类实战项目,包含完整的项目架构和代码实现。项目采用AlexNet网络结构,通过YAML配置文件管理参数,实现了模块化开发。文章详细讲解了数据集处理(5类花卉)、模型训练(包含早停机制和学习率调度)、验证评估和预测功能。项目采用YOLOv5风格的目录结构,包含configs、models、utils等模块,支持命令行参数配置。实验结果显示该架构比简单实现准确率提升4%,最终预测功能可输出top-k分类结果及置信度。完整代码已开源,适合深度学习初学者进阶学习。
207 0
|
28天前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch深度学习实战 |层归一化层和FeedForward
本文介绍了PyTorch深度学习中Add&Norm层和FeedForward层的实现原理。Add&Norm层由残差连接(Add)和层归一化(Norm)组成,能加速模型收敛并稳定训练。层归一化会对神经网络每层的输出进行归一化处理,文中详细展示了其计算方法和PyTorch实现代码。FeedForward层是一个两层的全连接网络,通过线性变换提取更深层次特征。文章还分析了Transformer模型中使用层归一化的原因,并提供了完整的代码实现,包括参数初始化和前向传播过程。
127 0
|
28天前
|
机器学习/深度学习 自然语言处理 PyTorch
PyTorch深度学习实战 |词嵌入和位置编码
本文介绍了PyTorch中词嵌入(Embedding)和位置编码(Positional Encoding)的实现与应用。词嵌入通过Word2Vec、GloVe等技术将单词转换为连续向量,其中使用nn.Embedding层加载预训练词向量,并展示如何可视化词向量间的语义关系(如"king-man≈queen-woman")。位置编码部分详细解释了如何通过正弦/余弦函数为词向量添加位置信息,包含公式推导和代码实现(PositionalEncoding类)。文章提供了完整的PyTorch代码示
171 0
|
28天前
|
机器学习/深度学习 存储 并行计算
PyTorch深度学习实战 |从深度学习入门到项目化的任务(以Alexnet网络花分类任务为例)
本文介绍了PyTorch深度学习实战中的项目化开发方法。作者指出实际项目与玩具数据集的不同之处,强调需要考虑数据标准化、模型保存和实验追踪等工程问题。文章详细讲解了项目化文件结构,包括configs、models、utils等模块的划分,并提供了关键工具函数的实现,如YAML配置加载、模型检查点保存/加载、评估指标计算等。通过一个AlexNet训练示例,展示了如何将模型训练、验证和预测逻辑分离,构建完整的工程闭环。文章强调深度学习实战中80%时间在处理工程问题,只有20%在模型架构上,帮助读者从学习阶段过渡
155 0
|
28天前
|
机器学习/深度学习 自然语言处理 算法
PyTorch深度学习实战 | Transformer模型初识
本文介绍了Transformer模型在机器翻译任务中的工作原理。主要内容包括:1)模型分为编码器(处理英文输入)和解码器(生成中文输出)两部分;2)推理时采用自回归模式,逐步生成翻译结果;3)训练时使用教师强制模式,始终以真实标签指导模型学习。文章详细阐述了编码器-解码器结构的工作流程,包括词嵌入、多头注意力机制等核心组件,以及如何通过残差连接和层标准化优化模型性能。最后解释了编码器和解码器三个关键计算步骤的张量维度变化过程。
184 0
|
28天前
|
机器学习/深度学习 编解码 算法
PyTorch深度学习实战 | 标准化和归一化
本文介绍了数据预处理中的标准化和归一化方法,包括计算过程、特性对比及实际应用。以波士顿房价数据集为例,展示了原始数据、标准化和归一化后的模型效果差异。同时讲解了深度学习中常用的层归一化(LN)和批量归一化(BN)技术,分别适用于文本和图像处理任务,并给出了PyTorch实现示例。LN通过调整词向量分布缓解梯度消失等问题,而BN则对图像各通道单独处理。两种技术都能提升模型训练效果,但适用场景不同。
158 0

热门文章

最新文章