NLP文本匹配任务Text Matching [有监督训练]:PointWise(单塔)、DSSM(双塔)、Sentence BERT(双塔)项目实践

news2024/11/25 10:35:43

NLP文本匹配任务Text Matching [有监督训练]:PointWise(单塔)、DSSM(双塔)、Sentence BERT(双塔)项目实践

0 背景介绍以及相关概念

本项目对3种常用的文本匹配的方法进行实现:PointWise(单塔)、DSSM(双塔)、Sentence BERT(双塔)。

文本匹配(Text Matching)是 NLP 下的一个分支,通常用于计算两个句子之间的相似程度,在推荐、推理等场景下都有着重要的作用。

举例来讲,今天我们有一堆评论数据,我们想要找到指定类别的评论数据,例如:

1. 为什么是开过的洗发水都流出来了,是用过的吗?是这样子包装的吗?
2. 喜欢折叠手机的我对这款手机情有独钟,简洁的外观设计非常符合当代年轻人的口味,给携带增添了一份愉悦。
3. 物流很快,但是到货时有的水果已经不新鲜了,坏掉了,不满意本次购物。
...

在这一堆评论中我们想找到跟「水果」相关的评论,那么第 3 条评论就应该被召回。这个问题可以被建模为文本分类对吧,通过训练一个文本分类模型也能达到同样的目的。

但,分类模型的主要问题是:分类标签是固定的。假如在训练的时候标签集合是「洗浴,电脑,水果」,今天再来一条「服饰」的评论,那么模型依旧只能在原有的标签集合里面进行推理,无论推到哪个都是错误的。因此,我们需要一个能够有一定自适应能力的模型,在加入一些新标签后不用重训模型也能很好的完成任务。

文本匹配目前比较常用的有两种结构:

  1. 单塔模型:准确率高,但计算速度慢。

  2. 双塔模型:计算速度快,准确率相对低一些。

下面我们对这两种方法分别进行介绍。

0.1 单塔模型

单塔模型顾名思义,是指在整个过程中只进行一次模型计算。这里的「塔」指的是进行「几次模型计算」,而不一定是「模型个数」,这个我们会放到双塔部分解释。在单塔模型下,我们需要把两句文本通过 [SEP] 进行拼接,将拼接好的数据喂给模型,通过 output 中的[CLS] token 做一个二分类任务。

单塔模型的 forward 部分长这样,完整源码在文末:

    def __init__(self, encoder, dropout=None):
        """
        init func.
        Args:
            encoder (transformers.AutoModel): backbone, 默认使用 ernie 3.0
            dropout (float): dropout 比例
        """
        super().__init__()
        self.encoder = encoder
        hidden_size = 768
        self.dropout = nn.Dropout(dropout if dropout is not None else 0.1)
        self.classifier = nn.Linear(768, 2)

    def forward(self,
                input_ids,
                token_type_ids,
                position_ids=None,
                attention_mask=None) -> torch.tensor:
        """
        Foward 函数,输入匹配好的pair对,返回二维向量(相似/不相似)。
        Args:
            input_ids (torch.LongTensor): (batch, seq_len)
            token_type_ids (torch.LongTensor): (batch, seq_len)
            position_ids (torch.LongTensor): (batch, seq_len)
            attention_mask (torch.LongTensor): (batch, seq_len)

        Returns:
            torch.tensor: (batch, 2)
        """
        pooled_embedding = self.encoder(
            input_ids=input_ids,
            token_type_ids=token_type_ids,
            position_ids=position_ids,
            attention_mask=attention_mask
        )["pooler_output"]                                  # (batch, hidden_size)
        pooled_embedding = self.dropout(pooled_embedding)   # (batch, hidden_size)
        logits = self.classifier(pooled_embedding)          # (batch, 2)
        
        return logits

单塔模型的优势在于准确率较高,但缺点在于:计算慢。

  • 为什么慢呢?

举例来讲,如果今天我们有三个类别「电脑、水果、洗浴」,那我们就需要将一句话跟每个类别都做一次拼接,并喂给模型去做推理:

水果[SEP]苹果不是很新鲜,不满意这次购物[SEP]
电脑[SEP]苹果不是很新鲜,不满意这次购物[SEP]
洗浴[SEP]苹果不是很新鲜,不满意这次购物[SEP]

