【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分

  • 概述
  • CNN 简介
  • 数据预处理
    • 查看数据格式
    • 加载数据集
  • 图像处理
    • 限定图像范围
    • 图像转换
    • 加载数据
  • CNN 模型
    • Inception 网络
    • ResNet 残差网络
    • 总结
    • 参数设置
  • 训练 Train
    • 模型初始化
    • 数据加载
    • 训练超参数
    • 训练循环
  • 验证 Valid
    • 验证过程
    • 模型评估
    • 记录 & 保存
  • 测试 Test
    • 加载模型
    • 加载测试数据
    • 预测结果
  • 参考文献

概述

现今技术日新月异, Artificial Intelligence 的发展正在迅速的改变我们的生活和工作方式. 尤其是在自然语言处理 (Natural Linguistic Processing) 和计算机视觉 (Computer Vision) 等领域.

传统的多模态对话研究主要集中在单一用户与系统之间的交互, 而忽视了多用户场景的复杂性. 视觉信息 (Visual Info) 往往会被边缘化, 仅作为维嘉信息而非对话的核心部分. 在实际应用中, 算法需要 “观察” 并与多个用户的交互, 这些用户有可能不是当前的发言人.

【CCF BDCI 2023】多模态多方对话场景下的发言人识别, 核心思想是通过多轮连续对话的内容和每轮对应的帧, 以及对应的人脸 bbox 和 name label, 从每轮对话中识别出发言人 (speaker).

CNN 简介

CNN (Convolutional Neural Network) 能够自动, 有效的从图像数据中学习空间层次结构. CNN 通过一系列的卷积层 (Convolutional Layer)
, 池化层 (Pool Layer) 和全连接层 (Fully Connected Layer) 来处理图像, 每一层专注于提取图像的不同几倍的特征. 卷积层负责提取局部特征, 如: 边缘和纹理; 池化层负责降低特征空间维度, 增强模型泛化能力; 全连接层则用于最终的分类或回归任务.

Baseline 的代码分为三个文件, 分别为convex_optimization.py, dialog_roberta-constrasive.py, finetune_cnn-multiturn.py. 下面小白我就来带大家详解一下各个部分.

数据预处理

再多模态发言人识别中, 数据预处理的目的是将原视频 (帧图片) 转换为我们可以理解和处理的格式. 包括下面三个部分: 加载数据集, 图像转换和数据增强.

查看数据格式

小白我先带大家来看一下数据的格式, 我们先打开train-metada.jason, 下面是文件的样子:

CCF BDCI 多模态发言人识别 数据格式
查看文件长度:

# 读取 jason
train_metadata = json.load(open('../final_dataset-5_turns_chusai/train-metadata.json'))
print(len(train_metadata))
print(train_metadata[0])

输出结果:

13584
[{"frame": "s06e07-000377", "speaker": "phoebe", "content": "Yeah, I know because you have all the good words. What do I get? I get \"it\u2019s,\" \"and\" oh I'm sorry, I have \"A.\" Forget it.", "start": 297, "end": 491, "faces": [[[752, 135, 881, 336], "rachel"], [[395, 111, 510, 329], "leslie"]]}, {"frame": "s06e07-000504", "speaker": "rachel", "content": "Phoebe, come on that's silly.", "start": 498, "end": 535, "faces": [[[466, 129, 615, 328], "phoebe"]]}, {"frame": "s06e07-000552", "speaker": "phoebe", "content": "All right, so let's switch.", "start": 535, "end": 569, "faces": [[[426, 120, 577, 320], "phoebe"]]}, {"frame": "s06e07-000629", "speaker": "rachel", "content": "No, I have all of the good words. OK, fine, fine, we can switch.", "start": 569, "end": 689, "faces": [[[420, 125, 559, 328], "phoebe"], [[652, 274, 771, 483], "rachel"]]}, {"frame": "s06e07-000892", "speaker": "phoebe", "content": "Please...wait, how did you do that?", "start": 879, "end": 906, "faces": [[[424, 133, 573, 334], "phoebe"], [[816, 197, 925, 399], "bonnie"]]}]

