赞
踩
目录
AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理
二、零样本学习 (Zero-shot Learning) 和少样本学习 (Few-shot Learning)
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的发展和应用中扮演关键角色。
(注意:以下代码运行,可能需要科学上网)
定义: 零样本学习是一种让模型能够在没有见过目标类别数据的情况下进行预测的技术。它主要依赖于预训练的语言模型和自然语言描述,利用模型在预训练期间学到的广泛知识来理解和推断新的任务。
实现方式:
基于语言模型的零样本分类: 使用预训练的语言模型,如 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)
定义: 少样本学习是一种让模型能够在只见过少量目标类别数据的情况下进行有效预测的技术。它通过对预训练模型进行微调,利用少量标注数据来学习新的任务。
实现方式:
基于预训练模型的微调: 使用预训练的 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模型甚至可以仅用几十个样本就能处理各种不同的任务,让人感到非常惊奇。
一般来说,模型的最优效果取决于任务、可用数据,以及可用数据被标注的比例。下图所示的结构能一定程度上帮助我们选择最恰当的方法。
按步骤对图中结构做如下说明:
我们需要明白,即使拥有少量的标注数据也可以为模型带来正向收益。如果根本没有标注数据,则可以用零样本学习方法,这通常会设置一个较高的基线指标。
如果拥有标注数据,那么决定模型性能的因素就是标注数据所占的比例。如果有大量的标注数据用于模型训练,就可以使用第2章介绍的标准微调方法来进行处理。
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,学习如何构建此类任务,以及如何获取数据。
接下来介绍的方法适用于文本分类场景,但在处理命名实体识别任务、问答任务或文本摘要生成任务这些更复杂的任务时,则可能需要其他技术的加持,比如数据增强。
进入Transformers代码仓库的issue模块(https://oreil.ly/StdH3),点击其中一个issue,就会得到如下图所示的页面,该页面包含一个标题、一段描述和一组标注集合。因此,该任务可以看作给定标题和描述,预测一个或多个标注,是一个典型的多标注文本分类任务。这比在之前介绍中遇到的多分类问题更具有挑战性,因为在之前介绍中,每条推文只会被标注一种情感。
为了获取代码仓库中的所有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开发分类器。其实在实践中,我们也很可能会构建两个分类器,因为这样方便对模型的性能进行更精细的控制。
现在我们知道了如何获取目标数据,下面来看看如何处理这些数据。
当我们下载好了所有的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 model body: #声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/849580
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。