那如果类别数目到达成百上千时,就需要拼接上千次,为了判断一个样本就需要过上次模型,而大模型的计算通常来讲是非常耗时的,这就导致了在类别数目很大的情况下,单塔模型的效率往往无法满足人们的需求。

0.2 双塔模型

单塔模型的劣势很明显,有多少类别就需要算多少次。但事实上,这些类别都是不会变的,唯一变的只有新的评论数据。所以我们能不能实现将这些不会变的「类别信息」「提前计算」存下来,只计算那些没有见过的「评论数据」呢?这就是双塔模型的思想。双塔模型的「双塔」含义就是:两次模型计算。即,类别特征计算一次,评论特征计算一次。

通过上图可以看到,「类别」和「评论」不再是拼接在一起喂给模型,而是单独喂给模型,并分别得到各自的 embedding 向量,再进行后续的计算。而上图中左右两个两个模型可以使用同一个模型(这种方式叫:同构模型),也可以用两个不同的模型(这种方式叫:异构模型)。因此「双塔」并不一定代表存在两个模型,而是代表需要需要进行两次模型计算。

0.2.1 DSSM(Deep Structured Semantic Models,深度结构化语义模型)

Paper Reference: https://posenhuang.github.io/papers/cikm2013_DSSM_fullversion.pdf

DSSM 是一篇比较早期的 paper,我们主要借鉴其通过 embedding 之间的余弦相似度进行召回排序的思想。我们分别将「类别」和「评论」文本过一遍模型,并得到两段文本的 embedding。将匹配的 pair 之间的余弦相似度 label 置为 1,不匹配的 pair 之间余弦相似度 label 置为 0。

Note: 余弦相似度的取值范围是 [-1, 1],但为了方便我将 label 置为 0 并用 MSE 去训练,也能取得不错的效果。

训练数据集长这样,匹配 pair 标签为 1,不匹配为 0:

蒙牛	不错还好挺不错	0
蒙牛	我喜欢demom制造的蒙牛奶	1
衣服	裤子太差了,刚穿一次屁股就起毛了。	1
...

实现中有两个关键函数:获得句子的 embedding 函数(用于推理)、获得句子对的余弦相似度(用于训练):

    def forward(
        self,
        input_ids: torch.tensor,
        token_type_ids: torch.tensor,
        attention_mask: torch.tensor
    ) -> torch.tensor:
        """
        forward 函数,输入单句子,获得单句子的embedding。
        Args:
            input_ids (torch.LongTensor): (batch, seq_len)
            token_type_ids (torch.LongTensor): (batch, seq_len)
            attention_mask (torch.LongTensor): (batch, seq_len)
        Returns:
            torch.tensor: embedding -> (batch, hidden_size)
        """
        embedding = self.encoder(
                input_ids=input_ids,
                token_type_ids=token_type_ids,
                attention_mask=attention_mask
            )["pooler_output"]                                  # (batch, hidden_size)
        return embedding

    def get_similarity(
        self,
        query_input_ids: torch.tensor,
        query_token_type_ids: torch.tensor,
        query_attention_mask: torch.tensor,
        doc_input_ids: torch.tensor,
        doc_token_type_ids: torch.tensor,
        doc_attention_mask: torch.tensor
    ) -> torch.tensor:
        """
        输入query和doc的向量,返回query和doc两个向量的余弦相似度。
        Args:
            query_input_ids (torch.LongTensor): (batch, seq_len)
            query_token_type_ids (torch.LongTensor): (batch, seq_len)
            query_attention_mask (torch.LongTensor): (batch, seq_len)
            doc_input_ids (torch.LongTensor): (batch, seq_len)
            doc_token_type_ids (torch.LongTensor): (batch, seq_len)
            doc_attention_mask (torch.LongTensor): (batch, seq_len)
        Returns:
            torch.tensor: (batch, 1)
        """
        query_embedding = self.encoder(
            input_ids=query_input_ids,
            token_type_ids=query_token_type_ids,
            attention_mask=query_attention_mask
        )["pooler_output"]                                 # (batch, hidden_size)
        query_embedding = self.dropout(query_embedding)

        doc_embedding = self.encoder(
            input_ids=doc_input_ids,
            token_type_ids=doc_token_type_ids,
            attention_mask=doc_attention_mask
        )["pooler_output"]                                  # (batch, hidden_size)
        doc_embedding = self.dropout(doc_embedding)

        similarity = nn.functional.cosine_similarity(query_embedding, doc_embedding)
        return similarity

