5月24日day35打卡

news2025/5/26 2:38:11

模型可视化与推理

知识点回顾:

  1. 三种不同的模型可视化方法:推荐torchinfo打印summary+权重分布可视化
  2. 进度条功能:手动和自动写法,让打印结果更加美观
  3. 推理的写法:评估模式

作业:调整模型定义时的超参数,对比下效果。

#  nn.Module 的内置功能,直接输出模型结构
print(model)

简单来说,你看到的是一个神经网络模型的“内部结构清单”,就像拆开一台机器后看到的零件列表。咱们用生活中的例子来理解:

假设你要做一个“判断水果类型”的模型(比如区分苹果、香蕉、橘子),输入可能是水果的4个特征(比如重量、颜色、表皮光滑度、大小),输出是3种水果的概率。这个MLP模型的结构就像一条“数据加工流水线”:

  1. 第一关(fc1层):有个“4转10的转换器”。它拿到输入的4个特征数据后,会做一些数学计算(比如加权求和+偏移),把这4个数据“变”成10个新的数据。就像把4种原材料加工成10种中间产物。

  2. 激活关(relu层):这一步会“过滤掉没用的中间产物”。比如如果某个中间产物算出来是负数(可能代表“没用的信息”),它就会直接变成0(相当于丢弃),只保留正数的部分。这样能让模型更“聪明”,只关注有用的信息。

  3. 第二关(fc2层):有个“10转3的转换器”。它拿到前面过滤后的10个数据,再做一次数学计算,最终把它们“浓缩”成3个结果。这3个结果就对应你要判断的3种水果的概率(比如哪个数值大,就更可能是对应的水果)。

所以整体看,这个模型就是通过两层“数据转换器”+一层“过滤无用信息”的步骤,把输入的4个特征,一步步处理成3个最终结果。

 

 

# nn.Module 的内置功能,返回模型的可训练参数迭代器
for name, param in model.named_parameters():
    print(f"Parameter name: {name}, Shape: {param.shape}")

这些是神经网络模型中各个层的可学习参数(权重和偏置)的信息,用大白话解释就是模型“内部用来做计算的规则”。我们一个一个看:


1. fc1.weight(形状 [10, 4])

这是第一个全连接层(fc1)的“权重矩阵”。

  • 作用:负责把输入的4维数据“转换”成10维数据。
  • 形状含义:10行(对应输出的10个维度),4列(对应输入的4个维度)。可以理解为:每个输出维度(共10个)需要和输入的4个维度“一一配对”计算,所以需要10×4个“小规则”(即权重参数)。

2. fc1.bias(形状 [10])

这是第一个全连接层的“偏置参数”。

  • 作用:给每个输出维度的计算结果加一个“偏移量”(类似数学里的 y = kx + b 中的 b),让模型能拟合更复杂的规律。
  • 形状含义:因为fc1输出是10维,所以需要10个偏置参数(每个输出维度对应1个)。

3. fc2.weight(形状 [3, 10])

这是第二个全连接层(fc2)的“权重矩阵”。

  • 作用:负责把前一层(fc1)输出的10维数据“转换”成最终的3维结果。
  • 形状含义:3行(对应最终输出的3个维度),10列(对应前一层输入的10个维度)。每个输出维度(共3个)需要和前一层的10个维度“配对”计算,所以需要3×10个权重参数。

4. fc2.bias(形状 [3])

这是第二个全连接层的“偏置参数”。

  • 作用:给最终输出的每个维度加一个“偏移量”。
  • 形状含义:因为最终输出是3维,所以需要3个偏置参数(每个输出维度对应1个)。

总结

这些参数是模型在训练过程中自动学习的(比如通过调整这些参数的值,让模型预测更准)。简单说,它们就像模型内部的“计算器”,输入数据经过这些参数的计算后,最终得到你想要的结果(比如分类、回归等)。

 

 

# 提取权重数据
import numpy as np
weight_data = {}
for name, param in model.named_parameters():
    if 'weight' in name:
        weight_data[name] = param.detach().cpu().numpy()

# 可视化权重分布
fig, axes = plt.subplots(1, len(weight_data), figsize=(15, 5))
fig.suptitle('Weight Distribution of Layers')

