基于Transformer的机器翻译——训练篇

前言

还在为机器翻译模型从理论到落地卡壳?系列博客第三弹——模型训练篇强势登场,手把手带你走完Transformer中日翻译项目的最后关键一步!

前两期我们搞定了数据预处理(分词、词表构建全流程)和模型搭建(词嵌入、位置编码、编码器解码器核心结构),而这一篇,将聚焦让模型“学会翻译”的核心秘籍:

  • 如何设计损失函数,让模型精准捕捉中日语言差异?
  • 优化器参数怎么调,才能让训练更稳定、收敛更快?

从数据到模型,再到训练全流程,一套代码跑通Transformer翻译任务。

本篇主要介绍机器翻译项目中模型训练部分是如何处理的,延续之前篇章预处理篇,模型篇的内容。
系列博客第一弹——基于Transformer的机器翻译——预处理篇
系列博客第二弹——基于Transformer的机器翻译——模型篇
系列博客第三弹——基于Transformer的机器翻译——训练篇

1.模型介绍

1.1模型参数配置

参数主要有源语言与目标语言词表大小词嵌入维度多头注意力头数前馈神经网络隐藏层维度编码器与解码器层数批量大小等,这些均属于超参数,需要在训练中不断调整,考虑到自身硬件条件,超参数设置如下:

# 模型参数配置
SRC_VOCAB_SIZE = len(ja_vocab)  # 源语言(日语)词汇表大小
TGT_VOCAB_SIZE = len(ch_vocab)  # 目标语言(中文)词汇表大小
EMB_SIZE = 512                  # 词嵌入维度(与Transformer的d_model一致)
NHEAD = 8                       # 多头注意力的头数
FFN_HID_DIM = 512               # 前馈网络隐藏层维度
BATCH_SIZE = 16                 # 批量大小(每次输入的样本数)
NUM_ENCODER_LAYERS = 3          # 编码器层数
NUM_DECODER_LAYERS = 3          # 解码器层数
NUM_EPOCHS = 16                 # 训练轮数(完整遍历数据集的次数)

接着初始化网络模型:

transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS,NUM_DECODER_LAYERS,EMB_SIZE,SRC_VOCAB_SIZE,TGT_VOCAB_SIZE,FFN_HID_DIM
)

1.2模型结构

可以直接打印模型,查看模型结构,如下:

transformer

输出结果:

Seq2SeqTransformer((transformer_encoder): TransformerEncoder((layers): ModuleList((0-2): 3 x TransformerEncoderLayer((self_attn): MultiheadAttention((out_proj): NonDynamicallyQuantizableLinear(in_features=512, out_features=512, bias=True))(linear1): Linear(in_features=512, out_features=512, bias=True)(dropout): Dropout(p=0.1, inplace=False)(linear2): Linear(in_features=512, out_features=512, bias=True)(norm1): LayerNorm((512,), eps=1e-05, elementwise_affine=True)(norm2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)(dropout1): Dropout(p=0.1, inplace=False)(dropout2): Dropout(p=0.1, inplace=False))))(transformer_decoder): TransformerDecoder((layers): ModuleList((0-2): 3 x TransformerDecoderLayer((self_attn): MultiheadAttention((out_proj): NonDynamicallyQuantizableLinear(in_features=512, out_features=512, bias=True))(multihead_attn): MultiheadAttention((out_proj): NonDynamicallyQuantizableLinear(in_features=512, out_features=512, bias=True))(linear1): Linear(in_features=512, out_features=512, bias=True)(dropout): Dropout(p=0.1, inplace=False)(linear2): Linear(in_features=512, out_features=512, bias=True)(norm1): LayerNorm((512,), eps=1e-05, elementwise_affine=True)(norm2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)(norm3): LayerNorm((512,), eps=1e-05, elementwise_affine=True)(dropout1): Dropout(p=0.1, inplace=False)(dropout2): Dropout(p=0.1, inplace=False)(dropout3): Dropout(p=0.1, inplace=False))))(generator): Linear(in_features=512, out_features=26854, bias=True)(src_tok_emb): TokenEmbedding((embedding): Embedding(24058, 512))(tgt_tok_emb): TokenEmbedding((embedding): Embedding(26854, 512))(positional_encoding): PositionalEncoding((dropout): Dropout(p=0.1, inplace=False))
)