0.2.2 Sentence Transformer

Paper Reference:https://arxiv.org/pdf/1908.10084.pdf

Sentence Transformer 也是一个双塔模型,只是在训练时不直接对两个句子的 embedding 算余弦相似度,而是将这两个 embedding 和 embedding 之间的差向量进行拼接,将这三个向量拼好后喂给一个判别层做二分类任务。

原 paper 中在 inference 的时候不再使用训练架构,而是采用了余弦相似度的方法做召回。但我在实现的时候在推理部分仍然沿用了训练的模型架构,原因是想抹除结构不一致的 gap,并且训练层也只是多了一层 Linear 层,在推理的时候也不至于消耗过多的时间。Sentence Transformer 在推理时需要同时传入「当前评论信息」以及事先计算好的「所有类别 embedding」,如下:

    def forward(
        self,
        query_input_ids: torch.tensor,
        query_token_type_ids: torch.tensor,
        query_attention_mask: torch.tensor,
        doc_embeddings: torch.tensor,
    ) -> torch.tensor:
        """
        forward 函数,输入query句子和doc_embedding向量,将query句子过一遍模型得到
        query embedding再和doc_embedding做二分类。
        Args:
            input_ids (torch.LongTensor): (batch, seq_len)
            token_type_ids (torch.LongTensor): (batch, seq_len)
            attention_mask (torch.LongTensor): (batch, seq_len)
            doc_embedding (torch.LongTensor): 所有需要匹配的doc_embedding -> (batch, doc_embedding_numbers, hidden_size)
        Returns:
            torch.tensor: embedding_match_logits -> (batch, doc_embedding_numbers, 2)
        """
        query_embedding = self.encoder(
            input_ids=query_input_ids,
            token_type_ids=query_token_type_ids,
            attention_mask=query_attention_mask
        )["last_hidden_state"]                                                  # (batch, seq_len, hidden_size)
        
        query_attention_mask = torch.unsqueeze(query_attention_mask, dim=-1)    # (batch, seq_len, 1)
        query_embedding = query_embedding * query_attention_mask                # (batch, seq_len, hidden_size)
        query_sum_embedding = torch.sum(query_embedding, dim=1)                 # (batch, hidden_size)
        query_sum_mask = torch.sum(query_attention_mask, dim=1)                 # (batch, 1)
        query_mean = query_sum_embedding / query_sum_mask                       # (batch, hidden_size)

        query_mean = query_mean.unsqueeze(dim=1).repeat(1, doc_embeddings.size()[1], 1)  # (batch, doc_embedding_numbers, hidden_size)
        sub = torch.abs(torch.subtract(query_mean, doc_embeddings))                      # (batch, doc_embedding_numbers, hidden_size)
        concat = torch.cat([query_mean, doc_embeddings, sub], dim=-1)                    # (batch, doc_embedding_numbers, hidden_size * 3)
        logits = self.classifier(concat)                                                 # (batch, doc_embedding_numbers, 2)
        return logits

1. 环境安装

本项目基于 pytorch + transformers 实现,运行前请安装相关依赖包:

pip install -r ../../requirements.txt

torch
transformers==4.22.1
datasets==2.4.0
evaluate==0.2.2
matplotlib==3.6.0
rich==12.5.1
scikit-learn==1.1.2
requests==2.28.1

2. 数据集准备

项目中提供了一部分示例数据,我们使用「商品评论」和「商品类别」来进行文本匹配任务,数据在 data/comment_classify

若想使用自定义数据训练,只需要仿照示例数据构建数据集即可:

衣服:指穿在身上遮体御寒并起美化作用的物品。	为什么是开过的洗发水都流出来了、是用过的吗?是这样子包装的吗?	0
衣服:指穿在身上遮体御寒并起美化作用的物品。	开始买回来大很多 后来换了回来又小了 号码区别太不正规 建议各位谨慎	1
...

每一行用 \t 分隔符分开,第一部分部分为商品类型(text1),中间部分为商品评论(text2),最后一部分为商品评论和商品类型是否一致(label)

3. 有监督-模型训练

3.1 PointWise(单塔)

3.1.1 模型训练

修改训练脚本 train_pointwise.sh 里的对应参数, 开启模型训练:

python train_pointwise.py \
    --model "nghuyong/ernie-3.0-base-zh" \  # backbone
    --train_path "data/comment_classify/train.txt" \    # 训练集
    --dev_path "data/comment_classify/dev.txt" \    #验证集
    --save_dir "checkpoints/comment_classify" \ # 训练模型存放地址
    --img_log_dir "logs/comment_classify" \ # loss曲线图保存位置
    --img_log_name "ERNIE-PointWise" \  # loss曲线图保存文件夹
    --batch_size 8 \
    --max_seq_len 128 \
    --valid_steps 50 \
    --logging_steps 10 \
    --num_train_epochs 10 \
    --device "cuda:0"

正确开启训练后,终端会打印以下信息:

...
global step 10, epoch: 1, loss: 0.77517, speed: 3.43 step/s
global step 20, epoch: 1, loss: 0.67356, speed: 4.15 step/s
global step 30, epoch: 1, loss: 0.53567, speed: 4.15 step/s
global step 40, epoch: 1, loss: 0.47579, speed: 4.15 step/s
global step 50, epoch: 2, loss: 0.43162, speed: 4.41 step/s
Evaluation precision: 0.88571, recall: 0.87736, F1: 0.88152
best F1 performence has been updated: 0.00000 --> 0.88152
global step 60, epoch: 2, loss: 0.40301, speed: 4.08 step/s
global step 70, epoch: 2, loss: 0.37792, speed: 4.03 step/s
global step 80, epoch: 2, loss: 0.35343, speed: 4.04 step/s
global step 90, epoch: 2, loss: 0.33623, speed: 4.23 step/s
global step 100, epoch: 3, loss: 0.31319, speed: 4.01 step/s
Evaluation precision: 0.96970, recall: 0.90566, F1: 0.93659
best F1 performence has been updated: 0.88152 --> 0.93659
...

logs/comment_classify 文件下将会保存训练曲线图:

3.1.2 模型推理

完成模型训练后,运行 inference_pointwise.py 以加载训练好的模型并应用:

...
    test_inference(
        '手机:一种可以在较广范围内使用的便携式电话终端。',     # 第一句话
        '味道非常好,京东送货速度也非常快,特别满意。',        # 第二句话
        max_seq_len=128
    )
...

运行推理程序:

python inference_pointwise.py

得到以下推理结果:

tensor([[ 1.8477, -2.0484]], device='cuda:0')   # 两句话不相似(0)的概率更大

3.2 DSSM(双塔)

3.2.1 模型训练

修改训练脚本 train_dssm.sh 里的对应参数, 开启模型训练:

python train_dssm.py \
    --model "nghuyong/ernie-3.0-base-zh" \
    --train_path "data/comment_classify/train.txt" \
    --dev_path "data/comment_classify/dev.txt" \
    --save_dir "checkpoints/comment_classify/dssm" \
    --img_log_dir "logs/comment_classify" \
    --img_log_name "ERNIE-DSSM" \
    --batch_size 8 \
    --max_seq_len 256 \
    --valid_steps 50 \
    --logging_steps 10 \
    --num_train_epochs 10 \
    --device "cuda:0"

正确开启训练后,终端会打印以下信息:

...
global step 0, epoch: 1, loss: 0.62319, speed: 15.16 step/s
Evaluation precision: 0.29912, recall: 0.96226, F1: 0.45638
best F1 performence has been updated: 0.00000 --> 0.45638
global step 10, epoch: 1, loss: 0.40931, speed: 3.64 step/s
global step 20, epoch: 1, loss: 0.36969, speed: 3.69 step/s
global step 30, epoch: 1, loss: 0.33927, speed: 3.69 step/s
global step 40, epoch: 1, loss: 0.31732, speed: 3.70 step/s
global step 50, epoch: 1, loss: 0.30996, speed: 3.68 step/s
...

logs/comment_classify 文件下将会保存训练曲线图:

3.2.2 模型推理

和单塔模型不一样的是,双塔模型可以事先计算所有候选类别的Embedding,当新来一个句子时,只需计算新句子的Embedding,并通过余弦相似度找到最优解即可。

因此,在推理之前,我们需要提前计算所有类别的Embedding并保存。

类别Embedding计算

运行 get_embedding.py 文件以计算对应类别embedding并存放到本地:

...
text_file = 'data/comment_classify/types_desc.txt'                       # 候选文本存放地址
output_file = 'embeddings/comment_classify/dssm_type_embeddings.json'    # embedding存放地址