for i, (name, weights) in enumerate(weight_data.items()):
    # 展平权重张量为一维数组
    weights_flat = weights.flatten()
    
    # 绘制直方图
    axes[i].hist(weights_flat, bins=50, alpha=0.7)
    axes[i].set_title(name)
    axes[i].set_xlabel('Weight Value')
    axes[i].set_ylabel('Frequency')
    axes[i].grid(True, linestyle='--', alpha=0.7)

plt.tight_layout()
plt.subplots_adjust(top=0.85)
plt.show()

# 计算并打印每层权重的统计信息
print("\n=== 权重统计信息 ===")
for name, weights in weight_data.items():
    mean = np.mean(weights)
    std = np.std(weights)
    min_val = np.min(weights)
    max_val = np.max(weights)
    print(f"{name}:")
    print(f"  均值: {mean:.6f}")
    print(f"  标准差: {std:.6f}")
    print(f"  最小值: {min_val:.6f}")
    print(f"  最大值: {max_val:.6f}")
    print("-" * 30)

 

这里的“每层权重”指的是神经网络中每个可学习层(比如全连接层)内部的“核心参数”,也就是模型通过训练自动调整的“规则数值”。咱们结合你的代码和神经网络的工作原理,用大白话解释:

1. 什么是“权重”?

简单说,权重是神经网络里神经元之间的“连接强度”。就像你做数学题时的“计算公式”——比如输入4个特征(x1, x2, x3, x4),输出10个中间结果(y1~y10),每个y的计算可能是:
y1 = x1*w11 + x2*w12 + x3*w13 + x4*w14
这里的w11、w12、w13、w14就是“权重”(weight),它们决定了输入特征对输出结果的影响程度。

2. “每层权重”具体指什么?

你的代码里,model.named_parameters()会遍历模型所有可训练参数(包括权重和偏置),而if 'weight' in name筛选出了属于“权重”的参数。例如:

  • 如果模型有fc1(第一个全连接层),对应的权重是fc1.weight
  • 如果有fc2(第二个全连接层),对应的权重是fc2.weight
    这些就是代码中“每层权重”的具体对象。

3. 代码在“提取”和“分析”它们的什么?

你的代码做了两件关键的事:

(1)提取权重数据

把模型中每个层的权重参数(比如fc1.weightfc2.weight)从PyTorch的张量(Tensor)转成numpy数组,存到weight_data字典里。这样后续可以用numpy和matplotlib分析。

(2)可视化+统计分析
  • 直方图:把每个层的权重值“摊平”成一维数组(比如fc1.weight是10×4的矩阵,摊平后是40个数值),然后统计这些数值的分布(比如大部分权重是0附近的小数?还是有很多大的正数/负数?)。
  • 统计信息:计算每个层权重的均值、标准差、最小/最大值。这些数值能帮你快速判断:
    • 权重是否初始化合理(比如均值是否接近0,标准差是否太小/太大);
    • 训练是否正常(比如训练后权重是否有明显变化,是否出现梯度消失/爆炸)。

总结

“每层权重”就是模型中每个层(比如全连接层)里用于“计算输入→输出”的核心参数。你的代码在“检查”这些参数的分布和统计特征,就像给模型做“体检”——通过它们的数值表现,你可以判断模型是否训练正常、参数初始化是否合理,甚至定位训练中的问题(比如权重全为0可能是初始化错误)。

 

from torchsummary import summary
# 打印模型摘要,可以放置在模型定义后面
summary(model, input_size=(4,))

 

这是用 torchsummary 工具打印的模型“详细体检报告”,帮你快速看清模型每一层的“输出尺寸”和“参数数量”。咱们逐行用大白话解释:

第一部分:各层的具体信息(表格)

