李宏毅2023机器学习作业HW07解析和代码分享

news2024/9/25 10:30:16

ML2023Spring - HW7 相关信息:

课程主页

课程视频

Kaggle link 回来了 : )

Sample code

HW07 视频

HW07 PDF

个人完整代码分享: GitHub | Gitee | GitCode

P.S. HW7 的代码都很易懂,可以和 2024 年的新课:生成式AI导论做一个很好的衔接,因为导论对于 Transformer 库的使用大多数是 HW7 所提到的一些函数。对 AIGC 感兴趣的同学可以去学习,完成 HW7 之后应该能够非常快的上手。

任务目标(抽取式问答)

  • 任务描述: 使用 BERT 模型进行抽取式问答
    • 目标: fine-tune BERT 模型,使其能够从文章中抽取出具体答案,完成问答任务。
      具体来说,可以当成是预测开始(Start)和结尾(End)的位置,然后抽出这一段作为答案,现在去看PDF中的图你应该更容易理解。
      image-20240920002112364

性能指标(EM)

  • 准确率 (Exact Match): 该指标用于衡量模型的预测答案与真实答案完全一致的比例。

数据解析

两个繁体中文阅读理解资料集:DRCD 和 ODSQA。

  • 训练(DRCD + DRCD-backtrans):包含 15,329 个段落和 26,918 个问题。一个文章段落可能对应多个问题。
  • 开发/用于验证(DRCD + DRCD-backtrans):包含 1,255 个段落和 2,863 个问题。
  • 测试(DRCD + ODSQA):包含 1,606 个段落和 3,504 个问题。其中段落没有给出答案,需模型进行预测。

训练,开发和测试数据的格式都是相同的:

  • id:问题序号
  • paragraph_id:文章段落序号
  • question_text:问题
  • answer_text:答案
  • answer_start:答案在文章中的起始点
  • answer_end:答案在文章中的终止点

数据集格式

数据下载(kaggle)

To use the Kaggle API, sign up for a Kaggle account at https://www.kaggle.com. Then go to the ‘Account’ tab of your user profile (https://www.kaggle.com/<username>/account) and select ‘Create API Token’. This will trigger the download of kaggle.json, a file containing your API credentials. Place this file in the location ~/.kaggle/kaggle.json (on Windows in the location C:\Users\<Windows-username>\.kaggle\kaggle.json - you can check the exact location, sans drive, with echo %HOMEPATH%). You can define a shell environment variable KAGGLE_CONFIG_DIR to change this location to $KAGGLE_CONFIG_DIR/kaggle.json (on Windows it will be %KAGGLE_CONFIG_DIR%\kaggle.json).

-- Official Kaggle API

替换<username>为你自己的用户名,https://www.kaggle.com/<username>/account,然后点击 Create New API Token,将下载下来的文件放去应该放的位置:

  • Mac 和 Linux 放在 ~/.kaggle
  • Windows 放在 C:\Users\<Windows-username>\.kaggle
pip install kaggle
# 你需要先在 Kaggle -> Account -> Create New API Token 中下载 kaggle.json
# mv kaggle.json ~/.kaggle/kaggle.json
kaggle competitions download -c ml2023spring-hw7
unzip ml2023spring-hw7.zip   

不过HW7的数据集比较小,所以我直接上传了 😃,你可以不用自己下载。

Gradescope

Question 1

训练/推理过程的差异
Fine-tuning: 进行梯度下降,调整模型参数。模型通过多个回合(epochs)的训练,学习特定任务的数据集。微调后的模型能够很好地解决特定任务。
In-context learning: 不进行梯度下降,只依赖少量样本,让预训练模型通过上下文提供答案。这是一种轻量的任务特化方式,可以快速得到结果,其实就是 prompt。

Label Words are Anchors: An Information Flow Perspective for Understanding In-Context Learning 2023年的这篇文章展示了 In-Context Learning 实际上真的在起作用,如果感兴趣可以阅读原文或者查看24年的视频 第11讲:大型语言模型在「想」什么呢? — 浅谈大型语言模型的可解释性。

