当前位置: 首页 > news >正文

【李宏毅深度学习——分类模型的PyTorch架构】Homework 2:Phoneme Classification

目录

一、数据集介绍

1、数据来源

2、数据预处理与格式

二、数据处理方面代码分析

1、下载数据集

2、为全项目过程设置随机数种子

3、预处理相关函数定义

(1)第一个函数:加载特征数据 load_feat

(2)第二个函数:移位操作 shift

(3)第三个函数:拼接特征 concat_feat

(4)第四个函数:声音数据文件预处理函数 preprocess_data

三、分类模型框架的搭建

1、创建自己的数据集类

2、创建分类模型

3、模型的超参数

4、Dataloader的创建

5、开始训练

6、总结与优化建议

总结:

优化建议:

四、模型使用过程


一、数据集介绍

1、数据来源

数据集: 文件中使用的是 LibriSpeech 数据集中的一个子集,即 train-clean-100

  • 训练集: 包含 3429 个预处理后的音频样本,所有样本共计 2116794 帧。

  • 测试集: 包含 857 个预处理后的音频样本,共计 527364 帧。

注意:测试数据没有标注,因此主要用于最终评估,需要在 Kaggle 上提交结果。

2、数据预处理与格式

  • 特征提取:

    • 采用了 MFCCs(Mel Frequency Cepstral Coefficients)作为声学特征,经过 CMVN(Cepstral Mean and Variance Normalization)归一化。

    • 每个语音样本保存为一个 .pt 文件,利用 torch.load() 可加载为一个张量,形状为 (T, 39),其中T 是帧数,每一帧有 39 维的特征。所有音素共有41个类别。

  • 上下文帧拼接:

    • 因为单独一帧(25ms 的时长)很难包含完整的音素信息,故一般会把相邻的若干帧进行拼接。例如,选取前后共 11 帧,拼接后维度为 11 * 39 = 117 。(实际代码中是选取前后共 3 帧,受超参数 concat_nframes 调控)

    • 这种拼接方式使得每次输入模型的向量能够携带更丰富的上下文信息,从而提高音素识别的准确性。

二、数据处理方面代码分析

1、下载数据集

!pip install --upgrade gdown# Main link
# !gdown --id '1N1eVIDe9hKM5uiNRGmifBlwSDGiVXPJe' --output libriphone.zip
!gdown --id '1qzCRnywKh30mTbWUEjXuNT2isOCAPdO1' --output libriphone.zip!unzip -q libriphone.zip
!ls libriphone

逐句分析:


(1)安装或升级 gdown

!pip install --upgrade gdown 
  • 作用说明
    这行代码用于通过 pip 安装或升级 Python 包 gdown

  • 逐句分析

    • !:在 Jupyter Notebook 中使用 ! 表示执行 shell 命令,而不是 Python 代码。

    • pip install --upgrade gdown

      • pip install:使用 pip 包管理器安装一个 Python 包。

      • --upgrade:这个参数确保如果系统中已经安装了 gdown,则会将其升级到最新版本。

      • gdown:这是一个专门用于从 Google Drive 下载文件的 Python 包。


(2)下载文件

!gdown --id '1qzCRnywKh30mTbWUEjXuNT2isOCAPdO1' --output libriphone.zip
  • 作用说明
    此命令使用 gdown 下载 Google Drive 上的文件。

  • 逐句分析

    • !gdown:调用 shell 命令 gdown

    • --id '1qzCRnywKh30mTbWUEjXuNT2isOCAPdO1'

      • --id 参数后面跟随的是 Google Drive 文件的唯一标识符(ID)。

      • 该 ID 表示需要下载的具体文件。

    • --output libriphone.zip

      • --output 参数指定下载后的文件名,这里文件会被保存为 libriphone.zip


(3)解压文件

!unzip -q libriphone.zip
  • 作用说明
    这条命令用于解压刚刚下载的 libriphone.zip 压缩文件。

  • 逐句分析

    • !unzip:调用系统中的 unzip 工具来解压文件。

    • -q:这是 unzip 的一个参数,表示 "quiet"(静默模式),即解压过程中不显示详细的信息。

    • libriphone.zip:这是要被解压的压缩文件名称。


(4)查看解压后的目录内容

!ls libriphone
  • 作用说明
    这行命令用于列出目录 libriphone 中的所有文件和文件夹,帮助用户确认解压是否成功及目录内容结构。

  • 逐句分析

    • !ls:调用 Unix/Linux 命令 ls 来列出目录中的文件。

    • libriphone:指定要列出内容的目录名称,这里假定解压操作会在当前工作目录下生成一个名为 libriphone 的文件夹。

2、为全项目过程设置随机数种子

import numpy as np
import torch
import randomdef same_seeds(seed):random.seed(seed) np.random.seed(seed)  torch.manual_seed(seed)if torch.cuda.is_available():torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed) torch.backends.cudnn.benchmark = Falsetorch.backends.cudnn.deterministic = True

逐句分析:


(1)导入所需的模块

import numpy as np
import torch
import random

(2)函数定义

def same_seeds(seed):

定义一个名为 same_seeds 的函数,该函数为全项目过程设置随机数种子。它接收一个参数 seed,用于设置随机种子,从而确保实验过程中的随机性操作可重现。


(3)在 Python 标准库中设置随机种子

    random.seed(seed)

使用 Python 的 random 模块设置随机种子。设置后,所有使用 random 模块生成的随机数序列将是可重复的,有助于实验结果的再现性。


(4)为 NumPy 设置随机种子

    np.random.seed(seed)

调用 NumPy 提供的 np.random.seed 方法来设置随机种子。这样后续所有使用 NumPy 随机模块的函数(比如 np.random.randnp.random.randint 等)生成的随机数都会按照固定的序列进行生成,以实现结果可复现。


(5)为 PyTorch 设置 CPU 随机种子

    torch.manual_seed(seed)

使用 torch.manual_seed 函数为 PyTorch 设置 CPU 的随机数种子。这确保在 CPU 运算中生成的随机数(如模型初始化、数据打乱等)是一致的,方便结果复现。


(6)检查并设置 CUDA 随机种子(若可用)

    if torch.cuda.is_available():torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)

整体作用

  • 这段代码首先使用 torch.cuda.is_available() 检查当前系统是否有可用的 CUDA GPU。若存在,则对 GPU 随机数生成进行设置。

  • 逐句分析

    • if torch.cuda.is_available():

      • 判断当前是否可检测到 GPU 硬件的可用性。

    • torch.cuda.manual_seed(seed)

      • 为当前 GPU 设置随机种子,这会影响到使用特定 GPU 的操作。

    • torch.cuda.manual_seed_all(seed)

      • 如果有多个 GPU,这个函数将为所有的 GPU 设置相同的随机种子,确保分布式或多 GPU 环境下的随机操作也能复现。


(7)设置 PyTorch 的 cuDNN 后端参数以实现结果可重复性

    torch.backends.cudnn.benchmark = Falsetorch.backends.cudnn.deterministic = True
  • 作用与解释

    • torch.backends.cudnn.benchmark = False

      • 该设置关闭 cuDNN 后端的基准测试模式。cuDNN 默认会根据网络结构和硬件自动寻找最优算法,以提高训练速度,但这可能引入非确定性(即同一代码多次运行的结果可能略有不同)。

    • torch.backends.cudnn.deterministic = True

      • 将这个参数设置为 True,可以强制使用确定性的算法,确保每次运行时的结果一致。

    • 注意

      • 在一些情况下,可能会牺牲部分性能换取结果确定性。这个设置特别适用于需要严格复现实验结果的场景。

3、预处理相关函数定义

(1)第一个函数:加载特征数据 load_feat

1.1 导入必要的模块

import os
import torch
from tqdm import tqdm

1.2 加载特征数据

# 这个函数用于加载存储在指定路径 (path) 的特征数据。
def load_feat(path):feat = torch.load(path)return feat
  • def load_feat(path):

    • 定义了函数名 load_feat,接收参数 path,它是存储数据文件的路径。

  • feat = torch.load(path)

    • 调用 torch.load 来加载指定路径的文件。这个函数能自动识别存储的对象格式(如张量、模型等),将文件中的数据加载到变量 feat 中。

  • return feat

    • 返回加载得到的特征数据,使得该函数调用处可以获得并使用这些数据。

(2)第二个函数:移位操作 shift