表格里的具体行:
  1. Linear-1(第一个全连接层)

    • Output Shape [-1, 10]:不管输入多少个样本(-1 代表批量大小),每个样本经过这层后会变成10个特征的数据。
    • Param # 50:这层有50个参数。计算方式是:输入4个特征,输出10个特征 → 权重参数是 10×4=40(类似“4转10的转换规则”),再加上10个偏置参数(每个输出特征一个),总共 40+10=50
  2. ReLU-2(激活函数层)

    • Output Shape [-1, 10]:激活函数不会改变数据的特征数量,所以输出还是10个特征。
    • Param # 0:ReLU激活函数(比如 max(0, x))没有需要学习的参数,只是单纯的数学运算,所以参数数为0。
  3. Linear-3(第二个全连接层)

    • Output Shape [-1, 3]:每个样本经过这层后会变成3个特征的数据(比如对应3分类任务的结果)。
    • Param # 33:输入是前一层的10个特征,输出3个特征 → 权重参数是 3×10=30,加上3个偏置参数,总共 30+3=33

第二部分:模型整体统计(表格下方)

  • Total params: 83:整个模型所有层的参数总和(50+0+33=83)。
  • Trainable params: 83:所有参数都可以被训练(比如用数据调整这些参数的值)。
  • Non-trainable params: 0:没有“冻结”的参数(有些模型会固定部分参数不训练,这里没有这种情况)。

第三部分:内存占用估计(最下方)

这部分是模型运行时的内存消耗估算(输入数据、计算过程、参数本身占用的内存)。这里显示 0.00 可能是因为输入数据的尺寸太小(比如输入是4维的小数),实际项目中如果输入数据很大(如图像),这里会显示具体数值。

总结:这个摘要是模型的“透明化报告”,让你一眼看清每一层怎么“加工数据”,以及模型总共需要学多少参数(参数越多,模型“记忆能力”越强,但可能越容易过拟合)。

 

该方法不显示输入层的尺寸,因为输入的神经网是自己设置的,所以不需要显示输入层的尺寸。但是在使用该方法时,input size=(4,)参数是必需的,因为PyTorch需要知道输入数据的形状才能推断模型各层的输出形状和参数数量。


这是因为PyTorch的模型在定义时是动态的,它不会预先知道输入数据的具体形状。nn.Linear(4,10)只定义了"输入维度是4,输出维度是10”,但不知道输入的批量大小和其他维度,比如卷积层需要知道输入的通道数、高度、宽度等信息。-并非所有输入数据都是结构化数据


因此,要生成模型摘要(如每层的输出形状、参数数量),必须提供一个示例输入形状,让PyTorch"运行”一次模型,从而推断出各层的信息。


summary函数的核心逻辑是:

  1. 创建一个与input size形状匹配的虚拟输入张量(通常填充零)
  2. 将虚拟输入传递给模型,执行一次前向传播(但不计算梯度)
  3. 记录每一层的输入和输出形状,以及参数数量
  4. 生成可读的摘要报告

构建神经网络的时候

  1. 输入层不需要写:x多少个特征输入层就有多少神经元
  2. 隐藏层需要写,从第一个隐藏层可以看出特征的个数
  3. 输出层的神经元和任务有关,比如分类任务,输出层有3个神经元,一个对应每个类别

 

上图只是帮助你理解,和上述架构不同,每条线记录权重w,在每个神经元内计算并且输出Relu(w*x+b)


可以看做,线记录权重,神经元记录偏置和损失函数


可学习参数计算

  1. Linear-1对应self.fc1=nn.Linear(4,10),表明前一层有4个神经元,这一层有10个神经元,每2个神经元之间靠着线相连,所有有4*10个权重参数+10个偏置参数=50个参数
  2. rlu层不涉及可学习参数,可以把它和前一个线性层看成一层,图上也是这个含义
  3. Linear-3层对应代码self.fc2=nn.Linear(10,3),10*3个权重参数+3个偏置=33个参数

总参数83个,占用内存几乎为0

 torchinfo是提供比torchsummary更详细的模型摘要信息,包括每层的输入输出形状、参数数量、计算量等。

torchinfo是提供比torchsummary更详细的模型摘要信息,包括每层的输入输出形状、参数数
量、计算量等。

进度条功能:

我们介绍下tgdm这个库,他非常适合用在循环中观察进度。尤其在深度学习这种训练是循环的场景中。他最核心的逻辑如下:

  1. 创建一个进度条对象,并传入总迭代次数。一般用wth语句创建对象,这样对象会在with语句结束后自动销毁,保证资源释放。wth是常见的上下文管理器,这样的使用方式还有用with打开文件,结束后会自动关闭文件。
  2. 更新进度条,通过pbar.update(n)指定每次前进的步数n(适用于非固定步长的循环)。

