序列到序列学习
seq2seq
就是把一个句子翻译到另外一个句子。
机器翻译
- 给定一个源语言的句子,自动翻译成目标语言
- 这两个句子可以有不同的长度
seq2seq 是一个 Encoder - Decoder 的架构
编码器是一个 RNN , 读取输入句子(可以是双向)
解码器使用另外一个 RNN 来输出
编码器 - 解码器细节
编码器是没有输出的 RNN
编码器最后时间步的隐状态用作解码器的初始隐状态
将最后一层的 RNN 的在最后一个时刻的隐藏状态,也就是输出,和句子的 Embedding 输入放在一起,作为输入
训练
训练时解码器使用目标句子作为输入
在训练的时候,是知道目标句子的,也就是知道真正的翻译。哪怕某一个时刻翻译错误,也是没有关系的,下一个时刻我还是可以给定正确的输入的。
推理
但是推理就不一样了,不知道目标句子的翻译,只能以当前时刻的输出作为下一时刻的翻译。
衡量生成序列的好坏的 BLEU
总结
- Seq2seq 从一个句子生成另一个句子
- 编码器和解码器都是 RNN
- 将编码器最后时间隐状态来初始解码器隐状态来完成信息传递
- 常用BLEU来衡量生成序列的好坏
代码实现
首先就是导入必要的环境
import collections
import math
import torch
from torch import nn
from d2l import torch as d2l
实现循环神经网络编码器
# @save
class Seq2SeqEncoder(d2l.Encoder):"""用于序列到序列学习的循环神经网络编码器""""""vocab_size: 词汇表大小,决定了嵌入层的输入维度。embed_size: 嵌入层的输出维度,也就是每个词被映射为向量的维度。num_hiddens: 隐藏层单元的数量,这决定了GRU(门控循环单元)层的输出维度。num_layers: GRU层的数量,可以堆叠多层GRU以增加模型的深度。dropout: dropout比例,用于正则化,防止过拟合,默认值为0,即不使用dropout。"""def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,dropout=0, **kwargs):super(Seq2SeqEncoder, self).__init__(**kwargs)# 嵌入层"""创建了一个嵌入层self.embedding,它将输入的词汇索引转换成固定大小的密集向量。创建了一个GRU层self.rnn,该层接受来自嵌入层的输入,并基于这些输入生成一系列隐藏状态。GRU是一种特殊的循环神经网络(RNN),能够捕捉序列中的长期依赖关系。"""self.embedding = nn.Embedding(vocab_size, embed_size)self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,dropout=dropout)def forward(self, X, *args):# 输出'X'的形状:(batch_size,num_steps,embed_size)X = self.embedding(X)# 在循环神经网络模型中,第一个轴对应于时间步X = X.permute(1, 0, 2) # 使得时间步位于第一个维度,以便与GRU层的要求相匹配。# 如果未提及状态,则默认为0output, state = self.rnn(X)# output的形状:(num_steps,batch_size,num_hiddens)# state的形状:(num_layers,batch_size,num_hiddens)return output, state
上述编码器的实现
# 创建一个序列到序列学习的循环神经网络编码器实例
# vocab_size=10: 输入词汇表大小为10
# embed_size=8: 嵌入层的输出维度为8
# num_hiddens=16: GRU隐藏单元的数量为16
# num_layers=2: GRU的层数为2
encoder = Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16,num_layers=2)# 将编码器设置为评估模式(evaluation mode),
# 这会关闭dropout等仅在训练时使用的功能
encoder.eval()# 创建一个形状为(4, 7)的输入张量X,表示批量大小为4,时间步数为7
# 数据类型为torch.long,表示每个元素是一个长整型(通常用于表示词汇索引)
X = torch.zeros((4, 7), dtype=torch.long)# 将输入X传入编码器,获取输出和状态
# output: 编码器在每个时间步的输出,形状为(num_steps, batch_size, num_hiddens)
# state: 编码器的最终隐状态,形状为(num_layers, batch_size, num_hiddens)
output, state = encoder(X)# 打印output的形状,验证编码器的输出维度是否符合预期
output.shape
# torch.Size([7, 4, 16])state.shape
# torch.Size([2, 4, 16])
解码器
class Seq2SeqDecoder(d2l.Decoder):"""用于序列到序列学习的循环神经网络解码器"""def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,dropout=0, **kwargs):"""初始化解码器:param vocab_size: 词汇表大小,决定了嵌入层的输入维度:param embed_size: 嵌入层的输出维度,也就是每个词被映射为向量的维度:param num_hiddens: 隐藏层单元的数量,这决定了GRU(门控循环单元)层的输出维度:param num_layers: GRU层的数量,可以堆叠多层GRU以增加模型的深度:param dropout: dropout比例,用于正则化,防止过拟合,默认值为0,即不使用dropout"""super(Seq2SeqDecoder, self).__init__(**kwargs)# 嵌入层:将输入的词汇索引转换成固定大小的密集向量self.embedding = nn.Embedding(vocab_size, embed_size)# GRU层:接受来自嵌入层和上下文向量的输入,并生成一系列隐藏状态# 输入维度为 embed_size + num_hiddens,因为输入拼接了上下文向量self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,dropout=dropout)# 输出层:将GRU的输出映射到词汇表大小的向量,用于预测下一个词元self.dense = nn.Linear(num_hiddens, vocab_size)def init_state(self, enc_outputs, *args):"""初始化解码器的隐状态:param enc_outputs: 编码器的输出,包含上下文向量和隐状态:return: 编码器的最终隐状态,用作解码器的初始隐状态"""return enc_outputs[1] # 提取编码器的隐状态def forward(self, X, state):"""前向传播:param X: 解码器的输入,形状为 (batch_size, num_steps):param state: 解码器的隐状态,形状为 (num_layers, batch_size, num_hiddens):return: 解码器的输出和更新后的隐状态"""# 嵌入层:将输入的词汇索引转换为特征向量# 输出'X'的形状:(batch_size, num_steps, embed_size)X = self.embedding(X).permute(1, 0, 2) # 调整维度以匹配GRU的输入要求# 获取上下文向量(编码器的最后一层隐状态)# 广播context,使其具有与X相同的时间步数 (num_steps)context = state[-1].repeat(X.shape[0], 1, 1)# 将输入特征向量和上下文向量拼接在一起# 拼接后的形状为 (num_steps, batch_size, embed_size + num_hiddens)# 这里也就是为啥前面的nn.GRU的输入维度是embed_size + num_hiddensX_and_context = torch.cat((X, context), 2)# GRU层:处理拼接后的输入,生成输出和更新后的隐状态# output的形状:(num_steps, batch_size, num_hiddens)# state的形状:(num_layers, batch_size, num_hiddens)output, state = self.rnn(X_and_context, state)# 输出层:将GRU的输出映射到词汇表大小的向量# 调整维度以匹配输出要求# output的最终形状:(batch_size, num_steps, vocab_size)output = self.dense(output).permute(1, 0, 2)# 返回解码器的输出和更新后的隐状态return output, state
实例化解码器
decoder = Seq2SeqDecoder(vocab_size=10, embed_size=8, num_hiddens=16,num_layers=2)
decoder.eval()
state = decoder.init_state(encoder(X))
output, state = decoder(X, state)
output.shape, state.shape# (torch.Size([4, 7, 10]), torch.Size([2, 4, 16]))
通过零值化屏蔽不相关的项
#@save
def sequence_mask(X, valid_len, value=0):"""在序列中屏蔽不相关的项。对于每个序列,根据其有效长度,将超出有效长度的部分用指定的值进行屏蔽。参数:X: 输入张量,形状为 (batch_size, num_steps),表示批量中的序列数据。valid_len: 有效长度张量,形状为 (batch_size,),表示每个序列的有效长度。value: 用于屏蔽的值,默认为 0。返回:屏蔽后的张量,形状与输入张量 X 相同。"""# 获取输入张量 X 的序列长度(即 num_steps)maxlen = X.size(1)# 创建一个布尔掩码张量,形状为 (batch_size, num_steps)# torch.arange(maxlen) 生成一个从 0 到 maxlen-1 的张量# valid_len[:, None] 将 valid_len 的形状从 (batch_size,) 扩展为 (batch_size, 1)# 比较操作生成一个布尔张量,表示每个序列中哪些位置是有效的mask = torch.arange((maxlen), dtype=torch.float32,device=X.device)[None, :] < valid_len[:, None]"""执行 valid_len[:, None] 后:valid_len 原本是一个形状为 (2,) 的一维张量:[1, 2]使用 [:, None] 或者 .unsqueeze(-1) 可以在指定位置增加一个维度,这里是在最后一个位置增加了一个维度,因此得到的结果是形状为 (2, 1) 的二维张量"""# 使用布尔掩码将无效位置设置为指定的屏蔽值# ~mask 表示取反操作,将无效位置标记为 True"""通过X[~mask] = value,所有标记为True的位置(即那些超过有效序列长度的位置)都会被赋值为value(在这个例子中是0)"""X[~mask] = value# 返回屏蔽后的张量return X# 示例输入张量 X,形状为 (2, 3),表示两个序列,每个序列有 3 个时间步
X = torch.tensor([[1, 2, 3], [4, 5, 6]])# 有效长度张量 valid_len,形状为 (2,),表示每个序列的有效长度
valid_len = torch.tensor([1, 2])# 调用 sequence_mask 函数,对输入张量进行屏蔽
sequence_mask(X, valid_len)
通过扩展 softmax 交叉熵损失函数来遮蔽不相关的预测
# @save
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):"""带遮蔽的softmax交叉熵损失函数"""# pred的形状:(batch_size,num_steps,vocab_size)# label的形状:(batch_size,num_steps)# valid_len的形状:(batch_size,)def forward(self, pred, label, valid_len):# 创建一个和 label 形状一样的全1张量,表示“初始情况下,所有位置都有效”。weights = torch.ones_like(label)# 将超出每个样本有效长度的位置设为 0。weights = sequence_mask(weights, valid_len) # 有效的就保留下来,其余的就全部变成 0 。# PyTorch 中的 CrossEntropyLoss 默认会做平均或求和,但我们这里要自己控制怎么处理损失,# 所以设置为 'none',这样它会返回每个时间步的损失值。self.reduction = 'none'"""注意输入的 pred 是 (batch_size, num_steps, vocab_size),而 PyTorch 的 CrossEntropyLoss 要求输入是 (batch_size, vocab_size, num_steps),所以要做一次 permute。这个操作后,unweighted_loss 的形状是 (batch_size, num_steps),即每个时间步都有一个损失值。"""unweighted_loss = super().forward(pred.permute(0, 2, 1), label)"""先对损失乘以权重,这样 padding 位置的损失就变成 0。然后对每个句子的时间步取平均(dim=1),得到一个形状为 (batch_size,) 的损失张量。"""weighted_loss = (unweighted_loss * weights).mean(dim=1) # 对每个句子的损失进行平均return weighted_loss
代码健全性检查
loss = MaskedSoftmaxCELoss()
loss(torch.ones(3, 4, 10), torch.ones((3, 4), dtype=torch.long),torch.tensor([4, 2, 0]))# tensor([2.3026, 1.1513, 0.0000])
训练
#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):"""训练序列到序列模型"""def xavier_init_weights(m):if type(m) == nn.Linear:nn.init.xavier_uniform_(m.weight)if type(m) == nn.GRU:for param in m._flat_weights_names:if "weight" in param:nn.init.xavier_uniform_(m._parameters[param])net.apply(xavier_init_weights)net.to(device)optimizer = torch.optim.Adam(net.parameters(), lr=lr)loss = MaskedSoftmaxCELoss()net.train()animator = d2l.Animator(xlabel='epoch', ylabel='loss',xlim=[10, num_epochs])for epoch in range(num_epochs):timer = d2l.Timer()metric = d2l.Accumulator(2) # 训练损失总和,词元数量for batch in data_iter:optimizer.zero_grad()X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]"""这里构造了解码器的输入 dec_input:<bos> 表示 “begin of sentence”,告诉模型开始生成。Y[:, :-1] 是目标句子去掉最后一个词的部分。拼接后变成 [<bos>, y1, y2, ..., y_{n-1}],这样解码器就可以在每个时间步根据前面的真实词来预测下一个词。这就是所谓的“强制教学”(teacher forcing):用真实标签作为下一个输入,而不是自己预测的结果。"""bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],device=device).reshape(-1, 1)dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学Y_hat, _ = net(X, dec_input, X_valid_len)l = loss(Y_hat, Y, Y_valid_len)l.sum().backward() # 损失函数的标量进行“反向传播”d2l.grad_clipping(net, 1)num_tokens = Y_valid_len.sum()optimizer.step()with torch.no_grad():metric.add(l.sum(), num_tokens)if (epoch + 1) % 10 == 0:animator.add(epoch + 1, (metric[0] / metric[1],))print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} 'f'tokens/sec on {str(device)}')
创建和训练一个循环神经网络“编码器-解码器”模型
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,dropout)
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,dropout)
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
预测
#@save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,device, save_attention_weights=False):"""序列到序列模型的预测"""# 将模型设为评估模式,关闭dropout、batchnorm等训练专用操作net.eval()# 将输入句子转为小写并按空格分割成单词列表,然后查出每个词在源语言词汇表中的索引# 最后添加一个 <eos>(end of sentence)标记作为句子结束src_tokens = src_vocab[src_sentence.lower().split(' ')] + [src_vocab['<eos>']]# 创建 enc_valid_len 张量,表示编码器的有效长度(即不包括 padding 的实际词数)enc_valid_len = torch.tensor([len(src_tokens)], device=device)# 对源句子进行填充或截断,使其长度等于 num_steps,并用 <pad> 填充不足部分src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])# 添加批量维度(batch dimension),因为模型输入需要 batch_size 维度# enc_X 形状:(1, num_steps),其中 1 是 batch_size=1enc_X = torch.unsqueeze(torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)# 编码器前向传播,得到编码后的输出和隐藏状态enc_outputs = net.encoder(enc_X, enc_valid_len)# 使用编码器的输出初始化解码器的状态(例如 GRU 的初始隐藏状态)dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)# 解码器的第一个输入是目标语言的起始标记 <bos># 同样添加批量维度,形状为 (1, 1)dec_X = torch.unsqueeze(torch.tensor([tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)# 存放最终生成的目标语言句子的 token 索引output_seq = []# 如果启用保存注意力权重,则保存每一步的注意力权重attention_weight_seq = []# 循环最多 num_steps 次,逐步生成目标语言句子for _ in range(num_steps):# 解码器前向传播,输入当前的 dec_X 和当前状态,输出 Y 和新的状态Y, dec_state = net.decoder(dec_X, dec_state)# 从输出 Y 中选择概率最大的词元作为当前时间步的预测结果# dec_X 变为这个预测值,用于下一个时间步的输入(即 greedy search)dec_X = Y.argmax(dim=2)# 将预测结果转换为 Python 标量整数(词元索引)pred = dec_X.squeeze(dim=0).type(torch.int32).item()# 如果启用了保存注意力权重,则记录当前步的注意力权重if save_attention_weights:attention_weight_seq.append(net.decoder.attention_weights)# 如果预测的是 <eos>,说明句子已经生成完毕,提前退出循环if pred == tgt_vocab['<eos>']:break# 否则将当前预测的词元加入输出序列output_seq.append(pred)# 将预测的词元索引转换回对应的单词,并拼接成字符串返回return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq
BLEU 代码的实现
def bleu(pred_seq, label_seq, k): #@save"""计算BLEU(Bilingual Evaluation Understudy)分数,用于评估预测序列与标签序列的相似度。参数:pred_seq: str,预测的序列(例如机器翻译的输出)。label_seq: str,标签序列(例如机器翻译的参考翻译)。k: int,最长的n元语法(n-grams)长度,用于计算BLEU分数。返回:float,BLEU分数。"""# 将预测序列和标签序列按空格分割成词元列表pred_tokens, label_tokens = pred_seq.split(' '), label_seq.split(' ')# 获取预测序列和标签序列的长度len_pred, len_label = len(pred_tokens), len(label_tokens)# 惩罚因子:如果预测序列比标签序列短,则会有惩罚# math.exp(min(0, 1 - len_label / len_pred)) 确保惩罚因子不大于1score = math.exp(min(0, 1 - len_label / len_pred))# 遍历从1到k的n元语法(n-grams)for n in range(1, k + 1):# 匹配的n元语法数量num_matches = 0# 用于存储标签序列中每个n元语法的出现次数label_subs = collections.defaultdict(int)# 遍历标签序列,提取所有长度为n的n元语法,并统计其出现次数for i in range(len_label - n + 1):label_subs[' '.join(label_tokens[i: i + n])] += 1# 遍历预测序列,提取所有长度为n的n元语法,并与标签序列中的n元语法进行匹配for i in range(len_pred - n + 1):ngram = ' '.join(pred_tokens[i: i + n]) # 当前的n元语法if label_subs[ngram] > 0: # 如果标签序列中存在该n元语法num_matches += 1 # 匹配数加1label_subs[ngram] -= 1 # 减少标签序列中该n元语法的计数# 计算当前n元语法的精确度,并乘以权重(权重为0.5的幂次)# math.pow(0.5, n) 确保更长的n元语法权重更小score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n))# 返回最终的BLEU分数return score
将几个英文句子翻译成法语
engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):translation, attention_weight_seq = predict_seq2seq(net, eng, src_vocab, tgt_vocab, num_steps, device)print(f'{eng} => {translation}, bleu {bleu(translation, fra, k=2):.3f}')
小结
- 根据“编码器-解码器”架构的设计,可以使用两个循环神经网络来设计一个序列到序列学习的模型。
- 在实现编码器和解码器时,可以使用多层循环神经网络。
- 可以使用遮蔽来过滤不相关的计算,例如在计算损失时。
- 在“编码器-解码器”训练中,强制教学方法将原始输出序列(而非预测结果)输入解码器。
- BLEU是一种常用的评估方法,它通过测量预测序列和标签序列之间的 n n n元语法的匹配度来评估预测。
相关文章:
序列到序列学习
seq2seq 就是把一个句子翻译到另外一个句子。 机器翻译 给定一个源语言的句子,自动翻译成目标语言这两个句子可以有不同的长度 seq2seq 是一个 Encoder - Decoder 的架构 编码器是一个 RNN , 读取输入句子(可以是双向) 解码…...
去打印店怎么打印手机文件,网上打印平台怎么打印
在数字化时代,手机已成为我们存储和传输文件的重要工具。然而,当需要将手机中的文件转化为纸质文档时,许多人会面临选择:是前往线下打印店,还是利用线上打印平台?本文将为您解析这两种方式的优劣࿰…...
LeetCode每日一题5.4
1128. 等价多米诺骨牌对的数量 问题 问题分析 等价的定义为:两个骨牌 [a, b] 和 [c, d] 等价,当且仅当 (a c 且 b d) 或者 (a d 且 b c)。 思路 标准化骨牌表示: 为了方便比较,我们可以将每个骨牌 [a, b] 标准化为 [min(a…...
前端小练习————表白墙+猜数字小游戏
1,猜数字游戏 实现一个这个样式的 要猜的目标数字 点击重新开始游戏之后: 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widt…...
五年级数学知识边界总结思考-上册
目录 一、背景二、过程1.小数乘法和除法小学五年级小数乘除法的知识点、由来、作用与意义解析**一、核心知识点梳理****二、知识点的由来****三、作用与意义****四、教学意义** **总结** 2.位置小学五年级“位置”知识点、由来、作用与意义解析**一、核心知识点梳理****二、知识…...
C与指针——内存操作与动态内存
1、内存常用操作 void* memcpy(void* dst,const void* src,size_t length); \\内存不允许重叠 void* memmove(void* dst,const void* src,size_t length); \\内存允许重叠 int memcmp(const void* dst,const void* src,size_t length); \\相等返回0 void* memset(void* dst,in…...
P3469 [POI 2008] BLO-Blockade
P3469 [POI 2008] BLO-Blockade 题目描述 B 城有 n n n 个城镇(从 1 1 1 到 n n n 标号)和 m m m 条双向道路。 每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。 把城镇看作节点,把道路看作边&…...
Linux网络编程 day3 五一结假
基本概念 三次握手 主动发起连接请求端,发送SYN标志位,请求建立连接。携带数据包包号、数据字节数(0)、滑动窗口大小。 被动接收连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据包包号、数据字节数…...
解释一下NGINX的反向代理和正向代理的区别?
大家好,我是锋哥。今天分享关于【解释一下NGINX的反向代理和正向代理的区别?】面试题。希望对大家有帮助; 解释一下NGINX的反向代理和正向代理的区别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 NGINX 作为一个高效的反向代理服务器&a…...
Coco AI 入驻 GitCode:打破数据孤岛,解锁智能协作新可能
在信息爆炸时代,企业正面临前所未有的挑战: 企业数据和信息分散,数据孤岛现象严重,员工往往浪费大量时间跨平台检索;跨部门协作困难,团队因信息隔阂导致项目延期;数据安全问题严峻,…...
【QT】QT中的网络编程(TCP 和 UDP通信)
QT中的网络编程(TCP 和 UDP通信) 1.tcp1.1 tcp通信1.1.1 相比linux中tcp通信:1.1.2 QT中的tcp通信: 1.2 tcp通信流程1.2.1 服务器流程:1.2.1.1 示例代码1.2.1.2 现象 1.2.2 客户端流程:1.2.2.1 示例代码1.2.2.2 现象: …...
个性化推荐:大数据引领电子商务精准营销新时代
个性化推荐:大数据引领电子商务精准营销新时代 引言 在电子商务的时代,个性化推荐系统已经成为提升用户体验、增强平台竞争力的重要技术。随着大数据技术的迅猛发展,传统的推荐方法已经无法满足用户日益增长的需求。为了精准地把握用户兴趣和消费倾向,商家们依赖大数据分析…...
【前端】【总复习】HTML
一、HTML(结构) HTML 是网页的骨架,主要负责网页的结构与语义表达,为 CSS 和 JavaScript 提供承载基础。 1.1 HTML 基本结构与语义化标签 1.1.1 HTML 基本结构 <!DOCTYPE html> <html lang"en"> <hea…...
Android 输入控件事件使用示例
一 前端 <EditTextandroid:id="@+id/editTextText2"android:layout_width="match_parent"android:layout_height="wrap_content"android:ems="10"android:inputType="text"android:text="Name" />二 后台代…...
JVM happens-before 原则有哪些?
理解Java Memory Model (JMM) 中的 happens-before 原则对于编写并发程序有很大帮助。 Happens-before 关系是 JMM 用来描述两个操作之间的内存可见性以及执行顺序的抽象概念。如果一个操作 A happens-before 另一个操作 B (记作 A hb B),那么 JMM 向你保证&#x…...
Python实例题:Python获取NBA数据
目录 Python实例题 题目 方式一:使用网页爬虫获取数据 代码解释 get_nba_schedule 函数: 主程序: 方式二:使用专业 API 获取数据 代码解释 运行思路 方式一 方式二 注意事项 以下是完整的 doubaocanvas 代码块&#…...
【中间件】brpc_基础_remote_task_queue
文章目录 remote task queue1 简介2 核心功能2.1 任务提交与分发2.2 无锁或低锁设计2.3 与 bthread 深度集成2.4 流量控制与背压 3 关键实现机制3.1 数据结构3.2 任务提交接口3.3 任务窃取(Work Stealing)3.4 同步与唤醒 4 性能优化5 典型应用场景6 代码…...
React-router v7 第七章(导航)
导航 在React-router V7中,大致有四种导航方式: 使用Link组件 link使用NavLink组件 navlink使用编程式导航useNavigate usenavigate使用redirect重定向 redirect Link Link组件是一个用于导航到其他页面的组件,他会被渲染成一个<a>…...
Terraform 中的 external 数据块是什么?如何使用?
在 Terraform 中,external 数据块(Data Block) 是一种特殊的数据源,允许你通过调用外部程序或脚本获取动态数据,并将结果集成到 Terraform 配置中。它适用于需要从 Terraform 外部的系统或工具获取信息的场景。 一、e…...
打印Excel表格时单元格文字内容被下一行遮盖的解决方法
本文介绍在打印Excel表格文件时,单元格最后一行的文字内容被下一行单元格遮挡的解决方法。 最近,需要打印一个Excel表格文件。其中,已知对于表格中的单元格,都设置了自动换行,如下图所示。 并且,也都设置了…...
【Linux】命令行参数与环境变量
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:Linux 目录 前言 一、命令行参数 1. 什么是命令行参数 2. 命令行参数的作用 二、环境变量 1. 基本概念 2. 常见的环境变量 3. 环境变量相关操作 定义…...
Dify 完全指南(一):从零搭建开源大模型应用平台(Ollama/VLLM本地模型接入实战)》
文章目录 1. 相关资源2. 核心特性3. 安装与使用(Docker Compose 部署)3.1 部署Dify3.2 更新Dify3.3 重启Dify3.4 访问Dify 4. 接入本地模型4.1 接入 Ollama 本地模型4.1.1 步骤4.1.2 常见问题 4.2 接入 Vllm 本地模型 5. 进阶应用场景6. 总结 1. 相关资源…...
民法学学习笔记(个人向) Part.3
民法学学习笔记(个人向) Part.3 8. 诉讼时效🌸 概念: 是指权利主体在法定期间内不行使权利,则债务人享有抗辩权,可以导致权利人无法胜诉的法律制度(有权你不用,别人就有话说了&#…...
C# 方法(返回值、返回语句和void方法)
本章内容: 方法的结构 方法体内部的代码执行 局部变量 局部常量 控制流 方法调用 返回值 返回语句和void方法 局部函数 参数 值参数 引用参数 引用类型作为值参数和引用参数 输出参数 参数数组 参数类型总结 方法重载 命名参数 可选参数 栈帧 递归 返回值 方法可以向调用代码返…...
打电话玩手机检测数据集VOC+YOLO格式8061张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):8061 标注数量(xml文件个数):8061 标注数量(txt文件个数):8061 …...
详解如何压测RocketMQ
目录 1.如何设计压测 2.压测工具 3.硬件配置 4.写代码压测 5.自带压测脚本 1.如何设计压测 二八定律法则:按业务峰值的 120% 设计压测目标(若线上峰值1000TPS,压测目标至少1200TPS) 关注三个指标 吞吐量(TPS&…...
实验三 触发器及基本时序电路
1.触发器的分类?各自的特点是什么? 1 、 D 触发器 特点:只有一个数据输入端 D ,在时钟脉冲的触发沿,输出 Q 的状态跟随输入端 D 的 状态变化,即 ,功能直观,利于理解和感受…...
双列集合——map集合和三种遍历方式
双列集合的特点 键和值一一对应,每个键只能对应自己的值 一个键和值整体称为键值对或键值对对象,java中叫做entry对象。 map常见的api map接口中定义了双列集合所有的共性方法,下面三个实现类就没有什么额外新的方法要学习了。 map接口…...
WebRTC 服务器之Janus视频会议插件信令交互
1.基础知识回顾 WebRTC 服务器之Janus概述和环境搭建-CSDN博客 WebRTC 服务器之Janus架构分析-CSDN博客 2.插件使用流程 我们要使⽤janus的功能时,通常要执⾏以下操作: 1. 在你的⽹⻚引入 Janus.js 库,即是包含janus.js; <…...
LabVIEW温控系统热敏电阻滞后问题
在 LabVIEW 构建的温控系统中,热敏电阻因热时间常数大(2 秒左右)产生的滞后效应,致使控温出现超调与波动。在不更换传感器的前提下,可从算法优化、硬件调整和系统设计等维度着手解决。 一、算法优化 1. 改进 PI…...
【Unity】使用XLua进行热修复
准备工作: (1)将XLua的Tool拖入Asset (2)配置热修复 (3)运行Genrate Code (4)运行Hotfix Inject In Editor 编写脚本(注意类上带有[Hotfix]) [Hot…...
GateWay使用
首先创建一个网关服务,添加对应的依赖 <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId&…...
如何使用责任链模式优雅实现功能(滴滴司机、家政服务、请假审批等)
在企业级开发中,我们经常会遇到一系列有先后顺序、逐步处理的逻辑链路,例如请假审批、报销审批、日志处理、事件处理、滴滴司机接单流程等。这些场景非常适合使用 责任链模式(Chain of Responsibility Pattern) 来优雅地实现。 本…...
opencv的contours
1.哪里来的contours: 我们常常用到这一句: contours, hierarchy cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 输出的contours的是一个tuple类型的: 1.它的len()就是contour的个数 2.每一…...
使用 OpenCV 和 Dlib实现轮廓绘制
文章目录 引言1.准备工作2.代码解析2.1 导入必要的库2.2 定义绘制直线函数2.3 定义绘制凸包函数2.4 加载图像和模型2.5 关键点检测与绘制2.6 显示结果 3.68个关键点索引说明4.应用场景5.优化建议6. 结语 引言 人脸关键点检测是计算机视觉中的重要任务,广泛应用于人…...
学习黑客Linux 命令
在操作下面的闯关题之前,给下学习资料 一图速览:20 条命令及练习手册 #命令 & 常用参数关键作用典型练习1ls -alh列文件(含隐藏 & 人类可读大小)(数字海洋)在 $HOME 统计目录数2cd / pwd切换、显示路径cd /tmp &&a…...
探秘 RocketMQ 的 DLedgerServer:MemberState 的技术解析与深度剖析
在 RocketMQ 构建高可靠、强一致性消息系统的架构中,DLedgerServer 扮演着举足轻重的角色,而 MemberState 作为 DLedgerServer 内部用于描述节点状态的核心类,更是整个分布式日志模块稳定运行的关键。深入理解 MemberState 的设计理念、功能特…...
【计算机网络】HTTP中GET和POST的区别是什么?
从以下几个方面去说明: 1.定义 2.参数传递方式 3.安全性 4.幂等性 1.定义: GET: 获取资源,通常请求数据而不改变服务器的状态。POST: 提交数据到服务器,通常会改变服务器的状态或副作用(如创建或更新资源…...
C++负载均衡远程调用学习之Agent代理模块基础构建
目录 1.课程复习 2.Lars-lbAgentV0.1-udpserver启动 3.Lars-lbAgentV0.1-dns-reporter-client-thread启动 4.Lars-lbAgentV0.1-dns-client实现 5.Lars-lbAgentV0.1-dns-client编译错误修正 6.Lars-lbAgentV0.1-reporter_client实现 1.课程复习 ### 11.2 完成Lars Reactor…...
游戏开发的TypeScript(4)TypeScript 的一些内置函数
在 TypeScript 中,内置函数分为两类:JavaScript 原生函数(TypeScript 继承)和TypeScript 特有函数(类型系统相关)。以下是详细分类介绍: 一、JavaScript 原生函数 TypeScript 继承了所有 Java…...
软考 系统架构设计师系列知识点之杂项集萃(52)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(51) 第82题 以下关于系统性能的叙述中,不正确的是()。 A. 常见的Web服务器性能评估方法有基准测试、压力测试和可靠性测试 B. 评价Web服务器的主…...
大连理工大学选修课——图形学:第五章 二维变换及二维观察
第五章 二维变换及二维观察 二维变换 基本几何变换 图形的几何变换是指对图形的几何信息经过平移、比例、旋转等变换后产生新的图形。 基本几何变换都是相对于坐标原点和坐标轴进行的几何变换。 平移变换 推导: x ′ x T x y ′ y T y xxT_x\\ yyT_y x′xT…...
观察者模式(Observer Pattern)详解
文章目录 1. 什么是观察者模式?2. 为什么需要观察者模式?3. 观察者模式的核心概念4. 观察者模式的结构5. 观察者模式的基本实现简单的气象站示例6. 观察者模式的进阶实现推模型 vs 拉模型6.1 推模型(Push Model)6.2 拉模型(Pull Model)7. 观察者模式的复杂实现7.1 在线商…...
复刻低成本机械臂 SO-ARM100 标定篇
视频讲解: 复刻低成本机械臂 SO-ARM100 标定篇 组装完机械臂后,要进行初始标定,参考github的markdown lerobot/examples/10_use_so100.md at main huggingface/lerobot 只有从臂,所以arms里面只填follower即可 python lerobot…...
idea创建springboot工程-指定阿里云地址创建工程报错
idea创建springboot工程-指定阿里云地址创建工程报错 提示:帮帮志会陆续更新非常多的IT技术知识,希望分享的内容对您有用。本章分享的是springboot的使用。前后每一小节的内容是存在的有:学习and理解的关联性。【帮帮志系列文章】࿱…...
OpenStack HA高可用集群Train版-0集群环境准备
OpenStack HA高可用集群Train版-0集群环境准备 目录 主机配置1.主机名2.网卡配置网卡UUID配置主机名解析配置免密登录防火墙相关配置时间同步配置 二、基础软件安装数据库构建数据库集群设置心跳检测clustercheck准备脚本创建心跳检测用户,(任意控制节点)检测配置文件每个控制节…...
Python3与Dubbo3.1通讯解决方案(dubbo-python)
【文章非VIP可读,如果发现阅读限制为系统自动修改阅读权限,请留言我改回】 概述 最近AI项目需要java与python通讯,两边都是比较新的版本。因此需要双方进行通讯,在这里记录一下所采用的方案和关键点。 JAVA调用Python python通…...
深入探索 Java 区块链技术:从核心原理到企业级实践
一、Java 与区块链的天然契合 1.1 区块链技术的核心特征 区块链作为一种分布式账本技术,其核心特征包括: 去中心化:通过 P2P 网络实现节点自治,消除对中央机构的依赖。不可篡改性:利用哈希链和共识机制确保数据一旦…...
NV214NV217美光闪存固态NV218NV225
NV214NV217美光闪存固态NV218NV225 在当今数据驱动的时代,固态硬盘(SSD)的性能直接决定了计算系统的效率上限。美光科技作为全球存储解决方案的领军者,其NV系列产品凭借尖端技术持续刷新行业标准。本文将围绕NV214、NV217、NV218、…...
第三方组件库:element-uiiviewVant
第三方组件库:element-ui 使用方法: 1.引入样式 <!-- 引入element-ui样式 --><link rel"stylesheet" type"text/css" href"http://unpkg.com/view-design/dist/styles/iview.css">2.引入vue <!-- 引入Vue …...