Transformer系列-3丨BERT模型和代码解析

news2024/9/20 22:42:44

1 前言

前面两篇文章,笔者从网络结构和代码实现角度较为深入地和大家解析了Transformer模型Vision Transformer模型(ViT),其具体的链接如下:

  • 基础Transformer解析

  • ViT模型与代码解析

本期内容,笔者想解析一下自然语言处理(NLP)中非常有名的基于变换器的双向编码器表示技术(即Bidirectional Encoder Representations from Transformers,BERT)。

BERT

想当年(2019年),BETR的出现也是横扫了自然语言处理领域多项任务,甚至压住了ChatGPT的早期版本(OpenAI GPT )。

这里需要注意,BERT并不是一类模型的缩写,笔者认为其更像是指代一套框架,核心在于其自监督训练策略

再看本文之前,我强烈推荐大家去看一下台大李宏毅老师的深度学习课程。具体链接如下:

https://www.bilibili.com/video/BV1m3411p7wD?p=48&vd_source=beab624366b929b20152279cfa775ff6

本文也将使用李老师的课程截图进行讲解(没办法,他讲的太好了)。

2 BERT解析

2.1 简介

BERT,全称叫做Bidirectional Encoder Representations from Transformers。从其名称可以看出,该模型是基于Transformer的。更具体地,BERT的网络结构其实就是Transformer的编码器,其输入一段序列(如文字序列),输出一段等长的序列

如下图1所示,训练时BERT先是基于大量未标注的语料库进行自监督学习(预训练),然后基于标注良好的数据集(特定的NLP任务,如情感分类等)进行模型微调。这种**“预训练+微调“**使得模型在各个NLP任务的效果大幅提升,其收敛速度也快了很多。

图1

2.2 自监督预训练

介绍BERT之前,需要和大家简单了解一下何为自监督学习。我们知道,Transformer的优势在于并行和表征能力强,但缺点就是需要大量标注数据进行训练

为了解决标注数据的数据量问题,近些年关于无监督自监督的研究开始变得火起来了 。从某种程度上来说,自监督也可以视为无监督,因为自监督的监督信号来源于数据本身的内容,而不是人工标注。

这里举个视觉领域常见的自监督学习例子,如下图2所示。为了监督模型学习到合适的图像特征,一些研究将图像进行旋转(如旋转90度)喂给视觉模型(比如CNN),监督信息就是旋转角度。在这种情况下,模型接受一张图像(可能被旋转了)为输入,预测该图像的旋转角度。以这种方式,模型无需人工费时费力的标注信息,也能学到较好的图像表征。

图2

那么,Transformer能否也利用自监督学习从大量未标注的语料库里面进行学习呢?

答案是显然的。本文要介绍的BERT,其核心就是一种自监督训练策略。具体地,该训练策略主要包含两个策略:

  • 一是**“猜词”训练**。也就是随机将输入序列的一些词汇进行遮挡或者随机换成其他词(遮挡也是替换成特殊字符),然后让模型去猜被遮挡的词是什么,如下图3所示。

图3

  • 二是**“判断句子是否连续”。也就是随机从语料库中抽两个句子,然后将两个句子用 特殊符号()分隔开,并且在两个句子前面加个特殊符号**()。然后取特殊符号处对应的输出特征,用以二分类任务输入(判断句子连续或不连续),如下图4所示。后续研究表明,这个训练策略效果不明显。按李宏毅老师的意思是,这个训练任务太简单了,模型学不到什么东西。

图4
针对所有自学遇到困难的同学们,我帮大家系统梳理大模型学习脉络,将这份 LLM大模型资料 分享出来:包括LLM大模型书籍、640套大模型行业报告、LLM大模型学习视频、LLM大模型学习路线、开源大模型学习教程等, 😝有需要的小伙伴,可以 扫描下方二维码领取🆓↓↓↓

👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]()👈

2.3 微调