结合图片我们可以看出 train 数据由 13584 条数据组成, 每条数据包含 5 段发言, 每段发言包含:

  • frame: 图片对应的帧, 例如: s06e07-000377
  • speaker: 发言人, 共计 11 人, 例如: phoebe
  • content: 发言内容
  • start: 开始帧
  • end: 结束帧
  • faces: 包含多个人脸的列表, 例如: [[[752, 135, 881, 336], "rachel"], [[395, 111, 510, 329], "leslie"]]. 其中每个人脸包含 2 部分数据:
    • 坐标数据: [x1, y1, x2, y2], 分别表示人脸左上到右下的框, 例如: [752, 135, 881, 336]
    • 人脸归属: 坐标中人脸对应的任务, 例如: rachel

CCF BDCI 发言人识别 人脸标注示例

加载数据集

下面我们来讲一下代码中的 FaceDataset 类, 这个类的主要功能是从指定的数据集中加载图像和标签, 并对图像进行必要的预处理. FaceDataset 根据不同的数据集分割加载相应的元数据, 并从中提取出每个帧的面部数据及其对应的标签. 这些信息被用于后续的模型训练和评估.

class FaceDataset:def __init__(self, base_folder, transform='default', split='train', debug=False):# 设置图像文件的基本路径self.frames_path = os.path.join(base_folder, 'images')# debug模式用于快速测试(加载较少的数据)self.debug = debug# 数据集的分割类型(训练、测试或验证)self.split = split# 根据数据集的分割类型加载相应的元数据文件if split == 'test':metadata = json.load(open(os.path.join(base_folder, 'test-metadata.json')))elif split == 'test-hard':metadata = json.load(open(os.path.join(base_folder, 'test-hard-metadata.json')))else:metadata = json.load(open(os.path.join(base_folder, 'train-metadata.json')))

baseline 选择了 s01 开头的人脸作为厕所集, 其他作为训练集.

        # 初始化存储样本和标签的列表self.examples, self.labels = list(), list()# 用于记录已处理的帧,避免重复处理frames_met = set()# 遍历元数据中的每个对话数据for dialog_data in metadata:# 遍历对话中的每个帧数据for frame_data in dialog_data:# 如果是验证集,只选择以's01'开头的帧if split == 'valid' and not frame_data['frame'].startswith('s01'):continue# 如果是训练集,排除以's01'开头的帧if split == 'train' and frame_data['frame'].startswith('s01'):continue# 检查帧是否已经处理过if frame_data['frame'] in frames_met:continue# 记录已处理的帧frames_met.add(frame_data['frame'])# 获取帧中的所有面部数据faces = frame_data['faces']      # [(bbox, id), (bbox, id), ...]

图像处理

限定图像范围

计算图片面积, 用于过滤无效图片.

def calculate_area(bbox):return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])

接着讲上面的 FaceDataset 部分, 再上面我们已经获得了每条数据每帧的人脸: Faces, 下面是如何获取 face 对应的 frame, face_label, bbox, label:

# 遍历每个面部数据
for bbox, face_label in faces:# 确保边界框的坐标在合理范围内bbox = [max(i, 0) for i in bbox]bbox = [min(bbox[0], 1280), min(bbox[1], 720), min(bbox[2], 1280), min(bbox[3], 720)]# 如果边界框面积为0,则跳过此面部if calculate_area(bbox) <= 0:continue# 标签为1如果面部是说话者,否则为0label = int(face_label == frame_data['speaker'])# 添加样本和标签到列表self.examples.append((frame_data['frame'], face_label, bbox, label))self.labels.append(label)

因为数据存在负数, 使用 max 和 min 限定标注为图片范围.

图像转换

我们需要将原始图像转化为我们需要的格式, 使得 CNN 模型能有效的处理数据. 这一过程包括调整图像大小, 标准化和图像增强.

transforms.RandomHorizontalFlip

代码:

