d2l BERT预训练(model+dataset*+train)

news2024/11/23 18:40:31

千呼万唤始出来,终于来到了bert。本篇博客先介绍预训练部分,dataset部分只介绍简洁输入输出,详细的另行更新新的blog。

目录

1.model

1.1bert总述

1.2输入表示

1.3Encoder

1.3.1验证输出

1.4掩敝语言模型mlm

1.4.1forward探索

LayerNorm与BatchNorm:

X[batch_idx, pred_positions]切片操作

1.4.2验证一下

1.5对下一句预测

1.6整合代码

2.BertDataset

3.预训练Bert

3.1准备数据

3.2定义一个小型的bert

3.3定义损失函数

3.4训练

3.5用Bert表示文本

3.5.1单个句子

3.5.2句子对


1.model

1.1bert总述

1.基于微调的nlp模型;

2.与训练模型时抽取了足够多的信息;

3.新的任务只需再添加输出层。

4.是只有编码器的transformer。

1.2输入表示

输入句子对+片段嵌入(segment)+可学习位置编码(pos)

### 给两个句子变成bert的输入
### 在句子前加上cls和sep分隔符;segment标记(段嵌入)为0,如果有第二个句子(句子对),在第二个句子后面加上sep分隔符,并segments赋予1

  总结一下:第一个句子+cls与sep;第二个句子+sep。

#@save
def get_tokens_and_segments(tokens_a, tokens_b=None):
    """获取输⼊序列的词元及其⽚段索引"""
    tokens = ['<cls>'] + tokens_a + ['<sep>']
    # 0和1分别标记⽚段A和B
    segments = [0] * (len(tokens_a) + 2)
    if tokens_b is not None:
        tokens += tokens_b + ['<sep>']
        segments += [1] * (len(tokens_b) + 1)
    return tokens, segments

1.3Encoder

### 新加了segment_emb,输入是2,因为句子对的segments分别为0,1
### 随机初始化pos,bs=1
### 类比Transformer多了一个segment_emb和可学习的pos
### 对于emb,传入(bs,T),传出(bs,T,emb),在此emb=h;;在emb之前,T里面的数[0,vocabsize],emb需要传入取值范围数目和emb嵌入数,将里面的T表达的数映射到emb个表达。

#@save
class BERTEncoder(nn.Module):
    """BERT编码器"""
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
                ffn_num_hiddens, num_heads, num_layers, dropout,
                max_len=1000, key_size=768, query_size=768, value_size=768,
                **kwargs):
        super(BERTEncoder, self).__init__(**kwargs)
        self.token_embedding = nn.Embedding(vocab_size, num_hiddens)
        self.segment_embedding = nn.Embedding(2, num_hiddens)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module(f"{i}", d2l.EncoderBlock(
                key_size, query_size, value_size, num_hiddens, norm_shape,
                ffn_num_input, ffn_num_hiddens, num_heads, dropout, True))
        # 在BERT中,位置嵌⼊是可学习的,因此我们创建⼀个⾜够⻓的位置嵌⼊参数
        self.pos_embedding = nn.Parameter(torch.randn(1, max_len,
                                                        num_hiddens))
        
    def forward(self, tokens, segments, valid_lens):
        # 在以下代码段中,X的形状保持不变:(批量⼤⼩,最⼤序列⻓度,num_hiddens)
        X = self.token_embedding(tokens) + self.segment_embedding(segments)
        X = X + self.pos_embedding.data[:, :X.shape[1], :]
        for blk in self.blks:
            X = blk(X, valid_lens)
        return X

  在forward里面,传入的只是前面函数的tokens(bs,T),与segments(bs,T),pos是在里面添加的,且tokens与seg也是在里面做emb的。---(bs,T)--(bs,T,emb(h)).  emb=h

  补充一下,emb里面的nn.Embedding(vocab_size, num_hiddens),表示原来(bs,T)中的T,值域为[0,vocabsize],所以使用emb也就是h来表示这么多数的独特位置。emb取值没有特定的要求,但通常会在几十到几百之间,具体取决于数据集的大小和复杂性。

  关于bert里面的pos切分:创建了一个randn的tensor,每次从里面抽取数值,且必须是3维,因为X是(bs,T,h),使用三维且第一维为1可利用广播在每个bs上添加pos

  最终得到的X为加上seg与pos的,加法形状是不会改变的,输出的为(bs,T,h)。

1.3.1验证输出