经过上面的自监督训练后,模型事实上只学会了两个任务,即 “猜词”“判断两个句子是否连续” 。如果要实现诸如语句情感分类、词性标注、自然语言推理、问答等任务,那么还需要对预训练的BERT进行微调。具体微调方式因任务的差异而不同,这里以4个任务为例。

(1)语句情感分类

语句情感分类任务中,网络模型以一段文字为输入,推理该文字背后所附带的情绪是什么。最常见的,比如商品的好评和差评分类。那么,如何使用预训练好的BERT来实现语句的情感分类呢?

如图5所示,先利用BERT来提取文字的语义特征,然后将特殊符号()处对应的特征输入至全连接层,输出类别概率。需要注意的是,这里的BERT的初始化权重为预训练BERT模型的权重,而全连接层的权重随机初始化。

图5

就类似于图像分类领域中使用基于ImageNet预训练的权重初始化CNN模型,然后随机初始化最后用于分类的全连接层,能够达到模型快速收敛的效果。

(2)词性标注

所谓词性标注任务,是将一段自然语言文本中的每个单词标注为其相应的词性,如名词、动词、形容词等。它是自然语言处理的基础任务之一,对于许多其他任务,如命名实体识别、句法分析等都有重要的作用。这样的话,输入模型一段文本序列,将获得一段等长的输出序列。

这种等长的Sequence2Sequence在BERT中是如何实现的呢?

图6

如图6所示,Transformer特点是输入序列和输出序列是等长的,那么只需要取BERT中输入序列对应位置的特征,然后利用全连接层将这些特征映射为相应的词性类别概率即可。这里和语句情感分类任务一样,BERT的初始化权重为预训练BERT模型的权重,而全连接层的权重随机初始化。

(3)自然语义推理

所谓自然语义推理,就是利用语言模型来判断句子间的关系,这种关系是以类别形式呈现。

例如,如图7所示,给定两个句子,一个句子是前提(premise),一个句子是假设(hypothesis),将这两个句子同时输入到模型中,判断这两个句子的关系,是矛盾、蕴涵还是中性的。

那么这种情况在BERT中是如何实现的呢?

如图8所示,作者将两个句子之间用特殊符号()隔开,在句子之前嵌入一个特殊符号()。之后,将该句子喂给BERT模型,然后取BERT中特殊符号()处对应的特征,将其输入至全连接层后,输出预测的类别概率。相同地,BERT的初始化权重为预训练BERT模型的权重,而全连接层的权重随机初始化。

上面这个微调方式与预训练中**“判断两个句子是否连续”**的方法如出一辙。

图8

(4)问答

最后,聊一个稍微难一点的任务,即问答。所谓问答,就是给定一个文档(稍微长一些的文字序列),给定一个问题(稍微短一些的文字序列),然后在文档中找到能够回应这个问题答案。需要注意的是,这个答案是可以直接在文档中找到的。

那么这个任务可以视为根据文档问题,在文档中找到答案的起止位置,如图9所示。

图9

该问题在BERT中的实现方法如下图10和11所示。

图10

图11

仍然是以两个句子(问题在前,文档在后)为输入,其中两个句子之间用特殊符号()隔开,在句子之前嵌入一个特殊符号()。

利用两个随机初始化的向量(不妨记做起始向量和终止向量)分别对文档()通过BERT后输出的各个特征进行内积,然后利用softmax分别输出起止位置的概率分布,如图10和11所示。

这里大家可能会有一个疑问,也就是为什么这么粗暴的向量设置也能使得模型训练起来?

事实上,只要起始向量和终止向量随机初始化后保持固定不变。那么在微调过程中,模型会逐渐知道随机初始化的起始向量会专注于寻找答案的左边界,而终止向量会专注于寻找答案的右边界

至此,关于BERT在不同NLP任务上的微调过程就基本解析完毕了。

2.4 Python代码

上面简单讲完BERT的预训练和微调后,相信大家应该有了初步的印象。这里我们提供一些代码+相应的注释,让大家对BERT的实现流程更熟悉,具体代码的链接为:

https://github.com/codertimo/BERT-pytorch

这里我们自顶而下看一下BERT的预训练过程。

class BERTTrainer:
    """
    BERTTrainer make the pretrained BERT model with two LM training method.

        1. Masked Language Model : 3.3.1 Task #1: Masked LM
        2. Next Sentence prediction : 3.3.2 Task #2: Next Sentence Prediction

    please check the details on README.md with simple example.

    """

    def __init__(self, bert: BERT, vocab_size: int,
                 train_dataloader: DataLoader, test_dataloader: DataLoader = None,
                 lr: float = 1e-4, betas=(0.9, 0.999), weight_decay: float = 0.01, warmup_steps=10000,
                 with_cuda: bool = True, cuda_devices=None, log_freq: int = 10):


        # Setup cuda device for BERT training, argument -c, --cuda should be true
        cuda_condition = torch.cuda.is_available() and with_cuda
        self.device = torch.device("cuda:0" if cuda_condition else "cpu")

        # This BERT model will be saved every epoch
        self.bert = bert
        # Initialize the BERT Language Model, with BERT model
        self.model = BERTLM(bert, vocab_size).to(self.device)

        # Distributed GPU training if CUDA can detect more than 1 GPU
        if with_cuda and torch.cuda.device_count() > 1:
            print("Using %d GPUS for BERT" % torch.cuda.device_count())
            self.model = nn.DataParallel(self.model, device_ids=cuda_devices)

        # Setting the train and test data loader
        self.train_data = train_dataloader
        self.test_data = test_dataloader

        # Setting the Adam optimizer with hyper-param
        self.optim = Adam(self.model.parameters(), lr=lr, betas=betas, weight_decay=weight_decay)
        self.optim_schedule = ScheduledOptim(self.optim, self.bert.hidden, n_warmup_steps=warmup_steps)

        # Using Negative Log Likelihood Loss function for predicting the masked_token
        self.criterion = nn.NLLLoss(ignore_index=0)

        self.log_freq = log_freq

        print("Total Parameters:", sum([p.nelement() for p in self.model.parameters()]))

    def train(self, epoch):
        self.iteration(epoch, self.train_data)

    def test(self, epoch):
        self.iteration(epoch, self.test_data, train=False)

    def iteration(self, epoch, data_loader, train=True):
        """
        loop over the data_loader for training or testing
        if on train status, backward operation is activated
        and also auto save the model every peoch

        :param epoch: current epoch index
        :param data_loader: torch.utils.data.DataLoader for iteration
        :param train: boolean value of is train or test
        :return: None
        """
        str_code = "train" if train else "test"

        # Setting the tqdm progress bar
        data_iter = tqdm.tqdm(enumerate(data_loader),
                              desc="EP_%s:%d" % (str_code, epoch),
                              total=len(data_loader),
                              bar_format="{l_bar}{r_bar}")

        avg_loss = 0.0
        total_correct = 0
        total_element = 0

        for i, data in data_iter:
            # 0. batch_data will be sent into the device(GPU or cpu)
            data = {key: value.to(self.device) for key, value in data.items()}

            # 1. forward the next_sentence_prediction and masked_lm model
            next_sent_output, mask_lm_output = self.model.forward(data["bert_input"], data["segment_label"])

            # 2-1. NLL(negative log likelihood) loss of is_next classification result
            next_loss = self.criterion(next_sent_output, data["is_next"])

            # 2-2. NLLLoss of predicting masked token word
            mask_loss = self.criterion(mask_lm_output.transpose(1, 2), data["bert_label"])

            # 2-3. Adding next_loss and mask_loss : 3.4 Pre-training Procedure
            loss = next_loss + mask_loss

            # 3. backward and optimization only in train
            if train:
                self.optim_schedule.zero_grad()
                loss.backward()
                self.optim_schedule.step_and_update_lr()

            # next sentence prediction accuracy
            correct = next_sent_output.argmax(dim=-1).eq(data["is_next"]).sum().item()
            avg_loss += loss.item()
            total_correct += correct
            total_element += data["is_next"].nelement()

            post_fix = {
                "epoch": epoch,
                "iter": i,
                "avg_loss": avg_loss / (i + 1),
                "avg_acc": total_correct / total_element * 100,
                "loss": loss.item()
            }

            if i % self.log_freq == 0:
                data_iter.write(str(post_fix))

        print("EP%d_%s, avg_loss=" % (epoch, str_code), avg_loss / len(data_iter), "total_acc=",
              total_correct * 100.0 / total_element)