# 这个函数用于对输入张量 x 进行元素的“移位”操作。
def shift(x, n):if n < 0:left = x[0].repeat(-n, 1)right = x[:n]elif n > 0:right = x[-1].repeat(n, 1)left = x[n:]else:return xreturn torch.cat((left, right), dim=0)
  • 函数说明

    • 函数 shift 用于对输入的二维张量 x 进行移位操作,根据参数 n 的值(正数、负数或 0)来决定具体的移位方式。

  • 逐句解析

    • def shift(x, n):

      • 定义 shift 函数,参数包括张量 x 和整型移位参数 n

    • if n < 0:

      • 当移位值 n 为负数时,说明需要将张量向左(向前)“移动”:

        • left = x[0].repeat(-n, 1)

          • x[0] 取出张量 x 的第一行。

          • .repeat(-n, 1) 将这行重复 -n 次(因为 n 是负数,所以取负值),重复的方向只在第一维(行)上,第二维(列)保持原状。

          • 作用:产生若干行填充在移位后的前部。

        • right = x[:n]

          • 切片 x[:n] 表示取从开头到倒数第 -n 行(注意:在 Python 中当 n 为负数时,切片会忽略最后几行)。

          • 作用:保留剩余部分作为右侧补充。

    • elif n > 0:

      • 当移位值 n 为正数时,表示需要将张量向右(向后)“移动”:

        • right = x[-1].repeat(n, 1)

          • x[-1] 取出张量 x 的最后一行。

          • .repeat(n, 1) 将这行重复 n 次,形成右侧补充的部分。

        • left = x[n:]

          • 切片 x[n:] 表示从第 n 行开始(从 0 开始计数)到最后一行。

          • 作用:保留移位后的左侧部分。

    • else:

      • 如果 n == 0 的情况,说明不需要移位,则直接返回原始张量。

    • return torch.cat((left, right), dim=0)

      • 使用 torch.cat 将两个部分 (leftright) 在行(第 0 维)上拼接起来,形成移位后的新张量。

(3)第三个函数:拼接特征 concat_feat