### 验证一下,其中vocab为1w,h=768,heads=4
### tokens为(bs,T),segments为(bs,T)为tokens里面相应的句段位置标记。
### 经过encoder后,shape为(bs,T,h),对每个句子提取了h维度的信息

vocab_size, num_hiddens, ffn_num_hiddens, num_heads = 10000, 768, 1024, 4
norm_shape, ffn_num_input, num_layers, dropout = [768], 768, 2, 0.2
encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape, ffn_num_input,
                    ffn_num_hiddens, num_heads, num_layers, dropout)

tokens = torch.randint(0, vocab_size, (2, 8))
segments = torch.tensor([[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 1, 1, 1, 1, 1]])
encoded_X = encoder(tokens, segments, None)
encoded_X.shape

'''
torch.Size([2, 8, 768])
'''

1.4掩敝语言模型mlm

### 传入encoder的输出X(bs,T,h),并传入pred_positions(bs,Tmask)表示要预测的位置。计算得到各个预测位置对应的预测值--类似cls。输出形状为(bs,Tmask,len(v)).

#@save
class MaskLM(nn.Module):
    """BERT的掩蔽语⾔模型任务"""
    def __init__(self, vocab_size, num_hiddens, num_inputs=768, **kwargs):
        super(MaskLM, self).__init__(**kwargs)
        self.mlp = nn.Sequential(nn.Linear(num_inputs, num_hiddens),
                                nn.ReLU(),
                                nn.LayerNorm(num_hiddens),
                                nn.Linear(num_hiddens, vocab_size))
        
    def forward(self, X, pred_positions):
        num_pred_positions = pred_positions.shape[1]
        pred_positions = pred_positions.reshape(-1)
        batch_size = X.shape[0]
        batch_idx = torch.arange(0, batch_size)
        # 假设batch_size=2,num_pred_positions=3
        # 那么batch_idx是np.array([0,0,0,1,1])
        batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)
        masked_X = X[batch_idx, pred_positions]
        masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
        mlm_Y_hat = self.mlp(masked_X)
        return mlm_Y_hat

1.4.1forward探索

LayerNorm与BatchNorm:

  LayerNorm是对每个每个样本的所有特征做归一化(0,1),对最后一维进行操作,所以传入的参数是最后一维(h)

  原代码验证一下,确实是对每个tensor的最后一维进行了归一化:

  BN则是对所有的bs进行了整体归一化:

X[batch_idx, pred_positions]切片操作

  X是个三维tensor,bidx和npp都是一维tensor,则切片的时候,对应的是第一个维度里面的数字代表X切的第一个维度位置,第二个维度里面的数字表示X要切的第二个维度位置。每次切片都是索引前两维位置,然后对应第三维的数全取。

  举个例子,这里好演示将h=2赋值。X1是(bs,T,h)也就是enc_output,要索引的位置分别为第一个bs[1,5,2];第二个[6,1,5]。所以分别repeat_interleave操作将batch_idx(0,1)分别对应npp的数量复制,确保npp里面的每个元素都有对应的batch_idx。以切片后的[18,19]为例,其对应的切片就是0,1;第1个bs里面的第1位。

 切片后为(bs*Tmask,h),再重新reshape为(bs,Tmask,h)其中Tmask为一个T中mask的数量。

1.4.2验证一下

### 标注的都是Tmask的位置,mlm_Y的形状为(bs,Tmask)
### 送入交叉熵的是hat(bs×T,vocab)表示对每个Tmask的预测概率值,共有vocab个预测,有些像cls;与(bs×Tmask,)每个元素都是对应Tmask的对应vocab的标记类:

mlm_Y = torch.tensor([[7, 8, 9], [10, 20, 30]])
loss = nn.CrossEntropyLoss(reduction='none')
mlm_l = loss(mlm_Y_hat.reshape((-1, vocab_size)), mlm_Y.reshape(-1))
mlm_l.shape

'''
torch.Size([6])
'''

1.5对下一句预测

  这里注意,是在整合的时候传入的是抽取<cls>类后的encoder_out,并不是全部的,所以输入尺寸为(bs,1,h).

#@save
class NextSentencePred(nn.Module):
    """BERT的下⼀句预测任务"""
    def __init__(self, num_inputs, **kwargs):
        super(NextSentencePred, self).__init__(**kwargs)
        self.output = nn.Linear(num_inputs, 2)
        
    def forward(self, X):
        # X的形状:(batchsize,T×num_hiddens)
        return self.output(X)
