AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理

AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理

目录

AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理

一、简单介绍

二、零样本学习 (Zero-shot Learning) 和少样本学习 (Few-shot Learning)

         1、零样本学习 (Zero-shot Learning)

         2、少样本学习 (Few-shot Learning)

三、构建GitHub issue标记任务

1、获取数据

2、准备数据

3、创建训练集

4、创建训练切片

四、基线模型

附录

一、当前案例环境 pacakge 的 版本如下


一、简单介绍

AGI,即通用人工智能(Artificial General Intelligence),是一种具备人类智能水平的人工智能系统。它不仅能够执行特定的任务,而且能够理解、学习和应用知识于广泛的问题解决中,具有较高的自主性和适应性。AGI的能力包括但不限于自我学习、自我改进、自我调整,并能在没有人为干预的情况下解决各种复杂问题。

  • AGI能做的事情非常广泛:

    跨领域任务执行:AGI能够处理多领域的任务,不受限于特定应用场景。
    自主学习与适应:AGI能够从经验中学习,并适应新环境和新情境。
    创造性思考:AGI能够进行创新思维,提出新的解决方案。
    社会交互:AGI能够与人类进行复杂的社会交互,理解情感和社会信号。

  • 关于AGI的未来发展前景,它被认为是人工智能研究的最终目标之一,具有巨大的变革潜力:

    技术创新:随着机器学习、神经网络等技术的进步,AGI的实现可能会越来越接近。
    跨学科整合:实现AGI需要整合计算机科学、神经科学、心理学等多个学科的知识。
    伦理和社会考量:AGI的发展需要考虑隐私、安全和就业等伦理和社会问题。
    增强学习和自适应能力:未来的AGI系统可能利用先进的算法,从环境中学习并优化行为。
    多模态交互:AGI将具备多种感知和交互方式,与人类和其他系统交互。

Hugging Face作为当前全球最受欢迎的开源机器学习社区和平台之一,在AGI时代扮演着重要角色。它提供了丰富的预训练模型和数据集资源,推动了机器学习领域的发展。Hugging Face的特点在于易用性和开放性,通过其Transformers库,为用户提供了方便的模型处理文本的方式。随着AI技术的发展,Hugging Face社区将继续发挥重要作用,推动AI技术的发展和应用,尤其是在多模态AI技术发展方面,Hugging Face社区将扩展其模型和数据集的多样性,包括图像、音频和视频等多模态数据。

  • 在AGI时代,Hugging Face可能会通过以下方式发挥作用:

        模型共享:作为模型共享的平台,Hugging Face将继续促进先进的AGI模型的共享和协作。
        开源生态:Hugging Face的开源生态将有助于加速AGI技术的发展和创新。
        工具和服务:提供丰富的工具和服务,支持开发者和研究者在AGI领域的研究和应用。
        伦理和社会责任:Hugging Face注重AI伦理,将推动负责任的AGI模型开发和应用,确保技术进步同时符合伦理标准。

AGI作为未来人工智能的高级形态,具有广泛的应用前景,而Hugging Face作为开源社区,将在推动AGI的发展和应用中扮演关键角色。

(注意:以下代码运行,可能需要科学上网)

二、零样本学习 (Zero-shot Learning) 和少样本学习 (Few-shot Learning)

1、零样本学习 (Zero-shot Learning)

定义: 零样本学习是一种让模型能够在没有见过目标类别数据的情况下进行预测的技术。它主要依赖于预训练的语言模型和自然语言描述,利用模型在预训练期间学到的广泛知识来理解和推断新的任务。

实现方式:

  • 基于语言模型的零样本分类: 使用预训练的语言模型,如 BERT、GPT-3,通过自然语言提示 (prompt) 进行分类。Hugging Face 提供了 zero-shot-classification pipeline,使这一过程非常简单。

from transformers import pipeline# 加载零样本分类 pipeline
zero_shot_classifier = pipeline("zero-shot-classification")# 定义待分类的文本
text = "Hugging Face's library is so easy to use!"# 定义候选标签
labels = ["education", "politics", "technology"]# 进行零样本分类
result = zero_shot_classifier(text, candidate_labels=labels)
print(result)
  • 利用嵌入向量和距离度量: 通过计算文本嵌入向量之间的相似度来实现零样本分类。模型在预训练期间学到的嵌入空间使得相似类别的文本在向量空间中更接近。

  • 自然语言推理 (NLI): 使用 NLI 模型,如 RoBERTa,对于每个候选标签,模型判断该标签是否是输入文本的合理推断。Hugging Face 提供了类似的模型,可以通过 NLI 的方式实现零样本学习。

from transformers import pipeline# 加载 NLI 模型
nli_model = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")# 定义待分类的文本
text = "Hugging Face's library is so easy to use!"# 定义候选标签
labels = ["education", "politics", "technology"]# 进行零样本分类
result = nli_model(text, candidate_labels=labels)
print(result)
2、少样本学习 (Few-shot Learning)

定义: 少样本学习是一种让模型能够在只见过少量目标类别数据的情况下进行有效预测的技术。它通过对预训练模型进行微调,利用少量标注数据来学习新的任务。

实现方式:

  • 基于预训练模型的微调: 使用预训练的 Transformer 模型(如 BERT、GPT-3),通过少量标注数据进行微调。Hugging Face 提供了 Trainer API,可以方便地进行微调。

from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from datasets import Dataset# 示例少样本数据
data = {"text": ["I love using Hugging Face!", "The library is very intuitive."],"label": [1, 1]
}# 创建 Dataset 对象
dataset = Dataset.from_dict(data)# 加载预训练模型和分词器
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)# 数据预处理
def preprocess_data(examples):return tokenizer(examples["text"], truncation=True, padding=True)tokenized_dataset = dataset.map(preprocess_data, batched=True)# 设置训练参数
training_args = TrainingArguments(output_dir="./results",num_train_epochs=3,per_device_train_batch_size=2,logging_dir="./logs",
)# 创建 Trainer 实例
trainer = Trainer(model=model,args=training_args,train_dataset=tokenized_dataset
)# 开始训练
trainer.train()
  • Prompt-based 学习: 通过设计好的提示语(prompts)进行少样本学习,将少量数据转化为对模型的提示。这个方法在 GPT-3 等模型上表现出色。

  • 元学习 (Meta-learning): 利用元学习算法,如 MAML (Model-Agnostic Meta-Learning),训练模型在少量新数据上快速适应。虽然 Hugging Face 目前没有直接的元学习 API,但可以结合 PyTorch 等库实现。