if transform == 'default':self.train_transform = transforms.Compose([np.float32,transforms.ToTensor(),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomAffine(degrees=20, shear=20),transforms.Resize((512, 512)),fixed_image_standardization])self.valid_transform = transforms.Compose([np.float32,transforms.ToTensor(),transforms.Resize((512, 512)),fixed_image_standardization])
else:self.train_transform = transformself.valid_transform = transform
  1. 调整图像大小: transforms.Resize((512, 512)), 将所有人脸调整为固定的 512*512 像素, 便于后续的 CNN 处理. FYI: 大部分 CNN 模型的输入都会拉成正方形
  2. 数据标准化: fixed_image_standardization, 具体为:
    • 缩放像素值, 通过像素值 / 255, 将像素值范围从 [0, 255] 缩放到 [0, 1]
    • 均值和标准差归一化 (Mean & Standard Deviation Normalization): 使得像素具有特定的均值和标准差. 具体的文档我没找到, 但是 ImageNet 的均值用的是 [0.485, 0.456, 0.406], 标准差是[0.229, 0.224, 0.225]
  3. 数据增强: baseline 使用的数据增强分为 2 部分: 水平翻转和随机仿射变换:
    • 随机水平翻转: transforms.RandomHorizontalFlip(p=0.5), 按照 0.5 的概率进行水平翻转图像. 通过水平翻转的增强, 模型可以学习到不同方向的特征, 增强模型的泛化性. 再人脸任任务中, 此方法特别使用, 因为左右脸是对称的 (差不多对称)
    • 随机仿射变化: transforms.RandomAffine(degrees=20, shear=20), 包括旋转, 缩放, 评议和剪切, degrees=20, 随机旋转上限 20 度, shear=20, 剪切上限 20 度, 通过仿射变化, 对图片进行各种视角和姿态的变化, 可以提高模型对人脸图片的姿势和表情的泛化能力

加载数据

__len__方法和__getitem__方法是继承自 PyTorch 数据集类 (Dataset). 下面小白我来分别解释一下两种方法.

__len__方法:

def __len__(self):return len(self.examples) if not self.debug else 32
  • 不在调试模式下: 返回self.example列表长度
  • 调试模式下: 返回 32 样本

_get_item方法, 也是 Dataset 类的一个标准接口:

def __getitem__(self, index):frame_name, face_label, bbox, label = self.examples[index]image = cv2.imread(os.path.join(self.frames_path, frame_name + '.jpg'))image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)image = image[bbox[1]:bbox[3], bbox[0]:bbox[2]]if self.split == 'valid' or self.split.startswith('test'):image = self.valid_transform(image)return image, label, frame_name, face_labelelse:image = self.train_transform(image)return image, label
  1. 提取样本信息: self.examples列表中获取指定的样本, 包含: 帧名 (frame_name), 人脸标签 (face_label), 边界框 (bbox), 标签 (label)
  2. 加载和处理图像: 使用 cv2.iamge读取 bbox 中的 人脸, 这个就不细说了
  3. 应用图像转换: 再训练集使用self.train_transform, 验证集和测试集使用slef.valid_transform
  4. 返回处理后的样本

__getitem__法的实现确保了每次请求数据集时都能获取到正确处理和转换后的图像样本.

CNN 模型

InceptionResnetV1 结合了 Inception 和 ResNet (残差网络) 的特点.

InceptionResnetV1

Inception 网络

Inception 由 GoogleNet 提出, 核心思想是在网络上同一层使用多种不同尺寸的卷积核 (Convolution Kernel), 以提取不同尺度的特征. 较小的卷积核可以捕捉到细节特征, 较大的卷积核则能捕捉大范围的特征.

ResNet 残差网络

ResNet 通过残差连接, 将前一层的输出结果添加到后面的层上, 解决深度学习中的梯度消失问题 (Vanishing Gradient Problem), 使得网络的层数得以加深.

关于梯度消失和梯度爆炸的详解, 参考我这篇博客.

总结

InceptionResnetV1 在 Inception 模块的基础上加入了残差连接. 以保持Inception 模块多尺度特征提取优势的同时, 避免了深层网络可能出现的梯度相关问题.

参数设置

argprase