encoded_X = torch.flatten(encoded_X, start_dim=1)
# NSP的输⼊形状:(batchsize,1×num_hiddens)
nsp = NextSentencePred(encoded_X.shape[-1])
nsp_Y_hat = nsp(encoded_X)
nsp_Y_hat.shape

'''
torch.Size([2, 2])
'''

  flatten里面的start_dim=1,表示从dim=1开始展平,输出为(bs,1×h)。

  最终输出为(bs,2)。

1.6整合代码

#@save
class BERTModel(nn.Module):
    """BERT模型"""
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
                ffn_num_hiddens, num_heads, num_layers, dropout,
                max_len=1000, key_size=768, query_size=768, value_size=768,
                hid_in_features=768, mlm_in_features=768,
                nsp_in_features=768):
        super(BERTModel, self).__init__()
        self.encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape,
                ffn_num_input, ffn_num_hiddens, num_heads, num_layers,
                dropout, max_len=max_len, key_size=key_size,
                query_size=query_size, value_size=value_size)
        self.hidden = nn.Sequential(nn.Linear(hid_in_features, num_hiddens),
                                    nn.Tanh())
        self.mlm = MaskLM(vocab_size, num_hiddens, mlm_in_features)
        self.nsp = NextSentencePred(nsp_in_features)
    
    def forward(self, tokens, segments, valid_lens=None,
                pred_positions=None):
        encoded_X = self.encoder(tokens, segments, valid_lens)
        if pred_positions is not None:
            mlm_Y_hat = self.mlm(encoded_X, pred_positions)
        else:
            mlm_Y_hat = None
        # ⽤于下⼀句预测的多层感知机分类器的隐藏层,0是“<cls>”标记的索引
        nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
        return encoded_X, mlm_Y_hat, nsp_Y_hat

  强调一下:encoder传入tokens(bs,T),输出(bs,T,len(v))。

2.BertDataset

这一块先简单写写传出值,bert的文本操作还是很复杂的,值得单独给他出一篇blog。

batch_size, max_len = 512, 64
train_iter, vocab = d2l.load_data_wiki(batch_size, max_len)

for (tokens_X, segments_X, valid_lens_x, pred_positions_X, mlm_weights_X,
        mlm_Y, nsp_y) in train_iter:
    print(tokens_X.shape, segments_X.shape, valid_lens_x.shape,
            pred_positions_X.shape, mlm_weights_X.shape, mlm_Y.shape,
            nsp_y.shape)
    break

'''
torch.Size([512, 64]) torch.Size([512, 64]) torch.Size([512]) torch.Size([512, 10]) torch.Size([512, 10]) torch.Size([512, 10]) torch.Size([512])
'''

tokens_X为传入数据(bs,T) ;

segments_X为传入数据的段位置(bs,T) ; valid_lens_x就是有效长度(bs) ;

pred_positions_X为预测mask的位置(bs,Tmask),其中Tmask是T的15% ;

mlm_weights_X为是否真的要预测(bs,Tmask),1为真,0为对应pad部分,不用预测 ;

mlm_Y为要预测的真实标记值(bs,Tmask) ; nsp_Y为是否下一句连续为(bs)。

3.预训练Bert

3.1准备数据

batch_size, max_len = 512, 64
train_iter, vocab = d2l.load_data_wiki(batch_size, max_len)

3.2定义一个小型的bert

### n_heads=2,h=128,n_layers=2

net = d2l.BERTModel(len(vocab), num_hiddens=128, norm_shape=[128],
                    ffn_num_input=128, ffn_num_hiddens=256, num_heads=2,
                    num_layers=2, dropout=0.2, key_size=128, query_size=128,
                    value_size=128, hid_in_features=128, mlm_in_features=128,
                    nsp_in_features=128)
devices = d2l.try_all_gpus()
loss = nn.CrossEntropyLoss()

3.3定义损失函数

### 构造辅助损失函数,用于计算mlm和nsp的损失值,相加

### mlm_Y_hat为(bs,Tmask,len(v))为对应Tmask预测的各个类分数;nsp_Y_hat为(bs,2),为对应句子对是否为相连的。