image-20240920154334405

拓展阅读:

  • Encoder-Only vs Decoder-Only vs Encoder-Decoder Transformer
  • Decoder-Only or Encoder-Decoder? Interpreting Language Model as a Regularized Encoder-Decoder

A. Encoder-only 模型(如 BERT 系列)如何在抽取式问答任务中确定答案?

BERT 等编码器模型中,抽取式问答的工作原理如下:

  1. 输入格式:输入由两个部分组成:

    • 问题(Question):作为第一个句子(句子 A)。
    • 段落(Passage):作为第二个句子(句子 B),其中答案位于该段落中。
  2. Tokenization(标记化):BERT 对问题和段落进行标记化,并将它们合并成一个输入序列,序列通常是 [CLS] Question [SEP] Passage [SEP]

  3. 编码器处理:BERT 对整个输入序列进行处理,生成每个标记的上下文表示。

  4. 预测开始和结束位置

    • BERT 通过两个线性层来分别预测答案的起始位置结束位置。这两个线性层输出每个标记的得分。

      # 打印 model 可以看到 qa_outputs。
      ...
      (qa_outputs): Linear(in_features=768, out_features=2, bias=True)
      
    • 最终的答案位置是通过找出问题段落中得分最高的开始标记结束标记的位置来确定。

      start_index = torch.argmax(output.start_logits, dim=1)
      end_index = torch.argmax(output.end_logits, dim=1)
      
  5. 输出答案:答案是段落中的一段文本,具体由起始位置和结束位置的标记对应的子序列来表示。这些标记会被解码回原始文本,从而得到最终答案。

B. Decoder-only 模型(如 GPT 系列)如何在抽取式问答任务中确定答案?

对于 GPT 等解码器模型,工作原理与 BERT 不同。GPT 生成输出的方式与 BERT 不同,但在处理抽取式问答时也可以通过以下方式确定答案:

  1. 输入格式

    • GPT 是一个生成式模型,输入格式通常是将问题和段落拼接在一起。例如:"Question: [问题文本] Passage: [段落文本]"
  2. 生成式预测

    • GPT 并不是直接输出答案的起始和结束位置。相反,它会根据自回归生成的方式,基于问题生成答案。
    • 在抽取式问答任务中,GPT 将段落和问题作为上下文,开始生成答案的第一个标记,并逐步生成后续标记,直到完成生成答案。
  3. 潜在的问题:由于 GPT 是生成式模型,它可能并不会严格“抽取”段落中的原始文本,而是会生成一个看似合理的答案。它依赖上下文来生成答案,不一定是段落中的原文子串。

  4. 与 BERT 的差异:GPT 更擅长生成性任务,而不是抽取任务。相比于 BERT 在抽取式问答中的精确性,GPT 更可能生成出看似合理但并不严格匹配原文的答案,也就是瞎编。

总结

  • BERT 系列(Encoder-only 模型)会根据标记的上下文表示,直接预测出答案在段落中的起始和结束位置。
  • GPT 系列(Decoder-only 模型)通过生成的方式给出答案,基于段落和问题的上下文生成答案,而不是直接定位段落中的子串。

Question 2

尝试不同的 Prompt 并观察 fine-tuning 和 in-context learning 的区别。

这里代码所下载的是 facebook/xglm-1.7B,实际上你也可以直接去 GPT 或者其他 AI 平台问,这里的目的是让你去调整自己的 prompt,从而使模型不经过微调也能获取到正确答案。

Prompt 对比(错误对比)

  1. Prompt 示例 1: “根据文章找出问题的答案:{问题}”。
  2. Prompt 示例 2: “请阅读文章并回答以下问题:{问题}”。
  3. Prompt 示例 3: “请根据文章信息回答下列问题:{问题}”。

Prompt 对比(正确对比)

image-20240920173255150

  • 中英对比
  • 不同 prompt 对比

代码解析

这里解释一下QA_Dataset,如果觉得太长,可以只查看重点部分。