1.3参数初始化

一般主要有两种初始化模型参数的方法,包括He初始化(即一般默认的初始化方法)Xavier初始化方法

  • He初始化:适用于使用ReLU或其变种Leaky ReLU这类激活函数的神经网络。
  • Xavier初始化:适用于tanhsigmoid等这类对称激活函数。

这里采用Xavier初始化,代码如下:

# 参数初始化(Xavier均匀初始化,缓解梯度消失/爆炸)
for p in transformer.parameters():if p.dim() > 1:  # 仅初始化非标量参数nn.init.xavier_uniform_(p)

为了加快模型的训练速度,这里使用GPU加速,即将模型迁移至GPU上进行训练。

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
transformer = transformer.to(device)

如果未配置GPU环境,此处会默认使用CPU。

2.训练策略

2.1损失函数

因为该问题最终是一个分类问题,预测词表中的一个单词。而分类问题,一般采用交叉熵损失函数。同时,因为此前我们填充<PAD_IDX>字符使输入句子长度保持一致,所以在计算交叉熵的时候,应该将该字符产生的损失忽略。
代码如下:

loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)

2.2优化器

优化器大都会选用自适应优化器Adam,相对于常规的SGD(随机梯度下降方法),避免了频繁调整参数,同时能够更快的收敛。代码如下:

optimizer = torch.optim.Adam(transformer.parameters(),  # 待优化的模型参数lr=0.0001,                  # 学习率(控制参数更新步长)betas=(0.9, 0.98),          # 动量参数(控制历史梯度的衰减)eps=1e-9                    # 数值稳定性参数(防止除零)
)

2.3训练过程

整体的训练过程主要包括五个步骤:

  1. 梯度清零
  2. 计算模型输出
  3. 计算损失
  4. 方向传播
  5. 参数更新

模型输入包括两部分,输入的源语言,和目标语言已经生成的部分,分别对应于编码器的输入和解码器的输入。
因为是逐个token进行预测,所以编码器的输入不包括句子的最后一个tokenEOS_IDX,模型预测输出到EOS_IDX即结束,相应的监督标签为目标语言不包括第一个token即BOS_IDX
相应代码为:

src = src.to(device)       # 将源序列移动到GPU/CPUtgt = tgt.to(device)       # 将目标序列移动到GPU/CPUtgt_input = tgt[:-1, :]    # 目标输入去掉最后一个词tgt_output = tgt[1:, :]   # 目标输出去掉第一个词

因为模型在进行训练时,不能关注到目标输入后续的token,因此需要掩码注意力机制

2.3.1顺序掩码

采用下三角掩码,防止模型在预测第i个词时关注到第i+1、i+2… 个词(即 “未来信息”)

  1. 通过torch.triu(torch.ones((3,3))) 生成上三角矩阵(对角线及以上为 1,其余为 0)
  2. ==1 转为布尔矩阵(True 表示原位置为 1),再 transpose(0,1) 转置(行变列,列变行)
  3. masked_fill 填充:True 位置(有效)填 0.0,False 位置(无效,未来信息)填-inf
    本案例主要通过函数generate_square_subsequent_mask函数实现,代码如下:
def generate_square_subsequent_mask(sz):# 1. 生成上三角矩阵(对角线及以上为1,其余为0)mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)# 2. 将0的位置填充为-inf(无效位置),1的位置填充为0(有效位置)mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))return mask

2.3.2完整掩码组合

这里主要4 种掩码,分别用于源序列和目标序列的注意力计算。

2.3.2.1源序列自注意力掩码

源序列是输入,无需掩盖 “未来信息”,因此全为 0(允许关注所有位置)。

2.3.2.2目标序列自注意力掩码

可以通过调用 generate_square_subsequent_mask实现。

2.3.2.3源序列填充掩码

标记源序列中PAD的位置,让模型在注意力计算时忽略这些无效位置。

2.3.2.4目标序列填充掩码

类似源序列,标记目标序列中PAD的位置。

2.3.2.5完整代码

