手动实现BERT

  本文重点介绍了如何从零训练一个BERT模型的过程,包括整体上BERT模型架构、数据集如何做预处理、MASK替换策略、训练模型和保存、加载模型和测试等。

一.BERT架构
  BERT设计初衷是作为一个通用的backbone,然后在下游接入各种任务,包括翻译任务、分类任务、回归任务等。BERT模型架构如下所示:

1.输入层
  BERT每次计算时输入两句话。
2.数据预处理
  包括移除不能识别的字符、将所有字母小写、多余的空格等。

3.随机将一些词替换为MASK
  BERT模型的训练过程包括两个子任务,其中一个即为预测被遮掩的词的原本的词,所以在计算之前,需要把句子中的一些词替换为MASK交给BERT预测。


4.编码句子
  把句子编码成向量,BERT同样也有位置编码层,以让处于不同位置的相同的词有不同的向量表示。与Transformer位置编码固定常量不同,BERT位置编码是一个可学习的参数。

5.编码器
  此处的编码器即为Transformer中的编码器,BERT使用了Transformer中的编码器来抽取文本特征。
6.预测两个句子的关系
  BERT的计算包括两个子任务,预测两个句子的关系为其中一个子任务,BERT要计算出输入的两个句子的关系,这一般是二分类任务。
7.预测MASK词
  这是BERT的另外一个子任务,要预测出句子中的MASK原本的词。

二.数据集介绍和预处理
1.数据集介绍
  数据集使用微软提供的MSR Paraphrase数据集进行训练,第1列的数字表示了这2个句子的意思是否相同,2列ID对于训练BERT模型没有用处,只需关注第1列和另外2列String。部分样例如下所示:

2.数据集预处理
  数据集预处理代码参考文献[2],处理后包括msr_paraphrase_data.csv和msr_paraphrase_vocab.csv这2个文件,样例数据如下所示:



三.PyTorch中的Transformer工具层
  本部分不再手工实现Transformer编解码器,更多的使用PyTorch中已实现的Transformer工具层,从而专注于BERT模型的构建。
1.定义测试数据
  模拟虚拟了2句话,每句话8个词,每句话的末尾有一些PAD,如下所示:

# 虚拟数据
import torch
# 假设有两句话,8个词
x = torch.ones(2, 8)
# 两句话中各有一些PAD
x[0, 6:] = 0
x[1, 7:] = 0
print(x)

  输出结果如下所示:

tensor([[1., 1., 1., 1., 1., 1., 0., 0.],[1., 1., 1., 1., 1., 1., 1., 0.]])

2.各个MASK的含义解释
  key_padding_mask作用是遮挡数据中的PAD位置,减少计算量;encode_attn_mask定义是否要忽略输入语句中某些词与词间的注意力,在编码器中是不需要的;decode_attn_mask定义是否忽略输出语句中某些词与词之间的注意力,在解码器中是需要的。如下所示:

# 2.各个MASK的含义解释
# 定义key_padding_mask
# key_padding_mask的定义方式,就是x中是pad的为True,否则是False
key_padding_mask = x == 0
print(key_padding_mask)# 定义encode_attn_mask
# 在encode阶段不需要定义encode_attn_mask
# 定义为None或者全False都可以
encode_attn_mask = torch.ones(8, 8) == 0
print(encode_attn_mask)# 定义decode_attn_mask
# 在decode阶段需要定义decode_attn_mask
# decode_attn_mask的定义方式是对角线以上为True的上三角矩阵
decode_attn_mask = torch.tril(torch.ones(8, 8)) == 0
print(decode_attn_mask)

  输出结果如下所示:

tensor([[False, False, False, False, False, False,  True,  True],[False, False, False, False, False, False, False,  True]])
tensor([[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False],[False, False, False, False, False, False, False, False]])
tensor([[False,  True,  True,  True,  True,  True,  True,  True],[False, False,  True,  True,  True,  True,  True,  True],[False, False, False,  True,  True,  True,  True,  True],[False, False, False, False,  True,  True,  True,  True],[False, False, False, False, False,  True,  True,  True],[False, False, False, False, False, False,  True,  True],[False, False, False, False, False, False, False,  True],[False, False, False, False, False, False, False, False]])