QA_Dataset 类功能解析(Medium / Strong 相关)

  1. 初始化(__init__

    • split 决定数据集的类型(训练、验证或测试)。
    • questions, tokenized_questions, 和 tokenized_paragraphs 是原问题和 tokenized 后的问题和段落。
    • max_question_lenmax_paragraph_len 分别设定了问题和段落的最大长度。
    • self.doc_stride:段落的窗口滑动步长(决定每个窗口之间的重叠部分)。
      • Sample code 中将其设置为 150,和 max_paragraph_len 一样,意味着窗口之间完全不重叠。
    • self.max_seq_len:定义了整个输入序列的最大长度(包含问题和段落)。
  2. __getitem__

    • 针对给定的索引 idx,获取对应问题和段落数据,返回模型需要的输入。
    • 训练集:定位答案的起始和结束位置,将包含答案的段落部分截取为一个窗口(中心在答案位置附近)。然后将问题和段落合并为一个输入序列,并进行填充。
    • 验证/测试集:将段落分成多个窗口,每个窗口之间的步长由 self.doc_stride 决定,然后将每个窗口作为模型的输入。验证和测试时不需要答案位置,因此只需生成多个窗口作为输入。
  3. 填充(padding

    • 输入序列可能比最大序列长度短,填充部分用 0 表示。对于问题部分和段落部分,token_type_ids 被用来区分它们(0 表示问题,1 表示段落)。attention_mask 用于标记有效的输入部分,防止模型对填充部分进行注意力计算。

重点

  • self.doc_stride 通过控制窗口之间的滑动步长,确保即使答案位于窗口边缘,模型也能通过多个窗口重叠的方式找到答案。
  • 训练阶段不需要使用 doc_stride,因为训练时我们已经知道答案的位置,可以直接截取包含答案的窗口。但在验证和测试阶段,由于模型并不知道答案的位置,doc_stride 保证每个窗口之间有足够的重叠,避免遗漏答案。
  • 所以这里存在一个问题,训练过程中模型可能学习到:答案就在中间这一模式。这是我们在 Strong baseline 中需要解决的。

Baselines

论文后遗症上来了,这次会包含多组对比 : ) 每次模块的增加简单基于上次最好的设置进行。

HW7 的代码更多的是在向你演示如何去微调一个能够提取正确答案的 LLM。

括号里是 Kaggle Leaderboard 中的 Public 分数。

Simple baseline (0.45573)

  • 运行所给的 sample code。

Medium baseline (0.67820)

  • 使用学习率调度器,这里演示两种方法,随意选择即可。

    1. PyTorch
      线性衰减。
    from torch.optim.lr_scheduler import LambdaLR
    
    total_steps = len(train_loader) * num_epoch
    
    lr_lambda = lambda step: max(0.0, 1.0 - step / total_steps)
    scheduler = LambdaLR(optimizer, lr_lambda=lr_lambda)
    

    Learning Rate Schedule using LambdaLR

    1. Hugging Face
      使用 warmup。
    from transformers import get_linear_schedule_with_warmup
    
    total_steps = len(train_loader) * num_epoch
    num_warmup_steps = int(0.2 * total_steps)  # 设置为 0 理论上等价于线性衰减,设置为 1 理论上等价于线性增加
    
    scheduler = get_linear_schedule_with_warmup(
        optimizer, num_warmup_steps=num_warmup_steps, num_training_steps=total_steps
    )
    

    不同num_warmup_steps对学习率的影响:

    Learning Rate Schedules with Different Warmup Fractions

    我在当前框架上简单进行了对比(注意,如果你想自己进行对比,请重新运行文件而不是重复运行单元格,不然会导致在之前的模型上继续训练):

    SchedulePrivate ScorePublic Score
    no schedule0.544260.56356
    Linear schedule (PyTorch)0.551640.56356
    Linear schedule with warmup (0)0.551640.56356
    Linear schedule with warmup (0.2)0.546530.56356
    Linear schedule with warmup (0.5)0.539160.54597
    Linear schedule with warmup (1.0)0.549940.55051

    这里简单选择Linear schedule with warmup (0)进行下一步实验。

  • 调整 doc_stride 参数提升模型表现

    doc_stride 在 sample code 代码默认为 150,也就是 max_paragraph_len,你可以理解为默认情况下,文本的窗口不重叠,也就是说第一个窗口从 0 开始 149 结束,第二个窗口从 150 开始,299 结束,这两个窗口之间的文本不会发生重叠,但这存在一个问题:问题的答案可能在 140-160 中,默认的设置会无法捕捉到这部分的答案。所以,我们需要调整这个参数,你可以将其理解为段落窗口滑动步长,也可以将其理解为卷积中的 stride。

    注意,doc_stride 没有用在训练阶段。

    这里给出一些对比,你可以继续实验:

    self.doc_stridePrivate ScorePublic Score
    =max_paragraph_len=1500.551640.56356
    =max_paragraph_len * 0.750.634500.62656
    =max_paragraph_len * 0.50.673660.68501
    =max_paragraph_len * 0.250.700900.68161

    这里让 self.doc_stride=max_paragraph_len * 0.25 进行下一步实验(在你实际实验时应该仅参考 Public Score,因为 Private Score 是最终的批改分数)。

Strong baseline (0.76220)

  • 修改模型的预处理过程 (TODO: Preprocessing)

    Sample code 对于训练的数据处理是直接以答案为中心选择文本窗口,这导致模型可能学到一个不该学习的模式:答案就在中间。

    这里随机偏移答案的窗口位置来解决这一问题,你可以修改 max_offset 的参数:

    			...
                # A single window is obtained by slicing the portion of paragraph containing the answer
                mid = (answer_start_token + answer_end_token) // 2
    
                # ---- Strong -----
                # Introduce random offset to prevent learning that answer is always in the middle
                max_offset = self.max_paragraph_len   # We allow up to 1/4 of the max length as offset
                random_offset = np.random.randint(-max_offset, max_offset)  # Random shift between -max_offset and +max_offset
    
                # Adjust paragraph start based on random offset
                paragraph_start = max(0, min(mid + random_offset - self.max_paragraph_len // 2, len(tokenized_paragraph) - self.max_paragraph_len))
                paragraph_end = paragraph_start + self.max_paragraph_len
                
                # ---- Strong -----
    			...
    
    max_offsetPrivate ScorePublic Score
    不进行偏移0.700900.68161
    = self.max_paragraph_len // 40.726440.72928
    = self.max_paragraph_len // 20.731550.72985
    = self.max_paragraph_len0.728140.72701

    这里直接选择self.max_paragraph_len // 2作为偏移进行下一步。
    注意,当前代码偏移 > self.max_paragraph_len // 2时并没有确保窗口一定包含答案,所以self.max_paragraph_len偏移效果不佳。

  • 选择 HuggingFace 上的其他预训练模型进行微调

    在这里找一个模型替换掉当前的:中文模型 – Hugging Face。

    Sample code当前使用的是google-bert/bert-base-chinese,一个103M参数的 Fill-Mask 模型,替换模型的话修改下面这行代码即可。

    model = AutoModelForQuestionAnswering.from_pretrained("bert-base-chinese").to(device)
    tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
    
    Pre-trained ModelParamsepoch备注Private ScorePublic Score
    google-bert/bert-base-chinese103M10.731550.72985
    20.771850.75993
    google-bert/bert-base-multilingual-cased179M10.745170.74006
    20.775250.76730
    FacebookAI/xlm-roberta-base279M1不使用token_type_ids0.689550.67139
    NchuNLP/Chinese-Question-Answering103M1不进行训练0.562990.57094
    NchuNLP/Chinese-Question-Answering103M1进行训练0.790010.78149

注意:NchuNLP/Chinese-Question-Answering 是一个基于 google-bert/bert-base-chinese 使用 DRCD dataset 进行微调后的问答模型,所以在 kaggle 用的话其实有点降维打击,因为其他模型的 ACC 都是从 0 开始的,而这个模型是从 0.56 开始的,不过可以简单将 epoch 设置高一点自己训练一下 bert-base-chinese,其实没什么差别。

token_type_ids 将输入序列划分为两个部分:

  • 0 表示第一个句子(句子 A),

  • 1 表示第二个句子(句子 B)。

    image-20240920153856156

Boss baseline (0.84506)

  • 修改后处理部分 (TODO: Postprocessing)

    查看 result.csv 文件时,可以发现有些结果是空的。这是因为在某些情况下,预测的结束位置小于开始位置,导致无法捕获到答案文本。我们需要修正这个问题,确保结束位置始终大于或等于开始位置(继续修改 TODO 部分)。

            # ---- Boss -----
            # Ensure the start_index is less than or equal to end_index
            # This avoids selecting a wrong pair of start and end positions
            if start_index <= end_index:
                # Calculate the combined probability of start and end positions
                prob = start_prob + end_prob
    
                # If this window has a higher probability answer, update the result
                if prob > max_prob:
                    max_prob = prob
                    # Convert token indices to the corresponding text answer
                    # Example: [1920, 7032] --> "大 金"
                    answer = tokenizer.decode(data[0][0][k][start_index : end_index + 1])
            else:
                # If start_index > end_index, skip this pair (potentially an error case)
                continue
            # ---- Boss -----
    

    注意,这部分修改的是 evaluate() 函数,所以不会影响训练,你可以随意的加上自己的想法然后直接运行 Testing 模块得到 result.csv 去查看效果。Boss 增加模块的对比见章末。

  • 梯度累积

    这是一个非常简单的想法,即不在每次 step 后都更新梯度,这样就等于变相的增加 batchsize,每 n 个 step 更新一次等价于 batchsize 设置为 n*batchsize 。适用于显存不足以跑大 batchsize 的情况。不过 batchsize 大并不意味着效果一定好。

    	# ---- Boss -----
        actual_logging_steps = 0  # Track the number of steps contributing to the current logging window
        for batch_idx, data in enumerate(tqdm(train_loader)):
            # Load all data into GPU
            data = [i.to(accelerator.device) for i in data]
    
            # Model inputs and forward pass
            output = model(input_ids=data[0], token_type_ids=data[1], attention_mask=data[2], start_positions=data[3], end_positions=data[4])
            
            # Accumulate loss
            loss = output.loss / gradient_accumulation_steps
            accelerator.backward(loss)
    
            # Update accuracy for the current mini-batch
            start_index = torch.argmax(output.start_logits, dim=1)
            end_index = torch.argmax(output.end_logits, dim=1)
            batch_acc = ((start_index == data[3]) & (end_index == data[4])).float().mean().item()
            
            train_acc += batch_acc
            train_loss += loss.item()
            actual_logging_steps += 1
    
            # Gradient accumulation: only update weights every gradient_accumulation_steps
            if (batch_idx + 1) % gradient_accumulation_steps == 0:
                optimizer.step()
                scheduler.step()  # Apply learning rate scheduler
                optimizer.zero_grad()
                step += 1
    
                # Logging
                if step % logging_step == 0:
                    # Average the loss and accuracy over all accumulated steps
                    avg_loss = train_loss / actual_logging_steps
                    avg_acc = train_acc / actual_logging_steps
                    
                    print(f"Epoch {epoch + 1} | Step {step} | loss = {avg_loss:.3f}, acc = {avg_acc:.3f}")
                    
                    # Reset the accumulators
                    train_loss = 0.0
                    train_acc = 0.0
                    actual_logging_steps = 0  # Reset after each logging
        # ---- Boss -----
    

    loss = output.loss / gradient_accumulation_steps是因为默认情况下 loss 的计算实际都已经做过平均了,所以我们这里也需要保持一致。

    另外说一句,其实你也可以直接使用 Accelerator(gradient_accumulation_steps=gradient_accumulation_steps)

  • 训练用上 dev 数据集
    去掉验证部分,你需要注意我在这里将 QA_Dataset("dev", ...) 改为了 QA_Dataset("train", ...)

    dev_set = QA_Dataset("train", dev_questions, dev_questions_tokenized, dev_paragraphs_tokenized)  # Boss
    
    ...
    
    combined_train_set = ConcatDataset([train_set, dev_set])
    train_loader = DataLoader(combined_dataset, batch_size=train_batch_size, shuffle=True, pin_memory=True)
    ...
    
        # if validation:
        #     print("Evaluating Dev Set ...")
        #     model.eval()
        #     with torch.no_grad():
        #         dev_acc = 0
        #         for i, data in enumerate(tqdm(dev_loader)):
        #             output = model(input_ids=data[0].squeeze(dim=0).to(device), token_type_ids=data[1].squeeze(dim=0).to(device),
        #                    attention_mask=data[2].squeeze(dim=0).to(device))
        #             # prediction is correct only if answer text exactly matches
        #             dev_acc += evaluate(data, output) == dev_questions[i]["answer_text"]
        #         print(f"Validation | Epoch {epoch + 1} | acc = {dev_acc / len(dev_loader):.3f}")
        #     model.train()
    

    Ensemble 和 early stop 之前我们都在作业中做过,所以不选择这两种方法,直接简单的拼接数据集用于训练看看效果。

简单过一个epoch看看效果(粗体部分就是选择使用的模块):

epochPrivate ScorePublic Score
-10.790010.78149
+ 修复后处理部分10.791710.78263
+ 梯度累积(4)10.791710.79114
梯度累积(8)10.790570.78603
梯度累积(16)10.783760.77355
+ dev数据集10.795110.78206

现在,我们直接“硬 train 一发”,毕竟该完成的已经完成了,看看最终成效。

作业 PDF 的描述中,Boss 时间是 Simple 的 18.75 倍,加上 dev 数据集后运行时间将增加 12%,所以设置 epochs=16 进行训练查看效果(因为直到 Strong 完成,我们都没有增加参数规模,所以当前训练时间等于 Simple)。

最终的结果(epoch=16):

  • Public Score:0.79114
  • Private Score:0.77525

虽然在训练集的结果上,第 16 个 epoch 比第 1 个 epoch 的 ACC 高了 0.073,但实际上,这是过拟合的,Kaggle 的最终提交结果甚至不如只训练 1 次的情况。(到这里其实就足够了,你已经学到了这份作业想要教你的知识,后面会是一些预训练模型的结果分享,或许能够帮你节省一些时间)。

所以,让我们换一个更大的模型 : )

Pre-trained ModelParamsepochPrivate ScorePublic Score
DaydreamerF/chinese-macbert-base-finetuned-accelerate101M20.788300.78660
IDEA-CCNL/Erlangshen-MacBERT-325M-TextMatch-Chinese324M20.788870.79228
hfl/chinese-macbert-large324M10.816680.82349
50.831440.83030
luhua/chinese_pretrain_mrc_macbert_large324M10.836540.82292
20.842220.83144
2 (FP32)0.841650.82973
30.837770.82065
50.829170.82463
qalover/chinese-pert-large-open-domain-mrc324M00.564130.54143
20.833140.82519

在导入大模型的时候你可能会遇到显存不够的情况,这时候降低 train_batch_size 增加gradient_accumulation_steps 就可以了。

#train_batch_size = 8
#gradient_accumulation_steps = 4

train_batch_size = 4
gradient_accumulation_steps = 8

如果想在 2 个 epoch 下达到 Boss baseline,可以寻找并选择大于 324M 的预训练模型。

最后补充一个 doc_string 的对比,doc_string 也是后处理的模块,下面的结果基于 luhua/chinese_pretrain_mrc_macbert_large 在 epoch=2 下进行修改。

doc_stringPrivate ScorePublic Score
=max_paragraph_len*0.250.842220.83144
=max_paragraph_len*0.10.843350.83087
=max_paragraph_len*0.050.838810.82746

至此,Homework7 就结束了,希望能对你有所帮助。

拓展链接

  • Encoder-Only vs Decoder-Only vs Encoder-Decoder Transformer

  • Decoder-Only or Encoder-Decoder? Interpreting Language Model as a Regularized Encoder-Decoder

  • 中文模型 – Hugging Face

  • Gradient Accumulation in PyTorch

进一步

我将 24 年的课程作业修改为了大陆可以访问的 API,并攥写了所有作业的中文引导和代码镜像,你可以在这里快速索引:
李宏毅2024生成式人工智能导论 中文镜像版指导与作业

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

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

相关文章

开源 AI 智能名片与 S2B2C 商城小程序:嫁接权威实现信任与增长

摘要&#xff1a;本文探讨了嫁接权威在产品营销中的重要性&#xff0c;并结合开源 AI 智能名片与 S2B2C 商城小程序&#xff0c;阐述了如何通过与权威关联来建立客户信任&#xff0c;提升产品竞争力。强调了在当今商业环境中&#xff0c;巧妙运用嫁接权威的方法&#xff0c;能够…

一款前后端分离设计的企业级快速开发平台,支持单体服务与微服务之间灵活切换(附源码)

前言 当前软件开发面临诸多挑战&#xff0c;诸如开发效率低下、重复工作多、维护成-本高等问题&#xff0c;这些问题在一定程度上阻碍了项目的进展。针对这些痛点&#xff0c;我们迫切需要一款既能提升开发效率又能降低维护成-本的处理方案。由此&#xff0c;一款基于前后端分…

HDMI20协议解析_Audio_Clock_Regeneration

HDMI20协议解析_Audio_Clock_Regeneration 1.版本说明 日期作者版本说明20240918风释雪初始版本 2.概述 当通过HDMI传输音频信号时&#xff0c;Audio Clock Regeneration&#xff08;ACR&#xff09;是必须要传输的数据包之一&#xff1b; HDMI传输过程中&#xff0c;音频采样…

数学建模-线性规划讲解(Matlab版本)

引言 相信不少小伙伴刚开始接触数学建模时&#xff0c;第一个学习的算法就是运筹学的重要分支--数学规划&#xff0c;而数学规划当中重要的分支就是线性规划了。在这里笔者参考了司守奎和孙玺菁老师的《数学建模算法与应用》(第三版&#xff09;这本书&#xff0c;以此来讲讲关…

同等学力申硕英语多少分及格

同等学力申硕全国统考与往年的分数线一样&#xff0c;英语、学科综合均为60分合格通过制&#xff0c;满分均100分。 单科分数未达到及格线的考生&#xff0c;次年5月可以参加单科的补考 同等学力申硕的意义和作用 授予同等学力人员硕士学位是国家为同等学力人员开辟的获得学位…

前端——阿里图标的使用

阿里图标 将小图标定义成字体&#xff0c;通过引入字体的方式来展示这些图标 1.打开阿里图标库 https://www.iconfont.cn/ 2.登录 / 注册一个账号 3.选中你需要使用的图标 并且把它加入购物车 4.全部选择完之后 点击右上角 购物车 然后下载代码 5.解压后你下载的文…

MySQL数据库的日志你知道几个?

1、前言 MySQL相信大家都用过&#xff0c;但MySQL中都有哪些日志&#xff0c;是干什么的&#xff0c;估计有小伙伴还没有搞清楚。可能有小伙伴只知道最重要的三个&#xff1a;undolog、redolog、binlog。其实这是不全的&#xff0c;MySQL中的日志有&#xff1a; undolog&…

双端搭建个人博客

1. 准备工作 确保你的两个虚拟机都安装了以下软件: 虚拟机1(Web服务器): Apache2, PHP虚拟机2(数据库服务器): MariaDB2. 安装步骤 虚拟机1(Web服务器) 安装Apache2和PHP 更新系统包列表: sudo apt update安装Apache2: sudo apt install apache2 -y安装PHP及其Apac…

python学习第十二节:python开发图形界面

python学习第十二节&#xff1a;python开发图形界面 创建一个窗口实例化窗口对象调用窗口设置窗口大小设置窗口的标题设置窗口图标否能够改变窗口设置窗口的背景 创建容器组件容器组件的介绍组件参数的介绍label标签label添加标签控件 label添加标签定位label的relief参数label…

网站建设中,常用的后台技术有哪些,他们分别擅长做什么网站平台

PHP、Python、JavaScript、Ruby、Java和.NET各自适用于不同类型的网站平台。以下是对这些编程语言适用场景的具体介绍&#xff1a; PHP Web开发&#xff1a;PHP是一种广泛使用的开源服务器端脚本语言&#xff0c;特别适合Web开发。全球有超过80%的网站使用PHP作为服务器端编程语…

SaaS(Software as a Service)软件的主流技术架构

在当今数字化时代&#xff0c;SaaS&#xff08;Software as a Service&#xff0c;软件即服务&#xff09;软件以其灵活、高效和成本效益高的特点&#xff0c;成为企业信息化建设的首选。为了实现SaaS软件的稳定、可靠和高效运行&#xff0c;其技术架构的设计显得尤为重要。本文…

页面在移动设备上显示不正常的原因及解决方案

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介页面在移动设备上显示不正常的原因及解决方案1. 缺少 viewport 元标签1.1 问题描述1.2 解决方案1.3 注意事项 2. 响应式设计未实现或设计不当2.1 问题描述2.2 解决方案示例&#xff1a;媒体查询的使用 2.3 常见的媒体查询断点 3. 固…

基于传感网技术的职业院校安防系统实训室

一、引言 随着信息技术的飞速发展和城市化的不断加速&#xff0c;智能楼宇及安防系统已成为现代城市建设的重要组成部分。高职院校作为培养技术型人才的重要基地&#xff0c;应积极响应市场需求&#xff0c;建设符合时代潮流的安防系统实训室&#xff0c;以提升学生的专业技能…

Linux之实战命令11:tload应用实例(四十五)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

【工具】语音朗读PDF的免费工具

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 背景介绍 看累了&#xff0c;不想看&#xff0c;能不能读给我听&#xff01; 工具介绍 Natural Readers Free Text to Speech Online with Realistic…

最优化理论与自动驾驶(二-补充):求解算法(梯度下降法、牛顿法、高斯牛顿法以及LM法,C++代码)

在之前的章节里面&#xff08;最优化理论与自动驾驶&#xff08;二&#xff09;&#xff1a;求解算法&#xff09;我们展示了最优化理论的基础求解算法&#xff0c;包括高斯-牛顿法&#xff08;Gauss-Newton Method&#xff09;、梯度下降法&#xff08;Gradient Descent Metho…

蓝桥杯【物联网】零基础到国奖之路:十一. LORA

蓝桥杯【物联网】零基础到国奖之路:十一. LORA 第一节 LORA理论第二节 Lora的无线收发数据1&#xff0c;硬件解读2&#xff0c;CubeMX配置3&#xff0c;MDK代码 第一节 LORA理论 Lora是一种长距离、低功耗的无线通信技术&#xff0c;专为iot和远程应用设计。Lora技术基于半双工…

傅里叶变换及其应用笔记

傅里叶变换 预备知识学习路线扼要描述两者之间的共同点&#xff1a;线性运算周期性现象对称性与周期性的关系周期性 预备知识 学习路线 从傅里叶级数&#xff0c;过度到傅里叶变换 扼要描述 傅里叶级数&#xff08;Fourier series&#xff09;&#xff0c;几乎等同于周期性…

针对考研的C语言学习(定制化快速掌握重点1)

1.printf函数的几个要点 printf函数中所有的输出都是右对齐的&#xff0c;除非在%后面添加负号&#xff0c;则表示左对齐 #include<stdio.h> int main() {int num 10;int nums 100;float f 1000.2333333333;printf("%3d\n", nums);//%3d表示输出的总宽度至…

Python画笔案例-064 绘制彩花之旋转羽毛

1、绘制彩花之旋转羽毛 通过 python 的turtle 库绘制 彩花之旋转羽毛,如下图: 2、实现代码 绘制 彩花之旋转羽毛,以下为实现代码: """彩花之旋转羽毛.py本程序需要coloradd模块支持,安装方法:pip install coloradd技术支持微信scartch8,QQ:406273900www.l…