上述四种掩码的生成代码可表述为以下代码:

def create_mask(src, tgt):src_seq_len = src.shape[0]  # 源序列长度(seq_len)tgt_seq_len = tgt.shape[0]  # 目标序列长度(seq_len)# 目标序列的注意力掩码(下三角掩码,防止关注未来词)tgt_mask = generate_square_subsequent_mask(tgt_seq_len)# 源序列的注意力掩码(全0,允许关注所有位置)src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)# 源序列的填充掩码(标记<pad>的位置)src_padding_mask = (src == PAD_IDX).transpose(0, 1)# 目标序列的填充掩码(标记<pad>的位置)tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask

2.3.3训练代码

由上述代码可得,训练过程可表述为以下代码:

  1. 获取当前批量的输入与目标输出序列
  2. 生成掩码
  3. 计算模型预测输出
  4. 清空梯度
  5. 计算预测输出与目标输出之间的损失
  6. 方向传播
  7. 更新梯度
for idx, (src, tgt) in enumerate(train_iter):  # 遍历训练数据迭代器src = src.to(device)       # 将源序列移动到GPU/CPUtgt = tgt.to(device)       # 将目标序列移动到GPU/CPUtgt_input = tgt[:-1, :]    # 目标输入去掉最后一个词tgt_output = tgt[1:, :]   # 目标输出去掉第一个词# 生成掩码(注意力掩码+填充掩码)src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)# 前向传播:模型预测logits = model(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)optimizer.zero_grad()      # 清空优化器梯度tgt_out = tgt[1:, :]       # 目标输出(去掉第一个词,与预测对齐)loss = loss_fn(            # 计算损失logits.reshape(-1, logits.shape[-1]),  # 展平预测结果([seq_len*batch_size, vocab_size])tgt_out.reshape(-1)                    # 展平真实标签([seq_len*batch_size]))loss.backward()            # 反向传播计算梯度optimizer.step()           # 更新模型参数losses += loss.item()      # 累加单批损失

为了减小梯度抖动,采用当前iter内所有损失的平均和,同时为了可视化损失折线图,加入了tensorboard可视化,并加入进度条来显示训练过程,当前训练过程。代码如下:

def train_epoch(model, train_iter, optimizer):model.train()  # 开启训练模式(启用Dropout等)losses = 0     # 累计损失值for idx, (src, tgt) in enumerate(train_iter):  # 遍历训练数据迭代器src = src.to(device)       # 将源序列移动到GPU/CPUtgt = tgt.to(device)       # 将目标序列移动到GPU/CPUtgt_input = tgt[:-1, :]    # 目标输入去掉最后一个词tgt_output = tgt[1:, :]   # 目标输出去掉第一个词# 生成掩码(注意力掩码+填充掩码)src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)# 前向传播:模型预测logits = model(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)optimizer.zero_grad()      # 清空优化器梯度tgt_out = tgt[1:, :]       # 目标输出(去掉第一个词,与预测对齐)loss = loss_fn(            # 计算损失logits.reshape(-1, logits.shape[-1]),  # 展平预测结果([seq_len*batch_size, vocab_size])tgt_out.reshape(-1)                    # 展平真实标签([seq_len*batch_size]))# 写入tensorboardwriter.add_scalar('train_loss',loss.item(),idx)loss.backward()            # 反向传播计算梯度optimizer.step()           # 更新模型参数losses += loss.item()      # 累加单批损失train_bar.set_postfix({'loss': '{:.6f}'.format(loss.item())})train_bar.update()return losses / len(train_iter)  # 返回平均训练损失

为了避免长时间等待,因此使用当前数据集的10%,其中9%作为训练集,1%作为测试集。代码如下:

train_iter = DataLoader(train_data[:int(len(train_data)*0.09)], batch_size=BATCH_SIZE,shuffle=True, collate_fn=generate_batch)
test_iter = DataLoader(train_data[int(len(train_data)*0.09):int(len(train_data)*0.1)], batch_size=BATCH_SIZE,shuffle=True, collate_fn=generate_batch)

3.训练结果