# 创建一个ArgumentParser对象,用于解析命令行参数
parser = argparse.ArgumentParser()# 添加'--func'参数,用于指定脚本的功能模式(如'test'或'train')。默认值为'test'。
parser.add_argument('--func', type=str, default='test')# 这行代码被注释掉了,但它展示了如何添加另一个'--func'参数的例子,其默认值为'train-friends'。
# parser.add_argument('--func', type=str, default='train-friends')# 添加'--debug'参数,这是一个标志参数。如果在命令行中出现,则其值为True,否则为False。
parser.add_argument('--debug', action='store_true')# 添加'--load_pretrained'参数,用于控制是否加载预训练模型。它是一个整数类型,1表示加载,0表示不加载。默认值为1。
parser.add_argument('--load_pretrained', type=int, default=1)# 添加'--data_base_folder'参数,用于指定数据集的基本路径。默认值为'../final_dataset-5_turns_chusai'。
parser.add_argument('--data_base_folder', type=str, default='../final_dataset-5_turns_chusai')# 添加'--batch_size'参数,用于指定批处理大小。默认值为16。
parser.add_argument('--batch_size', type=int, default=16)# 添加'--num_workers'参数,用于指定数据加载时使用的工作线程数。默认值为2。
parser.add_argument('--num_workers', type=int, default=2)# 添加'--num_epochs'参数,用于指定训练的总周期数。默认值为40。
parser.add_argument('--num_epochs', type=int, default=40)# 添加'--lr'(学习率)参数,用于指定优化器的学习率。默认值为0.001。
parser.add_argument('--lr', type=float, default=0.001)# 添加'--weight_decay'参数,用于指定优化器的权重衰减。默认值为0。
parser.add_argument('--weight_decay', type=float, default=0.)# 添加'--decay_interval'参数,用于指定学习率衰减的间隔周期。默认值为3。
parser.add_argument('--decay_interval', type=int, default=3)# 添加'--output_path'参数,用于指定输出文件的路径。默认值为'out'。
parser.add_argument('--output_path', type=str, default='out')# 添加'--model_ckpt'参数,用于指定模型检查点的路径。默认值为'../ckpts/5_turns/cnn-best_model.pth'。
parser.add_argument('--model_ckpt', type=str, default='../ckpts/5_turns/cnn-best_model.pth')# 解析命令行输入的参数
args = parser.parse_args()# 打印解析得到的参数值
print(args)

训练 Train

模型初始化

if args.func.split('-')[0] == 'train':if args.load_pretrained:resnet = InceptionResnetV1(classify=True, pretrained='../pretrain_model/torch/20180402-114759-vggface2.pt', num_classes=2).to(device)else:resnet = InceptionResnetV1(classify=True, num_classes=2).to(device)softmax = torch.nn.Softmax()
  1. 判断是否为 train
  2. 判断是否加载预训练模型 vggfacenet2
  3. 初始化 softmax 激活函数, 将最终概率限制到 [0, 1]

数据加载

train_dataset = FaceDataset(args.data_base_folder, split='train', debug=args.debug)
valid_dataset = FaceDataset(args.data_base_folder, split='valid', debug=args.debug)
train_loader = DataLoader(train_dataset, num_workers=args.num_workers, batch_size=args.batch_size, shuffle=True, drop_last=True)
valid_loader = DataLoader(valid_dataset, num_workers=args.num_workers, batch_size=args.batch_size, shuffle=False)

使用 Dataloader 加载训练和验证数据.

训练超参数

loss_fn = torch.nn.CrossEntropyLoss()
metrics = {'fps': training.BatchTimer(), 'acc': training.accuracy}
writer = SummaryWriter(os.path.join(args.output_path, 'tensorboard_logs'))
optimizer = optim.Adam(resnet.parameters(), lr=args.lr, weight_decay=args.weight_decay)
scheduler = MultiStepLR(optimizer, list(range(args.decay_interval, args.num_epochs, args.decay_interval)))
  • loss_fn: 损失函数, 交叉熵
  • metrics: 评估标准, acc
  • optimizer: 优化器, Adam
  • scheduler: 学习率递减

训练循环

for epoch_i in range(args.num_epochs):print('\nEpoch {}/{}'.format(epoch_i + 1, args.num_epochs))resnet.train()train_loss, train_metrics = training.pass_epoch(resnet, loss_fn, train_loader, optimizer, scheduler,batch_metrics=metrics, show_running=False, device=device,writer=writer)
  • training.pass_epoch: 是封装好的函数, 用于训练数据

验证 Valid

验证过程

with torch.no_grad():resnet.eval()test_output = dict()for images, labels, frame_names, face_labels in valid_loader:images = images.to(device)labels = labels.to(device)y_preds = softmax(resnet(images))[:, 1]for y_pred, frame_name, face_label, label in zip(y_preds, frame_names, face_labels, labels):y_pred = float(y_pred.cpu())label = int(label.cpu())if frame_name not in test_output:test_output[frame_name] = dict()test_output[frame_name][face_label] = {'pred': y_pred, 'label': label}
  1. 禁用梯度: with torch.no_grad()
  2. 模型评估模式: resnet.eval(), 忽略 Droupout 和 Batch Normalization 层, 具体表现为eval()的 Dropout 不会关闭神经元, Batch Normalization 会使用训练的全局均值和方差, 而不是根据每个 batch 调整

