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

news2024/12/23 16:53:28

【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如果面部是说话者,否则为0
    label = 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 = transform
    self.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_label
    else:
        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] = 0
    else:
        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] = 0
    else:
        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.coloradmin.cn/o/1307974.html

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

相关文章

idea一些报错

java: 非法字符: \ufeff 使用IDEA修改文件编码 在IDEA右下角&#xff0c;将编码改为GBK&#xff0c;再转为UTF-8&#xff0c;重新启动项目。具体步骤如下&#xff1a; 在IDEA右下角找到UTF-8字样的编码格式设计项&#xff0c;点击选择第一项GBK&#xff0c;然后Convert&#xf…

电脑出现msvcr120_1.dll丢失如何解决,怎么修复

一、msvcr120.dll_1.dll文件的作用&#xff1a; msvcr120.dll_1.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable Package的一部分。该文件包含了许多常用的函数和类&#xff0c;这些函数和类被许多应用程序所共享和使用。因此&#xff0c;当您在…

“ABCD“[(int)qrand() % 4]作用

ABCD[(int)qrand() % 4] 作用 具体来说&#xff1a; qrand() 是一个函数&#xff0c;通常在C中用于生成一个随机整数。% 4 会取 qrand() 生成的随机数除以4的余数。因为4只有四个不同的余数&#xff08;0, 1, 2, 3&#xff09;&#xff0c;所以这实际上会生成一个0到3之间的随…

力扣40. 组合总和 II(java 回溯法)

Problem: 40. 组合总和 II 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 在使用回溯之前我们首先可以明确该题目也是一种元素存在重复但不可复用的组合类型问题。而此题目可以参考下面一题的大体处理思路&#xff1a; Problem: 90. 子集 II 具体的&#xff1a; 1.首…

自助式可视化开发,ETLCloud的集成之路

自助式可视化开发 自助式可视化开发是指利用可视化工具和平台&#xff0c;使非技术人员能够自主创建、定制和部署数据分析和应用程序的过程。 传统上&#xff0c;数据分析和应用程序开发需要专业的编程和开发技能。但是&#xff0c;自助式可视化开发工具的出现&#xff0c;使…

Unity 通过鼠标控制模拟人物移动和旋转视角

要通过鼠标控制并模拟人物移动和转换视角&#xff0c;将会使用射线检测、鼠标点击和鼠标水平移动&#xff0c;配合物体旋转和移动方法共同实现。 首先搭建个由一个Plane地板和若干cube组成的简单场景&#xff1a; 其次创建一个Capsule作为移动物体&#xff0c;并把摄像头拉到该…

Leetcode—10.正则表达式匹配【困难】

2023每日刷题&#xff08;五十八&#xff09; Leetcode—10.正则表达式匹配 算法思想 参考题解 实现代码 class Solution { public:bool isMatch(string s, string p) {int m s.size(), n p.size();vector<vector<bool>> dp(m 1, vector<bool>(n …

VMware安装ContOS 7 提示“客户机操作系统已禁用 CPU。请关闭或重置虚拟机。”

目录 实验环境报错截图报错原因猜测&#xff08;根据实验现象&#xff09;解决办法如下 实验环境 Vmware Workstation 17.5 CentOS7 镜像版本&#xff1a;2207-02版本 注意&#xff1a;2009版本并无该错误 报错截图 报错原因猜测&#xff08;根据实验现象&#xff09; CentO…

深入理解Java虚拟机---类加载机制

类加载机制 什么是类加载机制类加载的时机类加载的过程加载验证文件格式验证元数据验证字节码验证符号引用验证 准备解析初始化 类加载器双亲委派模型 什么是类加载机制 虚拟机把描述类的数据从 Class 文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff…

C++初阶(十五)Stack和Queue

文章目录 一、Stack的模拟实现二、Queue的模拟实现三、容器适配器1、什么是容器适配器2、STL标准库中stack和queue的底层结构3、 deque的简单介绍(了解)1、deque的原理介绍2、deque的缺陷 4、为什么选择deque作为stack和queue的底层默认容器 一、Stack的模拟实现 #include<…

Servlet 的初步学习

前言 咱们最核心的目标是基于tomcat编程, 做出网站来 ~~ 基于tomcat进行网站后端的开发. 网站后端, http服务器,肯定是需要针对http 协议进行一系列操作的. 幸运的是, tomcat,已经把这些http相关的底层操作,封装好了只需要调用tomcat给咱们提供的api即可 Servlet 是什么 Servl…

ReLU(Rectified Linear Unit)和Sigmoid激活函数

ReLU&#xff08;Rectified Linear Unit&#xff09;和Sigmoid都是神经网络中常用的激活函数。 特点&#xff1a; ReLU是一种简单而有效的激活函数。它对于正数部分直接返回输入&#xff0c;对于负数部分返回零。这种非线性转换有助于网络学习更复杂的表示。ReLU在许多深度学习…

1.【Multisim仿真】数电模电学习,仿真软件的初步使用

学习计划路径&#xff1a; >Multisim电路仿真软件熟练掌握 >数字电路基础课程 >逻辑电路设计与应用 >熟练掌握存储器、脉冲波形发生器、D/A和A/D转换器原理 >基本元器件熟练掌握 >晶体管放大电路及负反馈放大电路 >集成运算放大器设计 >电压变电流电路…

Unity_ET框架项目-斗地主_启动运行流程

unity_ET框架项目-斗地主_启动运行流程 项目源码地址&#xff1a; Viagi/LandlordsCore: ET斗地主Demohttps://github.com/Viagi/LandlordsCore下载项目到本地。 启动运行步骤&#xff1a; 下载目录如下&#xff1a; 1. VS&#xff08;我用是2022版VisualStudio&#xff09…

C++共享和保护——(2)生存期

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 生命如同寓言&#xff0c;其价值不在于…

Leetcode—709.转换成小写字母【简单】

2023每日刷题&#xff08;五十八&#xff09; Leetcode—709.转换成小写字母 实现代码 char* toLowerCase(char* s) {int len strlen(s);for(int i 0; i < len; i) {if(s[i] > A && s[i] < Z) {s[i] tolower(s[i]);}}return s; }运行结果 之后我会持续更…

IO零拷贝

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

遗留系统现代化:7步入门数字化转型

遗留系统对企业来说可能是一把双刃剑。虽然它们曾经对企业很有帮助&#xff0c;但随着时间的推移&#xff0c;这些应用程序、系统和架构变得过时&#xff0c;最终会限制企业发展。 为了实现保持竞争优势所需的扩展、业务敏捷性和数字化转型&#xff0c;越来越多企业意识到需要…

【初阶C++】入门(超详解)

C入门 前言1. C关键字(C98)2. 命名空间2.1 命名空间定义2.2 命名空间使用2.3嵌套命名空间 3. C输入&输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.1 函数重载概念5.2 C支持函数重载的原理--名字修饰(name Mangling) 6. 引用6.1 引用概念6.2 引用特性6.3 …

C/C++ 表达式求值(含多位数)

个人主页&#xff1a;仍有未知等待探索_C语言疑难,数据结构,算法-CSDN博客 专题分栏&#xff1a;算法_仍有未知等待探索的博客-CSDN博客 目录 一、前言 二、解析 分析 最后直接上代码&#xff01; 一、前言 表达式求值是一个比较基础的代码关于栈的使用。在写的时候充分锻炼…