基于MindSpore通过GPT实现情感分类
python
%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
python
# 该案例在 mindnlp 0.3.1 版本完成适配,如果发现案例跑不通,可以指定mindnlp版本,执行`!pip install mindnlp==0.3.1`
!pip install mindnlp
!pip install jieba
%env HF_ENDPOINT=https://hf-mirror.com
python
imdb_ds = load_dataset('imdb', split=['train', 'test'])
imdb_train = imdb_ds['train']
imdb_test = imdb_ds['test']
Downloading readme: 0.00B [00:00, ?B/s]
Downloading data: 0%| | 0.00/21.0M [00:00<?, ?B/s]
Downloading data: 0%| | 0.00/20.5M [00:00<?, ?B/s]
Downloading data: 0%| | 0.00/42.0M [00:00<?, ?B/s]
Generating train split: 0%| | 0/25000 [00:00<?, ? examples/s]
Generating test split: 0%| | 0/25000 [00:00<?, ? examples/s]
Generating unsupervised split: 0%| | 0/50000 [00:00<?, ? examples/s]
python
imdb_train.get_dataset_size()
25000
python
import numpy as np
def process_dataset(dataset, tokenizer, max_seq_len=512, batch_size=4, shuffle=False):
is_ascend = mindspore.get_context('device_target') == 'Ascend'
def tokenize(text):
if is_ascend:
tokenized = tokenizer(text, padding='max_length', truncation=True, max_length=max_seq_len)
else:
tokenized = tokenizer(text, truncation=True, max_length=max_seq_len)
return tokenized['input_ids'], tokenized['attention_mask']
if shuffle:
dataset = dataset.shuffle(batch_size)
# map dataset
dataset = dataset.map(operations=[tokenize], input_columns="text", output_columns=['input_ids', 'attention_mask'])
dataset = dataset.map(operations=transforms.TypeCast(mindspore.int32), input_columns="label", output_columns="labels")
# batch dataset
if is_ascend:
dataset = dataset.batch(batch_size)
else:
dataset = dataset.padded_batch(batch_size, pad_info={'input_ids': (None, tokenizer.pad_token_id),
'attention_mask': (None, 0)})
return dataset
python
from mindnlp.transformers import GPTTokenizer
# tokenizer
gpt_tokenizer = GPTTokenizer.from_pretrained('openai-gpt')
# add sepcial token: <PAD>
special_tokens_dict = {
"bos_token": "<bos>",
"eos_token": "<eos>",
"pad_token": "<pad>",
}
num_added_toks = gpt_tokenizer.add_special_tokens(special_tokens_dict)
---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent call last)
Cell In[9], line 3
1 from mindnlp.transformers import GPTTokenizer
2 # tokenizer
----> 3 gpt_tokenizer = GPTTokenizer.from_pretrained('openai-gpt')
5 # add sepcial token: <PAD>
6 special_tokens_dict = {
7 "bos_token": "<bos>",
8 "eos_token": "<eos>",
9 "pad_token": "<pad>",
10 }
File ~/miniconda/envs/jupyter/lib/python3.9/site-packages/mindnlp/transformers/tokenization_utils_base.py:1723, in PreTrainedTokenizerBase.from_pretrained(cls, pretrained_model_name_or_path, cache_dir, force_download, local_files_only, token, mirror, *init_inputs, **kwargs)
1720 else:
1721 logger.info(f"loading file {file_path} from cache at {resolved_vocab_files[file_id]}")
-> 1723 return cls._from_pretrained(
1724 resolved_vocab_files,
1725 pretrained_model_name_or_path,
1726 init_configuration,
1727 *init_inputs,
1728 cache_dir=cache_dir,
1729 local_files_only=local_files_only,
1730 _is_local=is_local,
1731 **kwargs,
1732 )
File ~/miniconda/envs/jupyter/lib/python3.9/site-packages/mindnlp/transformers/tokenization_utils_base.py:1923, in PreTrainedTokenizerBase._from_pretrained(cls, resolved_vocab_files, pretrained_model_name_or_path, init_configuration, token, cache_dir, local_files_only, _is_local, *init_inputs, **kwargs)
1920 if "Fast" not in cls.__name__ and tokenizer_file is not None:
1921 # This is for slow so can be done before
1922 with open(tokenizer_file, encoding="utf-8") as tokenizer_file_handle:
-> 1923 tokenizer_file_handle = json.load(tokenizer_file_handle)
1924 added_tokens = tokenizer_file_handle.pop("added_tokens")
1925 for serialized_tokens in added_tokens:
File ~/miniconda/envs/jupyter/lib/python3.9/json/__init__.py:293, in load(fp, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
274 def load(fp, *, cls=None, object_hook=None, parse_float=None,
275 parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
276 """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
277 a JSON document) to a Python object.
278
(...)
291 kwarg; otherwise ``JSONDecoder`` is used.
292 """
--> 293 return loads(fp.read(),
294 cls=cls, object_hook=object_hook,
295 parse_float=parse_float, parse_int=parse_int,
296 parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
File ~/miniconda/envs/jupyter/lib/python3.9/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
341 s = s.decode(detect_encoding(s), 'surrogatepass')
343 if (cls is None and object_hook is None and
344 parse_int is None and parse_float is None and
345 parse_constant is None and object_pairs_hook is None and not kw):
--> 346 return _default_decoder.decode(s)
347 if cls is None:
348 cls = JSONDecoder
File ~/miniconda/envs/jupyter/lib/python3.9/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
332 def decode(self, s, _w=WHITESPACE.match):
333 """Return the Python representation of ``s`` (a ``str`` instance
334 containing a JSON document).
335
336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
338 end = _w(s, end).end()
339 if end != len(s):
File ~/miniconda/envs/jupyter/lib/python3.9/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
353 obj, end = self.scan_once(s, idx)
354 except StopIteration as err:
--> 355 raise JSONDecodeError("Expecting value", s, err.value) from None
356 return obj, end
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
GPTTokenizer.from_pretrained('openai-gpt')
这行代码是从Hugging Face的Transformers库中加载预训练的GPT(Generative Pretrained Transformer)模型的分词器。下面我会详细解释这一过程:
1. 检查本地缓存
首先,from_pretrained
方法会检查是否有之前下载的预训练模型和分词器配置文件存在于本地缓存目录中。默认的缓存目录是~/.cache/huggingface/transformers
(在Windows上可能是C:\Users\{username}\.cache\huggingface\transformers
)。如果已经存在,它会直接加载这些文件。
2. 下载预训练资源
如果没有找到本地缓存,from_pretrained
方法会从Hugging Face的Model Hub(模型仓库)下载预训练的GPT模型的分词器配置文件。这些文件通常包括:
vocab.json
:包含了分词器的词汇表,每个token对应一个ID。merges.txt
:用于BPE(Byte Pair Encoding)分词算法,记录了字符对的合并规则。config.json
:包含了分词器的配置信息,如特殊token的定义等。
3. 构建分词器实例
一旦下载或读取了上述文件,GPTTokenizer
类会使用这些文件初始化一个分词器实例。分词器会使用BPE算法将输入文本转化为一系列的token ID,这些ID可以直接用于预训练的GPT模型的输入。
4. 分词器方法
初始化后的GPTTokenizer
实例提供了多种方法来处理文本,包括:
encode
:将文本转化为token ID列表。decode
:将token ID列表转化为文本。batch_encode_plus
:批量处理多个文本,可以设置padding和truncation等参数。prepare_for_model
:为模型准备输入,包括添加特殊token、padding和truncation。
示例
假设我们要使用GPTTokenizer
来处理一段文本:
python
from transformers import GPTTokenizer
# 加载预训练的GPT分词器
tokenizer = GPTTokenizer.from_pretrained('openai-gpt')
# 待分词的文本
text = "Hello, how are you doing today?"
# 对文本进行编码
encoded_input = tokenizer.encode(text, return_tensors='pt')
# 输出token ID列表
print(encoded_input)
# 解码token ID列表回文本
decoded_output = tokenizer.decode(encoded_input[0])
print(decoded_output)
在这个示例中,我们首先加载了预训练的GPT分词器,然后使用encode
方法将一段文本转化为token ID列表,并使用decode
方法将token ID列表还原为文本。通过这种方式,我们可以准备数据供GPT模型使用。
在自然语言处理(NLP)任务中,特别是在使用神经网络模型时,padding
和truncation
是两个常见的预处理步骤,用于处理变长的文本序列,以便它们可以被模型以固定尺寸的批次(batches)处理。下面我将详细解释这两个概念:
Padding(填充)
定义 :Padding 是在较短的序列末尾添加特殊标记(如 <PAD>
或 0),以使所有序列达到相同的长度。这是因为许多深度学习模型(如RNN、LSTM、Transformer等)要求输入具有固定的形状,而实际文本序列的长度往往是可变的。
作用:
- 允许模型以固定大小的批次处理不同长度的文本。
- 防止模型需要为每个不同长度的序列动态调整其输入层。
使用场景:
- 当一个批次中的序列长度不一致时,需要进行padding。
Truncation(截断)
定义:Truncation 是在序列超出预定义的最大长度时,从序列的开头、中间或结尾裁剪掉多余的部分。这是为了避免过长的序列导致内存不足或计算成本过高。
作用:
- 控制输入序列的长度,以适应模型的输入限制。
- 防止过长的序列占用过多的计算资源,特别是在GPU上。
使用场景:
- 当序列长度超过模型或硬件限制时,需要进行truncation。
结合使用
在实践中,padding
和truncation
经常结合使用,以确保所有序列都符合模型的输入要求。例如,在处理一个批次的文本时,我们首先确定一个最大长度(通常是批次中最长序列的长度或一个预定义的值),然后对所有序列进行truncation,使其不超过这个长度,之后对所有较短的序列进行padding,直到它们达到这个最大长度。
实现示例
在使用Hugging Face的Transformers库时,GPTTokenizer
和其他分词器提供了batch_encode_plus
或encode_plus
方法,可以设置padding
和truncation
参数。例如:
python
from transformers import GPTTokenizer
tokenizer = GPTTokenizer.from_pretrained('gpt2')
texts = ["This is a short sentence.", "This is a much longer sentence that might exceed the maximum length."]
# 对文本进行编码,设置padding和truncation
encoded_texts = tokenizer.batch_encode_plus(
texts,
padding='max_length',
truncation=True,
max_length=10,
return_tensors='pt'
)
# 输出将包括输入ID和注意力掩码
print(encoded_texts['input_ids'])
print(encoded_texts['attention_mask'])
在这个示例中,padding='max_length'
意味着所有的序列都将被填充到最大长度(在这里是10),而truncation=True
则意味着任何超过10个token的序列将被截断。return_tensors='pt'
表示返回的将是PyTorch的张量格式。
python
# split train dataset into train and valid datasets
imdb_train, imdb_val = imdb_train.split([0.7, 0.3])
python
dataset_train = process_dataset(imdb_train, gpt_tokenizer, shuffle=True)
dataset_val = process_dataset(imdb_val, gpt_tokenizer)
dataset_test = process_dataset(imdb_test, gpt_tokenizer)
python
next(dataset_train.create_tuple_iterator())
python
from mindnlp.transformers import GPTForSequenceClassification
from mindspore.experimental.optim import Adam
# set bert config and define parameters for training
model = GPTForSequenceClassification.from_pretrained('openai-gpt', num_labels=2)
model.config.pad_token_id = gpt_tokenizer.pad_token_id
model.resize_token_embeddings(model.config.vocab_size + 3)
optimizer = nn.Adam(model.trainable_params(), learning_rate=2e-5)
metric = Accuracy()
# define callbacks to save checkpoints
ckpoint_cb = CheckpointCallback(save_path='checkpoint', ckpt_name='gpt_imdb_finetune', epochs=1, keep_checkpoint_max=2)
best_model_cb = BestModelCallback(save_path='checkpoint', ckpt_name='gpt_imdb_finetune_best', auto_load=True)
trainer = Trainer(network=model, train_dataset=dataset_train,
eval_dataset=dataset_train, metrics=metric,
epochs=1, optimizer=optimizer, callbacks=[ckpoint_cb, best_model_cb],
jit=False)
python
trainer.run(tgt_columns="labels")
python
evaluator = Evaluator(network=model, eval_dataset=dataset_test, metrics=metric)
evaluator.run(tgt_columns="labels")
这段代码展示了如何使用MindNLP和MindSpore框架对IMDB电影评论数据集进行情感分析的端到端训练过程。下面是对代码逻辑的详细解析:
-
导入依赖库和数据集:
- 导入MindSpore、MindNLP的相关模块,包括数据加载、预处理、模型定义、优化器、训练器、评估器等。
- 从MindNLP加载IMDB数据集,将其分为训练集和测试集。
-
数据预处理:
- 定义
process_dataset
函数,该函数接收一个数据集、一个分词器、最大序列长度和批大小作为参数。 - 根据设备类型(Ascend或GPU/CPU)选择不同的分词方式。
- 数据集进行shuffle、tokenization、类型转换和batching处理。
- 使用GPTTokenizer进行文本的tokenization,添加特殊token,并对tokenizer进行更新。
- 训练集和验证集通过
split
函数从原训练集中分离出来。
- 定义
-
模型定义与配置:
- 从预训练的
GPTForSequenceClassification
模型创建一个实例,用于序列分类任务。 - 更新模型配置,设置
pad_token_id
,并调整嵌入层大小以适应新加入的特殊token。
- 从预训练的
-
训练配置:
- 定义Adam优化器,设置学习率为2e-5。
- 创建训练器
Trainer
,传入模型、训练数据集、评估数据集、评估指标、训练轮数、优化器和回调函数。 - 设置两个回调函数:
CheckpointCallback
用于保存训练过程中的模型检查点,BestModelCallback
用于保存最佳模型。 - 设置训练器的jit模式为False,禁用自动图优化。
-
模型训练与评估:
- 使用
trainer.run
函数开始模型训练,指定目标列(标签)为"labels"。 - 训练结束后,使用
Evaluator
类对测试数据集进行评估,同样指定目标列为"labels"。
- 使用
这段代码的主要功能是利用预训练的GPT模型对IMDB数据集进行微调,以执行二分类情感分析任务。通过数据增强、模型微调、评估和模型保存,实现了一个完整的情感分析模型训练和评估流程。
python
print("yanggemindspore打卡22天之基于MindSpore通过GPT实现情感分类 2024 07 11")
yanggemindspore打卡22天之基于MindSpore通过GPT实现情感分类 2024 07 11
python