模型评估

计算帧准确率 (acc):

val_metrics = dict()
frame_acc_dict, dialog_acc_list = dict(), list()
for frame_id, frame_pred in test_output.items():y_preds = [i['pred'] for i in frame_pred.values()]y_golds = [i['label'] for i in frame_pred.values()]if not any(y_golds):  # 都是0,说明说话人不在frame中frame_acc_dict[frame_id] = 0else:frame_acc_dict[frame_id] = (np.argmax(y_preds) == np.argmax(y_golds))val_metrics['frame_acc'] = sum(frame_acc_dict.values()) / len(frame_acc_dict)

计算对话准确率 (acc):

val_metadata = json.load(open(os.path.join(args.data_base_folder, 'train-metadata.json')))
for dialog_data in val_metadata:for frame_data in dialog_data:if frame_data['frame'] in frame_acc_dict:dialog_acc_list.append(frame_acc_dict[frame_data['frame']])
val_metrics['dialog_acc'] = sum(dialog_acc_list) / len(dialog_acc_list)

记录 & 保存

for key in val_metrics:writer.add_scalar('valid/' + key, val_metrics[key], epoch_i)print('epoch %d' % epoch_i)
print('dialog level acc: {}'.format(val_metrics['dialog_acc']))
print('val acc: {}, best_acc: {}'.format(val_metrics['frame_acc'], best_acc))
if val_metrics['frame_acc'] > best_acc:best_acc = val_metrics['frame_acc']torch.save(resnet.state_dict(), os.path.join(args.output_path, 'best_model.pth'))json.dump(test_output, open(os.path.join(args.output_path, 'valid_output.json'), 'w'))
  • 记录评估指标
  • 控制台输出评估指标
  • 保存模型

测试 Test

加载模型

elif args.func.startswith('test'):resnet = InceptionResnetV1(classify=True, num_classes=2).to(device)resnet.eval()print('loading checkpoint from %s' % args.model_ckpt)resnet.load_state_dict(torch.load(args.model_ckpt))softmax = torch.nn.Softmax()
  1. 初始化模型: InceptionResnetV1
  2. 设置评估模式: reset.eval()
  3. 加载训练好的模型: resnet.load_state_dict(torch.load(args.model_ckpt))

加载测试数据

for split in ['test', 'test-hard']:test_dataset = FaceDataset(args.data_base_folder, split=split, debug=args.debug)test_loader = DataLoader(test_dataset, num_workers=args.num_workers, batch_size=args.batch_size, shuffle=False)
  • 分别加载 test / test-hard 测试数据

预测结果

with torch.no_grad():resnet.eval()test_output = dict()for images, labels, frame_names, face_labels in test_loader:images = images.to(device)labels = labels.to(device)y_preds = softmax(resnet(images))[:, 1]for y_pred, frame_name, face_label, label in zip(y_preds, frame_names, face_labels, labels):y_pred = float(y_pred.cpu())label = int(label.cpu())if frame_name not in test_output:test_output[frame_name] = dict()test_output[frame_name][face_label] = {'pred': y_pred, 'label': label}test_metrics = dict()
frame_acc_dict, dialog_acc_list = dict(), list()
for frame_id, frame_pred in test_output.items():y_preds = [i['pred'] for i in frame_pred.values()]y_golds = [i['label'] for i in frame_pred.values()]if not any(y_golds):        # 都是0,说明说话人不在frame中frame_acc_dict[frame_id] = 0else:frame_acc_dict[frame_id] = (np.argmax(y_preds) == np.argmax(y_golds))test_metrics['frame_acc'] = sum(frame_acc_dict.values()) / len(frame_acc_dict)
if split.startswith('test'):test_metadata = json.load(open(os.path.join(args.data_base_folder, 'test-metadata.json')))
else:test_metadata = [example for example in json.load(open(os.path.join(args.data_base_folder, 'train-metadata.json'))) if example[0]['frame'].startswith('s01')]for dialog_data in test_metadata:for frame_data in dialog_data:if frame_data['frame'] in frame_acc_dict:dialog_acc_list.append(frame_acc_dict[frame_data['frame']])else:dialog_acc_list.append(0)       # 没有人脸,因此本模型无从判断
test_metrics['dialog_acc'] = sum(dialog_acc_list) / len(dialog_acc_list)print('dialog level acc: {}'.format(test_metrics['dialog_acc']))
print('frame level acc: {}'.format(test_metrics['frame_acc']))
json.dump(test_output, open(os.path.join(args.output_path, '%s_output.json' % split), 'w'))