# 该函数用于将输入的特征数据 x 进行拼接处理,并对数据进行移位操作。
def concat_feat(x, concat_n):assert concat_n % 2 == 1 # n must be oddif concat_n < 2:return xseq_len, feature_dim = x.size(0), x.size(1)x = x.repeat(1, concat_n)x = x.view(seq_len, concat_n, feature_dim).permute(1, 0, 2) # concat_n, seq_len, feature_dimmid = (concat_n // 2)for r_idx in range(1, mid+1):x[mid + r_idx, :] = shift(x[mid + r_idx], r_idx)x[mid - r_idx, :] = shift(x[mid - r_idx], -r_idx)return x.permute(1, 0, 2).view(seq_len, concat_n * feature_dim)
  • 函数说明

    • concat_feat 函数的作用是对输入的二维特征数据 x 进行上下文拼接。拼接的目标是将原始特征扩展为多个上下文帧,同时对拼接的部分进行适当的移位处理。这在很多语音识别或时间序列建模中常用于数据的上下文扩充。

  • 逐句解析

    • def concat_feat(x, concat_n):

      • 定义函数 concat_feat,参数 x 为输入张量(通常是二维 [序列长度, 特征维度]),concat_n 指定拼接的数量,即采集多少上下文帧。

    • assert concat_n % 2 == 1 # n must be odd

      • 使用断言确保 concat_n 为奇数。

      • 理由:通常拼接的上下文帧中,中间那一帧代表当前帧,两侧需要对称地各有一定数量的前帧和后帧,所以总帧数必须为奇数。

    • if concat_n < 2:

      • 判断如果拼接帧数小于 2(实际上只有 1 帧,等价于原始特征),则不做拼接,直接返回输入张量。

    • seq_len, feature_dim = x.size(0), x.size(1)

      • 获取输入张量 x 的形状信息:

        • seq_len:序列长度,即帧数。

        • feature_dim:单帧特征的维度。

    • x = x.repeat(1, concat_n)

      • 使用 repeat 方法将每一行的特征向量沿列方向复制 concat_n 次。

      • 结果形状从 [seq_len, feature_dim] 变为 [seq_len, feature_dim * concat_n],不过目前还是没有分离出各个上下文帧。

    • x = x.view(seq_len, concat_n, feature_dim).permute(1, 0, 2) # concat_n, seq_len, feature_dim

      • 第一部分 x.view(seq_len, concat_n, feature_dim) 将重复后的张量重新形状为 [seq_len, concat_n, feature_dim],把拼接的部分分组为多个“帧”块。

      • 第二部分 .permute(1, 0, 2) 改变张量的维度顺序,使得形状变为 [concat_n, seq_len, feature_dim]

      • 目的:将所有的上下文帧数据排列到第一维,使得后续通过索引更方便进行移位调整。

    • mid = (concat_n // 2)

      • 计算中心帧的位置索引,由于 concat_n 是奇数,中心帧位于 concat_n // 2 的位置(整数除法)。

    • for r_idx in range(1, mid+1):

      • 开启一个循环,遍历范围从 1 到 mid(包括 mid),逐个处理中心帧左右两侧的帧。

      • 循环体内部

        • x[mid + r_idx, :] = shift(x[mid + r_idx], r_idx)

          • 对中心帧右侧的第 r_idx 帧进行移位操作。

          • 调用前面定义的 shift 函数,将该帧内的序列根据正数 r_idx 进行向右移位。

          • 结果:右侧帧的每一行数据会被调整,使其在时间序列上“向后”偏移了对应数量的单位。

        • x[mid - r_idx, :] = shift(x[mid - r_idx], -r_idx)

          • 对中心帧左侧的第 r_idx 帧进行移位操作。

          • 调用 shift 函数,并传入负数 -r_idx 进行向左移位。

          • 结果:左侧帧的每一行数据相应“向前”偏移。

    • return x.permute(1, 0, 2).view(seq_len, concat_n * feature_dim)

      • 最后一步,将调整后的张量从 [concat_n, seq_len, feature_dim] 重新转换回原始顺序:

        • .permute(1, 0, 2) 交换维度顺序,恢复到 [seq_len, concat_n, feature_dim]

        • .view(seq_len, concat_n * feature_dim) 将第二和第三维展平成一维,得到最终的拼接结果,其中每个序列帧包含了相邻多个帧拼接而成的特征向量。

(4)第四个函数:声音数据文件预处理函数 preprocess_data

def preprocess_data(split, feat_dir, phone_path, concat_nframes, train_ratio=0.8):class_num = 41 # NOTE: pre-computed, should not need changeif split == 'train' or split == 'val':mode = 'train'elif split == 'test':mode = 'test'else:raise ValueError('Invalid \'split\' argument for dataset: PhoneDataset!')label_dict = {}if mode == 'train':for line in open(os.path.join(phone_path, f'{mode}_labels.txt')).readlines():line = line.strip('\n').split(' ')label_dict[line[0]] = [int(p) for p in line[1:]]# split training and validation datausage_list = open(os.path.join(phone_path, 'train_split.txt')).readlines()random.shuffle(usage_list)train_len = int(len(usage_list) * train_ratio)usage_list = usage_list[:train_len] if split == 'train' else usage_list[train_len:]elif mode == 'test':usage_list = open(os.path.join(phone_path, 'test_split.txt')).readlines()usage_list = [line.strip('\n') for line in usage_list]print('[Dataset] - # phone classes: ' + str(class_num) + ', number of utterances for ' + split + ': ' + str(len(usage_list)))max_len = 3000000X = torch.empty(max_len, 39 * concat_nframes)if mode == 'train':y = torch.empty(max_len, dtype=torch.long)idx = 0for i, fname in tqdm(enumerate(usage_list)):feat = load_feat(os.path.join(feat_dir, mode, f'{fname}.pt'))cur_len = len(feat)feat = concat_feat(feat, concat_nframes)if mode == 'train':label = torch.LongTensor(label_dict[fname])X[idx: idx + cur_len, :] = featif mode == 'train':y[idx: idx + cur_len] = labelidx += cur_lenX = X[:idx, :]if mode == 'train':y = y[:idx]print(f'[INFO] {split} set')print(X.shape)if mode == 'train':print(y.shape)return X, yelse:return X

4.1 函数声明与参数

def preprocess_data(split, feat_dir, phone_path, concat_nframes, train_ratio=0.8):class_num = 41 # NOTE: pre-computed, should not need change
  • 函数声明

    • 定义函数 preprocess_data,它的参数包括:

      • split:指明数据集的用途,这里可能是 'train', 'val''test'

      • feat_dir:特征数据存储的根目录路径。

      • phone_path:与“phone”(语音或音素)相关数据的路径,如标签文件和划分文件。

      • concat_nframes:每个特征点拼接的帧数,即上下文帧扩充的数量(后续将用于 concat_feat 函数)。

      • train_ratio:训练数据划分比率(默认值为 0.8),用于训练和验证数据的切分。

  • 内部变量

    • class_num = 41:预先定义了类别数,通常表示音素或电话类的数量,这个值已预计算好,不需要修改。


4.2 根据 split 参数确定数据模式

    if split == 'train' or split == 'val':mode = 'train'elif split == 'test':mode = 'test'else:raise ValueError('Invalid \'split\' argument for dataset: PhoneDataset!')
  • 功能说明

    • 判断 split 参数的取值,若为 'train''val',则设定 mode'train';若为 'test',则设为 'test'

    • 对于其他无效的 split 值,则抛出 ValueError 异常,提示用户参数错误。

  • 细节说明

    • 将训练和验证数据都统一放在 'train' 模式下进行处理,后续会根据训练集比例进行分割。


4.3 读取标签信息和划分文件

    label_dict = {}if mode == 'train':for line in open(os.path.join(phone_path, f'{mode}_labels.txt')).readlines():line = line.strip('\n').split(' ')label_dict[line[0]] = [int(p) for p in line[1:]]# split training and validation datausage_list = open(os.path.join(phone_path, 'train_split.txt')).readlines()random.shuffle(usage_list)train_len = int(len(usage_list) * train_ratio)usage_list = usage_list[:train_len] if split == 'train' else usage_list[train_len:]elif mode == 'test':usage_list = open(os.path.join(phone_path, 'test_split.txt')).readlines()

读取标签(train 模式)

  • 初始化空字典 label_dict 用于存储每个数据文件(通常以文件名为 key)的标签。label_dict 字典中 “键值对” 的 “键” 是存储声音数据的文件名, “键值对” 的 “值” 是存储对应文件中的各个桢的类别标签所组成的列表。

  • 通过 openos.path.join 组合路径读取 'train_labels.txt' 文件(文件名根据 mode 动态拼接)。

  • 对于每一行:

    • 使用 strip('\n') 去除换行符,再使用 split(' ') 按空格拆分。

    • 第一部分(line[0])作为标识符,后续部分转换成整数列表作为标签,保存到 label_dict 中。

  • 训练与验证数据划分

    • 读取 train_split.txt 文件,得到所有数据文件的列表 usage_list

    • 对列表进行随机打乱,确保训练和验证数据混合随机。

    • 根据 train_ratio 计算训练数据的长度 train_len

    • 如果当前 split'train',则保留前 train_len 个文件;如果是 'val',则保留剩余部分。

  • 测试数据读取

    • 如果 mode'test',则直接读取 test_split.txt 文件,usage_list 中包含测试文件的列表。

  • 共同操作

    • usage_list 中每行去掉换行符,得到干净的文件名列表。

    • usage_list 中存储的是 存储声音数据的文件名列表。

    • 打印数据集统计信息,显示类别数和当前 split 下语句数量。


4.4 初始化张量 X 与 y(仅对训练模式)

    max_len = 3000000X = torch.empty(max_len, 39 * concat_nframes)if mode == 'train':y = torch.empty(max_len, dtype=torch.long)
  • 目的

    • 为存储整个数据集的特征(和标签)预先分配足够的内存。

  • 说明

    • max_len = 3000000:设定一个足够大的上限,表示所有拼接后的特征数据将被保存在大小为 max_len 的第一维中。

    • X = torch.empty(max_len, 39 * concat_nframes)

      • 创建一个空张量 X,形状为 [max_len, 39 * concat_nframes]

      • 这里 39 可能代表原始特征的维度数,经过上下文拼接后,每一行的特征维度变为 39 * concat_nframes

    • 如果是训练模式,则也初始化标签张量 y,数据类型为长整型(torch.long)。


4.5 遍历所有文件,加载和处理特征数据

    idx = 0for i, fname in tqdm(enumerate(usage_list)):feat = load_feat(os.path.join(feat_dir, mode, f'{fname}.pt'))cur_len = len(feat)feat = concat_feat(feat, concat_nframes)if mode == 'train':label = torch.LongTensor(label_dict[fname])X[idx: idx + cur_len, :] = featif mode == 'train':y[idx: idx + cur_len] = labelidx += cur_len
  • 初始化索引

    • idx = 0 用于记录已经存入张量 X(和 y)的数据的帧数(每个文件中都有 cur_len 个桢数据)。

  • 遍历文件列表

    • 使用 enumerate(usage_list) 配合 tqdm 显示进度条,逐个处理每个文件名 fname

    • 对每个文件:

      • 构造文件路径:使用 os.path.join 拼接 feat_dirmode 和文件名(加 .pt 扩展名),读取保存的特征文件,调用前面定义的 load_feat

      • 得到特征数据 feat 后,计算当前数据长度 cur_len = len(feat)(通常代表时间帧数)。

      • 调用 concat_feat(feat, concat_nframes) 对原始特征进行上下文拼接,扩充后特征维度变化为 39 * concat_nframes

      • 如果为训练模式:

        • 利用 label_dict[fname] 获取对应标签,并转换为 torch.LongTensor

      • 将处理好的特征数据存入预分配张量 X 中,从 idxidx + cur_len 的位置。

      • 若为训练模式,则同样将标签存入 y 的相应位置。

      • 更新 idx(索引增加当前文件的帧数),为下一个文件的数据腾出存储位置。


4.6 截断多余的预分配内存,并返回数据

    X = X[:idx, :]if mode == 'train':y = y[:idx]print(f'[INFO] {split} set')print(X.shape)if mode == 'train':print(y.shape)return X, yelse:return X
  • 截取有效部分

    • 由于事先预分配的张量尺寸可能大于实际存入的数据量,使用切片操作 X = X[:idx, :] 将张量截取为实际填充的部分。

    • 若是训练模式,同样对标签张量 y 进行截取。

  • 打印信息

    • 输出当前数据集的类型(split)以及特征数据 X 的形状,若为训练模式还输出标签 y 的形状,方便检查预处理结果。

  • 返回结果

    • 如果是训练模式,函数返回 (X, y),即特征数据与对应的标签;

    • 如果是测试模式,仅返回特征数据 X

三、分类模型框架的搭建

1、创建自己的数据集类

import torch
from torch.utils.data import Datasetclass LibriDataset(Dataset):def __init__(self, X, y=None):self.data = Xif y is not None:self.label = torch.LongTensor(y)else:self.label = Nonedef __getitem__(self, idx):if self.label is not None:return self.data[idx], self.label[idx]else:return self.data[idx]def __len__(self):return len(self.data)

2、创建分类模型

import torch.nn as nnclass BasicBlock(nn.Module):def __init__(self, input_dim, output_dim):super(BasicBlock, self).__init__()# TODO: apply batch normalization and dropout for strong baseline.# Reference: https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm1d.html (batch normalization)#       https://pytorch.org/docs/stable/generated/torch.nn.Dropout.html (dropout)self.block = nn.Sequential(nn.Linear(input_dim, output_dim),nn.ReLU(),)def forward(self, x):x = self.block(x)return xclass Classifier(nn.Module):def __init__(self, input_dim, output_dim=41, hidden_layers=1, hidden_dim=256):super(Classifier, self).__init__()self.fc = nn.Sequential(BasicBlock(input_dim, hidden_dim),*[BasicBlock(hidden_dim, hidden_dim) for _ in range(hidden_layers)],nn.Linear(hidden_dim, output_dim))def forward(self, x):x = self.fc(x)return x

在模型创建过程中,先创建一个 BasicBlock 模块,该模块包含一个隐藏层(一个线性层 + 一个激活函数)。

下面就是模型模块 Classifier,其中调用了 BasicBlock 模块 ,这样的一个模型创建架构可以灵活控制隐藏层的层数,比较灵活。


模型优化:

在注释部分,有对模型优化的建议,即在实际项目中,为了提高模型的泛化能力和稳定性,可以在激活函数之前或之后添加批归一化(BatchNorm1d)和 Dropout 层。

常见的添加方法为:

  1. 批归一化:通常在线性层之后、激活函数之前,作用是将特征标准化,使得输出有零均值和单位方差,从而加速训练和提高稳定性。

  2. Dropout:可在激活函数后加入,防止过拟合,尤其是在网络较深或者数据量不足时效果明显。

改进示例(如果需要, BasicBlock 模块对隐藏层做如下修改):

self.block = nn.Sequential(nn.Linear(input_dim, output_dim),nn.BatchNorm1d(output_dim),  # 对 output 进行归一化nn.ReLU(),nn.Dropout(p=0.5)  # 设定 dropout 率,根据实际需求调整
)

3、模型的超参数

(1)数据相关参数

concat_nframes = 3   # the number of frames to concat with, n must be odd (total 2k+1 = n frames)
train_ratio = 0.75   # the ratio of data used for training, the rest will be used for validation
  • concat_nframes

    • 意义:该参数指定了在特征预处理时,每个时刻的特征旁边需要连接多少帧信息。

    • 约束:参数必须为奇数,使得当前帧处在连接帧的正中间。比如 3 = 2*1 + 1,表示当前帧以及前一帧和后一帧;可扩展到更多帧以增加上下文信息。

    • 改进思考:根据任务(例如语音识别、情感分析或其他时序任务)的具体情况,可以调整此参数。较大的上下文有时能提供更多信息,但也可能使模型过于复杂或增加计算量。注释中提到可以针对 medium baseline 做调整。

  • train_ratio

    • 意义:定义了训练集占总数据的比例。剩余的数据将用于验证。

    • 作用:平衡训练和验证数据,使得模型在训练过程中可以及时评估泛化性能。

    • 调整方向:依赖于数据集大小和任务需求。一般来说,数据量较大时可以适当降低训练比率;数据量较小时则需要更多数据用于训练,但也要留出足够数据用于验证。


(2)训练相关参数

seed = 1213          # random seed
batch_size = 512     # batch size
num_epoch = 10       # the number of training epoch
learning_rate = 1e-4 # learning rate
model_path = './model.ckpt'  # the path where the checkpoint will be saved
  • seed

    • 意义:用于设置随机数生成器的种子,保证实验的可复现性。

    • 注意:不同环境中(如 CPU/GPU)可能需要分别设置,确保所有随机相关操作都有统一的种子。但由于有函数 same_seeds,seed 传入函数 same_seeds 后可实现全流程的随机数设置。

  • batch_size

    • 意义:每个训练批次中的样本数。

    • 作用:较大的批次能稳定梯度估计,但占用更多内存;较小的批次能使模型更“嘈杂”地更新,有时有助于避免陷入局部最优。

    • 调整方向:根据实际硬件资源以及数据集大小调节。

  • num_epoch

    • 意义:训练时整个数据集将被完整遍历的次数。

    • 作用:决定模型训练的充分程度。

    • 调整思考:较复杂的模型或大数据集可能需要更多轮;可以根据验证集性能动态调节。

  • learning_rate

    • 意义:学习率控制每次参数更新的步长。

    • 作用:较低的学习率会使训练更稳定,但可能收敛较慢;较高的学习率加快收敛,但可能导致不稳定或发散。

    • 调整方向:通常需要在实验中进行调参,可能结合学习率衰减策略(learning rate scheduler)以获得较好的效果。

  • model_path

    • 意义:模型参数保存的路径,用于保存检查点(checkpoint)。

    • 作用:在训练过程中保存模型状态,便于恢复或后续推理。

    • 注意:路径应确保有写入权限,并在多个实验中做好区分以免覆盖。


(3)模型相关参数

input_dim = 39 * concat_nframes  # the input dim of the model, you should not change the value
hidden_layers = 2          # the number of hidden layers
hidden_dim = 64            # the hidden dim
  • input_dim

    • 意义:模型输入的维度。这里是固定为 39 * concat_nframes

    • 约束:通常 39 表示原始输入特征的维数,而通过帧拼接后的总维度则依赖于 concat_nframes

    • 说明:不建议手动改变此值,因为它依赖于前期特征提取和上下文拼接的设计,如果修改不当可能导致维度不匹配。

  • hidden_layers

    • 意义:网络中隐含层(除了输入和输出层之外)的数量。

    • 作用:影响模型深度和容量。更多隐含层理论上可以让模型捕捉更复杂的特征,但也更容易过拟合或导致训练困难。

    • 改进思考:根据任务和数据复杂性来调整;中等baseline设计中可能需要适度增加深度以获得更好的表达能力,同时需搭配正则化方法。

  • hidden_dim

    • 意义:隐含层中每一层的神经元数。

    • 作用:决定每层的表示能力以及模型整体的参数量。

    • 调整思考:较小的 hidden_dim 能减少计算量和防止过拟合,但可能限制模型表达能力;较大的 hidden_dim 能捕获更多信息,但需要更多计算资源和可能更容易过拟合。

    • 调参:在一些baseline实验中,可能需要对这个参数进行调整,以求在复杂度与性能之间取得平衡。

4、Dataloader的创建

from torch.utils.data import DataLoader
import gcsame_seeds(seed)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'DEVICE: {device}')# preprocess data
train_X, train_y = preprocess_data(split='train', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes, train_ratio=train_ratio)
val_X, val_y = preprocess_data(split='val', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes, train_ratio=train_ratio)# get dataset
train_set = LibriDataset(train_X, train_y)
val_set = LibriDataset(val_X, val_y)# remove raw feature to save memory
# del train_X, train_y, val_X, val_y
gc.collect()# get dataloader
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)