device = 'cuda:0'                                                        # 指定GPU设备
model_type = 'dssm'                                                      # 使用DSSM还是Sentence Transformer
saved_model_path = './checkpoints/comment_classify/dssm/model_best/'     # 训练模型存放地址
tokenizer = AutoTokenizer.from_pretrained(saved_model_path) 
model = torch.load(os.path.join(saved_model_path, 'model.pt'))
model.to(device).eval()
...

其中,所有需要预先计算的内容都存放在 types_desc.txt 文件中。

文件用 \t 分隔,分别代表 类别id类别名称类别描述

0	水果	指多汁且主要味觉为甜味和酸味,可食用的植物果实。
1	洗浴	洗浴用品。
2	平板	也叫便携式电脑,是一种小型、方便携带的个人电脑,以触摸屏作为基本的输入设备。
...

执行 python get_embeddings.py 命令后,会在代码中设置的embedding存放地址中找到对应的embedding文件:

{
    "0": {"label": "水果", "text": "水果:指多汁且主要味觉为甜味和酸味,可食用的植物果实。", "embedding": [0.3363891839981079, -0.8757723569869995, -0.4140555262565613, 0.8288457989692688, -0.8255823850631714, 0.9906797409057617, -0.9985526204109192, 0.9907819032669067, -0.9326567649841309, -0.9372553825378418, 0.11966298520565033, -0.7452883720397949,...]},
    "1": ...,
    ...
}

模型推理

完成预计算后,接下来就可以开始推理了。

我们构建一条新评论:这个破笔记本卡的不要不要的,差评

运行 python inference_dssm.py,得到下面结果:

[
    ('平板', 0.9515482187271118),
    ('电脑', 0.8216977119445801),
    ('洗浴', 0.12220608443021774),
    ('衣服', 0.1199738010764122),
    ('手机', 0.07764233648777008),
    ('酒店', 0.044791921973228455),
    ('水果', -0.050112202763557434),
    ('电器', -0.07554933428764343),
    ('书籍', -0.08481660485267639),
    ('蒙牛', -0.16164332628250122)
]

函数将输出(类别,余弦相似度)的二元组,并按照相似度做倒排(相似度取值范围:[-1, 1])。

3.3 Sentence Transformer(双塔)

3.3.1 模型训练

修改训练脚本 train_sentence_transformer.sh 里的对应参数, 开启模型训练:

python train_sentence_transformer.py \
    --model "nghuyong/ernie-3.0-base-zh" \
    --train_path "data/comment_classify/train.txt" \
    --dev_path "data/comment_classify/dev.txt" \
    --save_dir "checkpoints/comment_classify/sentence_transformer" \
    --img_log_dir "logs/comment_classify" \
    --img_log_name "Sentence-Ernie" \
    --batch_size 8 \
    --max_seq_len 256 \
    --valid_steps 50 \
    --logging_steps 10 \
    --num_train_epochs 10 \
    --device "cuda:0"

正确开启训练后,终端会打印以下信息:

...
Evaluation precision: 0.81928, recall: 0.64151, F1: 0.71958
best F1 performence has been updated: 0.46120 --> 0.71958
global step 260, epoch: 2, loss: 0.58730, speed: 3.53 step/s
global step 270, epoch: 2, loss: 0.58171, speed: 3.55 step/s
global step 280, epoch: 2, loss: 0.57529, speed: 3.48 step/s
global step 290, epoch: 2, loss: 0.56687, speed: 3.55 step/s
global step 300, epoch: 2, loss: 0.56033, speed: 3.55 step/s
...

logs/comment_classify 文件下将会保存训练曲线图:

3.2.2 模型推理

Sentence Transformer 同样也是双塔模型,因此我们需要事先计算所有候选文本的embedding值。

类别Embedding计算

运行 get_embedding.py 文件以计算对应类别embedding并存放到本地:

...
text_file = 'data/comment_classify/types_desc.txt'                       # 候选文本存放地址
output_file = 'embeddings/comment_classify/sentence_transformer_type_embeddings.json'    # embedding存放地址

device = 'cuda:0'                                                        # 指定GPU设备
model_type = 'sentence_transformer'                                                      # 使用DSSM还是Sentence Transformer
saved_model_path = './checkpoints/comment_classify/sentence_transformer/model_best/'     # 训练模型存放地址
tokenizer = AutoTokenizer.from_pretrained(saved_model_path) 
model = torch.load(os.path.join(saved_model_path, 'model.pt'))
model.to(device).eval()
...