手动更新:

from tqdm import tqdm  # 先导入tqdm库
import time  # 用于模拟耗时操作

# 创建一个总步数为10的进度条
with tqdm(total=10) as pbar:  # pbar是进度条对象的变量名
    # pbar 是 progress bar(进度条)的缩写,约定俗成的命名习惯。
    for i in range(10):  # 循环10次(对应进度条的10步)
        time.sleep(0.5)  # 模拟每次循环耗时0.5秒
        pbar.update(1)  # 每次循环后,进度条前进1步

 

from tqdm import tqdm
import time

# 创建进度条时添加描述(desc)和单位(unit)
with tqdm(total=5, desc="下载文件", unit="个") as pbar:
    # 进度条这个对象,可以设置描述和单位
    # desc是描述,在左侧显示
    # unit是单位,在进度条右侧显示
    for i in range(5):
        time.sleep(1)
        pbar.update(1)  # 每次循环进度+1

 

unit参数的核心作用是明确进度条中每个进度单位的含义,使可视化信息更具可读性。在深度学习训练中,常用的单位包括:

  • epoch:训练轮次(遍历整个数据集一次)。
  • batch:批次(每次梯度更新处理的样本组)。
  • sample:样本(单个数据点)

自动更新:

from tqdm import tqdm
import time

# 直接将range(3)传给tqdm,自动生成进度条
# 这个写法我觉得是有点神奇的,直接可以给这个对象内部传入一个可迭代对象,然后自动生成进度条
for i in tqdm(range(3), desc="处理任务", unit="epoch"):
    time.sleep(1)

 

for i in tqdm(range(3),desc="处理任务",unit="个")这个写法则不需要在循环中调用update()方法,更加简洁
实际上这2种写法都随意选取,这里都介绍下 

# 用tqdm的set_postfix方法在进度条右侧显示实时数据(如当前循环的数值、计算结果等):
from tqdm import tqdm
import time

total = 0  # 初始化总和
with tqdm(total=10, desc="累加进度") as pbar:
    for i in range(1, 11):
        time.sleep(0.3)
        total += i  # 累加1+2+3+...+10
        pbar.update(1)  # 进度+1
        pbar.set_postfix({"当前总和": total})  # 显示实时总和

 

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import time
import matplotlib.pyplot as plt
from tqdm import tqdm  # 导入tqdm库用于进度条显示

# 设置GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data  # 特征数据
y = iris.target  # 标签数据

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 归一化数据
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 将数据转换为PyTorch张量并移至GPU
X_train = torch.FloatTensor(X_train).to(device)
y_train = torch.LongTensor(y_train).to(device)
X_test = torch.FloatTensor(X_test).to(device)
y_test = torch.LongTensor(y_test).to(device)

class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(4, 10)  # 输入层到隐藏层
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(10, 3)  # 隐藏层到输出层

    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        return out

# 实例化模型并移至GPU
model = MLP().to(device)

# 分类问题使用交叉熵损失函数
criterion = nn.CrossEntropyLoss()

# 使用随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 训练模型
num_epochs = 20000  # 训练的轮数

# 用于存储每100个epoch的损失值和对应的epoch数
losses = []
epochs = []

start_time = time.time()  # 记录开始时间

# 创建tqdm进度条
with tqdm(total=num_epochs, desc="训练进度", unit="epoch") as pbar:
    # 训练模型
    for epoch in range(num_epochs):
        # 前向传播
        outputs = model(X_train)  # 隐式调用forward函数
        loss = criterion(outputs, y_train)

        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 记录损失值并更新进度条
        if (epoch + 1) % 200 == 0:
            losses.append(loss.item())
            epochs.append(epoch + 1)
            # 更新进度条的描述信息
            pbar.set_postfix({'Loss': f'{loss.item():.4f}'})

        # 每1000个epoch更新一次进度条
        if (epoch + 1) % 1000 == 0:
            pbar.update(1000)  # 更新进度条

    # 确保进度条达到100%
    if pbar.n < num_epochs:
        pbar.update(num_epochs - pbar.n)  # 计算剩余的进度并更新