将测试过程中的输出保存为 json 文件.

参考文献

比赛链接

Baseline 完整代码

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

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

相关文章

Python 从入门到精通 学习笔记 Day04

Python 从入门到精通 第四天 今日目标 数据类型-又见str、数据类型-又见list 列表切片&排序&反转&循环、字典 数据类型 - 又见str 字符串定义 字符串是一个有序的字符的集合&#xff0c;用于在计算机里存储和表示文本信息 创建 a "Hello ,my name is Ha…

Android---Kotlin 学习005

substring 字符串截取。相加与 java&#xff0c;kt 里面的 substring 函数支持 IntRange 类型&#xff08;表示一个整数范围的类型&#xff09;的参数&#xff0c;until 创建的范围不包括上限值。 const val NAME "Jimmys friend" fun main(){val index NAME.ind…

TrustGeo代码理解(二)test.py

代码链接&#xff1a;https://github.com/ICDM-UESTC/TrustGeo 一、加载检查点&#xff08;checkpoint&#xff09;并进行测试 # -*- coding: utf-8 -*-"""load checkpoint and then test """ 该脚本的目的是加载之前训练过的模型的检查点&am…

【Linux】基于sed正则指令修改application.properties属性值

文章目录 背景sed介绍演示结果 背景 服务器上搭建了SpringCloud微服务&#xff0c;每个服务中都有application.properties文件&#xff0c;在部署过程中需要修改每个微服务的redis.ip和redis.port。如果手动去修改会很麻烦&#xff0c;所以下面写了一个shell脚本&#xff08;基…

idea的快捷键

1.调整字体的大小 文件夹的循序:setting-Editor-Font 界面: 2.删除当前行 文件夹的循序:setting-Keymap-DeleteLine 界面: 3.导入该行需要的类 文件夹的循序:setting-Editor-General-Auto import 界面: 4.格式化代码 文件夹的循序:setting-keymap-Reformat 界面: 5.快速…

大模型微调方法:冻结方法 Freeze、P-Tuning 系列、LoRA、QLoRA

大模型微调方法 冻结方法 FreezeP-Tuning 系列Prefix-TuningPrompt TuningP-Tuning v1P-Tuning v2 LoRAQLoRA 冻结方法 Freeze 方法意思是&#xff0c;只用少部分参数训练&#xff0c;把模型的大部分参数冻结。 只要设置微调层的参数&#xff1a; # 遍历模型的所有参数和名称…

程序员的养生之道:延寿健康的十大秘诀(下)

程序员的养生之道&#xff1a;延寿健康的十大秘诀&#xff08;上&#xff09;-CSDN博客 目录 6. 心理调节&#xff0c;减轻压力 6.1 程序员常见的心理问题 6.2 压力管理的重要性 6.3 放松技巧与应对策略 6.4 积极心态与心理健康 7. 正确坐姿&#xff0c;保护颈椎腰椎 …

VLAN协议与单臂路由

文章目录 VLAN协议与单臂路由一、VLAN的概念及优势1、分割广播域2、VLAN的优势3、VLAN数据帧 二、VLAN的种类1、静态VLAN2、动态VLAN3、VLAN划分方式 三、静态VLAN的配置1、VLAN的范围2、静态VLAN的配置2.1 配置静态VLAN的步骤2.2 vlan三种端口类型举例&#xff1a;配置静态VLA…

VR全景直播来临,沉浸式代入吸引百万人目光

如今&#xff0c;VR技术正在不断改变着直播行业的面貌&#xff0c;为观众提供沉浸式的观看体验。在5G技术的快速发展背景下&#xff0c;VR全景直播作为5G应用的场景&#xff0c;被广泛应用在体育赛事、新闻会议、演唱会、教育医疗等场景&#xff0c;通过VR技术进行全景直播&…