其中,所有需要预先计算的内容都存放在 types_desc.txt 文件中。

文件用 \t 分隔,分别代表 类别id类别名称类别描述

0	水果	指多汁且主要味觉为甜味和酸味,可食用的植物果实。
1	洗浴	洗浴用品。
2	平板	也叫便携式电脑,是一种小型、方便携带的个人电脑,以触摸屏作为基本的输入设备。
...

执行 python get_embeddings.py 命令后,会在代码中设置的embedding存放地址中找到对应的embedding文件:

{
    "0": {"label": "水果", "text": "水果:指多汁且主要味觉为甜味和酸味,可食用的植物果实。", "embedding": [0.32447007298469543, -1.0908259153366089, -0.14340722560882568, 0.058471400290727615, -0.33798110485076904, -0.050156619399785995, 0.041511114686727524, 0.671889066696167, 0.2313404232263565, 1.3200652599334717, -1.10829496383667, 0.4710233509540558, -0.08577515184879303, -0.41730815172195435, -0.1956728845834732, 0.05548520386219025, ...]}
    "1": ...,
    ...
}

模型推理

完成预计算后,接下来就可以开始推理了。

我们构建一条新评论:这个破笔记本卡的不要不要的,差评

运行 python inference_sentence_transformer.py,函数会输出所有类别里「匹配通过」的类别及其匹配值,得到下面结果:

Used 0.5233056545257568s.
[
    ('平板', 1.136274814605713), 
    ('电脑', 0.8851938247680664)
]

函数将输出(匹配通过的类别,匹配值)的二元组,并按照匹配值(越大则越匹配)做倒排。

参考链接:https://github.com/HarderThenHarder/transformers_tasks/blob/main/text_matching/supervised

github无法连接的可以在:https://download.csdn.net/download/sinat_39620217/88214437 下载

更多优质内容请关注公号:汀丶人工智能;会提供一些相关的资源和优质文章,免费获取阅读。

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

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

相关文章

物联网和不断发展的ITSM

物联网将改变社会,整个技术行业关于对机器连接都通过嵌入式传感器、软件和收集和交换数据的电子设备每天都在更新中。Gartner 预测,全球将有4亿台互联设备投入使用。 无论企业采用物联网的速度如何,连接设备都将成为新常态,IT服务…

iVX引领自动编程新时代:从百万应用到普适AST转换的技术突破

一、引言 在人工智能和自动编程的交汇点上,iVX以其独特的自动编程训练模型,正引领着新一轮的技术革命。通过百万个通过iVX IDE生成的应用进行有监督训练,iVX成功地将任意网站或网页转成了iVX IDE中的AST,展示了其强大的技术实力。…

浅谈GIS和三维GIS的区别?

GIS(地理信息系统)和三维GIS(3D地理信息系统)是地理信息领域的两个重要概念,它们在地理数据的处理和分析方面具有不同的特点和应用。可能很多人分不清二者的区别,本文就带大家简单了解一下二者的区别。 定义…

【闲侃历史】 唐朝----安史之乱那些事(1)

说到安史之乱,可谓是唐朝最乱的一段时期,据说唐朝当时也就5000多万人,而经历了这一战,人口只剩1000多万人了。著名的杨国忠和杨贵妃也是在这个时候死的。这个系列我们就先来侃侃发起安史之乱的两个人----安禄山和史思明 一. 安禄…

免费插件集-illustrator插件-Ai插件-路径编辑-统一线宽

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.示例6.总结 1.介绍 本文介绍一款免费插件,加强illustrator使用人员工作效率,统一路径线宽。首先从下载网址下载这款插件 https://download.csdn.net/download/m0_67316550/87890501&am…