零样本学习和少样本学习是解决数据有限情况下进行有效机器学习的重要方法。通过使用 Hugging Face 提供的预训练模型和工具,可以轻松实现这两种技术:

  • 零样本学习: 依赖于预训练语言模型,通过自然语言提示或嵌入向量实现分类和推理。
  • 少样本学习: 通过微调预训练模型或使用提示语进行学习,以适应新的任务和数据。

在每一个算法专家的脑海中都有一个根深蒂固的观念,那就是在每个新项目开始的时候都会面临一个问题:有多少标注数据?大多数时候面临的情况是“没有”或者是“很少”,但是需求方可不关心这个,模型必须具备符合预期的效果。而现实是,在小型数据集上训练出的模型通常不会有很好的效果,最有成效的解决方案是标注更多的数据提供给模型进行训练。然而,数据标注的过程需要耗费很多的人力物力,尤其是那种需要具备专业知识才能进行标注的数据。

幸运的是,对于这种标注数据短缺的情况,业界已经有了一些解决方案。你可能已经对其中的某些方法有所耳闻,比如零样本学习(zero-shot learning)或少样本学习(few-shot learning),而GPT-3模型甚至可以仅用几十个样本就能处理各种不同的任务,让人感到非常惊奇。

一般来说,模型的最优效果取决于任务、可用数据,以及可用数据被标注的比例。下图所示的结构能一定程度上帮助我们选择最恰当的方法。

在缺少大量标注数据的情况下,可用于提高模型性能的几种技术

按步骤对图中结构做如下说明:

  • 1.

我们需要明白,即使拥有少量的标注数据也可以为模型带来正向收益。如果根本没有标注数据,则可以用零样本学习方法,这通常会设置一个较高的基线指标。

  • 2.

如果拥有标注数据,那么决定模型性能的因素就是标注数据所占的比例。如果有大量的标注数据用于模型训练,就可以使用第2章介绍的标准微调方法来进行处理。

  • 3.