#@save
def _get_batch_loss_bert(net, loss, vocab_size, tokens_X,
                        segments_X, valid_lens_x,
                        pred_positions_X, mlm_weights_X,
                        mlm_Y, nsp_y):
    # 前向传播
    _, mlm_Y_hat, nsp_Y_hat = net(tokens_X, segments_X,
                                    valid_lens_x.reshape(-1),
                                    pred_positions_X)
    # 计算遮蔽语⾔模型损失
    mlm_l = loss(mlm_Y_hat.reshape(-1, vocab_size), mlm_Y.reshape(-1)) *\
        mlm_weights_X.reshape(-1, 1)
    mlm_l = mlm_l.sum() / (mlm_weights_X.sum() + 1e-8)
    # 计算下⼀句⼦预测任务的损失
    nsp_l = loss(nsp_Y_hat, nsp_y)
    l = mlm_l + nsp_l
    return mlm_l, nsp_l, l

  这里计算损失就不要encoder_out了。注意mlm_l计算式,除weights是<pad>不算loss,最后再除weights数表示对每个预测取平均值。

3.4训练

def train_bert(train_iter, net, loss, vocab_size, devices, num_steps):
    net = nn.DataParallel(net, device_ids=devices).to(devices[0])
    trainer = torch.optim.Adam(net.parameters(), lr=0.01)
    step, timer = 0, d2l.Timer()
    animator = d2l.Animator(xlabel='step', ylabel='loss',
                            xlim=[1, num_steps], legend=['mlm', 'nsp'])
    # 遮蔽语⾔模型损失的和,下⼀句预测任务损失的和,句⼦对的数量,计数
    metric = d2l.Accumulator(4)
    num_steps_reached = False
    while step < num_steps and not num_steps_reached:
        for tokens_X, segments_X, valid_lens_x, pred_positions_X,\
            mlm_weights_X, mlm_Y, nsp_y in train_iter:

            tokens_X = tokens_X.to(devices[0])
            segments_X = segments_X.to(devices[0])
            valid_lens_x = valid_lens_x.to(devices[0])
            pred_positions_X = pred_positions_X.to(devices[0])
            mlm_weights_X = mlm_weights_X.to(devices[0])
            mlm_Y, nsp_y = mlm_Y.to(devices[0]), nsp_y.to(devices[0])

            trainer.zero_grad()
            timer.start()
            mlm_l, nsp_l, l = _get_batch_loss_bert(
                net, loss, vocab_size, tokens_X, segments_X, valid_lens_x,
                pred_positions_X, mlm_weights_X, mlm_Y, nsp_y)
            l.backward()
            trainer.step()
            metric.add(mlm_l, nsp_l, tokens_X.shape[0], 1)
            timer.stop()
            animator.add(step + 1,
                        (metric[0] / metric[3], metric[1] / metric[3]))
            step += 1
            if step == num_steps:
                num_steps_reached = True
                break
                
    print(f'MLM loss {metric[0] / metric[3]:.3f}, '
        f'NSP loss {metric[1] / metric[3]:.3f}')
    print(f'{metric[2] / timer.sum():.1f} sentence pairs/sec on '
        f'{str(devices)}')

 中间有一托都是挪到gpu上

  训练命令行:

train_bert(train_iter, net, loss, len(vocab), devices, 50)

3.5用Bert表示文本

训练完bert后,可以用它来表示单个文本、文本对或其中的任何单元。

def get_bert_encoding(net, tokens_a, tokens_b=None):
    tokens, segments = d2l.get_tokens_and_segments(tokens_a, tokens_b)
    token_ids = torch.tensor(vocab[tokens], device=devices[0]).unsqueeze(0)
    segments = torch.tensor(segments, device=devices[0]).unsqueeze(0)
    valid_len = torch.tensor(len(tokens), device=devices[0]).unsqueeze(0)
    encoded_X, _, _ = net(token_ids, segments, valid_len)
    return encoded_X

  返回的是经过encoder的(1,T,h)

3.5.1单个句子

tokens_a = ['a', 'crane', 'is', 'flying']
encoded_text = get_bert_encoding(net, tokens_a)
# 词元:'<cls>','a','crane','is','flying','<sep>'
encoded_text_cls = encoded_text[:, 0, :]
encoded_text_crane = encoded_text[:, 2, :]
encoded_text.shape, encoded_text_cls.shape, encoded_text_crane[0][:3]

'''
(torch.Size([1, 6, 128]),
 torch.Size([1, 128]),
 tensor([0.0302, 1.1762, 0.0397], device='cuda:0', grad_fn=<SliceBackward0>))
'''

  其中,0表示的是'<cls>'词元。encoded_text[:, 0, :]其本质是将句子中<cls>对应的h个encoder信息抽出来,其表示整个句子的BERT表示。