(1)设置随机种子和设备

same_seeds(seed)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'DEVICE: {device}')
  • same_seeds(seed)

    • 该函数的作用是设置训练中的随机种子,确保实验结果具有可复现性。seed 被传入函数中,以确保所有涉及随机操作的部分(如权重初始化、数据加载等)都能得到相同的结果。

    • 通常,same_seeds 函数内部会设置以下内容:

      • torch.manual_seed(seed):设置 PyTorch 随机数种子。

      • np.random.seed(seed):设置 NumPy 随机数种子(如果在数据处理中有用到 NumPy)。

      • random.seed(seed):设置 Python 标准库中的随机数种子。

      • torch.backends.cudnn.deterministic = Truetorch.backends.cudnn.benchmark = False:确保 CUDA 后端是确定性的,避免因为硬件加速而导致的不同实验结果。

  • device

    • 通过 torch.cuda.is_available() 检查是否有 GPU 可用。如果有,则使用 'cuda';否则,回退到 'cpu'

    • device 将会在后续的模型和数据移动过程中使用,确保模型和数据被正确加载到对应的设备上。


(2)调用函数 preprocess_data 进行数据预处理

train_X, train_y = preprocess_data(split='train', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes, train_ratio=train_ratio)
val_X, val_y = preprocess_data(split='val', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes, train_ratio=train_ratio)
  • 功能

    • 这两行代码通过 preprocess_data 函数分别处理训练数据和验证数据,返回的 train_Xtrain_y 是训练特征和标签,val_Xval_y 是验证特征和标签。

    • 主要作用是进行数据的读取、标签获取和特征拼接(上下文帧拼接),并根据 train_ratio 划分数据集(训练集和验证集)。

  • 参数说明

    • split='train'split='val':指定了分别处理训练集和验证集的数据。

    • feat_dir='./libriphone/feat'phone_path='./libriphone':指定了特征数据和标签文件所在的路径。

    • concat_nframes=concat_nframes:使用上下文帧拼接,concat_nframes 由外部参数设定。

    • train_ratio=train_ratio:设定训练集和验证集的划分比例。


(3)创建数据集对象

train_set = LibriDataset(train_X, train_y)
val_set = LibriDataset(val_X, val_y)
  • 功能

    • 将预处理好的训练特征和标签 (train_X, train_y) 以及验证特征和标签 (val_X, val_y) 封装为 LibriDataset 数据集对象。

    • LibriDataset 是您之前定义的自定义数据集类,它继承自 PyTorch 的 Dataset 类,确保数据可以通过索引获取,同时为 DataLoader 提供支持。

  • 作用

    • 将数据封装为一个 PyTorch Dataset 对象后,可以更方便地在模型训练中使用 DataLoader 进行批量加载、打乱和多线程处理。