假如此时按照每个iter来进行记录损失(上述代码就是如此),结果会出现剧烈波动,整体还是会保持下降趋势,结果如下:
在这里插入图片描述
此时将其改成每个epoch来记录损失,取当前epoch的平均值,结果如下:
在这里插入图片描述
此时只统计了12个点,当然你可以通过增加epoch轮数来增加统计的点数,但是曲线的变化趋势往往是判断模型过拟合和欠拟合的关键,因此一般采用每多个iter内的加权值来统计一次损失。这里为了方便起见,采用的仍是按照epoch进行统计的。

同时为了可视化训练进度,此处加入了进度条来实时展示训练的进度。统计损失使用tensorboard进行可视化。
完整训练过程代码为:
训练函数:

writer=SummaryWriter('./logs')
def train_epoch(model, train_iter, optimizer,epoch):model.train()  # 开启训练模式(启用Dropout等)losses = 0     # 累计损失值for idx, (src, tgt) in enumerate(train_iter):  # 遍历训练数据迭代器src = src.to(device)       # 将源序列移动到GPU/CPUtgt = tgt.to(device)       # 将目标序列移动到GPU/CPUtgt_input = tgt[:-1, :]    # 目标输入去掉最后一个词tgt_output = tgt[1:, :]   # 目标输出去掉第一个词# 生成掩码(注意力掩码+填充掩码)src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)# 前向传播:模型预测logits = model(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)optimizer.zero_grad()      # 清空优化器梯度tgt_out = tgt[1:, :]       # 目标输出(去掉第一个词,与预测对齐)loss = loss_fn(            # 计算损失logits.reshape(-1, logits.shape[-1]),  # 展平预测结果([seq_len*batch_size, vocab_size])tgt_out.reshape(-1)                    # 展平真实标签([seq_len*batch_size]))loss.backward()            # 反向传播计算梯度optimizer.step()           # 更新模型参数losses += loss.item()      # 累加单批损失train_bar.set_postfix({'loss': '{:.6f}'.format(loss.item())})train_bar.update()# 写入tensorboardwriter.add_scalar('train_loss',losses / len(train_iter),epoch)return losses / len(train_iter)  # 返回平均训练损失

验证函数:

def evaluate(model, test_iter,epoch):model.eval()losses = 0for src, tgt in test_iter:src = src.to(device)tgt = tgt.to(device)tgt_input = tgt[:-1, :]tgt_output = tgt[1:, :]src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)logits = model(src, tgt_input, src_mask, tgt_mask, src_padding_mask, tgt_padding_mask, src_padding_mask)tgt_out = tgt[1:, :]loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))losses += loss.item()test_bar.set_postfix({'loss': '{:.6f}'.format(loss.item())})test_bar.update()writer.add_scalar('test_loss',losses / len(test_iter),epoch)return losses / len(test_iter)

调用函数:

from tqdm.notebook import tqdm
epoch_bar = tqdm(desc='training routine',total=NUM_EPOCHS,position=0)train_bar = tqdm(desc='split=train',total=len(train_iter),position=0,leave=True)
test_bar=tqdm(desc='split=test',total=len(test_iter),position=0,leave=True)
best_loss = float('inf')
for epoch in range(1, NUM_EPOCHS+1):start_time = time.time()train_loss = train_epoch(transformer, train_iter, optimizer,epoch)test_loss = evaluate(transformer, test_iter,epoch)if test_loss < best_loss:best_loss = test_loss# 保存时分开保存torch.save(transformer.state_dict(), './share/model_weights.pth')  # 只保存权重torch.save({'ja_vocab': ja_vocab, 'ch_vocab': ch_vocab}, './share/vocab.pth')  # 保存词表print(f"Epoch: {epoch}, Train loss: {train_loss:.3f}, Model saved.")epoch_bar.set_postfix({'train_loss': '{:.6f}'.format(train_loss)})epoch_bar.update()train_bar.n = 0test_bar.n=0end_time = time.time()print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "f"Epoch time = {(end_time - start_time):.3f}s"))

训练结果:
在这里插入图片描述
在这里插入图片描述

结语