上述代码的核心在iteration函数中,可见BERT的训练受到两个损失函数(即next_loss 和mask_loss)的监督。这两个损失函数分别来自两个不同的任务,即前面提到的**“判断两个句子是否连续”以及“猜词”**。

接着,我们来通过代码看一下这个BERT的网络结果长啥样,如下:

class BERT(nn.Module):
    """
    BERT model : Bidirectional Encoder Representations from Transformers.
    """

    def __init__(self, vocab_size, hidden=768, n_layers=12, attn_heads=12, dropout=0.1):
        """
        :param vocab_size: vocab_size of total words
        :param hidden: BERT model hidden size
        :param n_layers: numbers of Transformer blocks(layers)
        :param attn_heads: number of attention heads
        :param dropout: dropout rate
        """

        super().__init__()
        self.hidden = hidden
        self.n_layers = n_layers
        self.attn_heads = attn_heads

        # paper noted they used 4*hidden_size for ff_network_hidden_size
        self.feed_forward_hidden = hidden * 4

        # embedding for BERT, sum of positional, segment, token embeddings
        self.embedding = BERTEmbedding(vocab_size=vocab_size, embed_size=hidden)

        # multi-layers transformer blocks, deep network
        self.transformer_blocks = nn.ModuleList(
            [TransformerBlock(hidden, attn_heads, hidden * 4, dropout) for _ in range(n_layers)])

    def forward(self, x, segment_info):
        # attention masking for padded token
        # torch.ByteTensor([batch_size, 1, seq_len, seq_len)
        mask = (x > 0).unsqueeze(1).repeat(1, x.size(1), 1).unsqueeze(1)

        # embedding the indexed sequence to sequence of vectors
        x = self.embedding(x, segment_info)

        # running over multiple transformer blocks
        for transformer in self.transformer_blocks:
            x = transformer.forward(x, mask)

        return x

这么一看,这好像就是Transformer的编码器部分。不同的是,在forward函数这里计算了一下mask。看这个mask的定义,应该是大于0的地方为mask。我们在数据集的定义中找到如下代码:

    def random_word(self, sentence):
        tokens = sentence.split()
        output_label = []

        for i, token in enumerate(tokens):
            prob = random.random()
            if prob < 0.15:
                prob /= 0.15

                # 80% randomly change token to mask token
                if prob < 0.8:
                    tokens[i] = self.vocab.mask_index

                # 10% randomly change token to random token
                elif prob < 0.9:
                    tokens[i] = random.randrange(len(self.vocab))

                # 10% randomly change token to current token
                else:
                    tokens[i] = self.vocab.stoi.get(token, self.vocab.unk_index)

                output_label.append(self.vocab.stoi.get(token, self.vocab.unk_index))

            else:
                tokens[i] = self.vocab.stoi.get(token, self.vocab.unk_index)
                output_label.append(0)

        return tokens, output_label

从上述代码中可见,作者设置了随机比例来将输入句子中的一部分词替换成特殊符号,也设置了一定的随机比例将一部分词随机替换成其他词汇。这些被替换的词汇所对应的位置被设定为大于0的整数,而未被替换的位置,设置为0。

这样一看,前面BERT的forward函数的第一行代码,即:

mask = (x > 0).unsqueeze(1).repeat(1, x.size(1), 1).unsqueeze(1)

就非常容易理解了。更多的细节建议大家将上述链接代码下载后自行查看。

3 总结