time_all = time.time() - start_time  # 计算训练时间
print(f'Training time: {time_all:.2f} seconds')

# # 可视化损失曲线
# plt.figure(figsize=(10, 6))
# plt.plot(epochs, losses)
# plt.xlabel('Epoch')
# plt.ylabel('Loss')
# plt.title('Training Loss over Epochs')
# plt.grid(True)
# plt.show()

 

 

模型的推理:

之前我们说完了训练模型,那么现在我们来测试模型。测试这个词在大模型领域叫做推理(inference),意味着把数据输入到训练好的模型的过程。
 

注意损失和优化器在训练阶段。 

“注意损失和优化器在训练阶段”是指:在深度学习模型的训练阶段(而非测试/评估阶段),损失函数(Loss Function)和优化器(Optimizer)是核心组件,用于更新模型参数;而在测试/评估阶段不需要这两个组件。

具体解释如下:

1. 损失函数(Loss Function)的作用

损失函数用于衡量模型预测值与真实值之间的误差。在训练阶段,我们需要通过损失函数计算当前模型的“错误程度”(例如分类任务常用交叉熵损失)。这个损失值会作为信号,指导优化器调整模型参数。

2. 优化器(Optimizer)的作用

优化器根据损失函数计算出的误差,通过反向传播(Backward Propagation)更新模型的权重参数(例如SGD、Adam等优化器)。训练的核心目标就是通过优化器不断调整参数,使得损失值逐渐降低,模型性能提升。

3. 为什么测试阶段不需要损失和优化器?

测试/评估阶段的目标是验证模型在未见过数据上的表现(如你提供的代码中的model.eval()torch.no_grad())。此时模型参数已经固定(通过训练阶段优化完成),只需前向传播得到预测结果(如outputs = model(X_test)),不需要计算损失(因为不关心“误差多大”,只关心“预测是否正确”),也不需要优化器(因为不更新参数)。

与你当前代码的关系

提供的代码是测试阶段的评估逻辑,因此没有出现损失函数和优化器的代码。但在训练阶段(例如for epoch in range(num_epochs)的循环中),一定会包含类似以下逻辑:

# 训练阶段示例代码(非修改你的原代码)
model.train()  # 开启训练模式
optimizer.zero_grad()  # 清空优化器梯度
outputs = model(X_train)  # 前向传播
loss = criterion(outputs, y_train)  # 计算损失(criterion是损失函数)
loss.backward()  # 反向传播计算梯度
optimizer.step()  # 优化器更新参数
# 在测试集上评估模型,此时model内部已经是训练好的参数了
# 评估模型
model.eval() # 设置模型为评估模式
with torch.no_grad(): # torch.no_grad()的作用是禁用梯度计算,可以提高模型推理速度
    outputs = model(X_test)  # 对测试数据进行前向传播,获得预测结果
    _, predicted = torch.max(outputs, 1) # torch.max(outputs, 1)返回每行的最大值和对应的索引
    #这个函数返回2个值,分别是最大值和对应索引,参数1是在第1维度(行)上找最大值,_ 是Python的约定,表示忽略这个返回值,所以这个写法是找到每一行最大值的下标
    # 此时outputs是一个tensor,p每一行是一个样本,每一行有3个值,分别是属于3个类别的概率,取最大值的下标就是预测的类别


    # predicted == y_test判断预测值和真实值是否相等,返回一个tensor,1表示相等,0表示不等,然后求和,再除以y_test.size(0)得到准确率
    # 因为这个时候数据是tensor,所以需要用item()方法将tensor转化为Python的标量
    # 之所以不用sklearn的accuracy_score函数,是因为这个函数是在CPU上运行的,需要将数据转移到CPU上,这样会慢一些
    # size(0)获取第0维的长度,即样本数量

    correct = (predicted == y_test).sum().item() # 计算预测正确的样本数
    accuracy = correct / y_test.size(0)
    print(f'测试集准确率: {accuracy * 100:.2f}%')

模型的评估模式简单来说就是评估阶段会关闭一些训练相关的操作和策略,比如更新参数正则化等操作,确保模型输出结果的稳定性和一致性。