3.编码数据
  将x编码为2×8×12,表示2句话、每句话8个词、每个词用12维的Embedding向量表示:

# 编码x
x = x.unsqueeze(2) # 在第2维增加一个维度
x = x.expand(-1, -1, 12) # 在第2维复制12份
print(x, x.shape)

  输出结果如下所示:

tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]]) torch.Size([2, 8, 12])

4.多头注意力计算函数
  在计算多头注意力机制市需要做2次线性变化,一次是对入参的Q、K和V矩阵分别做线性变换,另一次是对注意力分数做线性变换,2次线性变换分别需要2组weight合bias参数,如下所示:

# 定义multi_head_attention_forward()所需要的参数
# in_proj就是Q、K、V线性变换的参数
in_proj_weight = torch.nn.Parameter(torch.randn(3 * 12, 12))
in_proj_bias = torch.nn.Parameter(torch.zeros((3 * 12)))
# out_proj就是输出时做线性变换的参数
out_proj_weight = torch.nn.Parameter(torch.randn(12, 12))
out_proj_bias = torch.nn.Parameter(torch.zeros(12))
print(in_proj_weight.shape, in_proj_bias.shape)
print(out_proj_weight.shape, out_proj_bias.shape)# 使用工具函数计算多头注意力
data = {# 因为不是batch_first的,所以需要进行变形'query': x.permute(1, 0, 2), # x原始为[2, 8, 12],x.permute为[8, 2, 12]'key': x.permute(1, 0, 2),'value': x.permute(1, 0, 2),'embed_dim_to_check': 12, # 用于检查维度是否正确'num_heads': 2, # 多头注意力的头数'in_proj_weight': in_proj_weight, # Q、K、V线性变换的参数'in_proj_bias': in_proj_bias, # Q、K、V线性变换的参数'bias_k': None,'bias_v': None,'add_zero_attn': False,'dropout_p': 0.2, # dropout的概率'out_proj_weight': out_proj_weight, # 输出时做线性变换的参数'out_proj_bias': out_proj_bias, # 输出时做线性变换的参数'key_padding_mask': key_padding_mask,'attn_mask': encode_attn_mask,
}
score, attn = torch.nn.functional.multi_head_attention_forward(**data)
print(score.shape, attn, attn.shape)

(1)bias_k、bias_v:是否要对K和V矩阵单独添加bias,一般设置为None。
(2)add_zero_attn:如果设置为True,那么会在Q、K的注意力结果中单独加一列0,一般设置为默认值False。
(3)key_padding_mask:是否要忽略语句中的某些位置,一般只需忽略PAD的位置。
(4)attn_mask:是否要忽略每个词之间的注意力,在编码器中一般只用全False的矩阵,在解码器中一般使用对角线以上全True的矩阵。
  输出结果如下所示:

torch.Size([36, 12]) torch.Size([36])
torch.Size([12, 12]) torch.Size([12])
torch.Size([8, 2, 12]) tensor([[[0.2083, 0.2083, 0.2083, 0.1042, 0.2083, 0.0000, 0.0000, 0.0000],[0.2083, 0.2083, 0.1042, 0.2083, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.1042, 0.1042, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.1042, 0.1042, 0.2083, 0.2083, 0.1042, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.1042, 0.2083, 0.2083, 0.1042, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.1042, 0.2083, 0.1042, 0.1042, 0.2083, 0.0000, 0.0000]],[[0.0893, 0.1786, 0.0893, 0.1786, 0.1786, 0.1786, 0.1786, 0.0000],[0.1786, 0.1786, 0.1786, 0.1786, 0.1786, 0.1786, 0.1786, 0.0000],[0.1786, 0.0000, 0.1786, 0.1786, 0.1786, 0.1786, 0.0893, 0.0000],[0.1786, 0.1786, 0.1786, 0.1786, 0.0893, 0.1786, 0.0893, 0.0000],[0.1786, 0.1786, 0.1786, 0.0000, 0.1786, 0.0893, 0.1786, 0.0000],[0.1786, 0.1786, 0.1786, 0.1786, 0.1786, 0.1786, 0.0893, 0.0000],[0.1786, 0.0893, 0.0893, 0.1786, 0.1786, 0.0893, 0.0000, 0.0000],[0.1786, 0.1786, 0.0893, 0.0893, 0.1786, 0.1786, 0.1786, 0.0000]]],grad_fn=<MeanBackward1>) torch.Size([2, 8, 8])