3.5.2句子对

tokens_a, tokens_b = ['a', 'crane', 'driver', 'came'], ['he', 'just', 'left']
encoded_pair = get_bert_encoding(net, tokens_a, tokens_b)
# 词元:'<cls>','a','crane','driver','came','<sep>','he','just',
# 'left','<sep>'
encoded_pair_cls = encoded_pair[:, 0, :]
encoded_pair_crane = encoded_pair[:, 2, :]
encoded_pair.shape, encoded_pair_cls.shape, encoded_pair_crane[0][:3]

'''
(torch.Size([1, 10, 128]),
 torch.Size([1, 128]),
 tensor([-1.1622,  0.5201, -0.1601], device='cuda:0', grad_fn=<SliceBackward0>))
'''

前一个句子+2(cls与sep),后一个句子+1(sep)。所以T=4+2+3+1=10。

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

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

相关文章

有什么台灯性价比高又实惠的品牌?护眼台灯性价比高的led大灯

不管你处在学生被动学习还是上班后主动学习的阶段&#xff0c;为自己挑选一款合适的台灯非常重要&#xff0c;因为夜晚的氛围能达到很高的学习效率&#xff0c;而台灯可以保证我们有一个舒适的阅读感受。那在为学习需求挑选台灯时&#xff0c;不应该以平价作为选购标准&#xf…

FA-PEG-Silane 叶酸-聚乙二醇-硅烷 Silane-PEG2000-FA,PEG分子量2000

FA-PEG-Silane 叶酸-聚乙二醇-硅烷 中文名称&#xff1a;叶酸聚乙二醇硅烷 英文名称&#xff1a;FA-PEG-Silane&#xff0c;Folic acid PEG Silane 性状&#xff1a;液体或者固体&#xff0c;取决于分子量 溶剂&#xff1a;溶于水、DMSO、DMF等常规性有机溶剂 分子量&…

第十二讲 常用数据结构之集合

在学习了列表和元组之后&#xff0c;我们再来学习一种容器型的数据类型&#xff0c;它的名字叫集合&#xff08;set&#xff09;。说到集合这个词大家一定不会陌生&#xff0c;在数学课本上就有这个概念。如果我们把一定范围的、确定的、可以区别的事物当作一个整体来看待&…

三维可视化智慧档案馆之八防环境监控系统平台白皮书

目录 一、智慧档案馆建设目的 二、智慧档案馆集成度 三、智慧档案馆架构 3.1库房环境监测 3.2库房安防监控 四、智慧档案馆功能简介 4.1档案室一体化控制管理系统建设方案 4.2温湿度检测建设方案 4.3恒温控制建设方案 4.4烟雾感应检测系统 4.5安防系统建设…

STM32开发(十七)STM32F103 片内资源 —— 独立看门狗 IWDG 详解

文章目录 一、基础知识点二、开发环境三、STM32CubeMX相关配置四、Vscode代码讲解五、结果演示 一、基础知识点 STM32F10xxx内置两个看门狗&#xff0c;提供了更高的安全性、时间的精确性和使用的灵活性。两个看门狗设备(独立看门狗和窗口看门狗)可用来检测和解决由软件错误引…

数据库做实验过程-------pyqt环境的配置

首先下载anacunda Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 找到windows最新版x86 64版本等待下载 双击运行安装包 此时一定要记录文件夹的位置&#xff0c;便于以后环境变量的配置。 别看是4.7但是以后可能会增加新的配置&…

学生管理系统

一、项目框架 二、 CommandInfo.cs: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Common; //using MySql.Data.MySqlClient;namespace WinStudent {public class CommandInf…

JMU Oracle 实验五

问的问题 看了一下log和logfile&#xff0c;就是重做日志组和日志文件的地址信息看了归档的地址如何执行归档&#xff0c;就是switch那个语句 1. 查询Oracle数据库当前使用的联机重做日志文件组及成员信息 v$log&#xff1a;记录有关重做日志文件组相关的信息。 v$logfile&a…

数据结构——栈的构建

在本次的博客当中我们来向大家介绍两个看似很新没有听过&#xff0c;实际上我们之前已经实现过了的数据结构——栈和队列。 &#x1f335;栈 实质上栈就是一个具有特殊要求的线性表。栈在定义上要求我们只能从一端插入和一段删除数据。举一个简单的例子&#xff1a;我们一次向栈…