(4)释放内存

# remove raw feature to save memory
del train_X, train_y, val_X, val_y
gc.collect()
  • 内存管理

    • 对原始数据的删除操作。原始的 train_X, train_y, val_X, val_y 是预处理数据的变量,数据已经被封装到 train_setval_set 中,因此可以手动删除原始数据以节省内存。

    • gc.collect() 是强制进行垃圾回收,清除内存中不再使用的对象。

    • 若数据集非常庞大(例如音频数据或图像数据),释放内存可以显著提高系统的效率。通常在处理非常大的数据集时,可以通过这种方式避免内存溢出。


(5)创建数据加载器(DataLoader)

train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)
  • 功能

    • DataLoader 是 PyTorch 用于数据加载的核心工具,它支持批量加载数据、自动化并行计算、数据打乱等功能。

    • train_loader 用于训练集的加载,val_loader 用于验证集的加载。

  • 参数说明

    • train_setval_set:分别是训练集和验证集的数据集对象。

    • batch_size=batch_size:每个批次的数据量,通常设置为 2 的幂次方(如 32、64、128 等)以便于 GPU 并行计算。

    • shuffle=True:训练数据每次迭代时都会进行打乱,增加训练的随机性,防止模型过拟合。

    • shuffle=False:验证数据通常不需要打乱,因为我们只是用它来评估模型性能。

5、开始训练

# create model, define a loss function, and optimizer
model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
criterion = nn.CrossEntropyLoss() 
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)best_acc = 0.0
for epoch in range(num_epoch):train_acc = 0.0train_loss = 0.0val_acc = 0.0val_loss = 0.0# trainingmodel.train() # set the model to training modefor i, batch in enumerate(tqdm(train_loader)):features, labels = batchfeatures = features.to(device)labels = labels.to(device)optimizer.zero_grad() outputs = model(features) loss = criterion(outputs, labels)loss.backward() optimizer.step() _, train_pred = torch.max(outputs, 1) # get the index of the class with the highest probabilitytrain_acc += (train_pred.detach() == labels.detach()).sum().item()train_loss += loss.item()# validationmodel.eval() # set the model to evaluation modewith torch.no_grad():for i, batch in enumerate(tqdm(val_loader)):features, labels = batchfeatures = features.to(device)labels = labels.to(device)outputs = model(features)loss = criterion(outputs, labels) _, val_pred = torch.max(outputs, 1) val_acc += (val_pred.cpu() == labels.cpu()).sum().item() # get the index of the class with the highest probabilityval_loss += loss.item()print(f'[{epoch+1:03d}/{num_epoch:03d}] Train Acc: {train_acc/len(train_set):3.5f} Loss: {train_loss/len(train_loader):3.5f} | Val Acc: {val_acc/len(val_set):3.5f} loss: {val_loss/len(val_loader):3.5f}')# if the model improves, save a checkpoint at this epochif val_acc > best_acc:best_acc = val_acctorch.save(model.state_dict(), model_path)print(f'saving model with acc {best_acc/len(val_set):.5f}')# 所有训练完毕后释放内存
del train_set, val_set
del train_loader, val_loader
gc.collect()

(1)创建模型、损失函数和优化器

model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

创建模型

  • model = Classifier(...):根据之前定义的 Classifier 类创建模型对象,并将其移动到计算设备(CPU 或 GPU)。

  • input_dim=input_dimhidden_layers=hidden_layers 等参数来自先前的配置,定义了模型的输入维度、隐藏层数和每个隐藏层的维度。

  • to(device) 确保模型在适当的设备上运行。无论是 CPU 还是 GPU,PyTorch 都能够自动将模型迁移到设备上。

定义损失函数

  • criterion = nn.CrossEntropyLoss():交叉熵损失函数用于多分类任务,是分类问题中最常见的损失函数之一。它结合了 softmax 激活函数和 log 损失。

定义优化器

  • optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    • 使用 Adam 优化器,这是一个自适应学习率优化器,常用于训练深度神经网络。

    • model.parameters() 提供了优化器需要更新的参数(即模型的权重和偏置)。

    • lr=learning_rate 设置了学习率,控制每次参数更新的步长。


(2)训练循环

model.train()  # set the model to training mode
for i, batch in enumerate(tqdm(train_loader)):features, labels = batchfeatures = features.to(device)labels = labels.to(device)optimizer.zero_grad()  # zero out the gradients from previous stepoutputs = model(features)  # forward passloss = criterion(outputs, labels)  # compute lossloss.backward()  # backward pass to compute gradientsoptimizer.step()  # update model parameters_, train_pred = torch.max(outputs, 1)  # get predicted classtrain_acc += (train_pred.detach() == labels.detach()).sum().item()  # calculate accuracytrain_loss += loss.item()  # accumulate loss
  • 训练模式

    • model.train() 将模型设置为训练模式。这对于包含 BatchNorm 或 Dropout 层的网络非常重要,因为它们的行为在训练和评估模式下不同。

  • 数据加载和设备迁移

    • features, labels = batch:从 train_loader 获取当前批次的特征和标签。

    • features.to(device)labels.to(device):将数据移动到计算设备上。

  • 梯度清零、前向传播、损失计算和反向传播

    • optimizer.zero_grad() 清除上一步计算的梯度,这是优化器的标准做法。

    • outputs = model(features) 进行前向传播,得到模型预测的输出。

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

    • loss.backward() 进行反向传播,计算梯度。

    • optimizer.step() 更新模型参数。

  • 计算训练准确率和损失

    • torch.max(outputs, 1) 获取预测的类别(outputs 是一个包含每个类概率的向量)。

    • train_acc += (train_pred.detach() == labels.detach()).sum().item():计算当前批次的准确率,并累加到 train_acc 中。

    • train_loss += loss.item():累计每个批次的损失。


(3)验证循环

model.eval()  # set the model to evaluation mode
with torch.no_grad():  # turn off gradient calculation for validationfor i, batch in enumerate(tqdm(val_loader)):features, labels = batchfeatures = features.to(device)labels = labels.to(device)outputs = model(features)loss = criterion(outputs, labels)  # compute loss_, val_pred = torch.max(outputs, 1)  # get predicted classval_acc += (val_pred.cpu() == labels.cpu()).sum().item()  # calculate accuracyval_loss += loss.item()  # accumulate loss
  • 评估模式

    • model.eval() 将模型设置为评估模式,关闭 Dropout 和 BatchNorm 层的训练行为。

  • 禁用梯度计算

    • with torch.no_grad() 在验证阶段不需要进行梯度计算,从而减少内存消耗并加快推理速度。

  • 与训练阶段类似

    • 通过与训练阶段类似的方式计算损失和准确率,唯一不同的是我们不进行梯度计算,也不会更新模型参数。


(4)结果可视化

print(f'[{epoch+1:03d}/{num_epoch:03d}] Train Acc: {train_acc/len(train_set):3.5f} Loss: {train_loss/len(train_loader):3.5f} | Val Acc: {val_acc/len(val_set):3.5f} loss: {val_loss/len(val_loader):3.5f}')
  • 打印每个 epoch 的训练和验证准确率以及损失,帮助您监控模型的训练进度和性能。

(5)保存最好的模型

if val_acc > best_acc:best_acc = val_acctorch.save(model.state_dict(), model_path)print(f'saving model with acc {best_acc/len(val_set):.5f}')

保存最佳模型

  • 如果当前 epoch 的验证准确率超过了历史最佳准确率 best_acc,则保存当前模型的参数(model.state_dict())。

  • 使用 torch.save() 将模型保存到指定路径 model_path,以便后续加载和推理。


(6)删除数据集对象(释放内存)

del train_set, val_set
del train_loader, val_loader
gc.collect()

三句分别是:

  • 删除数据集对象
  • 删除数据加载器对象
  • 强制垃圾回收

6、总结与优化建议

总结:

  1. 训练过程

    • 通过逐批次处理训练数据并更新模型参数,使用交叉熵损失计算和 Adam 优化器进行训练。

    • 每个 epoch 结束后,输出当前的训练和验证损失与准确率,帮助检查模型是否收敛。

  2. 验证过程

    • 在验证阶段,通过关闭梯度计算来提高推理效率,计算验证集的准确率和损失。

  3. 保存最佳模型

    • 在每个 epoch 结束时,如果验证准确率有所提高,保存当前模型状态,这样可以确保模型训练过程中始终保存最佳表现。