2023年05月 C/C++(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:输出第二个整数 输入三个整数,把第二个输入的整数输出。 时间限制:1000 内存限制:65536 输入 只有一行,共三个整数,整数之间由一个空格分隔。整数是32位有符号整数。 输出 只有一行,一个整…

支持M1 Syncovery for mac 文件备份同步工具

Syncovery for Mac 是一款功能强大、易于使用的文件备份和同步软件,适用于需要备份和同步数据的个人用户和企业用户。Syncovery 提供了一个直观的用户界面,使用户可以轻松设置备份和同步任务。用户可以选择备份的文件类型、备份目录、备份频率等&#xf…

【python实现向日葵控制软件功能】手机远程控制电脑

大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人_python人工智能视觉(opencv)从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了: https://blog.csdn.net/lbcy…

Redis数据结构——Redis简单动态字符串SDS

定义 众所周知,Redis是由C语言写的。 对于字符串类型的数据存储,Redis并没有直接使用C语言中的字符串。 而是自己构建了一个结构体,叫做“简单动态字符串”,简称SDS,比C语言中的字符串更加灵活。 SDS的结构体是这样的…

vue + less 实现动态主题换肤功能

文章目录 前言一、前提条件1. 初始化vue项目2. 安装插件 二、新建文件夹主题theme1.style.less文件2.model.js文件3.theme.js文件theme文件夹最终效果 三、修改vue.config.js文件四、页面上的具体使用1. index.vue 页面2. index.vue 页面注意点说明3. index.vue 效果 五、在js中…

学会这一招,轻松玩转小程序自动化

jmeter 可以做性能测试,这个很多人都知道,那你知道,jmeter 可以在启动运行时,指定线程数和运行时间,自定义性能场景吗? jmeter 性能测试,动态设定性能场景 平时,我们使用 jmeter 进…

《图解HTTP》——HTTP协议详解

一、HTTP协议概述 HTTP是一个属于应用层的面向对象协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP…

TiDB Bot:用 Generative AI 构建企业专属的用户助手机器人

本文介绍了 PingCAP 是如何用 Generative AI 构建一个使用企业专属知识库的用户助手机器人。除了使用业界常用的基于知识库的回答方法外,还尝试使用模型在 few shot 方法下判断毒性。 最终,该机器人在用户使用后,点踩的比例低于 5%&#xff0…

安科瑞电力监控系统在某区块页岩气地面集输工程中的应用

摘要:Acrel-2000Z电力监控系统适用于35kV及以下电压等级的各类变电站,可以帮助用户掌握配电系统实时运行状态, 获取预警、告警等各类事件,实现区域的无人值守,提高监管水平。本文介绍了安科瑞电力监控系统Acrel-2000在…

elasticsearch-head 插件

1、elastic 插件说明 **Head** 是第三方提供的一款很优秀的插件,集监控、查询、配置一体的web功能系统,可以在系统中进行创建、删除索引 、文档。以及查询、配置索引等功能,深受广大开发者的喜爱 **Kopf** 是另一个第三方提供的一款很优秀…

【Java】2022 RoboCom 机器人开发者大赛-高职组(省赛)题解

7-15 您好呀 本届比赛的主题是“智能照护”,那么就请你首先为智能照护机器人写一个最简单的问候程序 —— 无论遇见谁,首先说一句“您好呀~”。 输入格式: 本题没有输入 输出格式: 在一行中输出问候语的汉语拼音 Nin Hao Ya ~…

《局外人》阅读笔记

《局外人》阅读笔记 2023年8月14日在杭州小屋读完。我看的是张雨彤编译的这本,这本书包含了两部分,第一部分是《局外人》是原版的小说故事,第二部分是《堕落》包括了六天内的自然自语,完全没看懂,写作风格突变&#xf…

Python爬虫IP代理池的建立和使用

写在前面 建立Python爬虫IP代理池可以提高爬虫的稳定性和效率,可以有效避免IP被封锁或限制访问等问题。 下面是建立Python爬虫IP代理池的详细步骤和代码实现: 1. 获取代理IP 我们可以从一些代理IP网站上获取免费或付费的代理IP,或者自己租…

【人工智能前沿弄潮】—— SAM系列:SAM自动生成物体mask

SAM自动生成物体mask 由于SAM可以高效处理提示,可以通过在图像上抽样大量的提示来生成整个图像的mask。这种方法被用来生成数据集SA-1B。 类SamAutomaticMaskGenerator实现了这个功能。它通过在图像上的网格中对单点输入提示进行抽样,从每个提示中SAM可…

报名小程序PowerActivity配置

https://github.com/zhihuliukanshan/PowerActivity/assets/100545532/9b3e2a3b-f810-4c1f-90d5-9596d99abbd3 导入代码后,需要配置的位置有: 1、miniprogram\setting\setting.js中的CLOUD_ID: module.exports {//### 环境相关 CLOUD_ID: …