至此,基于Transformer的机器翻译任务就介绍完毕了,至于模型性能的评价指标如BLEU并未做介绍,感兴趣的可以自行探索,希望本案例,能够对你有所帮助,感谢支持!
系列博客第一弹——基于Transformer的机器翻译——预处理篇
系列博客第二弹——基于Transformer的机器翻译——模型篇
系列博客第三弹——基于Transformer的机器翻译——训练篇

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

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

相关文章

智能编程中的智能体与 AI 应用:概念、架构与实践场景

一、智能体&#xff08;Intelligent Agent&#xff09;在编程中的定义与架构1. 智能体的核心概念 智能体是指在特定环境中能够自主感知、决策并执行动作的软件实体&#xff0c;具备以下特征&#xff1a;自主性&#xff1a;无需人工干预即可根据环境变化调整行为。交互性&#x…

数组实现各类数据结构

目录 一、数组实现单链表 二、数组实现双链表 三、数组实现栈 四、数组模拟队列 五、数组模拟单调栈 六、数组模拟单调队列&#xff08;滑动窗口&#xff09; 七、数组模拟堆 一、数组实现单链表 #include<iostream> #include<algorithm> #include<cstr…

数据处理与统计分析 —— apply自定义函数

目录 一、向量化与伪向量化 1、向量化 2、np.vectorize 伪向量化&#xff08;特定场景&#xff09; 3、apply&#xff08;自定义函数&#xff09; 二、apply函数 1、对series中使用apply 2、对dataframe中使用apply 3、apply函数案例-泰坦尼克号数据集] 数据集下载链接&#xf…

如何有效利用大语言模型来智能加速产业联盟的产业链转化路径?

观点作者&#xff1a;科易网AI技术转移研究院在科技创新浪潮席卷全球的今天&#xff0c;科技成果转化已成为衡量一个国家创新能力的重要标志。然而&#xff0c;一项权威调查显示&#xff0c;我国科技成果转化率不足30%&#xff0c;大量有价值的创新成果仍停留在实验室阶段&…

视频加水印 视频加水印软件 视频加动态水印

如果你有一个视频&#xff0c;你想给他加一个水印&#xff0c;那么你可以使用这个工具&#xff0c;准备好你的视频和水印。水印一般采用PNG&#xff0c;打开这个工具&#xff0c;把你的视频和水印拖进这个方框当中。视频限制是MP4&#xff0c;水印限制是PNG&#xff0c;它可以把…

面向DeepSeek chat coding实录(二)

向DeepSeek的提问 帮我设计以下两个python class Span 属性&#xff1a; hash值&#xff08;在init函数中通过时间初始化&#xff09; 创建时间&#xff1a;时间&#xff08;在init函数中通过时间初始化&#xff09; 结束时间&#xff1a;时间&#xff08;可选&#xff0c;默认…

Hi3516CV610-00S 海思SOC芯片 可申请开发资料

1.1 概述Hi3516CV610 是一颗应用在安防市场的 IPC SoC。在开放操作系统、新一代视频编解码标准、网络安全和隐私保护、人工智能方面引领行业发展&#xff0c;主要面向室内外场景下的枪机、球机、半球机、海螺机、枪球一体机、双目长短焦机等产品形态&#xff0c;打造极具竞争力…

算法题Day4

目录 13. 练习13 : 整数十位 14. 练习14 : 时间转换 15. 练习15 : 小雨的游泳时间 13. 练习13 : 整数十位 解题方法: #include <iostream> using namespace std; int a; int main() {cin >> a;cout << a % 100 / 10 << endl;return 0; } 14. 练习…

加速你的故障排查:使用 Elasticsearch 构建家电手册的 RAG 应用

作者&#xff1a;来自 Elastic Alessandro Brofferio 学习如何使用 Elasticsearch 构建 RAG 应用&#xff0c;轻松排查你的家电问题。 想要获得 Elastic 认证吗&#xff1f;来看看下一次 Elasticsearch 工程师培训什么时候开始吧&#xff01; Elasticsearch 拥有大量新功能&am…

6.Shell脚本修炼手册---grep命令使用指南

grep 命令&#xff1a;从文本中精准筛选信息的实用指南 文章目录grep 命令&#xff1a;从文本中精准筛选信息的实用指南一、什么是 grep&#xff1f;为什么要用它&#xff1f;二、grep 基本语法三、常用选项详解&#xff08;附实例&#xff09;&#xff08;一&#xff09;模式选…