优化建议:

  1. 学习率衰减

    • 可以考虑添加学习率衰减策略,如使用 torch.optim.lr_scheduler.StepLRReduceLROnPlateau,使得学习率在训练过程中逐渐减小,从而帮助模型更好地收敛。

  2. 混合精度训练

    • 如果使用 GPU,启用混合精度训练(例如使用 torch.cuda.amp)可以加速训练并减少显存使用。

  3. Early Stopping

    • 引入早停(Early Stopping)机制,在验证性能不再提升时提前终止训练,避免浪费计算资源。

  4. 更多模型检查点

    • 除了保存验证集最优模型外,可以考虑每隔几个 epoch 保存一次模型检查点,便于后期恢复。

四、模型使用过程

这部分展示如何用模型进行预测一些没标签的数据集。

 # load data
test_X = preprocess_data(split='test', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes)
test_set = LibriDataset(test_X, None)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)# load model
model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
model.load_state_dict(torch.load(model_path))pred = np.array([], dtype=np.int32)
model.eval()
with torch.no_grad():for i, batch in enumerate(tqdm(test_loader)):features = batchfeatures = features.to(device)outputs = model(features)_, test_pred = torch.max(outputs, 1) # get the index of the class with the highest probabilitypred = np.concatenate((pred, test_pred.cpu().numpy()), axis=0)with open('prediction.csv', 'w') as f:f.write('Id,Class\n')for i, y in enumerate(pred):f.write('{},{}\n'.format(i, y))

(1)测试数据加载和预处理

# load data
test_X = preprocess_data(split='test', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes)
test_set = LibriDataset(test_X, None)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)
  • 预处理数据

    • 调用 preprocess_data 函数,并传入参数 split='test'

      • feat_dirphone_path 分别指定了特征目录和标签文件所在路径。

      • concat_nframes 指定了特征拼接的帧数。

    • 作用:该函数会读取测试数据的特征(这里测试集没有标签,因此只处理特征部分),并进行预处理操作(如归一化、帧拼接等)。

  • 构建数据集对象

    • 使用 LibriDataset(test_X, None) 将测试数据封装成一个数据集对象。

      • 注意这里标签参数传入的是 None,因为测试集没有标签,需要进行预测。

  • 创建数据加载器

    • 通过 DataLoader 对测试数据进行批量加载,设置的 batch_size 保持一致,并且不打乱数据(shuffle=False)。

    • 作用:利用 DataLoader 可以方便地在预测时逐批次处理数据,同时保证数据顺序与原始顺序一致(以便后续输出顺序对应测试样本的顺序)。


(2)加载模型

# load model
model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
model.load_state_dict(torch.load(model_path))
  • 模型构建

    • 根据之前同样的配置参数重新实例化模型对象,并调用 .to(device) 将模型加载到指定的设备(CPU 或 GPU)上。

    • 参数 input_dim, hidden_layers, hidden_dim 需要和训练时保持一致,否则无法加载已保存的模型权重。

  • 加载模型权重

    • 使用 torch.load(model_path) 读取保存的模型检查点,并用 model.load_state_dict() 加载模型参数。

    • 作用:使当前模型拥有训练期间保存下来的最佳权重,从而能在测试集上进行预测获得正确的输出。


(3)进行预测

pred = np.array([], dtype=np.int32)
model.eval()
with torch.no_grad():for i, batch in enumerate(tqdm(test_loader)):features = batchfeatures = features.to(device)outputs = model(features)_, test_pred = torch.max(outputs, 1) # get the index of the class with the highest probabilitypred = np.concatenate((pred, test_pred.cpu().numpy()), axis=0)
  • 初始化预测数组

    • pred = np.array([], dtype=np.int32) 用于存储预测类别的数组,数据类型设置为 32 位整数。

  • 设置模型为评估模式

    • model.eval() 将模型设置为评估(推理)模式。这样会关闭 Dropout 或 BatchNorm 层的训练行为,保证预测结果稳定且一致。

    • with torch.no_grad(): 则禁用梯度计算,能减少内存消耗,加速推理过程。

  • 批次预测

    • 使用 for i, batch in enumerate(tqdm(test_loader)): 遍历测试加载器。tqdm 用于显示进度条,方便监控当前进度。

    • 取出每个批次 batch,将其移动到相应设备 device 上。

    • 将输入特征送入模型获得 outputs

    • torch.max(outputs, 1) 函数返回每一行预测概率中的最大值的索引,这个索引就是模型预测的类别。

    • np.concatenate 将每个批次获得的预测结果与累积的 pred 数组拼接在一起,最终得到所有测试样本的预测结果。


(4)保存预测结果到 CSV 文件

with open('prediction.csv', 'w') as f:f.write('Id,Class\n')for i, y in enumerate(pred):f.write('{},{}\n'.format(i, y))
  • 打开 CSV 文件

    • 使用 open('prediction.csv', 'w') 打开(或创建)一个 CSV 文件,模式为写模式。

  • 写入表头

    • f.write('Id,Class\n') 写入 CSV 文件的表头,定义了两列:IdClass

  • 循环写入预测结果

    • 遍历预测数组 pred 中的每个预测结果。

    • enumerate(pred) 提供了样本的索引 i 和预测类别 y

    • 使用 f.write('{},{}\n'.format(i, y)) 将索引和预测类别格式化后写入文件,每行一个测试样本的预测结果。

  • 作用

    • 生成的 prediction.csv 文件可以直接用于后续评测或结果提交,例如在比赛或项目评估中使用。

相关文章:

【李宏毅深度学习——分类模型的PyTorch架构】Homework 2:Phoneme Classification

目录 一、数据集介绍 1、数据来源 2、数据预处理与格式 二、数据处理方面代码分析 1、下载数据集 2、为全项目过程设置随机数种子 3、预处理相关函数定义 &#xff08;1&#xff09;第一个函数&#xff1a;加载特征数据 load_feat &#xff08;2&#xff09;第二个函数…...

如何实现一个“纯净”的空对象(无原型链属性)?

在 JavaScript 中&#xff0c;要创建一个完全“纯净”的空对象&#xff08;无原型链属性&#xff0c;即对象的原型链指向 null&#xff09;&#xff0c;可以通过以下方法实现&#xff1a; 核心方法&#xff1a;Object.create(null) const pureObject Object.create(null);特性…...

STM32江科大----------PID算法

声明&#xff1a;本人跟随b站江科大学习&#xff0c;本文章是观看完视频后的一些个人总结和经验分享&#xff0c;也同时为了方便日后的复习&#xff0c;如果有错误请各位大佬指出&#xff0c;如果对你有帮助可以点个赞小小鼓励一下&#xff0c;本文章建议配合原视频使用❤️ 如…...

计算机视觉相机模型与标定:如何让计算机“看懂”三维世界?

计算机视觉相机模型与标定:如何让计算机“看懂”三维世界? 一、前言二、相机模型基础​2.1 针孔相机模型​2.1.1 模型原理​2.1.2 代码示例​2.2 透视变换与相机内参​2.2.1 透视变换矩阵​2.2.2 内参矩阵的作用​2.3 相机外参​2.3.1 世界坐标系与相机坐标系的转换​2.3.2 外…...

ETL数据集成平台在制造业有哪些应用场景

在制造业的数字化转型中&#xff0c;数据如同散落的拼图——生产线的实时参数、供应链的物流轨迹、质量检测的海量报告……每一块都承载着关键信息&#xff0c;却因系统割裂、格式混乱而难以拼出完整价值图谱。如何让数据从“成本负担”变为“战略资产”&#xff1f;ETL&#x…...

Redis-07-常见Redis使用场景

文章目录 01.缓存数据&#xff08;Cache&#xff09;02.布式锁&#xff08;Distributed Lock&#xff09;03.计数器&#xff08;Counter&#xff09;04.排行榜&#xff08;Leaderboard&#xff09;05.消息队列&#xff08;Message Queue&#xff09;06.限流&#xff08;Rate Li…...

开源模型应用落地-Podcastfy-从文本到声音的智能跃迁-Gradio(一)

一、前言 在当今信息呈现方式越来越多样化的背景下&#xff0c;如何将文字、图片甚至视频高效转化为可听的音频体验&#xff0c;已经成为内容创作者、教育者和研究者们共同关注的重要话题。Podcastfy是一款基于Python的开源工具&#xff0c;它专注于将多种形式的内容智能转换成…...