MySQL的ID用完了,怎么办?

目 录 一 首先首先分情况 二 自增ID 1 mysql 数据库创建一个自增键的表 2 导出表结构 3 重新创建 自增键是4294967295的表 4 查看表结构 5 异常测试 三 填充主键 1 首先创建一个test 表&#xff0c;主键不自增 2 插入主键最大值 3 再次插入主键最大值1 四 没有声明…

SSO、CAS、OAuth、OIDC

参考 简单了解概念&#xff1a; https://www.bilibili.com/video/BV1XG411w7DN/简单了解操作&#xff1a; https://www.bilibili.com/video/BV1334y11739/ openid-connect&#x1f44d;流程图解&#xff1a; https://www.youtube.com/watch?vt18YB3xDfXI &#xff08;图&#…

conda虚拟环境配置

文章目录 1 下载Anaconda2 创建自己的虚拟环境3 配置自己的虚拟环境 1 下载Anaconda 直接官网下载 Anaconda官网 2 创建自己的虚拟环境 可以直接在anaconda软件上添加 还可以通过命令行指令&#xff0c;打开终端输入conda create -n 名字 python3.7 3 配置自己的虚拟环境…

大厂高频面试:底层的源码逻辑知多少?

你好&#xff0c;我是何辉。今天我们来聊一聊Dubbo的大厂高频面试题。 大厂面试&#xff0c;一般重点考察对技术理解的深度&#xff0c;和中小厂的区别在于&#xff0c;不仅要你精于实战&#xff0c;还要你深懂原理&#xff0c;勤于思考并针对功能进行合理的设计。 网上一直流…

一文读懂RabbitMQ消息队列

一.什么是消息队列 1.简介 在介绍消息队列之前&#xff0c;应该先了解什么是 AMQP&#xff08;Advanced Message Queuing Protocol, 高级消息队列协议,点击查看&#xff09; 消息&#xff08;Message&#xff09;是指在应用间 传送的数据&#xff0c;消息可以非常简单&#xff…

【Golang开发入门】你真的会用Go写“Hello world“吗?

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: Go语言核心编程近期目标&#xff1a;写好专栏的每一篇文章 目录 一、Go项…

Zynq-7000、FMQL45T900的GPIO控制(六)---linux驱动层配置GPIO输入输出控制

本文使用的驱动代码 Zynq-7000、FMQL45T900的GPIO控制&#xff08;六&#xff09;-linux驱动层配置GPIO输入输出控制资源-CSDN文库 在Zynq-7000、FMQL45T900驱动层也时常会用到对GPIO的控制&#xff0c;这里就针对实际使用的情况进行说明&#xff0c;首先根据之前的帖子确实使…

监测HDD smart信息的脚本编写

最近需要完成一个测试HDD的项目&#xff0c;因为接的HDD太多&#xff0c;手动查看smart信息太麻烦&#xff0c;所以需要写一个自动帮我们检查smart信息的脚本。此遍文章只介绍直连或者JBOD模式下的信息监测&#xff0c;没有涉及到组RAID模式。 1 首先看下HDD的smart信息&#x…

谁在成为产业经济发展的推车人?

区域发展的新蓝图中&#xff0c;京东云能做什么&#xff1f;它的角色是什么&#xff1f;这个问题背后&#xff0c;隐藏的不仅是京东云自身的能力和价值&#xff0c;更是其作为中国互联网云厂商的代表之一&#xff0c;对“技术产业”的新论证。 作者|皮爷 出品|产业家 关于云…

饮用水中的六价铬去除工艺

铬是人体必需的微量元素&#xff0c;天然水不含铬&#xff0c;海水中铬的平均浓度为0.05μg/L&#xff0c;饮用水中铬含量更低。 铬在水中主要以三价和六价形式存在&#xff0c;三价的铬是对人体有益的元素&#xff0c;而六价铬是有毒的。由于其毒性之高&#xff0c;已被国家列…

固定转向和行进速度下的车辆轨迹计算方法

游戏车辆固定转向轨迹计算 概述 车辆游戏是我们经常接触到的一类游戏&#xff0c;这里游戏在只用键盘操作时&#xff0c;往往非常不方便。这是因为这一类游戏大部分都是按下按键时转向&#xff0c;释放按键时方向就会自动转正。这种控制方式在实现方面比较容易。但是缺点也很明…