Python day51

浙大疏锦行 Python day51 复习日&#xff0c;DDPM class DenoiseDiffusion():def __init__(self, eps_model: nn.Module, n_steps: int, device: torch.device):super().__init__()self.eps_model eps_modelself.n_steps n_stepsself.device deviceself.beta torch.linsp…

数据结构:生成 (Generating) 一棵 AVL 树

目录 搭建“创世”的舞台 注入序列&#xff0c;观察演化 注入 10 注入 20 注入 30 注入 40 注入 50 注入 25 再次审视 上一讲&#xff0c;我们已经从最根本的逻辑出发&#xff0c;推导出了 AVL 树失衡时所必需的修复操作——旋转 (Rotation)。 现在&#xff0c;我们将…

github 上传代码步骤

登录GitHub → 点击右上角 ​​ → New Repository​​。填写仓库名称&#xff08;建议与本地项目同名&#xff09;&#xff0c;选择 ​​Public/Private​​。​​关键&#xff1a;不要勾选​​ “Initialize with README”&#xff08;避免与本地仓库冲突&#xff09;。点击 …

陪诊小程序系统开发:开启智慧就医新时代

在数字化浪潮的推动下&#xff0c;智慧医疗正逐渐成为现实。陪诊小程序系统的开发&#xff0c;作为智慧医疗领域的一次重要创新&#xff0c;正以其独特的魅力与优势&#xff0c;引领着就医新时代的到来。它不仅改变了传统就医模式&#xff0c;更以科技的力量&#xff0c;让医疗…

朝花夕拾(七)--------从混淆矩阵到分类报告全面解析​

目录 ​​机器学习模型评估指南&#xff1a;从混淆矩阵到分类报告全面解析​​ ​​1. 引言​​ ​​2. 混淆矩阵&#xff1a;模型评估的基石​​ ​​2.1 什么是混淆矩阵&#xff1f;​​ 2.2二分类问题的混淆矩阵 ​​二分类场景下的具体案例​ ​分析案例: 1.​​案例…

Python读取和设置PNG图片的像素值

在Python中&#xff0c;可以使用Pillow库或OpenCV库来读取和写入PNG图片的像素值。以下是两种方法的详细说明&#xff1a;1. 使用Pillow库Pillow是Python中常用的图像处理库&#xff0c;支持多种图像格式&#xff0c;包括PNG。读取像素值from PIL import Imageimg Image.open(…

SkyWalking + Elasticsearch8 容器化部署指南:国内镜像加速与生产级调优

SkyWalking Elasticsearch8 Docker 部署文档本文提供在 Ubuntu 服务器上&#xff0c;使用 Docker Compose 部署 SkyWalking&#xff08;OAPUI&#xff09;与 Elasticsearch 8 的完整步骤&#xff0c;数据/日志落地到 /media/disk2 前置条件 Ubuntu&#xff0c;已具备 sudo 权限…

有符号和无符号的区别

有符号&#xff08;Signed&#xff09;和无符号&#xff08;Unsigned&#xff09;是计算机编程中用来描述整数数据类型能否表示负数的两个概念。它们的主要区别在于能否表示负数以及数值的表示范围。以下是它们的核心区别&#xff1a;1. 能否表示负数有符号&#xff08;Signed&…

8月21日作业

1、Makefile中头文件发生过修改的解决&#xff1a; 处插入*.h依赖&#xff0c;对.h文件打的时间戳进行检查2、头删和输出//五、头删 void delete_head(seq_p s) {empty(s);for(int i1;i<s->len;i){s->data[i-1]s->data[i];}s->len--; }//六、输出 void output(s…

Lucene 8.5.0 的 `.pos` 文件**逻辑结构**

Lucene 8.5.0 的 .pos 文件**逻辑结构**&#xff08;按真实实现重新整理&#xff09; .pos 文件 ├─ Header (CodecHeader) ├─ TermPositions TermCount ← 每个 term 一段&#xff0c;顺序由词典隐式决定 │ ├─ PackedPosDeltaBlock N ← 仅当 **无 payl…