5.多头注意力层
  封装程度更高的多头注意力层实现方式如下所示:

# 使用多头注意力工具层
multihead_attention = torch.nn.MultiheadAttention(embed_dim=12, num_heads=2, dropout=0.2, batch_first=True)
data = {'query': x,'key': x,'value': x,'key_padding_mask': key_padding_mask,'attn_mask': encode_attn_mask,
}
score, attn = multihead_attention(**data)
print(score.shape, attn, attn.shape)

  输出结果如下所示:

torch.Size([2, 8, 12]) tensor([[[0.1042, 0.2083, 0.0000, 0.1042, 0.1042, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.1042, 0.2083, 0.0000, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.2083, 0.0000, 0.2083, 0.0000, 0.0000],[0.1042, 0.2083, 0.2083, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.1042, 0.1042, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.1042, 0.2083, 0.1042, 0.0000, 0.0000],[0.1042, 0.0000, 0.2083, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000],[0.2083, 0.2083, 0.2083, 0.1042, 0.2083, 0.2083, 0.0000, 0.0000]],[[0.1786, 0.1786, 0.0893, 0.0000, 0.1786, 0.1786, 0.1786, 0.0000],[0.1786, 0.1786, 0.1786, 0.0893, 0.1786, 0.0893, 0.0893, 0.0000],[0.0893, 0.0893, 0.0893, 0.0893, 0.1786, 0.1786, 0.1786, 0.0000],[0.1786, 0.0893, 0.0893, 0.1786, 0.1786, 0.1786, 0.0893, 0.0000],[0.1786, 0.0893, 0.1786, 0.1786, 0.0893, 0.0893, 0.0000, 0.0000],[0.1786, 0.1786, 0.1786, 0.1786, 0.0000, 0.1786, 0.0893, 0.0000],[0.1786, 0.0000, 0.1786, 0.0893, 0.1786, 0.0893, 0.1786, 0.0000],[0.1786, 0.0893, 0.0893, 0.0893, 0.0893, 0.1786, 0.0893, 0.0000]]],grad_fn=<MeanBackward1>) torch.Size([2, 8, 8])

  其中,batch_first=True,表示input和output张量的shape为(batch, seq, feature)。默认为False,input和output张量的shape为(seq, batch, feature)。
6.编码器层
  编码器包含多个编码器层,其中batch_first表示输入的第1维度是否是batch_size,norm_first通过该参数指定是否将标准化层前置计算。如下所示:

# 使用单层编码器工具层
encoder_layer = torch.nn.TransformerEncoderLayer(d_model=12,                          # 词向量的维度nhead=2,                             # 多头注意力的头数dim_feedforward=24,                  # 前馈神经网络的隐层维度dropout=0.2,                         # dropout的概率activation=torch.nn.functional.relu, # 激活函数batch_first=True,                    # 输入数据的第一维是batchnorm_first=True)                     # 归一化层在前
data = {'src': x,                                 # 输入数据'src_mask': encode_attn_mask,             # 输入数据的mask'src_key_padding_mask': key_padding_mask, # 输入数据的key_padding_mask
}
out = encoder_layer(**data)
print(out.shape) #torch.Size([2, 8, 12])# 使用编码器工具层
encoder = torch.nn.TransformerEncoder(encoder_layer=encoder_layer,                  # 编码器层num_layers=3,                                 # 编码器层数norm=torch.nn.LayerNorm(normalized_shape=12)) # 归一化层
data = {'src': x, # 输入数据'mask': encode_attn_mask,                     # 输入数据的mask'src_key_padding_mask': key_padding_mask,     # 输入数据的key_padding_mask
}
out = encoder(**data)
print(out.shape) #torch.Size([2, 8, 12])

7.解码器层
  BERT当中不会用到Transformer的解码器,解码器包含多个解码器层,如下所示:

# 7.解码器层
#  使用单层解码器工具层
decoder_layer = torch.nn.TransformerDecoderLayer(    # 解码器层d_model=12,                          # 词向量的维度nhead=2,                             # 多头注意力的头数dim_feedforward=24,                  # 前馈神经网络的隐层维度dropout=0.2,                         # dropout的概率activation=torch.nn.functional.relu, # 激活函数batch_first=True,                    # 输入数据的第一维是batchnorm_first=True)                     # 归一化层在前
data = {'tgt': x,                                        # 解码输出的目标语句,即target'memory': x,                                     # 编码器的编码结果,即解码器解码时的根据数据'tgt_mask': decode_attn_mask,                    # 定义是否要忽略词与词之间的注意力,即decode_attn_mask'memory_mask': encode_attn_mask,                 # 定义是否要忽略memory内的部分词与词之间的注意力,一般不需要要忽略'tgt_key_padding_mask': key_padding_mask,        # 定义target内哪些位置是PAD,以忽略对PAD的注意力'memory_key_padding_mask': key_padding_mask,     # 定义memory内哪些位置是PAD,以忽略对PAD的注意力
}
out = decoder_layer(**data)
print(out.shape) #(2,8,12)# 使用编码器工具层
decoder = torch.nn.TransformerDecoder(    # 解码器层decoder_layer=decoder_layer,          # 解码器层num_layers=3,                         # 解码器层数norm=torch.nn.LayerNorm(normalized_shape=12))
data = {'tgt': x,'memory': x,'tgt_mask': decode_attn_mask,'memory_mask': encode_attn_mask,'tgt_key_padding_mask': key_padding_mask,'memory_key_padding_mask': key_padding_mask,
}
out = decoder(**data)
print(out.shape) #(2,8,12)

8.完整的Transformer模型
  Transformer主模型由编码器和解码器组成,如下所示:

# 使用Transformer工具模型
transformer = torch.nn.Transformer(d_model=12,               # 词向量的维度nhead=2,                             # 多头注意力的头数num_encoder_layers=3,                # 编码器层数num_decoder_layers=3,                # 解码器层数dim_feedforward=24,                  # 前馈神经网络的隐层维度dropout=0.2,                         # dropout的概率activation=torch.nn.functional.relu, # 激活函数custom_encoder=encoder,              # 自定义编码器,如果指定为None,那么会使用默认的编码器层堆叠num_encoder_layers层组成编码器custom_decoder=decoder,              # 自定义解码器,如果指定为None,那么会使用默认的解码器层堆叠num_decoder_layers层组成解码器batch_first=True,                    # 输入数据的第一维是batchnorm_first=True)                     # 归一化层在前
data = {'src': x,'tgt': x,'src_mask': encode_attn_mask,'tgt_mask': decode_attn_mask,'memory_mask': encode_attn_mask,'src_key_padding_mask': key_padding_mask,'tgt_key_padding_mask': key_padding_mask,'memory_key_padding_mask': key_padding_mask,
}
out = transformer(**data)
print(out.shape) #torch.Size([2, 8, 12])

四.手动实现BERT模型
  因为这部分代码较长,就不放出来了,详细参考文献[4]。需要说明的是BERT在训练阶段有两个子任务,分别为预测两句话的意思是否一致,以及被遮掩的词的原本的词。把编码器抽取的文本特征分别输入两个线性神经网络,并且以此计算这两个输出。重点说下random_replace()函数对所有句子的替换策略,如下所示:

# 定义随机替换函数
def random_replace(sent):# sent = [b,63]# 不影响原来的sentsent = sent.clone()# 替换矩阵,形状和sent一样,被替换过的位置是True,其他位置是Falsereplace = sent == -1# 遍历所有的词for i in range(len(sent)):for j in range(len(sent[i])):# 如果是符号就不操作了,只替换词if sent[i, j] <= 10:continue# 以0.15的概率进行操作if random.random() > 0.15:pass# 对被操作过的位置进行标记,这里的操作包括什么也不做replace[i, j] = True# 分概率做不同的操作p = random.random()# 以O.8的概率替换为MASKif p < 0.8:sent[i, j] = vocab.loc['<MASK>'].token# 以0.1的概率不替换elif p < 0.9:continue# 以0.1的概率替换成随机词else:# 随机生成一个不是符号的词rand_word = 0while rand_word <= 10:rand_word = random.randint(0, len(vocab) - 1)sent[i, j] = rand_wordreturn sent, replace

  每个句子中的每个词都有15%的概率被替换,而替换也不仅有替换为MASK这一种情况。在被判定为当前词要替换后,该词有80%的概率被替换为MASK,有10%的概率被替换为一个随机词,有10%的概率不替换为任何词。如下所示:


参考文献:
[1]《HuggingFace自然语言处理详解:基于BERT中文模型的任务实战》
[2]https://github.com/ai408/nlp-engineering/blob/main/20230625_HuggingFace自然语言处理详解/第14章:手动实现BERT_数据预处理.py
[3]https://github.com/ai408/nlp-engineering/blob/main/20230625_HuggingFace自然语言处理详解/第14章:手动实现BERT_PyTorch中的Transformer工具层.py
[4]https://github.com/ai408/nlp-engineering/blob/main/20230625_HuggingFace自然语言处理详解/第14章:手动实现BERT_训练和测试.py
[5]Bert模型的细节到底是怎么样的:https://www.zhihu.com/question/534763354
[6]BERT模型参数量:https://zhuanlan.zhihu.com/p/452267359
[7]HuggingFace Transformers最新版本源码解读:https://zhuanlan.zhihu.com/p/360988428
[8]NLP Course:https://huggingface.co/learn/nlp-course/zh-CN/chapter1/1

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

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

相关文章

《MySQL高级篇》十六、主从复制

文章目录 1、主从复制概述1.1 如何提升数据库并发能力1.2 主从复制的作用 2、主从复制的原理2.1 原理剖析2.2 复制的基本原则 3、一主一从架构搭建3.1 准备工作3.2 主机配置文件3.3 从机配置文件3.4 主机&#xff1a;建立账户并授权3.5 从机&#xff1a;配置需要复制的主机3.6 …

面试记录_

1&#xff1a;面试杉岩数据&#xff08;python开发&#xff09; 1.1.1 选择题 for(int i0;i<n;i){for(int j0;j<n;jji) } }O(n) * (O(0) O(n/1) O(n/2) O(n/3) ... O(n/n)) 在最坏情况下&#xff0c;内部循环的迭代次数为 n/1 n/2 n/3 ... n/n&#xff0c;这是…

笔试强训Day8

链接&#xff1a;求最小公倍数__牛客网 T1:求最小公倍数 正整数A和正整数B 的最小公倍数是指 能被A和B整除的最小的正整数值&#xff0c;设计一个算法&#xff0c;求输入A和B的最小公倍数。 数据范围&#xff1a;1≤a,b≤100000 #include<iostream> using namespace…

【算法|贪心算法系列No.2】leetcode2208. 将数组和减半的最少操作次数

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

Unity把UGUI再World模式下显示到相机最前方

Unity把UGUI再World模式下显示到相机最前方 通过脚本修改Shader 再VR里有时候要把3D的UI显示到相机最前方&#xff0c;加个UI相机会坏事&#xff0c;可以通过修改unity_GUIZTestMode来解决。 测试用例 测试用例如下&#xff1a; 场景包含一个红色的盒子&#xff0c;一个UI…

MonkeyRunner自动化测试

一&#xff1a;简介 MonkeyRunner提供了一个API&#xff0c;使用此API写出的程序可以在Android代码之外控制Android设备和模拟器。通过monkeyrunner&#xff0c;您可以写出一个Python程序去安装一个Android应用程序或测试包&#xff0c;运行它&#xff0c;向它发送模拟击键&…

Linux C/C++下收集指定域名的子域名信息(类似dnsmap实现)

我们知道dnsmap是一个工具&#xff0c;主要用于收集指定域名的子域名信息。它对于渗透测试人员在基础结构安全评估的信息收集和枚举阶段非常有用&#xff0c;可以帮助他们发现目标公司的IP网络地址段、域名等信息。 dnsmap的操作原理 dnsmap&#xff08;DNS Mapping&#xff…

Xmake v2.8.3 发布,改进 Wasm 并支持 Xmake 源码调试

Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没有任何依赖&#xff0c;因为它内置了 Lua 运行时。 它使用 xmake.lua 维护项目构建&#xff0c;相比 makefile/CMakeLists.txt&#xff0c;配置语法更加简洁直观&#xff0c;对新手非常友好&#x…

学信息系统项目管理师第4版系列14_沟通管理

1. 与IT项目成功有关的最重要的四个因素 1.1. 主管层的支持 1.2. 用户参与 1.3. 有经验的项目经理 1.4. 清晰的业务目标 1.5. 依赖于项目经理和团队具有良好的沟通能力 2. 沟通的主旨 2.1. 互动双方建立彼此相互了解的关系 2.2. 相互回应 2.3. 期待能经由沟通的行为与…

软件过程的介绍

软件过程概述 软件的诞生和生命周期是一个过程&#xff0c;我们总体上称这个过程为软件过程。软件过程是为了开发出软件产品&#xff0c;或者是为了完成软件工程项目而需要完成的有关软件工程的活动&#xff0c;每一项活动又可以分为一系列的工程任务。任何一个软件开发组织&a…

ESP32官方MPU6050组件介绍

前言 &#xff08;1&#xff09;因为我需要使用MPU6050的组件&#xff0c;但是又需要在这条I2C总线上挂载多个设备&#xff0c;所以我本人打算自己对官方的MPU6050的组件进行微调。建立一个I2C总线&#xff0c;设备依赖于这个总线挂载。 &#xff08;2&#xff09;既然要做移植…

【AI视野·今日Robot 机器人论文速览 第四十四期】Fri, 29 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Fri, 29 Sep 2023 Totally 38 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;NCF,基于Neural Contact Fields神经接触场的方法实现有效的外部接触估计和插入操作。 (from FAIR ) 操作插入处理结果&am…

凉鞋的 Godot 笔记 101. Hello Godot!

101. Hello Godot 学习任何一门技术&#xff0c;第一件事就是先完成 Hello World&#xff01;的输出 所以我们也来先完成 Godot 的 Hello World。 我们所使用的 Godot 版本是 4.x 版本。 安装的过程就不给大家展示了&#xff0c;笔者更推荐初学者用 Steam 版本的 Godot&…

第一次作业题解

第一次作业题解 P5717 【深基3.习8】三角形分类 思路 考的是if()的使用,还要给三条边判断大小 判断优先级&#xff1a; 三角形&#xff1f;直角、钝角、锐角等腰等边 判断按题给顺序来 代码 #include <stdio.h> int main() {int a 0, b 0, c 0, x 0, y 0, z 0…

紫光同创FPGA图像视频采集系统,基于OV7725实现,提供工程源码和技术支持

目录 1、前言免责声明 2、设计思路框架视频源选择OV7725摄像头配置及采集动态彩条HDMA图像缓存输入输出视频HDMA缓冲FIFOHDMA控制模块HDMI输出 3、PDS工程详解4、上板调试验证并演示准备工作静态演示动态演示 5、福利&#xff1a;工程源码获取 紫光同创FPGA图像视频采集系统&am…

[每周一更]-(第64期):Dockerfile构造php定制化镜像

利用php官网镜像php:7.3-fpm&#xff0c;会存在部分插件缺失的情况&#xff0c;自行搭建可适用业务的镜像&#xff0c;才是真理 Dockerhub 上 PHP 官方基础镜像主要分为三个分支&#xff1a; cli: 没有开启 CGI 也就是说不能运行fpm。只可以运行命令行。fpm: 开启了CGI&#x…

Docker从认识到实践再到底层原理(九)|Docker Compose 容器编排

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

libtorch之tensor的使用

1. tensor的创建 tensor的创建有三种常用的形式&#xff0c;如下所示 ones创建一个指定维度&#xff0c;数据全为1的tensor. 例子中的维度是2维&#xff0c;5行3列。 torch::Tensor t torch::ones({5,3}); zeros创建一个指定维度&#xff0c;数据全为0的tensor&#xff0c;例子…

Java 基于 SpringBoot 的简历招聘系统

文章目录 1、效果演示2、 前言介绍3、主要技术4 **系统设计**4.1 系统体系结构4.2开发流程设计4.3 数据库设计原则 5 **系统详细设计**5.1管理员功能模块5.2用户功能模块5.3前台首页功能模块 6 源码咨询 1、效果演示 大家好&#xff0c;今天为大家带来的是基于 SpringBoot简历…

【AI视野·今日Robot 机器人论文速览 第四十一期】Tue, 26 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Tue, 26 Sep 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Extreme Parkour with Legged Robots Authors Xuxin Cheng, Kexin Shi, Ananye Agarwal, Deepak Pathak人类可以通过以高度动态…