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

简介: 代码实现# 定义模型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


目录
相关文章
|
5月前
|
机器学习/深度学习 PyTorch 测试技术
PyTorch深度学习实战 |多头注意力机制
摘要:本文详细介绍了Transformer中的多头自注意力机制,从整体结构到实现细节,包括四部分内容:(1)多头自注意力的基本架构;(2)内部计算流程解析;(3)注意力计算公式详解;(4)代码实现。重点阐述了多头并行的计算方式、缩放点积注意力的计算步骤(QK转置、缩放、softmax和加权求和),以及残差连接和层归一化的作用。通过"Are you OK?"示例展示了输入张量如何经过8个64维注意力头处理后拼接成512维输出。文章最后提供了完整的PyTorch实现代码,并附测试用例验证模型
357 0
|
5月前
|
机器学习/深度学习 数据挖掘 PyTorch
PyTorch深度学习实战 |手算​​FCN全卷积神经网络
本文介绍了FCN-8s语义分割网络的实现细节。首先解释了语义分割的概念及其与图像分类的区别,重点分析了FCN网络结构中的全卷积化、上采样和跳跃连接三个关键技术。全卷积化将传统CNN的全连接层改为卷积层,实现像素级分类;上采样通过双线性插值恢复特征图尺寸;跳跃连接则融合高低层特征以提升细节表现。文章详细推导了损失函数的计算过程,并提供了完整的PyTorch实现代码,包括双线性插值权重初始化、VGG16骨干网络和FCN-8s主体结构。最后通过测试验证了模型能正确输出与输入尺寸匹配的预测结果。
463 3
|
5月前
|
机器学习/深度学习 编解码 算法
PyTorch深度学习实战 |手算​​U-net
本文详细解析了U-Net网络架构及其在医学图像分割中的应用。重点对比了U-Net与FCN的核心区别:U-Net采用特征拼接(Concat)保留所有层级信息,而FCN使用特征相加(Add)进行融合。文章深入剖析了U-Net的编码器-瓶颈-解码器结构,解释了其独特的裁剪拼接机制和Overlap-tile策略,并提供了完整的PyTorch实现代码。现代U-Net通过SamePadding实现了输入输出尺寸一致,显著提升了分割精度。文章还探讨了弹性形变数据增强和带空间权重的损失函数设计,为医学图像分析提供了实用解决
434 2
|
5月前
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch深度学习实战 | 基于LSTM的时间序列预测任务
本文介绍了使用LSTM模型预测印度德里市平均温度的两个项目。项目1对温度数据进行归一化处理,采用滑动窗口法构建监督学习样本,设计5层LSTM网络结构,并详细说明了模型训练过程及评估方法。项目2在数据处理上增加了标准化和周期性特征,改进了网络架构,引入了学习率调整和早停机制优化训练过程。两个项目均通过可视化对比预测值和真实值,验证了LSTM模型在时间序列预测中的有效性。文章从数据处理、模型构建到训练优化,完整呈现了温度预测的实现流程,为时序预测任务提供了实用参考。
340 0
|
5月前
|
机器学习/深度学习 自然语言处理 PyTorch
PyTorch深度学习实战 |手动计算 Transformer和完整的代码实现
本文介绍了基于PyTorch实现Transformer模型的完整过程。主要内容包括:1)Transformer架构的核心组件实现,如多头注意力机制、位置前馈网络、位置编码等;2)模型构建步骤,包括词嵌入层、编码器/解码器块和输出层的实现;3)完整的训练流程,包含数据处理、损失计算和参数优化;4)评估方法验证模型性能。文章通过代码示例详细展示了如何从零开始构建Transformer,并应用于机器翻译任务,同时对模型各层的输入输出维度进行了说明。该实现可作为深度学习实践者学习Transformer架构的实用指南
414 0
|
5月前
|
人工智能 自然语言处理 计算机视觉
人工智能|大白话Meshed-Memory Transformer
M2Transformer是一种图像描述生成模型,由三部分构成:骨干编码器(Faster R-CNN)提取区域特征;记忆增强编码器(Transformer)对特征进行语义细化;网格解码器(Transformer)将增强特征转化为自然语言描述。结构清晰、层次分明,兼顾准确性与可解释性。(239字)
266 4
|
5月前
|
机器学习/深度学习 存储 人工智能
图解人工智能的数学基础(线性代数)
本文系统讲解线性代数核心概念,涵盖向量(定义、几何/坐标表示、内积)、矩阵(含义、运算、秩、逆、相似、分解)、行列式(几何意义与变换关系)、线性方程组、特征值与特征向量、二次型、向量空间及范数等,强调其在AI与神经网络中的实际应用。
648 7
|
5月前
|
机器学习/深度学习 人工智能 算法
人工智能|大白话DETR 模型
DETR(检测变换器)是首个端到端目标检测模型,摒弃锚框与NMS后处理。它以CNN提取特征,经Transformer编码器-解码器处理,配合100个可学习目标查询(OQ),通过二分图匹配实现预测框与真实框的一对一最优分配,直接输出类别与坐标。(239字)
517 3
|
5月前
|
弹性计算 前端开发 Ubuntu
阿里云服务器ECS的租用教程和简单的前端页面部署
本文详解阿里云学生福利领取(含300元卡券)及ECS轻量服务器选购与部署全流程:涵盖学生机免费申领、配置选型建议(Ubuntu/CentOS/Windows)、安全组设置、Nginx安装、网页部署及Xshell远程连接等实操步骤,新手友好。
593 8
|
5月前
|
机器学习/深度学习 人工智能 PyTorch
PyTorch深度学习实战 | 人工智能项目从训练到部署
本项目基于LSTM模型对污水处理厂总曝气量(旧区+新区)进行时序预测。通过数据清洗、Min-Max归一化、滑动窗口构造(12小时输入→预测未来1小时),构建并训练轻量级LSTM模型,支持API部署与实时调用,已实现端到端预测流程及模型保存。
389 6