【AI News | 20250416】每日AI进展

AI Repos 1、Tutorial-Codebase-Knowledge 自动分析 GitHub 仓库并生成适合初学者的通俗易懂教程&#xff0c;清晰解释代码如何运行&#xff0c;还能生成可视化内容来展示核心功能。爬取 GitHub 仓库并从代码中构建知识库&#xff1b;分析整个代码库以识别核心抽象概念及其交互…...

iOS内存管理中的强引用问题

iOS内存管理 关于强引用循环 强引用循环是 ARC 无法自动处理的常见问题。如果两个对象互相强引用对方&#xff0c;就会造成引用计数不为零&#xff0c;导致对象无法释放。典型的情况是在闭包中引用 self 时&#xff0c;self 和闭包之间可能会互相持有&#xff0c;形成强引用循…...

电脑一直不关机会怎么样?电脑长时间不关机的影响

现代生活中&#xff0c;许多人会让自己的电脑24小时不间断运行&#xff0c;无论是为了持续的工作、娱乐&#xff0c;还是出于忘记关机的习惯。然而&#xff0c;电脑长时间不关机&#xff0c;除了提供便利之外&#xff0c;也可能对设备的健康产生一系列影响。本文将为大家介绍电…...

openGauss DataVec + Dify,快速搭建你的智能助手平台

在当今数字化和智能化的时代&#xff0c;大语言模型&#xff08;LLM&#xff09;的应用正以前所未有的速度改变着各个领域的工作方式和用户体验。Dify 作为一个开源的大语言模型应用开发平台&#xff0c;为开发者们提供了便捷且强大的工具&#xff0c;助力构建从基础智能体到复…...

React 入门教程:构建第一个 React 应用

本教程将带你从零开始构建你的第一个 React 应用。我们将创建一个简单的计数器应用&#xff0c;涵盖 React 的基本概念和开发流程。 准备工作 在开始之前&#xff0c;请确保你的开发环境满足以下要求&#xff1a; Node.js (建议使用最新的 LTS 版本) npm 或 yarn (Node.js 安…...

【数字图像处理】数字图像空间域增强(3)

图像锐化 图像细节增强 图像轮廓&#xff1a;灰度值陡然变化的部分 空间变化&#xff1a;计算灰度变化程度 图像微分法&#xff1a;微分计算灰度梯度突变的速率 一阶微分&#xff1a;单向差值 二阶微分&#xff1a;双向插值 一阶微分滤波 1&#xff1a;梯度法 梯度&#xff1…...

mapstruct使用详解

一、背景&#xff1a;为什么需要 mapstruct 在 Java 开发中&#xff0c;对象之间的映射&#xff08;如 DTO 转实体类、实体类转 VO&#xff09;是一项高频且繁琐的任务。手动编写映射代码存在以下问题&#xff1a; 冗余代码多&#xff1a;每个类都需要重复编写 setter 和 get…...

Token与axios拦截器

目录 一、Token 详解 1. Token 的定义与作用 2. Token 的工作流程 3. Token 的优势 4. Token 的安全实践 5. JWT 结构示例 二、Axios 拦截器详解 1. 拦截器的作用 2. 请求拦截器 3. 响应拦截器 4. 拦截器常见场景 5. 移除拦截器 三、完整代码示例 四、总结 五、…...

windows上安装Jenkins

1. 下载windows版 jenkins安装包 2. 配置本地安全策略 在 Windows 11/10 上打开本地安全策略。 Secpol.msc 或本地安全策略编辑器是一个 Windows 管理工具&#xff0c;允许您在本地计算机上配置和管理与安全相关的策略。 安全设置-》本地策略-》用户权限分配-》作为服务登录…...

鸿蒙NEXT开发文件预览工具类(ArkTs)