Q. Xie et al., “Unsupervised Data Augmentation for Consistency Training”(https://arxiv.org/abs/1904.12848),(2019); S. Mukherjee and A.H. Awadallah,“Uncertainty-Aware Self-Training for Few-Shot Text Classification”(https://arxiv.org/abs/2006.15315),(2020).

如果只有少量的标注数据,但是还有大量未标注的原始数据,则这对于模型训练也是很有帮助的。假如能获得这样的未标注数据,就能在训练分类器(Classfier)之前用它来微调模型,或者使用更复杂的方法,如无监督数据增强(Unsupervised Data Augmentation,UDA)或不确定性自我训练(Uncertainty-aware Self-Training,UST) 。如果没有这样的未标注数据,也就无法去标注更多的数据,在这种情况下,就需要使用少样本学习技术,或者使用预训练语言模型的嵌入,通过最近邻搜索(nearest neighbor search),来对目标进行分类。

接下来将基于上图的思路来帮助我们在使用Jira(https://oreil.ly/TVqZQ)或GitHub(https://oreil.ly/e0Bd1)的时候,根据issue的描述自动为其打上标注。这些标注包括issue类型、导致issue的组件名称,或者负责issue的团队。将这种打标注的任务进行自动化处理,会对生产力产生很大的影响,因为这样就可以让项目的维护团队能够专注于解决用户提出的问题,而不是将时间浪费在对问题的分类上面。本章以Hugging Face在GitHub上的Transformers代码仓库为例,带领大家分析此代码仓库中的issue,学习如何构建此类任务,以及如何获取数据。

接下来介绍的方法适用于文本分类场景,但在处理命名实体识别任务、问答任务或文本摘要生成任务这些更复杂的任务时,则可能需要其他技术的加持,比如数据增强。

三、GitHub issue

进入Transformers代码仓库的issue模块(https://oreil.ly/StdH3),点击其中一个issue,就会得到如下图所示的页面,该页面包含一个标题、一段描述和一组标注集合。因此,该任务可以看作给定标题和描述,预测一个或多个标注,是一个典型的多标注文本分类任务。这比在之前介绍中遇到的多分类问题更具有挑战性,因为在之前介绍中,每条推文只会被标注一种情感。

Hugging Face Transformers代码仓库中某个GitHub issue的详细展示页面

1、

为了获取代码仓库中的所有issue信息,我们将使用GitHub提供的REST API(https://oreil.ly/q605k)来轮询issues端点(https://oreil.ly/qXdWV)。这个端点会返回一个JSON对象列表,每个对象都包含大量关于当前issue的字段,包括该issue的状态(打开或关闭)、issue的发起者,以及在上图中可以看到的标题、正文和标注。

由于获取所有issue信息需要耗费一些时间,本书的GitHub代码仓库(https://oreil.ly/if2dm)中提供了一个github-issues-transformers.jsonl文件,以及一个fetch_issues()函数,你可以自行下载它们。

GitHub REST API会将pull请求也加入issue当中,因此我们的数据集里面混合了原始issue和pull请求issue。为了不让任务变得复杂,我们将为这两种类型的issue开发分类器。其实在实践中,我们也很可能会构建两个分类器,因为这样方便对模型的性能进行更精细的控制。

现在我们知道了如何获取目标数据,下面来看看如何处理这些数据。

2、准

当我们下载好了所有的issue,就可以使用Pandas来加载它们:

# 导入 pandas 库,并使用缩写 pd 以便后续使用
import pandas as pd# 定义数据集 URL,该 URL 指向一个 JSON 数据文件
dataset_url = "https://git.io/nlp-with-transformers"# 使用 pandas 的 read_json 函数读取 JSON 数据,指定 lines=True 表示每行都是一个 JSON 对象
df_issues = pd.read_json(dataset_url, lines=True)# 打印 DataFrame 的形状(行数和列数)
print(f"DataFrame shape: {df_issues.shape}")# 该行代码输出 DataFrame 的形状,格式为 (行数, 列数)

运行结果:

DataFrame shape: (9930, 26)

结果显示,在数据集中有近10 000个issue,查看单行数据,我们可以看到其中包含的许多字段,如URL、ID、日期、用户、标题、正文以及标注:

# 定义要选择的列名称列表
cols = ["url", "id", "title", "user", "labels", "state", "created_at", "body"]# 使用 loc 索引器选择 DataFrame 中的第 2 行,并仅选择指定的列
# 然后将该行转换为一个 DataFrame
df_issues.loc[2, cols].to_frame()# 该行代码将第 2 行指定列的值转换为 DataFrame 并返回
# loc[2, cols] 选择 DataFrame 中的第 2 行和指定的列
# to_frame() 将结果转换为单列 DataFrame

运行结果:

其中的labels列就是标注数据,它包含了一个JSON对象列表,示例如下:

[{'id': 2659267025,'node_id': 'MDU6TGFiZWwyNjU5MjY3MDI1','url': 'https://api.github.com/repos/huggingface/transformers/labels/DeepSpeed','name': 'DeepSpeed','color': '4D34F7','default': False,'description': ''
}]

每个JSON对象都包含一个标注的信息,这里我们需要的信息是标注的名称,也就是其中的name字段,下面将标注名称提取出来覆盖labels列的内容:

# 对 DataFrame 的 "labels" 列进行操作
# 使用 apply 函数对每个元素应用 lambda 函数
# lambda 函数提取每个元素(列表)中字典的 "name" 属性,并返回包含这些名称的新列表
df_issues["labels"] = (df_issues["labels"].apply(lambda x: [meta["name"] for meta in x]))# 打印 DataFrame 的 "labels" 列的前几行
print(df_issues[["labels"]].head())# 这段代码将 "labels" 列中的每个元素(列表)中的字典的 "name" 属性提取出来,形成新的列表
# 然后,打印 "labels" 列的前几行,查看转换后的结果

运行结果:

        labels
0           []
1           []
2  [DeepSpeed]
3           []
4           []

现在,labels列中的每行都是GitHub的标注名称列表,这样就能得出issue的标注数量分布情况:

# 对 DataFrame 的 "labels" 列进行操作
# 使用 apply 函数对每个元素应用 lambda 函数
# lambda 函数计算每个元素(列表)的长度
labels_length_counts = df_issues["labels"].apply(lambda x : len(x)).value_counts()# 将结果转换为 DataFrame 并转置
labels_length_counts_df = labels_length_counts.to_frame().T# 打印转置后的 DataFrame
print(labels_length_counts_df)# 这段代码首先计算 "labels" 列中每个元素(列表)的长度
# 然后统计这些长度出现的频率,并将结果转换为 DataFrame 并转置,以便更直观地查看结果

运行结果:

labels     0     1    2    3   4  5
count   6440  3057  305  100  25  3

可以看出,大多数issue都有0或1个标注,有1个以上标注的issue则少得多。下面我们来看看数据集中最频繁出现的10个标注。在Pandas中,可以通过explode()函数来展开Labels列,这样列表中的每个标注会成为行,然后简单计算每个标注出现的次数:

# 对 DataFrame 的 "labels" 列进行操作
# 使用 explode 函数将列表展开,将每个列表元素分成单独的行
df_counts = df_issues["labels"].explode().value_counts()# 打印标签的总数
print(f"Number of labels: {len(df_counts)}")# 将结果转换为 DataFrame 并显示前 8 个标签类别的频次
top_8_labels = df_counts.to_frame().head(8).T# 打印前 8 个标签类别的 DataFrame
print(top_8_labels)# 这段代码首先将 "labels" 列中的列表展开成单独的行,然后统计每个标签的出现频率
# 打印标签的总数量,并将频次最高的前 8 个标签转换为 DataFrame 并打印

运行结果:

Number of labels: 65
labels  wontfix  model card  Core: Tokenization  New model  Core: Modeling  \
count      2284         649                 106         98              64   labels  Help wanted  Good First Issue  Usage  
count            52                50     46  

从结果可以看出,数据集中有65种标注,这些标注数量差异很大,分布非常不均匀。其中“wontfix”和“model card”是最频繁出现的标注。有些标注很难通过标题来推测,比如“Good First Issue”或“Help Wanted”;有些标注可以基于规则来判断,比如“model card”,就可以根据仓库是否添加模型卡片来确定。所以,能够用于预测的标注只是标注集的一个子集,要把不需要预测的标注去除掉。

以下代码段对数据集进行过滤,以获得我们要处理的标注子集,同时对标注名称进行规范化处理,使其更易阅读:

# 定义标签映射字典,将原始标签映射到新标签
label_map = {"Core: Tokenization": "tokenization","New model": "new model","Core: Modeling": "model training","Usage": "usage","Core: Pipeline": "pipeline","TensorFlow": "tensorflow or tf","PyTorch": "pytorch","Examples": "examples","Documentation": "documentation"}# 定义过滤标签的函数
# 该函数接收一个标签列表,返回映射后的标签列表
def filter_labels(x):return [label_map[label] for label in x if label in label_map]# 对 DataFrame 的 "labels" 列应用过滤标签的函数
df_issues["labels"] = df_issues["labels"].apply(filter_labels)# 生成所有标签的列表
all_labels = list(label_map.values())# 打印转换后的 "labels" 列和所有标签列表
print(df_issues["labels"].head())  # 查看转换后的 "labels" 列前几行
print(all_labels)  # 打印所有标签列表

运行结果:

0    []
1    []
2    []
3    []
4    []
Name: labels, dtype: object
['tokenization', 'new model', 'model training', 'usage', 'pipeline', 'tensorflow or tf', 'pytorch', 'examples', 'documentation']

现在我们来看看新标注的分布情况:

# 对 DataFrame 的 "labels" 列进行操作
# 使用 explode 函数将列表展开,将每个列表元素分成单独的行
df_counts = df_issues["labels"].explode().value_counts()# 将结果转换为 DataFrame 并转置
df_counts_df = df_counts.to_frame().T# 打印转置后的 DataFrame
print(df_counts_df)# 这段代码首先将 "labels" 列中的列表展开成单独的行,然后统计每个标签的出现频率
# 将频次结果转换为 DataFrame 并转置,以便更直观地查看结果

运行结果:

labels  tokenization  new model  model training  usage  pipeline  \
count            106         98              64     46        42   labels  tensorflow or tf  pytorch  documentation  examples  
count                 41       37             28        24  

在后面的内容中,我们会发现,未打标注的issue在训练过程中可以当作单独的分片来处理。所以这里我们创建一个新的列,来表示该issue是否打了标注:

# 新增一列 "split",初始值全部设置为 "unlabeled"
df_issues["split"] = "unlabeled"# 创建一个布尔掩码,标识 "labels" 列中非空列表的行
mask = df_issues["labels"].apply(lambda x: len(x)) > 0# 使用 loc 索引器和掩码,将 "labels" 列中非空列表的行的 "split" 列值设置为 "labeled"
df_issues.loc[mask, "split"] = "labeled"# 统计 "split" 列中每个类别的数量,并将结果转换为 DataFrame
split_counts_df = df_issues["split"].value_counts().to_frame()# 打印 "split" 列中每个类别的数量
print(split_counts_df)# 这段代码首先新增一列 "split",初始值全部设置为 "unlabeled"
# 然后根据 "labels" 列的内容创建一个布尔掩码,将包含标签的行的 "split" 列值设置为 "labeled"
# 最后统计 "split" 列中每个类别的数量,并将结果转换为 DataFrame 以便查看

运行结果:

           count
split           
unlabeled   9489
labeled      441

下面来看一个例子:

# 对指定的列进行操作,打印每列第 26 行的前 500 个字符
# 使用一个 for 循环遍历列名称列表
for column in ["title", "body", "labels"]:# 打印列名称print(f"{column}: {df_issues[column].iloc[26][:500]}\n")# 这段代码对 "title"、"body" 和 "labels" 列进行操作
# 对于每列,使用 iloc 索引器选择 DataFrame 中第 26 行
# 然后切片 [:500] 获取前 500 个字符,并打印结果

运行结果:

title: Add new CANINE modelbody: # 🌟 New model addition## Model descriptionGoogle recently proposed a new **C**haracter **A**rchitecture with **N**o tokenization **I**n **N**eural **E**ncoders architecture (CANINE). Not only the title is exciting:> Pipelined NLP systems have largely been superseded by end-to-end neural modeling, yet nearly all commonly-used models still require an explicit tokenization step. While recent tokenization approaches based on data-derived subword lexicons are less brittle than manually enlabels: ['new model']

在该例子中,我们提出了一种新的模型架构,因此new model的添加对于该模型是有意义的。还可以看出,title包含了对分类器有用的信息,所以可以把它和body字段中的issue描述拼接起来:

# 使用 apply 函数将 "title" 和 "body" 列的内容合并到新列 "text" 中
df_issues["text"] = (df_issues.apply(lambda x: x["title"] + "\n\n" + x["body"], axis=1))# 这段代码使用 apply 函数,对 DataFrame 中的每一行应用 lambda 函数
# lambda 函数接收行对象 x,将其 "title" 和 "body" 列的内容合并成一个新的字符串
# 然后将合并后的字符串赋值给新列 "text"

在查看其他数据之前,先检查一下数据中是否有重复的地方,如果有,则使用drop_duplicates()方法将它们去重:

# 计算操作前 DataFrame 的行数
len_before = len(df_issues)# 使用 drop_duplicates 函数基于 "text" 列删除重复的行
df_issues = df_issues.drop_duplicates(subset="text")# 计算删除重复行的比例,并打印提示信息
removed_percentage = (len_before - len(df_issues)) / len_before
print(f"Removed {removed_percentage:.2%} duplicates.")# 这段代码首先计算操作前 DataFrame 的行数
# 然后使用 drop_duplicates 函数基于 "text" 列删除重复的行
# 最后计算删除重复行的比例,并打印提示信息显示删除的比例

运行结果:

Removed 1.88% duplicates.

从去重结果中可以获知,在我们的数据集中有一些重复的issue,但只占很小的比例(1.88%)。下面我们先查看文本的单词数量,再按照模型的上下文大小将其截断,看看是否会丢失信息:

import numpy as np
import matplotlib.pyplot as plt# 使用 str.split() 和 apply(len) 计算每个 "text" 列中的单词数量,并绘制直方图
(df_issues["text"].str.split().apply(len).hist(bins=np.linspace(0, 500, 50), grid=False, edgecolor="C0"))# 添加标题和轴标签
plt.title("Words per issue")
plt.xlabel("Number of words")
plt.ylabel("Number of issues")# 保存图表为文件,并设置自适应大小
plt.savefig('images/WordsPerIssue.png', bbox_inches='tight')# 显示图形
plt.show()# 这段代码首先使用 str.split() 将每个 "text" 列中的文本按单词分割成列表
# 然后使用 apply(len) 计算每个列表的长度(即单词数量),并绘制直方图
# 设置直方图的 bins、grid、edgecolor 参数
# 最后添加标题和轴标签,并显示绘制的直方图

运行结果:

以上分布结果符合大多数文本数据集的长尾特征,因为大多数issue都较短,很少有超过500个单词的issue。有超过500个单词的长issue也是很常见的,特别是当错误消息和代码片段合并到一起发布成为issue时。鉴于大多数Transformer模型的上下文大小为512个词元或更多,截断少数的长issue也不会对整体性能有什么大的影响。现在我们已经探索和清洗了我们的数据集,在这之后要做的是创建训练集和验证集,以便为我们的分类器设定基准指标。下面来看看如何实现。

3、创

对于多标注任务来说,创建训练集和验证集相对麻烦,因为无法保证所有标注在两个数据集分布情况一致。我们可以做一些近似操作,使用Scikit-multilearn库(http://scikit.ml)的MultiLabelBinarizer类将标注转换为独热编码形式。先传入一个标注名称列表,并创建一个向量,其中的0代表没有的标注,1代表有的标注。下面我们将all_labels传给MultiLabelBinarizer进行拟合,来看看标注名称与ID的映射关系,如下所示:

from sklearn.preprocessing import MultiLabelBinarizer# 初始化 MultiLabelBinarizer 对象
mlb = MultiLabelBinarizer()# 使用所有标签列表进行拟合
mlb.fit([all_labels])# 对两个示例标签列表进行二值化处理
# 第一个标签列表包含 "tokenization" 和 "new model"
# 第二个标签列表包含 "pytorch"
binary_labels = mlb.transform([["tokenization", "new model"], ["pytorch"]])# 打印二值化后的标签
print(binary_labels)# 这段代码首先初始化一个 MultiLabelBinarizer 对象
# 然后使用所有标签列表对其进行拟合
# 接下来,对两个示例标签列表进行二值化处理,得到二值化后的标签矩阵
# 最后打印二值化后的标签矩阵

运行结果:

[[0 0 0 1 0 0 0 1 0][0 0 0 0 0 1 0 0 0]]

在这个简单例子中,我们可以看到第一行有两个1分别对应于tokenization和new model的标注,而第二行只有一个1对应于pytorch。

然后使用Scikit-multilearn的iterative_train_test_split()函数将数据拆分为训练集和测试集。由于我们采用迭代式生成训练样本和测试样本,因此这个函数可以保证生成的训练集和测试集中的标注分布是一致的。我们把它封装成一个可以应用于DataFrames的函数,由于该函数期望有一个二维特征矩阵,我们需要在进行拆分之前给可能的索引增加一个维度:

# 导入 iterative_train_test_split 函数,用于进行分层多标签数据集划分
from skmultilearn.model_selection import iterative_train_test_split# 定义一个函数,进行平衡的训练集和测试集划分
def balanced_split(df, test_size=0.5):# 创建索引数组,并扩展维度以适应 split 函数的输入要求ind = np.expand_dims(np.arange(len(df)), axis=1)# 使用 MultiLabelBinarizer 将标签进行二值化labels = mlb.transform(df["labels"])# 使用 iterative_train_test_split 函数进行分层多标签数据集划分ind_train, _, ind_test, _ = iterative_train_test_split(ind, labels, test_size)# 根据划分的索引返回训练集和测试集return df.iloc[ind_train[:, 0]], df.iloc[ind_test[:, 0]]# 这段代码首先导入 iterative_train_test_split 函数,用于进行分层多标签数据集划分
# 定义了一个函数 balanced_split,该函数接收一个 DataFrame 和测试集比例 test_size
# 函数内部首先创建索引数组,并扩展维度以适应 split 函数的输入要求
# 使用 MultiLabelBinarizer 将标签进行二值化处理
# 然后使用 iterative_train_test_split 函数根据索引数组和二值化标签进行分层多标签数据集划分
# 最后根据划分的索引返回训练集和测试集

(如果没有安装skmultilearn,jupyter notebook 上使用命令  !pip install scikit-multilearn 安装)

定义好了balanced_split()函数,我们可以把数据拆分成监督和无监督部分组成的数据集,然后为监督部分创建标注分布一致的训练、验证和测试集:

from sklearn.model_selection import train_test_split# 创建一个新的 DataFrame,仅包含 "text"、"labels" 和 "split" 列,并重置索引
df_clean = df_issues[["text", "labels", "split"]].reset_index(drop=True).copy()# 提取未标注的数据集
df_unsup = df_clean.loc[df_clean["split"] == "unlabeled", ["text", "labels"]]# 提取已标注的数据集
df_sup = df_clean.loc[df_clean["split"] == "labeled", ["text", "labels"]]# 设置随机种子以确保结果可复现
np.random.seed(0)# 使用 balanced_split 函数,将已标注的数据集划分为训练集和临时集,比例为 50%
df_train, df_tmp = balanced_split(df_sup, test_size=0.5)# 再次使用 balanced_split 函数,将临时集划分为验证集和测试集,比例为 50%
df_valid, df_test = balanced_split(df_tmp, test_size=0.5)# 打印数据集的大小
print(f"Train set size: {len(df_train)}, Validation set size: {len(df_valid)}, Test set size: {len(df_test)}")# 这段代码首先创建一个仅包含 "text"、"labels" 和 "split" 列的 DataFrame,并重置索引
# 然后根据 "split" 列的值,提取未标注和已标注的数据集
# 设置随机种子以确保结果可复现
# 使用 balanced_split 函数,将已标注的数据集划分为训练集和临时集,比例为 50%
# 再次使用 balanced_split 函数,将临时集划分为验证集和测试集,比例为 50%
# 最后打印数据集的大小,确保划分的结果符合预期

运行结果:

Train set size: 223, Validation set size: 106, Test set size: 111

接着,将它们转为DatasetDict格式,这样就能轻松对数据集进行词元化,并与Trainer整合,方便训练。这里,我们将使用from_pandas()方法,直接从相应的Pandas DataFrame中加载每个拆分部分:

from datasets import Dataset, DatasetDict# 将训练、验证、测试和未标注的数据集 DataFrame 转换为 Hugging Face 的 Dataset 对象
# 并将这些 Dataset 对象存储在 DatasetDict 中
ds = DatasetDict({"train": Dataset.from_pandas(df_train.reset_index(drop=True)),  # 将训练集 DataFrame 转换为 Dataset 对象"valid": Dataset.from_pandas(df_valid.reset_index(drop=True)),  # 将验证集 DataFrame 转换为 Dataset 对象"test": Dataset.from_pandas(df_test.reset_index(drop=True)),    # 将测试集 DataFrame 转换为 Dataset 对象"unsup": Dataset.from_pandas(df_unsup.reset_index(drop=True))   # 将未标注的数据集 DataFrame 转换为 Dataset 对象
})# 这段代码首先导入所需的 Dataset 和 DatasetDict 模块
# 然后将训练集、验证集、测试集和未标注的数据集 DataFrame 转换为 Hugging Face 的 Dataset 对象
# 使用 reset_index(drop=True) 来重置 DataFrame 的索引
# 最后,将这些 Dataset 对象存储在 DatasetDict 中,以便于后续的处理和使用

最后,创建一些训练切片,以便评估每个分类器的性能与训练集大小的关系。

4、创

该数据集具有两个我们在本章要研究的特性:稀疏标注数据和多标注分类。训练集中只有220个可供训练的样本,即使是迁移学习,这也是一个挑战。为了深入研究模型在小标注数据量上的效果,我们还将会创建样本更少的训练数据切片。然后将样本的数量与性能做比较,研究模型在不同数据量下的效果。我们将从每个标记只有8个样本开始,使用iterative_train_test_split()函数创建有不同样本的数据集,直到覆盖全部训练集:

# 设置随机种子以确保结果可复现
np.random.seed(0)# 获取训练集的所有索引,并扩展维度以适应 split 函数的输入要求
all_indices = np.expand_dims(list(range(len(ds["train"]))), axis=1)# 初始化索引池为所有训练集的索引
indices_pool = all_indices# 将训练集的标签进行二值化处理
labels = mlb.transform(ds["train"]["labels"])# 定义需要创建的训练样本集的大小
train_samples = [8, 16, 32, 64, 128]# 初始化存储不同大小训练样本集的列表和上一次划分的样本数
train_slices, last_k = [], 0# 循环遍历每一个训练样本集的大小
for i, k in enumerate(train_samples):# 使用 iterative_train_test_split 函数,从索引池中分离出新的样本集indices_pool, labels, new_slice, _ = iterative_train_test_split(indices_pool, labels, (k-last_k)/len(labels))# 更新上一次划分的样本数last_k = k# 如果是第一个样本集,直接添加到 train_slicesif i == 0: train_slices.append(new_slice)# 否则,将新样本集与上一个样本集合并后添加到 train_sliceselse: train_slices.append(np.concatenate((train_slices[-1], new_slice)))# 添加完整数据集作为最后一个样本集
train_slices.append(all_indices)
train_samples.append(len(ds["train"]))# 将每个样本集的索引从二维数组压缩为一维数组
train_slices = [np.squeeze(train_slice) for train_slice in train_slices]# 这段代码首先设置随机种子以确保结果可复现
# 获取训练集的所有索引,并扩展维度以适应 split 函数的输入要求
# 将训练集的标签进行二值化处理
# 定义需要创建的训练样本集的大小
# 初始化存储不同大小训练样本集的列表和上一次划分的样本数
# 循环遍历每一个训练样本集的大小,使用 iterative_train_test_split 函数,从索引池中分离出新的样本集
# 更新上一次划分的样本数,如果是第一个样本集,直接添加到 train_slices,否则将新样本集与上一个样本集合并后添加到 train_slices
# 添加完整数据集作为最后一个样本集,将每个样本集的索引从二维数组压缩为一维数组

注意,这种迭代方法只是将样本近似拆分成所需大小,因为如果严格指定大小,并不一定能找到标注分布一致的数据集分片:

# 打印目标训练样本集的大小
print("Target split sizes:")
print(train_samples)# 打印实际训练样本集的大小
print("Actual split sizes:")
print([len(x) for x in train_slices])# 这段代码首先打印目标训练样本集的大小,即我们希望创建的样本集大小列表
# 然后打印实际生成的每个训练样本集的大小,确保生成的样本集大小与目标大小一致

运行结果:

Target split sizes:
[8, 16, 32, 64, 128, 223]
Actual split sizes:
[10, 19, 36, 68, 134, 223]

我们将使用特定的数据集分片大小作为后文图表的刻度。到这里,我们准备好了用于训练的数据集分片,接下来看看如何训练一个强大的基线模型。

四、基线

——朴素贝叶斯

每当开启一个新的NLP项目时,创建一个强大的基线模型是很有必要的,主要有两个原因:

  • 1.如果能够使用正则表达式,或借助一个非常简单的模型就能解决实际问题,那么就没有必要使用更复杂的模型,因为其带来的提升也会非常有限,而且像Transformer这样的模型的部署和维护通常也比较复杂。
  • 2.基线模型可以给复杂模型提供快速对比的参考依据。例如,假设你训练了一个BERT-large模型,并在验证集仅获得了80%的准确率,你很可能会认为这是验证集而不是模型的问题。在这之后,当你知道像逻辑回归这样的简单分类器也能得到95%的准确率时,你就会转变思路,认为验证集是没有问题的,而是模型出了问题,然后你就可能会去一遍遍地调试你的模型。

因此,训练一个不错的基线模型是很有必要的。对于文本分类这样的任务来说,朴素贝叶斯分类器(Naive Bayes classifier)是最基础的分类模型之一,它非常简单,能够快速训练,并有一定的稳健性。朴素贝叶斯的Scikit-learn实现并不具备开箱即用的多标注分类能力,不过我们可以使用Scikit-multilearn库将多标注分类转换为对每个标注的二分类问题,为L标注训练L个二进制分类器。首先使用MultiLabelBinarizer在我们的训练集中创建一个新的label_ids列,再使用map()函数来一次性解决所有处理步骤:

# 定义一个函数,用于准备标签
def prepare_labels(batch):# 使用 MultiLabelBinarizer 将标签转换为二值化的标签 IDbatch["label_ids"] = mlb.transform(batch["labels"])return batch# 将 prepare_labels 函数映射到整个数据集上,应用于每个批次
ds = ds.map(prepare_labels, batched=True)# 这段代码首先定义了一个函数 prepare_labels,用于将标签转换为二值化的标签 ID
# 然后使用 Dataset 对象的 map 方法,将 prepare_labels 函数应用于整个数据集的每个批次
# 通过 batched=True 参数,确保函数以批处理方式应用,能够高效地处理较大的数据集

运行结果:

为了评估分类器的性能,这里使用微观和宏观的F1分数,其中前者跟踪出现较频繁的标注上的性能,后者跟踪不考虑频率的所有标注上的性能。由于我们将在不同大小的训练分片中评估每个模型,因此我们将创建一个defaultdict,用一个列表来保存每个训练分片的分数:

from collections import defaultdict# 初始化存储宏观评分和微观评分的字典
# 这两个字典将用于存储不同训练样本集大小下的评估结果
macro_scores, micro_scores = defaultdict(list), defaultdict(list)# 这段代码首先导入 defaultdict 模块
# 然后分别初始化宏观评分(macro_scores)和微观评分(micro_scores)的字典
# 这些字典用于存储不同训练样本集大小下的评估结果,以便后续进行比较和分析

到这一步,训练基线模型的准备工作已经就绪,下面是训练基线模型的代码,在训练集规模持续增加的情况下评估分类器性能:

from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
from skmultilearn.problem_transform import BinaryRelevance
from sklearn.feature_extraction.text import CountVectorizer# 循环遍历每个训练样本集
for train_slice in train_slices:# 获取当前训练样本集和测试数据ds_train_sample = ds["train"].select(train_slice)y_train = np.array(ds_train_sample["label_ids"])y_test = np.array(ds["test"]["label_ids"])# 使用简单的词频向量化器将文本编码为词频计数count_vect = CountVectorizer()X_train_counts = count_vect.fit_transform(ds_train_sample["text"])X_test_counts = count_vect.transform(ds["test"]["text"])# 创建并训练模型classifier = BinaryRelevance(classifier=MultinomialNB())classifier.fit(X_train_counts, y_train)# 生成预测并进行评估y_pred_test = classifier.predict(X_test_counts)clf_report = classification_report(y_test, y_pred_test, target_names=mlb.classes_, zero_division=0,output_dict=True)# 存储评估指标macro_scores["Naive Bayes"].append(clf_report["macro avg"]["f1-score"])micro_scores["Naive Bayes"].append(clf_report["micro avg"]["f1-score"])# 这段代码首先导入必要的库,包括 MultinomialNB、classification_report、BinaryRelevance 和 CountVectorizer
# 然后在 for 循环中,遍历每个训练样本集,获取当前训练样本集和测试数据
# 使用 CountVectorizer 将文本编码为词频计数
# 创建并训练朴素贝叶斯模型,生成预测并进行评估,最后将评估指标存储在宏观评分和微观评分的字典中

上面是一段较长的代码片段,这其中进行了许多操作,我们来一探究竟。首先,我们获取训练切片并对标注进行编码。然后,我们使用CountVectorizer对文本进行编码,简单地创建一个与词表大小相关的向量,其中每个条目对应于文本中某一个词元出现的频率。这种方式被称为词袋(bag-of-words),因为所有词汇的顺序信息都会丢失。最后,我们训练分类器,并在测试集上使用预测结果,通过分类报告获得微观和宏观的F1分数。

使用下面的辅助函数,可以绘制这个实验的结果:

import matplotlib.pyplot as pltdef plot_metrics(micro_scores, macro_scores, sample_sizes, current_model, save_path=None):"""绘制微观和宏观 F1 分数随训练样本数量变化的曲线图,并可选择保存图片。Args:- micro_scores (dict): 包含每个模型微观 F1 分数的字典。- macro_scores (dict): 包含每个模型宏观 F1 分数的字典。- sample_sizes (list): 训练样本集大小的列表。- current_model (str): 当前模型的名称,将用粗实线表示。- save_path (str or None, optional): 图片保存路径。如果为 None,则不保存图片,默认为 None。Returns:- None: 直接显示绘制的图形或保存图片到指定路径。"""fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(10, 4), sharey=True)for run in micro_scores.keys():if run == current_model:ax0.plot(sample_sizes, micro_scores[run], label=run, linewidth=2)ax1.plot(sample_sizes, macro_scores[run], label=run, linewidth=2)else:ax0.plot(sample_sizes, micro_scores[run], label=run, linestyle="dashed")ax1.plot(sample_sizes, macro_scores[run], label=run, linestyle="dashed")ax0.set_title("Micro F1 scores")ax1.set_title("Macro F1 scores")ax0.set_ylabel("Test set F1 score")ax0.legend(loc="lower right")for ax in [ax0, ax1]:ax.set_xlabel("Number of training samples")ax.set_xscale("log")ax.set_xticks(sample_sizes)ax.set_xticklabels(sample_sizes)ax.minorticks_off()plt.tight_layout()# 判断是否保存图片if save_path:plt.savefig(save_path, bbox_inches='tight')else:plt.show()# 绘制 Naive Bayes 模型的性能指标曲线图
plot_metrics(micro_scores, macro_scores, train_samples, "Naive Bayes","images/Naive_Bayes.png")

运行结果:

注意,这里将样本的数量绘制在对数坐标轴刻度之上。从结果图中可以看出,随着训练样本数量的增加,微观和宏观F1分数都有所升高。但可用于训练的样本太少,结果包含许多噪声,因为每个切片都可能有不同类别的分布。尽管如此,我们只需要得到这个结果的趋势即可。下面我们看看这些结果与基于Transformer的模型相比效果如何。

附录

一、当前案例环境 pacakge 的 版本如下

Package                   Version
------------------------- --------------
aiohttp                   3.9.5
aiosignal                 1.3.1
alembic                   1.13.2
anyio                     4.4.0
argon2-cffi               23.1.0
argon2-cffi-bindings      21.2.0
arrow                     1.3.0
asttokens                 2.4.1
async-lru                 2.0.4
attrs                     23.2.0
Babel                     2.15.0
beautifulsoup4            4.12.3
bleach                    6.1.0
certifi                   2024.7.4
cffi                      1.16.0
charset-normalizer        3.3.2
colorama                  0.4.6
coloredlogs               15.0.1
colorlog                  6.8.2
comm                      0.2.2
contourpy                 1.2.1
cycler                    0.12.1
datasets                  2.20.0
debugpy                   1.8.2
decorator                 5.1.1
defusedxml                0.7.1
dill                      0.3.8
executing                 2.0.1
fastjsonschema            2.20.0
filelock                  3.15.4
flatbuffers               24.3.25
fonttools                 4.53.1
fqdn                      1.5.1
frozenlist                1.4.1
fsspec                    2024.5.0
greenlet                  3.0.3
h11                       0.14.0
httpcore                  1.0.5
httpx                     0.27.0
huggingface-hub           0.23.4
humanfriendly             10.0
idna                      3.7
ipykernel                 6.29.5
ipython                   8.26.0
ipywidgets                8.1.3
isoduration               20.11.0
jedi                      0.19.1
Jinja2                    3.1.4
joblib                    1.4.2
json5                     0.9.25
jsonpointer               3.0.0
jsonschema                4.23.0
jsonschema-specifications 2023.12.1
jupyter                   1.0.0
jupyter_client            8.6.2
jupyter-console           6.6.3
jupyter_core              5.7.2
jupyter-events            0.10.0
jupyter-lsp               2.2.5
jupyter_server            2.14.2
jupyter_server_terminals  0.5.3
jupyterlab                4.2.3
jupyterlab_pygments       0.3.0
jupyterlab_server         2.27.2
jupyterlab_widgets        3.0.11
kiwisolver                1.4.5
Mako                      1.3.5
MarkupSafe                2.1.5
matplotlib                3.9.1
matplotlib-inline         0.1.7
mistune                   3.0.2
mpmath                    1.3.0
multidict                 6.0.5
multiprocess              0.70.16
nbclient                  0.10.0
nbconvert                 7.16.4
nbformat                  5.10.4
nest-asyncio              1.6.0
networkx                  3.3
notebook                  7.2.1
notebook_shim             0.2.4
numpy                     1.26.4
onnx                      1.16.1
onnxruntime               1.18.1
optuna                    3.6.1
overrides                 7.7.0
packaging                 24.1
pandas                    2.2.2
pandocfilters             1.5.1
parso                     0.8.4
pillow                    10.4.0
pip                       24.1.2
platformdirs              4.2.2
prometheus_client         0.20.0
prompt_toolkit            3.0.47
protobuf                  5.27.2
psutil                    6.0.0
pure-eval                 0.2.2
pyarrow                   16.1.0
pyarrow-hotfix            0.6
pycparser                 2.22
Pygments                  2.18.0
pyparsing                 3.1.2
pyreadline3               3.4.1
python-dateutil           2.9.0.post0
python-json-logger        2.0.7
pytz                      2024.1
pywin32                   306
pywinpty                  2.0.13
PyYAML                    6.0.1
pyzmq                     26.0.3
qtconsole                 5.5.2
QtPy                      2.4.1
referencing               0.35.1
regex                     2024.5.15
requests                  2.32.3
rfc3339-validator         0.1.4
rfc3986-validator         0.1.1
rpds-py                   0.19.0
scikit-learn              1.5.1
scikit-multilearn         0.2.0
scipy                     1.14.0
Send2Trash                1.8.3
sentencepiece             0.2.0
setuptools                70.0.0
six                       1.16.0
sniffio                   1.3.1
soupsieve                 2.5
SQLAlchemy                2.0.31
stack-data                0.6.3
sympy                     1.13.0
terminado                 0.18.1
threadpoolctl             3.5.0
tinycss2                  1.3.0
tokenizers                0.13.3
torch                     2.2.1
tornado                   6.4.1
tqdm                      4.66.4
traitlets                 5.14.3
transformers              4.24.0
types-python-dateutil     2.9.0.20240316
typing_extensions         4.12.2
tzdata                    2024.1
uri-template              1.3.0
urllib3                   2.2.2
wcwidth                   0.2.13
webcolors                 24.6.0
webencodings              0.5.1
websocket-client          1.8.0
wheel                     0.43.0
widgetsnbextension        4.0.11
xxhash                    3.4.1
yarl                      1.9.4

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

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

相关文章

【博主推荐】HTML5实现简洁的实用的个人网站、个人主页七个页面源码

文章目录 1.设计来源1.1 个人主页界面1.2 关于我界面1.3 我的技能界面1.4 我的经验界面1.5 我的教育界面1.6 我的项目界面1.7 联系我界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板,程序开发,在线开发,在线沟通 作者:…

ipsec协议簇(详解)

IPSEC协议簇 IPSEC协议簇 --- 基于网络层的,应用密码学的安全通信协议组 IPV6中,IPSEC是要求强制使用的,但是,IPV4中作为可选项使用 IPSEC可以提供的安全服务 机密性 --- 数据加密 完整性 --- 防篡改可用性 数据源鉴别 -- 身份…

长效代理IP如何选用及代理服务分析

在这个数据为王、信息瞬息万变的时代,长效代理IP成为了众多开发者、数据科学家乃至普通网民手中的一把利器。它不仅能帮助我们解决地域管理,还能在保护隐私的同时,确保数据传输的稳定与安全。但面对市面上琳琅满目的代理服务,如何…

IVI(In-Vehicle Infotainment,智能座舱的信息娱乐系统)

IVI能够实现包括三维导航、实时路况、辅助驾驶等在线娱乐功能。 IVI人机交互形式(三板斧):声音、图像、文字 IVI人机交互媒介I(四件套):中控屏幕(显示、触控)、仪表显示、语言、方…

目标检测 | YOLO v1、YOLO v2、YOLO v3与YOLO v3 SPP理论讲解

☀️教程:霹雳吧啦Wz ☀️链接:https://www.bilibili.com/video/BV1yi4y1g7ro?p1&vd_sourcec7e390079ff3e10b79e23fb333bea49d 一、YOLO v1 针对于two-stage目标检测算法普遍存在的运算速度慢的缺点,YOLO创造性的提出了one-stage目标检测…

2024-07-20 Unity插件 Odin Serializer2 —— 序列化教程

文章目录 1 由根对象决定序列化2 实现 Odin 序列化器2.1 继承已有序列化类2.2 自定义序列化类 3 避免 Unity 无限深度警告4 指定序列化秘钥4.1 External String Reference Resolver4.2 External GUID Reference Resolver4.3 External Index Reference Resolver 4 功能与限制4.1…

为什么我不建议用Excel做进销存系统?

进销存管理系统是一个企业中非常关键的部分,它涉及商品的采购、销售和库存管理等复杂流程。虽然EXCEL作为一个办公软件,它的通用性和灵活性使其能够处理这类数据,但实际上,使用它来构建专业的进销存管理系统存在一些明显的局限性。…

haproxy服务介绍

haproxy 搭建使用开启HAProxy的界面UI配置负载均衡配置web代理 HAProxy(High Availability Proxy)是一个高性能的TCP/HTTP负载均衡器和代理服务器,广泛用于提升Web应用的可用性和性能。[官网说明](https://docs.haproxy.org/2.8/intro.html#3…

NLP: 词袋模型和TFIDF模型

文章目录 词袋模型TF-IDF模型词汇表模型 词袋模型 文本特征提取有两个非常重要的模型: 词集模型:单词构成的集合,集合自然每个元素都只有一个,也即词集中的每个单词都只有一个。 词袋模型:在词集的基础上如果一个单词…

autoxjs的安装与配置

AutoxJs 是一个基于 JavaScript 的自动化工具,用于在 Android 平台上创建自动化脚本。它是在原 Auto.js 项目的基础上继续维护和升级而来的。 AutoxJs 的优势主要包括以下几点: 无需 root 权限:可以在没有 root 权限的设备上运行大部分功能&…

JavaWeb系列二十三: web 应用常用功能(文件上传下载)

文件上传下载 基本介绍文件上传基本原理文件上传应用实例文件上传注意事项和细节 文件下载基本原理文件下载应用实例文件下载注意事项 ⬅️ 上一篇: JavaWeb系列二十二: 线程数据共享和安全(ThreadLocal) 🎉 欢迎来到 JavaWeb系列二十三: web 应用常用功能(文件上传…

创建最佳实践创建 XML 站点地图--SEO

您是否正在努力让您的网站被搜索引擎索引?您想提高您网站的搜索引擎知名度吗?如果是,您可能会错过 XML 站点地图的重要性。XML 站点地图在改善您网站的 SEO 方面发挥着至关重要的作用。‍ XML 站点地图是您网站结构的蓝图,可帮助…

YOLOv5项目梳理

1 项目介绍 参考项目:YOLO项目 1.1训练模型 YOLOv5模型 train.py 训练预训练模型 ... ... def parse_opt(knownFalse):# 命令行参数解析器初始化parser argparse.ArgumentParser()# 初始权重路径,默认为 ROOT / yolov5s.pt,用于指定模…

Navicat 17 for Mac 数据库管理软件

Mac分享吧 文章目录 效果一、准备工作二、开始安装1. 双击运行软件,将其从左侧拖入右侧文件夹中,等待安装完毕。2. 应用程序/启动台显示Navicat图标,表示安装成功。 二、运行测试运行后提示:“Navicat Premium.pp”已损坏&#x…

在qt的c++程序嵌入一个qml窗口

//拖拽一个QQuickWidget c端和qml通信的桥梁 找到qml的main.qml的路径 ui->quickWidget->setSource(QUrl::fromLocalFile("../../../code/main.qml"));// QML 与 Qt Widgets 通信//窗口就成了一个类实例对象pRoot (QObject*)ui->quickWidget->rootObje…

redis面试基础知识

redis的数据类型 Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样: 五种基本数据类型 Redis 通用命令 通用指令是部分数据类型的,都可以使用的指令,常见的有: KEYS&#xff…

思维(交互题),CF 1990E2 - Catch the Mole(Hard Version)

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 E2 - Catch the Mole(Hard Version) 二、解题报告 1、思路分析 考虑每次误判都会让鼹鼠上升一层,相应的,最外层的一层结点都没用了 由于数据范围为5000,我们随便找个叶子…

Electron案例解析-编写一个简单的electron程序

index.html <!DOCTYPE html> <html> <head><meta charset"UTF-8" /><!-- 内容安全策略--><metahttp-equiv"Content-Security-Policy"content"default-src self; script-src self"/><metahttp-equiv&quo…

C语言-栈和队列

文章目录 &#x1f3af;引言&#x1f453;栈和队列1.栈1.1栈的概念与结构1.2栈的实现 2.队列2.1队列的概念与结构2.2队列的实现 &#x1f947;结语 &#x1f3af;引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在之前的文章中&#xff0c;我们详细介绍了链表及其操作方法。…

8年前端总结和感想(转)~

我是牛奶&#xff0c;本文是我前端工作 8 年的一些总结和感想 主要记录下个人点滴、前端知识点、场景应用、未来的憧憬以及个人规划&#xff0c;供自己以后查漏补缺&#xff0c;也欢迎同道朋友交流学习。 自我介绍 我是一名工作在非知名公司的 8 年前端&#xff0c;双非普通本…