为什么评估模式不关闭梯度计算,推理不是不需要更新参数么?


主要还是因为在某些场景下,评估阶段可能需要计算梯度(虽然不更新参数)。例如:计算梯度用于可视化(如CAM热力图,主要用于cnn相关)。所以为了避免这种需求不被满足,还是需要手动关闭梯度计算。
 @浙大疏锦行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux(7)——进程(概念篇)

目录 一、基本概念 二、描述进程——PCB 1.task_struct——PCB的一种 2.task_struct的内容分类 三、查看进程 1.通过系统目录查看 2.通过ps命令查看 四、通过系统调用获取进程的PID和PPID 五、通过系统调用创建进程 1.fork函数创建子进程 2.使用if来引出问题 六、L…

前端流行框架Vue3教程:24.动态组件

24.动态组件 有些场景会需要在两个组件间来回切换&#xff0c;比如 Tab 界面 我们准备好A B两个组件ComponentA ComponentA App.vue代码如下&#xff1a; <script> import ComponentA from "./components/ComponentA.vue" import ComponentB from "./…

Unity3D仿星露谷物语开发48之显示树桩效果

1、目标 砍完橡树之后会露出树桩&#xff0c;然后树桩可以用斧头收割&#xff0c;并将创建一个新的砍树桩的粒子效果。 这里有&#xff1a;一种作物收获后创造另一种作物的逻辑。 2、分析 在SO_CropDetailsList中&#xff0c;Harvested Transform Item Code可以指定收获后生…

[Datagear] 实现按月颗粒度选择日期的方案

在使用 Datagear 构建数据分析报表时,常常会遇到一个问题:如果数据的目标颗粒度是“月”,默认的日期控件却是精确到“日”的,这在用户交互和数据处理层面会带来不必要的复杂度。本文将分享两种解决方案,帮助你更好地控制日期控件的颗粒度,实现以月为单位的日期筛选功能。…

漏洞检测与渗透检验在功能及范围上究竟有何显著差异?

漏洞检测与渗透检验是确保系统安全的重要途径&#xff0c;这两种方法各具特色和功效&#xff0c;它们在功能上有着显著的差异。 目的不同 漏洞扫描的主要任务是揭示系统内已知的安全漏洞和隐患&#xff0c;这就像是对系统进行一次全面的健康检查&#xff0c;看是否有已知的疾…

DB-GPT扩展自定义Agent配置说明

简介 文章主要介绍了如何扩展一个自定义Agent&#xff0c;这里是用官方提供的总结摘要的Agent做了个示例&#xff0c;先给大家看下显示效果 代码目录 博主将代码放在core目录了&#xff0c;后续经过对源码的解读感觉放在dbgpt_serve.agent.agents.expand目录下可能更合适&…

家政维修平台实战09:推送数据到多维表格

目录 1 API调试2 创建云函数3 前端调用整体效果总结 上一篇我们搭建了服务分类的后台功能&#xff0c;对于分类的图标通过集成TOS拿到了可以公开访问的地址&#xff0c;本篇我们将写入的数据推送至多维表格中。 1 API调试 要想推送多维表格的数据&#xff0c;首先要利用官方的…

前端框架token相关bug,前后端本地联调

今天我搭建框架的时候&#xff0c;我想请求我自己的本地&#xff01;然后我自己想链接我自己的本地后端&#xff0c;我之前用的前端项目&#xff0c;都是链别人的后端&#xff0c;基本上很少情况会链接自己的后端&#xff01;所以我当时想的是&#xff0c;我前后端接口一样&…

卷积神经网络(CNN)可视化技术详解:从特征学到演化分析

在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;常被称为“黑箱”&#xff0c;其内部特征提取过程难以直接观测。而 可视化技术 是打开这一“黑箱”的关键工具&#xff0c;通过可视化可直观了解网络各层学到了什么、训练过程中如何演化&#xff0c;以及模型…

QT之INI、JSON、XML处理

文章目录 INI文件处理写配置文件读配置文件 JSON 文件处理写入JSON读取JSON XML文件处理写XML文件读XML文件 INI文件处理 首先得引入QSettings QSettings 是用来存储和读取应用程序设置的一个类 #include "wrinifile.h"#include <QSettings> #include <QtD…