import { uniformTypeDescriptor } from kit.ArkData; import { filePreview } from kit.PreviewKit; import { FileUtil } from ./FileUtil; import { AppUtil } from ./AppUtil; import { WantUtil } from ./WantUtil;/*** 文件预览工具类* 提供文件预览、加载、判断等功能。…...

【WPF-VisionMaster源代码】应用OpenCVSharp仿Vision Master页面开发的软件源代码

一、目的&#xff1a;开放WPF-VisionMaster源代码 二、简介 WPF-Vision Master 视觉处理软件源码 WPF-Vision Master是基于WPF-Control的UI框架与OpenCVSharp计算机视觉库联合&#xff0c;并参考Vision Master界面开发的视觉处理软件。该平台深度融合WPF强大的界面控制能力和Op…...

软件研发过程中的技术债

引言&#xff1a;数字时代的“技术利息” 在金融领域&#xff0c;债务是推动发展的杠杆&#xff1b;而在软件开发中&#xff0c;技术债&#xff08;Technical Debt&#xff09;却是一把双刃剑。据行业调查显示&#xff0c;70%的软件项目存在技术债&#xff0c;其中超过半数团队…...

鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析

一、项目初始化与环境准备 1. 创建鸿蒙工程 src/main/ets/ ├── api/ │ ├── api.ets # 接口聚合入口 │ ├── login.ets # 登录模块接口 │ └── request.ets # 网络请求核心封装 └── pages/ └── login.ets # 登录页面逻辑…...

重新定义“边缘”:边缘计算如何重塑人类与数据的关系

在数字化浪潮中&#xff0c;云计算曾是科技界的宠儿&#xff0c;但如今&#xff0c;边缘计算正在悄然改变游戏规则。它不仅是一种技术进步&#xff0c;更是对人类与数据关系的一次深刻反思。本文将探讨边缘计算如何从“中心化”走向“分布式”&#xff0c;以及它如何在效率、隐…...

Flink CDC 出现错误码 1236 和 SQL 状态 HY000 的原因及解决方法

Flink CDC 出现错误码 1236 和 SQL 状态 HY000 的原因及解决方法 常见原因 server-id 冲突:当多个 Flink CDC 任务连接同一个 MySQL 实例,且使用了相同的 server-id 时,会导致该冲突。因为 MySQL 服务器通过 server-id 来区分不同的从服务器,如果多个 Flink CDC 任务使用相…...

如何解除Excel只读状态?4种方法全解析

在日常办公中&#xff0c;我们经常会遇到Excel文件被设置为只读模式的情况。只读模式可以防止文件被意外修改&#xff0c;但在需要编辑时&#xff0c;解除只读模式就显得尤为重要。下面小编分享退出Excel只读方式的4种方法&#xff0c;让你能够轻松编辑工作表。 方法1&#xf…...

深度学习与 Flask 应用常见问题解析

在深度学习和 Flask 应用开发过程中&#xff0c;我们常常会遇到一些关键的知识点和容易混淆的问题。下面我们就来对这些问题进行详细的解析。 一、卷积神经网络&#xff08;CNN&#xff09;常用层 在定义卷积神经网络时&#xff0c;有一些常用的层&#xff1a; Conv2D&#x…...

零浪费,最高效率:通往0%废品率的道路

在工业自动化领域&#xff0c;努力实现废品率为 0% 是最大的挑战之一。这意味着不生产任何有缺陷的产品 —— 从而减少浪费、降低成本&#xff0c;并提高客户满意度。尽管实现这一目标颇具雄心壮志&#xff0c;但企业可以采取几个步骤来改进自身流程&#xff0c;以达成这一目标…...

【c++深入系列】:new和delete运算符详解

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; “生活不会向你许诺什么&#xff0c;尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去&#xff0c;终有一天&…...

基础(测试用例:介绍,测试用例格式,案例)

目录 测试用例介绍 测试用例编写格式 案例 测试用例介绍 用例&#xff1a;用户使用软件的案例场景 测试用例&#xff1a;是为测试项目而设计的测试执行文档 测试用例的作用&#xff1a; 防止漏测是实施测试的标准可以作为测试工作量的评估 测试用例编写格式 用例编号 用例…...

MCP协议,.Net 使用示例

服务器端示例 基础服务器 以下是一个基础的 MCP 服务器示例&#xff0c;它使用标准输入输出&#xff08;stdio&#xff09;作为传输方式&#xff0c;并实现了一个简单的回显工具&#xff1a; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.H…...

Node.js 数据库 事务 项目示例

1、参考&#xff1a;JavaScript语言的事务管理_js 函数 事务性-CSDN博客 或者百度搜索&#xff1a;Nodejs控制事务&#xff0c; 2、实践 2.1、对于MySQL或MariaDB&#xff0c;你可以使用mysql或mysql2库&#xff0c;并结合Promise或async/await语法来控制事务。 使用 mysql2…...

【AI插件开发】Notepad++ AI插件开发实践:支持多平台多模型

引言 上篇文章我们的Notepad插件介绍到Dock窗口集成&#xff0c;本篇将继续完善插件功能&#xff0c;主要包括两个部分&#xff1a; 支持多平台、多模型支持多种授权验证、接口类型 一、多平台 原先的配置项很简单&#xff1a; // PluginConf.h class PlatformConf { publ…...

微信小程序数字滚动效果

效果图 .wxml <view class"container"><view class"container-num" wx:for"{{number}}" wx:key"index"><view class"num-container" style"--h:{{h}}px;--y:{{-item * h }}px;"><view wx:f…...

wx219基于ssm+vue+uniapp的教师管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…...

Python 注释进阶之Google风格

文章目录 1. Google 风格 Docstring 的核心特点2. Google 风格的基本结构3. 编写规则和注意事项4. 最常用的 Google 风格 Docstring 示例示例 1&#xff1a;普通函数 示例 2&#xff1a;带默认参数和可变参数的函数示例 3&#xff1a;类示例 4&#xff1a;生成器函数示例 5&…...

写测试文档时,需要的环境配置怎么查看

操作系统 cat /etc/os-releaseCPU信息 lscpu 内存 sudo dmidecode --type memory | grep -E "Size:|Type:|Speed:"硬盘 列出当前系统中 所有块设备&#xff08;Block Devices&#xff09; 的信息&#xff0c;并显示指定列&#xff08;-o 参数&#xff09; lsblk…...

强化学习的数学原理(十)actor-critic 方法

由于全文太长&#xff0c;只好分开发了。(已完结&#xff01;在专栏查看本系列其他文章&#xff09; 个人博客可以直接看全文~ 本系列为在学习赵世钰老师的“强化学习的数学原理” 课程后所作笔记。 课堂视频链接https://www.bilibili.com/video/BV1sd4y167NS/ 第十章 acto…...

多个定时器同时工作时,会出现哪些常见的bug ,如何解决??(定时任务未实时更新但刷新后正常的问题分析)

1. 定时器冲突与覆盖 问题&#xff1a;后设置的定时器可能覆盖先前的定时器&#xff0c;导致前一个定时器失效 原因&#xff1a;未正确管理定时器ID或未清除前一个定时器 2. 性能问题 内存泄漏&#xff1a;未清除不再需要的定时器会导致内存占用不断增加 CPU过载&#xff1a…...

代码随想录算法训练营day5(哈希表)

华子目录 有效的字母异位词思路 有效的字母异位词 https://leetcode.cn/problems/valid-anagram/description/ 思路 使用哈希表&#xff0c;这里哈希表使用数组先申请一个26空间的大小的数组遍历第一个字符串&#xff0c;记录每个字符出现的次数1遍历第二个字符串&#xff0c…...

Python(17)Python字符编码完全指南:从存储原理到乱码终结实战

目录 背景介绍一、字符编码核心原理1. 计算机存储本质2. Python3的编码革命3. 主流编码格式对比 二、编码转换核心方法1. 编码&#xff08;Encode&#xff09;过程2. 解码&#xff08;Decode&#xff09;过程3. 错误处理策略 三、文件操作编码实战1. 文本文件读写2. 二进制模式…...

Node.js 文件读取与复制相关内容

Node.js 文件读取与复制相关内容的系统总结&#xff0c;包括 同步读取、异步读取、流式读取、复制操作、两者对比及内存测试。 &#x1f9e9; 一、Node.js 文件读取方式总结 Node.js 使用 fs&#xff08;文件系统&#xff09;模块进行文件操作&#xff1a; 1. 同步读取&#…...

大数据面试问答-HBase/ClickHouse

1. HBase 1.1 概念 HBase是构建在Hadoop HDFS之上的分布式NoSQL数据库&#xff0c;采用列式存储模型&#xff0c;支持海量数据的实时读写和随机访问。适用于高吞吐、低延迟的场景&#xff0c;如实时日志处理、在线交易等。 RowKey&#xff08;行键&#xff09; 定义&#xf…...

jupyter 文件浏览器,加强版,超好用,免费exe

第一步&#xff1a;github搜索 lukairui的 jupyter-viewer-plus 仓库 第二步&#xff1a; git clone 到本地。 解压zip包 第三步&#xff1a; 进入压缩包&#xff0c;第一次双击打开jupyter-viewer-plus.exe运行&#xff0c;第一次运行后&#xff0c;界面上有一个“设为…...

【AI工具】用大模型生成脑图初试

刚试用了一下通过大模型生成脑图&#xff0c;非常简单&#xff0c;记录一下 一、用大模型生成脑图文件 关键&#xff1a;存在markdown文件 举例&#xff1a;使用Deepseek&#xff0c;输入问题&#xff1a;“针对大模型的后训练&#xff0c;生成一个开发计划&#xff0c;用ma…...

数据结构-树与二叉树

一、树的定义与基本术语 1.1 树的定义 树&#xff08;Tree&#xff09;是一种非线性的数据结构&#xff0c;它是由 n&#xff08;n ≥ 0&#xff09;个有限节点组成的集合。如果 n 0&#xff0c;称为空树&#xff1b;如果 n > 0&#xff0c;则&#xff1a; 有一个特定的节…...

STL_unordered_map_01_基本用法

&#x1f44b; Hi, I’m liubo&#x1f440; I’m interested in harmony&#x1f331; I’m currently learning harmony&#x1f49e;️ I’m looking to collaborate on …&#x1f4eb; How to reach me …&#x1f4c7; sssssdsdsdsdsdsdasd&#x1f383; dsdsdsdsdsddfsg…...

ARCGIS国土超级工具集1.5更新说明

ARCGIS国土超级工具集V1.5版本更新说明&#xff1a;因作者近段时间工作比较忙及正在编写ARCGISPro国土超级工具集&#xff08;截图附后&#xff09;的原因&#xff0c;故本次更新为小更新&#xff08;没有增加新功能&#xff0c;只更新了已有的工具&#xff09;。本次更新主要修…...

主流物理仿真引擎和机器人/强化学习仿真平台对比

以下是当前主流的物理仿真引擎和机器人/强化学习仿真平台的特点和适用场景&#xff0c;方便根据需求选择&#xff1a; &#x1f9e0; NVIDIA 系列 ✅ Isaac Lab v1.4 / v2 特点&#xff1a; 基于 Omniverse Isaac Sim&#xff0c;属于高端视觉机器人仿真框架v2 更加模块化&a…...

STM32 HAL库内部 Flash 读写实现

一、STM32F407 内部 Flash 概述 1.1 Flash 存储器的基本概念 Flash 存储器是一种非易失性存储器&#xff0c;它可以在掉电的情况下保持数据。STM32F407 系列微控制器内部集成了一定容量的 Flash 存储器&#xff0c;用于存储程序代码和数据。Flash 存储器具有擦除和编程次数的…...

C++学习:六个月从基础到就业——面向对象编程:构造函数与析构函数

C学习&#xff1a;六个月从基础到就业——面向对象编程&#xff1a;构造函数与析构函数 本文是我C学习之旅系列的第十篇技术文章&#xff0c;主要讨论C中构造函数与析构函数的概念、特点和使用技巧。这些是C对象生命周期管理的关键组成部分。查看完整系列目录了解更多内容。 引…...

dfs二叉树中的深搜(回溯、剪枝)--力扣129、814、230、257

目录 1.1题目链接&#xff1a;129.求根节点到叶结点数字之和 1.2题目描述&#xff1a;给你一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 1.3解法(dfs-前序遍历)&#xff1a; 2.1题目链接&#xff1a;814.二叉树剪枝 2.2题目描述&…...

Python Selenium 一小时速通教程

Python Selenium 一小时速通教程 实战案例 一、环境配置&#xff08;10分钟&#xff09; 安装Python 确保已安装Python 3.x&#xff08;官网下载&#xff09;。 安装Selenium 在终端运行&#xff1a; pip install selenium下载浏览器驱动 Chrome&#xff1a;访问 ChromeDriv…...