写到这里,关于BERT的基本流程和网络结构都讲解完毕了。如果大家熟悉Transformer的话,应该会觉得BERT其实比较容易理解。当然,如果大家觉得看起来比较吃力的话,这里非常建议大家可以自学/温习一下Transformer后再来看一遍!

如何学习AI大模型?

大模型时代,火爆出圈的LLM大模型让程序员们开始重新评估自己的本领。 “AI会取代那些行业?”“谁的饭碗又将不保了?”等问题热议不断。

不如成为「掌握AI工具的技术人」,毕竟AI时代,谁先尝试,谁就能占得先机!

但是LLM相关的内容很多,现在网上的老课程老教材关于LLM又太少。所以现在小白入门就只能靠自学,学习成本和门槛很高

针对所有自学遇到困难的同学们,我帮大家系统梳理大模型学习脉络,将这份 LLM大模型资料 分享出来:包括LLM大模型书籍、640套大模型行业报告、LLM大模型学习视频、LLM大模型学习路线、开源大模型学习教程等, 😝有需要的小伙伴,可以 扫描下方二维码领取🆓↓↓↓

👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]()👈

学习路线

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

在这里插入图片描述

👉学会后的收获:👈

• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

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

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

相关文章

贪心+构造,CF 1592F1 - Alice and Recoloring 1

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1592F1 - Alice and Recoloring 1 二、解题报告 1、思路分析 操作2、3可以…

C++系列-多态的基本语法

多态的基本语法 多态的含义静态多态动态多态 多态的底层原理多态中的final和overridefinaloverride: 多态的应用和优点计算器简单实现电脑组装的实现 《游山西村》 南宋陆游 莫笑农家腊酒浑&#xff0c;丰年留客足鸡豚。 山重水复疑无路&#xff0c;柳暗花明又一村。 箫鼓追…

leetcode118. 杨辉三角,老题又做

leetcode118. 杨辉三角 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1…

AI视频创作应用

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

appium下载及安装

下载地址&#xff1a;https://github.com/appium/appium-desktop/releases 双击安装就可以

信号的变换

信号的变换 在实践中&#xff0c;缩放和时间平移是遇到的两个最重要的信号变换。缩放改变了振幅轴上的因变量的值&#xff0c;而时间平移则影响了时间轴上的自变量的值。 加法 对于两个离散时间信号的加法&#xff0c;例如 x [ n ] x[n] x[n] 和 y [ n ] y[n] y[n]&#x…

Flutter【02】mobx原理

简介&#xff1a; 概念 MobX 区分了以下几个应用中的概念&#xff1a; State(状态) 状态 是驱动应用的数据。 通常有像待办事项列表这样的领域特定状态&#xff0c;还有像当前已选元素的视图状态。 记住&#xff0c;状态就像是有数据的excel表格。 Derivations(衍生) 任何…

Ps:首选项 - 性能

Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K Photoshop 首选项中的“性能” Performance选项卡允许用户通过调整内存使用、GPU 设置、高速缓存设置以及多线程处理等选项&#xff0c;来优化 Photoshop 的性能。这对于处理大文件、复杂图像或需要…

Python 数据分析之Numpy学习(一)

Python 数据分析之Numpy学习&#xff08;一&#xff09; 一、Numpy的引入 1.1 矩阵/向量的按位运算 需求&#xff1a;矩阵的按位相加 [0,1,4] [0,1,8] [0,2,12] 1.1.1 利用python实现矩阵/向量的按位运算 # 1.通过列表实现 list1 [0, 1, 4] list2 [0, 1, 8]# 列表使用…

(17)ELK大型储存库的搭建

前言&#xff1a; els是大型数据储存体系&#xff0c;类似于一种分片式存储方式。elasticsearch有强大的查询功能&#xff0c;基于java开发的工具&#xff0c;结合logstash收集工具&#xff0c;收集数据。kibana图形化展示数据&#xff0c;可以很好在大量的消息中准确的找到符…