微信小程序调用蓝牙API “wx.writeBLECharacteristicValue()“ 报 errCode: 10008 的解决方案

1、问题现象 问题:在开发微信小程序蓝牙通信功能时,常常会遇到莫名其妙的错误,查阅官方文档可能也无法找到答案。如在写入蓝牙数据时,报了这样的错误: {errno: 1500104, errCode: 10008, errMsg: "writeBLECharacteristicValue:fail:system error, status: UNKNOW…

【Java基础笔记vlog】Java中常见的几种数组排序算法汇总详解

Java中常见的几种排序算法&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;希尔排序&#xff08;Shell Sort&#xff09;归并排序&#xff08;Merge Sort&#xff09…

WebRTC与RTSP|RTMP的技术对比:低延迟与稳定性如何决定音视频直播的未来

引言 音视频直播技术已经深刻影响了我们的生活方式&#xff0c;尤其是在教育、医疗、安防、娱乐等行业中&#xff0c;音视频技术成为了行业发展的重要推动力。近年来&#xff0c;WebRTC作为一种开源的实时通信技术&#xff0c;成为了音视频领域的重要选择&#xff0c;它使得浏览…

spring cloud alibaba Sentinel详解

spring cloud alibaba Sentinel详解 spring cloud alibaba Sentinel介绍 Sentinel 是阿里巴巴开源的一款动态流量控制组件&#xff0c;主要用于保障微服务架构中的服务稳定性。它能够对微服务中的各种资源&#xff08;如接口、服务方法等&#xff09;进行实时监控、流量控制、…

React19源码系列之渲染阶段performUnitOfWork

在 React 内部实现中&#xff0c;将 render 函数分为两个阶段&#xff1a; 渲染阶段提交阶段 其中渲染阶段可以分为 beginWork 和 completeWork 两个阶段&#xff0c;而提交阶段对应着 commitWork。 在之前的root.render过程中&#xff0c;渲染过程无论是并发模式执行还是同…

DL00987-基于深度学习YOLOv11的红外鸟类目标检测含完整数据集

提升科研能力&#xff0c;精准识别红外鸟类目标&#xff01; 完整代码数据集见文末 针对科研人员&#xff0c;尤其是研究生们&#xff0c;是否在鸟类目标检测中遇到过数据不够精准、处理困难等问题&#xff1f;现在&#xff0c;我们为你提供一款基于深度学习YOLOv11的红外鸟类…

黑马程序员C++2024新版笔记 第4章 函数和结构体

1.结构体的基本应用 结构体struct是一种用户自定义的复合数据类型&#xff0c;可以包含不同类型的成员。例如&#xff1a; struct Studet {string name;int age;string gender; } 结构体的声明定义和使用的基本语法&#xff1a; struct 结构体类型 {成员1类型 成员1名称;成…

数据仓库,扫描量

有五种通用技术用于限制数据的扫描量&#xff0c;正如图3 - 4所示。第一种技术是扫描那些被打上时戳的数据。当一个应用对记录的最近一次变化或更改打上时戳时&#xff0c;数据仓库扫描就能够很有效地进行&#xff0c;因为日期不相符的数据就接触不到了。然而&#xff0c;目前的…

Vue3性能优化: 大规模列表渲染解决方案

# Vue3性能优化: 大规模列表渲染解决方案 一、背景与挑战 背景 在大规模应用中&#xff0c;Vue3的列表渲染性能一直是开发者关注的焦点。大规模列表渲染往往会导致卡顿、内存占用过高等问题&#xff0c;影响用户体验和系统整体性能。 挑战 渲染大规模列表时&#xff0c;DOM操作…

【RocketMQ 生产者和消费者】- 生产者启动源码 - MQClientInstance 定时任务(4)

文章目录 1. 前言2. startScheduledTask 启动定时任务2.1 fetchNameServerAddr 拉取名称服务地址2.2 updateTopicRouteInfoFromNameServer 更新 topic 路由信息2.2.1 topic 路由信息2.2.2 updateTopicRouteInfoFromNameServer 获取 topic2.2.3 updateTopicRouteInfoFromNameSer…