c语言结构体调用格式与对齐

1.声明形式&#xff1a; struct 结构体名字 { 结构体成员 }结构体变量名&#xff1b; 2.赋值方法 3.结构体对齐&#xff1a; 1.起始偏移量&#xff1a;默认结构体第一个元素对齐0起始偏移量&#xff0c;第一个元素占一个字节&#xff0c;此时偏移量为1. 2.标准数&#xff…

基于stm32 FP-AUD-SMARTMIC1 音频系统开发

基于stm32 FP-AUD-SMARTMIC1 音频系统开发 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙耳机音频,DSP音频项目核心开发资料, FP-AUD-SMARTMIC1 是一个用于 STM32F4Discovery …

《使用ThinkPHP6开发项目》 - 创建控制器

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 《使用ThinkPHP6开发项目》 - 设置项目环境变量-CSDN博客 《使用ThinkPHP6开发项目》 - 项目使用多应用开发-CSDN博客 《使用ThinkPHP6开发项目》 - 创建应用-CSDN博客 使用ThinkPHP6创建控制器&#xff0c;开始项…

css 纯样式实现绘出进度条

效果&#xff1a; css代码&#xff1a; .bar{height: 14px;width: 100%;font-size: 10px;margin-top: 5px;background-color: #f5f5f5;}.bar::before{display: block;counter-reset: progress var(--precent); content: ;width: calc(1% * var(--precent));color: #fff;height:…

【C++进阶篇】二叉搜索数

目录 前言&#xff1a; 以后我们要学map&#xff0c;set&#xff0c;AVL&#xff0c;红黑数所以必须要有二叉搜索数做铺垫 1、二叉搜索树概念 2.二叉搜索树操作 1.二叉搜索树的查找 a、从根开始比较&#xff0c;查找&#xff0c;比根大则往右边走查找&#xff0c;比根小则…

SpringBoot 实现动态切换数据源

最近在做业务需求时&#xff0c;需要从不同的数据库中获取数据然后写入到当前数据库中&#xff0c;因此涉及到切换数据源问题。本来想着使用Mybatis-plus中提供的动态数据源SpringBoot的starter&#xff1a;dynamic-datasource-spring-boot-starter来实现。 结果引入后发现由于…

IO零拷贝

在介绍零拷贝之前我们先看看传统的 Java 网络 IO 编程是怎样的。 下面代码展示了一个典型的 Java 网络程序。 File file new File("index.jsp");RandomAccessFile rdf new RandomAccessFile(file, "rw");byte[] arr new byte[(int) file.length()];rdf…

Tcl语言语法精炼总结

一、置换符号 1.变量置换 $ TCl解释器会将认为$后面为变量名&#xff0c;将变量名置换成它的值 2.命令置换 [] []内是一个独立的TCL语句 3.反斜杠置换 \ 换行符、空格、[、$等被TCL解释器当作特殊符号处理。加上反斜杠后变成普通字符 \t TAB \n 换行符 4.双引号 “” “…

鸿蒙开发之页面与组件生命周期

一、页面间的跳转 创建文件的时候记得选择创建page文件&#xff0c;这样就可以在main->resources->profile->main_pages.json中自动形成页面对应的路由了。如果创建的时候你选择了ArkTS文件&#xff0c;那么需要手动修改main_pages.json文件中&#xff0c;添加相应的…

关于大模型ChatGLM3-6B在CPU下运行

最近在调研市场上语言大模型&#xff0c;为公司的产品上虚拟人的推出做准备。各厂提供语言模型都很丰富&#xff0c;使用上也很方便&#xff0c;有API接口可以调用。但唯一的不足&#xff0c;对于提供给百万用户使用的产品&#xff0c;相比价格都比较贵。所以对ChatGLM3-6B的使…

基于pandoraNext使用chatgpt4

1.登陆GitHub 获取pandoraNext项目GitHub - pandora-next/deploy: Pandora Cloud Pandora Server Shared Chat BackendAPI Proxy Chat2API Signup Free PandoraNext. New GPTs(Gizmo) UI, All in one! 在release中选择相应版本操作系统的安装包进行下载 2.获取license_…