Marimo:下一代Python编程环境,颠覆传统Jupyter笔记本,自动化执行所有依赖代码块,告别繁琐手动操作

Marimo 是一个颠覆传统笔记本的全新编程环境&#xff0c;它以其反应式、交互式、可执行和可共享等特性&#xff0c;为开发者们带来前所未有的编程体验。Marimo 确保您的笔记本代码、输出和程序状态始终保持一致。它解决了传统笔记本&#xff08;如 Jupyter&#xff09;的许多问…

流媒体服务器如何让WebRTC支持H.265,同时又能支持Web js硬解码、软解码(MSE硬解、WASM软解)

为了这一整套的解决方案&#xff0c;调研研发整整花费了差不多半年多的时间&#xff0c;需达成的目标&#xff1a; 流媒体服务器端不需要将H.265转码成H.264&#xff0c;就能让Chrome解码播放H.265&#xff1b; 注意&#xff1a;现在很多市面上的软硬件通过转码H.265成H.264的…

CSP-CCF 202312-1 仓库规划

一、问题描述 二、解答 思路&#xff1a;定义二维数组&#xff0c;比较不同行的相同列数 代码如下&#xff1a; #include<iostream> using namespace std; int main() {int n, m;cin >> n >> m;int a[1001][11] { 0 };for (int i 1; i < n; i){for (…

贪心 + 分层图bfs,newcoder 76652/B

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 https://ac.nowcoder.com/acm/contest/76652/B 二、解题报告 1、思路分析…

ELK基础搭建

一、认识ELK ELK是一套开源的日志分析系统&#xff0c;由elasticsearchlogstashKibana组成。 官网说明&#xff1a;https://www.elastic.co/cn/products 首先: 先一句话简单了解 E&#xff0c;L&#xff0c;K 这三个软件 elasticsearch: 分布式搜索引擎 logstash: 日志收集与…

领英(LinkedIn)公司主页创建方法分享

上次写了几篇关于领英注册的文章&#xff0c;也是有不少人加我&#xff0c;说有用。当然了也有还是不行的&#xff0c;还是不行的话一般都是比较复杂的问题&#xff0c;需要一些技术性的手段去解决。 然后最近也是有一些外贸朋友问公司主页注册创建的一些事情&#xff0c;今天的…

指挥调度平台——数字赋能,让出行更有温度

智慧交通指挥调度平台是基于信息技术和智能化系统的创新解决方案&#xff0c;旨在提升城市交通管理效率、改善交通流畅度、减少拥堵问题&#xff0c;以及增强城市交通运行的智能化水平。该平台整合了大数据分析、实时监测、智能优化算法等技术&#xff0c;为交通管理部门提供全…

虚拟现实技术的发展现状如何?

虚拟现实&#xff08;VR&#xff09;技术自2016年被广泛认为是元年之后&#xff0c;经历了快速增长和随后的调整期。目前&#xff0c;VR行业正处于快速发展期&#xff0c;技术不断进步&#xff0c;应用场景持续拓展。2024年VR技术发展现状概述&#xff1a; 1、行业发展阶段&am…

独家揭秘丨GreatSQL 的MDL锁策略升级对执行的影响

独家揭秘丨GreatSQL 的MDL锁策略升级对执行的影响 一、MDL锁策略介绍 GreatSQL 的MDL锁有个策略方法类MDL_lock_strategy&#xff0c;它根据对象的类型分为了scope类型和object类型&#xff0c;前者主要用于GLOBAL, COMMIT, TABLESPACE, BACKUP_LOCK and SCHEMA ,RESOURCE_GR…

基于tinymce实现多人在线实时协同文本编辑

基于tinymce实现多人在线实时协同文本编辑 前言 这可能是最后一次写tinymce相关的文章了&#xff0c;一方面tinymce的底层设计限制了很多功能的实现&#xff0c;另一方面tinymce本身越来越商业化&#xff0c;最新的7版本已经必须配置key&#xff0c;否则面临无法使用的问题。…