• Transformers基本组件(二)快速入门Datasets、Evaluate、Trainer


    Transformers基本组件(二)快速入门Datasets、Evaluate、Trainer

    1、基础组件Datasets

    数据集部分的工作,一部分在于数据集的收集,另一部分在于数据集的处理。Datasets库的出现,一定程度上也使得这两部分的工作变得简单了许多。

    1.1 加载数据集

    加载在线数据集

    from datasets import load_dataset
    
    # 加载公开数据集只需要两步,导入Datasets包,然后加载想要的数据集即可
    # 经过短暂的下载后(当然,大概率会出现443错误),便可以看到数据集已经被成功加载。
    # 而且已经被划分为了训练集和验证集两部分,训练集5850条,验证集1679条。
    
    
    # 官网中列出了公开数据集,点击具体的数据集后,可以查看详细信息。
    datasets = load_dataset("madao33/new-title-chinese")
    datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    DatasetDict({
        train: Dataset({
            features: ['title', 'content'],
            num_rows: 5850
        })
        validation: Dataset({
            features: ['title', 'content'],
            num_rows: 1679
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    加载子数据集

    super_glue是一系列任务的合集,里面包含多个数据集,假设要加载boolq数据集,要如何做呢

    # 直接加子数据集参数
    boolq_dataset = load_dataset("super_glue", "boolq")
    boolq_dataset
    
    • 1
    • 2
    • 3
    DatasetDict({
        train: Dataset({
            features: ['question', 'passage', 'idx', 'label'],
            num_rows: 9427
        })
        validation: Dataset({
            features: ['question', 'passage', 'idx', 'label'],
            num_rows: 3270
        })
        test: Dataset({
            features: ['question', 'passage', 'idx', 'label'],
            num_rows: 3245
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    按照数据集划分进行加载

    # 前面加载的数据集都是将全部数据集加载了,包括训练集、验证集、测试集。
    # 我们也可以根据数据集的划分,选择要加载的数据集划分,只需要指定split参数。
    # 假设我们要加载前面中文新闻数据集中的训练集,那么代码可以这样:
    
    dataset = load_dataset("madao33/new-title-chinese", split="train")
    dataset
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Dataset({
        features: ['title', 'content'],
        num_rows: 5850
    })
    
    • 1
    • 2
    • 3
    • 4
    # 其他形式的加载
    
    # 按照条数加载
    dataset = load_dataset("madao33/new-title-chinese", split="train[10:100]")
    
    # 按照比例加载
    dataset = load_dataset("madao33/new-title-chinese", split="train[:50%]")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.2 数据集常用操作

    1.2.1 查看数据集

    datasets = load_dataset("madao33/new-title-chinese")
    
    print(datasets["train"][0])
    print(datasets["train"]["title"][:5])
    print(datasets["train"].column_names)
    print(datasets["train"].features)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.2.2 数据集划分

    dataset = datasets["train"]
    
    # 这里我们对原始的train数据集进行了划分,可以看到数据按照9:1的比例重新进行了划分。
    dataset.train_test_split(test_size=0.1)
    
    • 1
    • 2
    • 3
    • 4
    DatasetDict({
        train: Dataset({
            features: ['title', 'content'],
            num_rows: 5265
        })
        test: Dataset({
            features: ['title', 'content'],
            num_rows: 585
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.2.3 数据集的选取与过滤

    # 选取
    datasets["train"].select([0, 1])
    
    • 1
    • 2
    Dataset({
        features: ['title', 'content'],
        num_rows: 2
    })
    
    • 1
    • 2
    • 3
    • 4
    # 过滤
    filter_dataset = datasets["train"].filter(lambda example: "中国" in example["title"])
    filter_dataset["title"][:5]
    
    • 1
    • 2
    • 3
    ['聚焦两会,世界探寻中国成功秘诀',
     '望海楼中国经济的信心来自哪里',
     '“中国奇迹”助力世界减贫跑出加速度',
     '和音瞩目历史交汇点上的中国',
     '中国风采感染世界']
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.2.4 数据映射

    def add_prefix(example):
        example["title"] = 'Prefix: ' + example["title"]
        return example
    
    
    prefix_dataset = datasets.map(add_prefix)
    prefix_dataset["train"][:10]["title"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    ['Prefix: 望海楼美国打“台湾牌”是危险的赌博',
     'Prefix: 大力推进高校治理能力建设',
     'Prefix: 坚持事业为上选贤任能',
     'Prefix: “大朋友”的话儿记心头',
     'Prefix: 用好可持续发展这把“金钥匙”',
     'Prefix: 跨越雄关,我们走在大路上',
     'Prefix: 脱贫奇迹彰显政治优势',
     'Prefix: 拱卫亿万人共同的绿色梦想',
     'Prefix: 为党育人、为国育才',
     'Prefix: 净化网络语言']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当然,真实情况下,我们不会只做这样的处理。

    在这里,一般会与Tokenizer做结合实现完整的数据处理函数,将其直接转换为模型想要的输入。

    from transformers import AutoTokenizer
    
    
    tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
    
    def preprocess_function(example, tokenizer=tokenizer):
        model_inputs = tokenizer(example["content"], max_length=512, truncation=True)
        labels = tokenizer(example["title"], max_length=32, truncation=True)
        # label就是title编码的结果
        model_inputs["labels"] = labels["input_ids"]
        return model_inputs
    
    # 处理完成后,在数据的字段中便多了四个字段,这些字段便是模型能够处理的字段。
    processed_datasets = datasets.map(preprocess_function)
    processed_datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    DatasetDict({
        train: Dataset({
            features: ['title', 'content', 'input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 5850
        })
        validation: Dataset({
            features: ['title', 'content', 'input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 1679
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    map函数还支持批量处理数据,只需要指定batched参数值为True。

    # 二者效果是一样的,但是使用批量数据处理效率要高的多。
    processed_datasets = datasets.map(preprocess_function, batched=True)
    processed_datasets
    
    • 1
    • 2
    • 3

    当然,我们一般只需要处理后的字段

    processed_datasets = datasets.map(preprocess_function, batched=True, remove_columns=datasets["train"].column_names)
    
    
    # 可以发现原始字段已经删除,只保留了处理后的字段
    processed_datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    DatasetDict({
        train: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 5850
        })
        validation: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 1679
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.2.5 数据保存和加载

    # 当我们处理完数据之后,我们可以将其保存到本地磁盘,需要用时下次直接加载即可,无需二次处理。
    processed_datasets.save_to_disk("./processed_data")
    
    processed_datasets = load_from_disk("./processed_data")
    processed_datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    DatasetDict({
        train: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 5850
        })
        validation: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 1679
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3 加载本地数据集

    多数情况下,公开数据集并不能满足我们的需求,需要加载自行准备的数据集。下面来介绍如何加载本地的数据集。

    1.3.1 直接加载文件

    # 本地文件支持csv、json文件的直接加载
    # 1、加载csv文件
    dataset = load_dataset("csv", data_files="./ChnSentiCorp_htl_all.csv", split="train")
    
    # 或者
    from datasets import Dataset
    dataset = Dataset.from_csv("./ChnSentiCorp_htl_all.csv")
    
    # 2、加载json文件
    load_dataset("json", data_files=["./cmrc2018_trial.json"], field="data")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3.2 加载文件夹中的文件

    # 加载整个文件夹的内容
    dataset = load_dataset("csv", data_dir='./all_data/', split='train')
    
    
    # 加载文件夹中特定的文件
    dataset = load_dataset("csv", data_files=["./all_data/ChnSentiCorp_htl_all.csv", "./all_data/ChnSentiCorp_htl_all_2.csv"], split='train')
    
    dataset
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Dataset({
        features: ['label', 'review'],
        num_rows: 15532
    })
    
    • 1
    • 2
    • 3
    • 4

    1.3.3 转换加载数据集

    import pandas as pd
    
    # 1、从pandas中加载
    data = pd.read_csv("./ChnSentiCorp_htl_all.csv")
    
    dataset = Dataset.from_pandas(data)
    
    # 2、从list中加载
    
    # List格式的数据需要内嵌{},明确数据字段
    data = [{"text": "abc"}, {"text": "def"}]
    # data = ["abc", "def"]
    Dataset.from_list(data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.3.4 利用脚本加载

    data中数据如下

          "paragraphs": [
            {
              "id": "TRIAL_800", 
              "context": "基于《跑跑卡丁车》与《泡泡堂》上所开发的游戏,由韩国Nexon开发与发行。中国大陆由盛大游戏运营,这是Nexon时隔6年再次授予盛大网络其游戏运营权。台湾由游戏橘子运营。......", 
              "qas": [
                {
                  "question": "生命数耗完即算为什么?", 
                  "id": "TRIAL_800_QUERY_0", 
                  "answers": [
                    {
                      "text": "踢爆", 
                      "answer_start": 127
                    }
                  ]
                }
              ]
            }
          ], 
          "id": "TRIAL_800", 
          "title": "泡泡战士"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    # 直接解析josn不能满足要求,只解析出'paragraphs', 'id', 'title'字段
    load_dataset("json", data_files="./cmrc2018_trial.json", field="data")
    
    • 1
    • 2
    DatasetDict({
        train: Dataset({
            features: ['paragraphs', 'id', 'title'],
            num_rows: 256
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用脚本进行加载

    load_dataset("./load_cmrc.py", split="train")
    
    • 1
    Dataset({
        features: ['id', 'context', 'question', 'answers'],
        num_rows: 1002
    })
    
    • 1
    • 2
    • 3
    • 4

    脚本如下

    import json
    import datasets
    from datasets import DownloadManager, DatasetInfo
    
    '''
    _info方法定义数据集结构
    _split_generators方法指定不同数据划分要加载的数据
    _generate_examples方法实现具体的读取数据代码
    '''
    class CMRC2018TRIAL(datasets.GeneratorBasedBuilder):
    
        def _info(self) -> DatasetInfo:
            """
                info方法,定义数据集的信息,这里要对数据的字段进行定义
            :return:
            """
            return datasets.DatasetInfo(
                description="CMRC2018 trial",
                features=datasets.Features({
                        "id": datasets.Value("string"),
                        "context": datasets.Value("string"),
                        "question": datasets.Value("string"),
                        "answers": datasets.features.Sequence(
                            {
                                "text": datasets.Value("string"),
                                "answer_start": datasets.Value("int32"),
                            }
                        )
                    }),
            )
    
        def _split_generators(self, dl_manager: DownloadManager):
            """
                返回datasets.SplitGenerator
                涉及两个参数:name和gen_kwargs
                name: 指定数据集的划分
                gen_kwargs: 指定要读取的文件的路径,与_generate_examples的入参数一致
            :param dl_manager:
            :return: [ datasets.SplitGenerator ]
            """
            return [datasets.SplitGenerator(name=datasets.Split.TRAIN, gen_kwargs={"filepath": "./cmrc2018_trial.json"})]
    
        def _generate_examples(self, filepath):
            """
                生成具体的样本,使用yield
                需要额外指定key,id从0开始自增就可以
            :param filepath:
            :return:
            """
            # Yields (key, example) tuples from the dataset
            with open(filepath, encoding="utf-8") as f:
                data = json.load(f)
                for example in data["data"]:
                    for paragraph in example["paragraphs"]:
                        context = paragraph["context"].strip()
                        for qa in paragraph["qas"]:
                            question = qa["question"].strip()
                            id_ = qa["id"]
    
                            answer_starts = [answer["answer_start"] for answer in qa["answers"]]
                            answers = [answer["text"].strip() for answer in qa["answers"]]
    
                            yield id_, {
                                "context": context,
                                "question": question,
                                "id": id_,
                                "answers": {
                                    "answer_start": answer_starts,
                                    "text": answers,
                                },
                            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    1.4 DataCollator

    from transformers import  DataCollatorWithPadding
    
    dataset = load_dataset("csv", data_files="./ChnSentiCorp_htl_all.csv", split='train')
    dataset = dataset.filter(lambda x: x["review"] is not None)
    dataset
    
    • 1
    • 2
    • 3
    • 4
    • 5
    Dataset({
        features: ['label', 'review'],
        num_rows: 7765
    })
    
    • 1
    • 2
    • 3
    • 4
    def process_function(examples):
        # 这里我们指定max_length为128,超过此长度,进行截断
        # 注意:这里,我们不指定填充,我们利用 DataCollator进行填充
        tokenized_examples = tokenizer(examples["review"], max_length=128, truncation=True)
        tokenized_examples["labels"] = examples["label"]
        return tokenized_examples
    
    
    
    tokenized_dataset = dataset.map(process_function, batched=True, remove_columns=dataset.column_names)
    
    # DataCollatorWithPadding:把一批样本补齐到【这批样本最长句子的长度】而非整个数据集的最大长度
    # 这样能够加快补齐速度
    collator = DataCollatorWithPadding(tokenizer=tokenizer)
    
    
    from torch.utils.data import DataLoader
    dl = DataLoader(tokenized_dataset, batch_size=4, collate_fn=collator, shuffle=True)
    
    num = 0
    for batch in dl:
        # 可以看到每个批次的样本长度不是一样的
        print(batch["input_ids"].size())
        num += 1
        if num > 10:
            break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    torch.Size([4, 117])
    torch.Size([4, 128])
    torch.Size([4, 128])
    torch.Size([4, 128])
    torch.Size([4, 128])
    torch.Size([4, 100])
    torch.Size([4, 82])
    torch.Size([4, 128])
    torch.Size([4, 128])
    torch.Size([4, 128])
    torch.Size([4, 91])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.5 利用Datasets优化情感分类

    1.5.1 加载数据集

    from transformers import AutoTokenizer, AutoModelForSequenceClassification
    from datasets import load_dataset
    
    # 1、加载数据集
    dataset = load_dataset("csv", data_files="./ChnSentiCorp_htl_all.csv", split="train")
    dataset = dataset.filter(lambda x: x["review"] is not None)
    
    # 2、划分数据集
    datasets = dataset.train_test_split(test_size=0.1)
    datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    DatasetDict({
        train: Dataset({
            features: ['label', 'review'],
            num_rows: 6988
        })
        test: Dataset({
            features: ['label', 'review'],
            num_rows: 777
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.5.2 创建Dataloader

    import torch
    from torch.utils.data import DataLoader
    from transformers import DataCollatorWithPadding
    
    # 1、加载离线模型
    model_path = '/root/autodl-fs/models/rbt3'
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    
    def process_function(examples):
        tokenized_examples = tokenizer(examples["review"], max_length=128, truncation=True)
        tokenized_examples["labels"] = examples["label"]
        return tokenized_examples
    
    # 2、词元化
    tokenized_datasets = datasets.map(process_function, batched=True, remove_columns=datasets["train"].column_names)
    
    
    
    # 3、创建Dataloader
    trainset, validset = tokenized_datasets["train"], tokenized_datasets["test"]
    trainloader = DataLoader(trainset, batch_size=32, shuffle=True, collate_fn=DataCollatorWithPadding(tokenizer))
    validloader = DataLoader(validset, batch_size=64, shuffle=False, collate_fn=DataCollatorWithPadding(tokenizer))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.5.3 创建模型及优化器

    from torch.optim import Adam
    
    # 1、创建模型
    model = AutoModelForSequenceClassification.from_pretrained(model_path)
    
    if torch.cuda.is_available():
        model = model.cuda()
    # 2、创建优化器
    optimizer = Adam(model.parameters(), lr=2e-5)    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.5.4 训练与验证

    def evaluate():
        model.eval()
        acc_num = 0
        with torch.inference_mode():
            for batch in validloader:
                if torch.cuda.is_available():
                    batch = {k: v.cuda() for k, v in batch.items()}
                output = model(**batch)
                pred = torch.argmax(output.logits, dim=-1)
                acc_num += (pred.long() == batch["labels"].long()).float().sum()
        return acc_num / len(validset)
    
    def train(epoch=3, log_step=100):
        global_step = 0
        for ep in range(epoch):
            model.train()
            for batch in trainloader:
                if torch.cuda.is_available():
                    batch = {k: v.cuda() for k, v in batch.items()}
                optimizer.zero_grad()
                output = model(**batch)
                output.loss.backward()
                optimizer.step()
                if global_step % log_step == 0:
                    print(f"ep: {ep}, global_step: {global_step}, loss: {output.loss.item()}")
                global_step += 1
            acc = evaluate()
            print(f"ep: {ep}, acc: {acc}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    train()
    
    • 1
    ep: 0, global_step: 0, loss: 0.7134996056556702
    ep: 0, global_step: 100, loss: 0.2385680228471756
    ep: 0, global_step: 200, loss: 0.3343896269798279
    ep: 0, acc: 0.8764479160308838
    ep: 1, global_step: 300, loss: 0.15788553655147552
    ep: 1, global_step: 400, loss: 0.339910626411438
    ep: 1, acc: 0.8867439031600952
    ep: 2, global_step: 500, loss: 0.24706700444221497
    ep: 2, global_step: 600, loss: 0.24163328111171722
    ep: 2, acc: 0.8828828930854797
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.5.5 模型预测

    sen = "我觉得这家酒店不错,饭很好吃!"
    id2_label = {0: "差评!", 1: "好评!"}
    model.eval()
    with torch.inference_mode():
        inputs = tokenizer(sen, return_tensors="pt")
        inputs = {k: v.cuda() for k, v in inputs.items()}
        logits = model(**inputs).logits
        pred = torch.argmax(logits, dim=-1)
        print(f"输入:{sen}\n模型预测结果:{id2_label.get(pred.item())}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    输入:我觉得这家酒店不错,饭很好吃!
    模型预测结果:好评!
    
    • 1
    • 2
    from transformers import pipeline
    
    model.config.id2label = id2_label
    pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)
    pipe(sen)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    [{'label': '好评!', 'score': 0.9937521815299988}]
    
    • 1

    2、基础组件Evaluate

    • 在训练模型的时候,我们往往会将数据划分为训练集和验证集,会根据模型在验证集的性能来评价模型的好坏。

    • 任务不同,对应的评价指标也不一样,常见的指标包括Accuracy、F1、Rouge等。

    • Evaluate包的出现,一定程度上简化了上述工作,我们可以通过像加载数据集一样加载对应的评估函数,进行模型评估。

    2.1 评估函数常用操作

    2.1.1 查看评估函数

    import evaluate
    
    # 查看支持的评估函数
    evaluate.list_evaluation_modules(include_community=False, with_details=True)
    
    • 1
    • 2
    • 3
    • 4
    # 在线加载评估函数
    accuracy = evaluate.load("accuracy")
    
    # 查看函数说明
    print(accuracy.description)
    
    print(accuracy.inputs_description)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.1.2 评估指标计算

    # 全局计算
    accuracy = evaluate.load("accuracy")
    results = accuracy.compute(references=[0, 1, 2, 0, 1, 2], predictions=[0, 1, 1, 2, 1, 0])
    results # {'accuracy': 0.5}
    
    • 1
    • 2
    • 3
    • 4
    # 迭代计算
    accuracy = evaluate.load("accuracy")
    for refs, preds in zip([[0,1],[0,1]], [[1,0],[0,1]]):
        accuracy.add_batch(references=refs, predictions=preds)
    accuracy.compute() # {'accuracy': 0.5}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    # 多个指标计算
    clf_metrics = evaluate.combine(["accuracy", "f1", "recall", "precision"])
    clf_metrics.compute(predictions=[0, 1, 0], references=[0, 1, 1])
    
    • 1
    • 2
    • 3
    {'accuracy': 0.6666666666666666,
     'f1': 0.6666666666666666,
     'recall': 0.5,
     'precision': 1.0
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.2 利用Evaluate优化情感分类

    加载数据集、创建Dataloader、创建模型及优化器和1.5一致。

    2.2.1 训练与验证

    import evaluate
    
    # clf_metrics = evaluate.combine(["accuracy", "f1"])
    
    """
    如果直接使用指标名称“accuracy”等,
    由于网络问题,无法顺利下载
    但是这个错误不会显示,因此导入进去会卡死
    
    
    解决方法:
    
    
    从官网下载metrics文件夹,放置在本地的路径,进行离线加载
    
    """
    accuracy_path = '/root/autodl-tmp/transformers-code/metrics/accuracy'
    f1_path = '/root/autodl-tmp/transformers-code/metrics/f1'
    clf_metrics = evaluate.combine([accuracy_path, f1_path])
    
    # 示例
    clf_metrics.compute(references=[0,1,0,1], predictions=[1,0,0,1])
    
    # {'accuracy': 0.5, 'f1': 0.5}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    def evaluate():
        model.eval()
        with torch.inference_mode():
            for batch in validloader:
                if torch.cuda.is_available():
                    batch = {k: v.cuda() for k, v in batch.items()}
                output = model(**batch)
                pred = torch.argmax(output.logits, dim=-1)
                clf_metrics.add_batch(predictions=pred.long(), references=batch["labels"].long())
        return clf_metrics.compute()
    
    def train(epoch=3, log_step=100):
        global_step = 0
        for ep in range(epoch):
            model.train()
            for batch in trainloader:
                if torch.cuda.is_available():
                    batch = {k: v.cuda() for k, v in batch.items()}
                optimizer.zero_grad()
                output = model(**batch)
                output.loss.backward()
                optimizer.step()
                if global_step % log_step == 0:
                    print(f"ep: {ep}, global_step: {global_step}, loss: {output.loss.item()}")
                global_step += 1
            clf = evaluate()
            print(f"ep: {ep}, {clf}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    # 进行训练
    train()
    
    • 1
    • 2
    ep: 0, global_step: 0, loss: 0.6701369285583496
    ep: 0, global_step: 100, loss: 0.3432188630104065
    ep: 0, global_step: 200, loss: 0.29397645592689514
    ep: 0, {'accuracy': 0.9086229086229086, 'f1': 0.9356300997280146}
    ep: 1, global_step: 300, loss: 0.2728956639766693
    ep: 1, global_step: 400, loss: 0.23207390308380127
    ep: 1, {'accuracy': 0.9137709137709138, 'f1': 0.9391462306993643}
    ep: 2, global_step: 500, loss: 0.10437105596065521
    ep: 2, global_step: 600, loss: 0.18092380464076996
    ep: 2, {'accuracy': 0.8996138996138996, 'f1': 0.9275092936802974}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.2.2 模型预测

    sen = "我觉得这家酒店不错,饭很好吃!"
    id2_label = {0: "差评!", 1: "好评!"}
    model.eval()
    with torch.inference_mode():
        inputs = tokenizer(sen, return_tensors="pt")
        inputs = {k: v.cuda() for k, v in inputs.items()}
        logits = model(**inputs).logits
        pred = torch.argmax(logits, dim=-1)
        print(f"输入:{sen}\n模型预测结果:{id2_label.get(pred.item())}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    输入:我觉得这家酒店不错,饭很好吃!
    模型预测结果:好评!
    
    • 1
    • 2
    from transformers import pipeline
    
    model.config.id2label = id2_label
    pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)
    
    • 1
    • 2
    • 3
    • 4
    pipe('这家酒店床上特别脏、隔音特别差、还特别贵')
    
    • 1
    [{'label': '差评!', 'score': 0.9784351587295532}]
    
    • 1

    3、基础组件Trainer

    • Trainer模块是基础组件的最后一个模块,它封装了一套完整的在数据集上训练、评估与预测的流程。借助Trainer模块,可以快速启动训练。

    • Trainer模块主要包含两部分的内容:TrainingArguments与Trainer,前者用于训练参数的设置,后者用于创建真正的训练器,进行训练、评估预测等实际操作。

    • 此外,针对Seq2Seq训练任务,提供了专门的Seq2SeqTrainingArguments与Seq2SeqTrainer,整体与TrainingArguments和Trainer类似,但是提供了专门用于生成的部分参数。

    3.1 TrainingArguments

    TrainingArguments中可以配置整个训练过程中使用的参数,默认版本是包含90个参数,涉及模型存储、模型优化、训练日志、GPU使用、模型精度、分布式训练等多方面的配置内容,等后面用到再介绍。

    Seq2SeqTrainingArguments中除了上述的内容还包括生成部分的参数设置,如是否要进行生成、最大长度等共94个参数。

    3.2 Trainer

    Trainer中配置具体的训练用到的内容,包括模型、训练参数、训练集、验证集、分词器、评估函数等内容。

    • 当指定完上述对应参数,便可以通过调用train方法进行模型训练;

    • 训练完成后可以通过调用evaluate方法对模型进行评估;

    • 得到满意的模型后,最后调用predict方法对数据集进行预测。

    from transformers import TrainingArguments, Trainer
    
    # 创建TrainingArguments
    training_args = TrainingArguments(...)
    # 创建Trainer
    trainer = Trainer(..., args=training_args, ...)
    
    
    # 模型训练
    trainer.train()
    # 模型评估
    trainer.evaluate()
    # 模型预测
    trainer.predict()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    需要特别注意的是,使用Trainer进行模型训练对模型的输入输出是有限制的,要求模型返回ModelOutput的元组或子类,同时如果提供了标签,模型要能返回loss结果,并且loss要作为ModelOutput元组的第一个值

    3.3 利用Trainer优化情感分类

    3.1 数据预处理

    from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
    from datasets import load_dataset
    
    import warnings
    warnings.filterwarnings("ignore")
    
    # 1、加载数据集
    dataset = load_dataset("csv", data_files="./ChnSentiCorp_htl_all.csv", split="train")
    dataset = dataset.filter(lambda x: x["review"] is not None)
    
    # 2、划分数据集
    datasets = dataset.train_test_split(test_size=0.1)
    
    
    # 3、词元化
    # 加载离线模型
    model_path = '/root/autodl-fs/models/rbt3'
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    
    def process_function(examples):
        tokenized_examples = tokenizer(examples["review"], max_length=128, truncation=True)
        tokenized_examples["labels"] = examples["label"]
        return tokenized_examples
    
    tokenized_datasets = datasets.map(process_function, batched=True, remove_columns=datasets["train"].column_names)
    
    tokenized_datasets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    DatasetDict({
        train: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 6988
        })
        test: Dataset({
            features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'],
            num_rows: 777
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2 创建模型

    model = AutoModelForSequenceClassification.from_pretrained(model_path)
    
    • 1

    3.3 创建模型评估函数

    import evaluate
    
    # 下面方式可能加载失败
    # acc_metric = evaluate.load("accuracy")
    # f1_metirc = evaluate.load("f1")
    
    # 这里采用离线加载
    accuracy_path = '/root/autodl-tmp/transformers-code/metrics/accuracy'
    f1_path = '/root/autodl-tmp/transformers-code/metrics/f1'
    
    acc_metric = evaluate.load(accuracy_path)
    f1_metirc = evaluate.load(f1_path)
    
    def eval_metric(eval_predict):
        predictions, labels = eval_predict
        predictions = predictions.argmax(axis=-1)
        acc = acc_metric.compute(predictions=predictions, references=labels)
        f1 = f1_metirc.compute(predictions=predictions, references=labels)
        acc.update(f1)
        return acc
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.4 创建TrainingArguments及Trainer

    创建TrainingArguments

    train_args = TrainingArguments(output_dir="./checkpoints",      # 输出文件夹
                                   per_device_train_batch_size=64,  # 训练时的batch_size
                                   per_device_eval_batch_size=128,  # 验证时的batch_size
                                   logging_steps=10,                # log 打印的频率
                                   evaluation_strategy="epoch",     # 评估策略
                                   save_strategy="epoch",           # 保存策略
                                   save_total_limit=3,              # 最大保存数
                                   learning_rate=2e-5,              # 学习率
                                   weight_decay=0.01,               # weight_decay
                                   metric_for_best_model="f1",      # 设定评估指标
                                   load_best_model_at_end=True)     # 训练完成后加载最优模型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建Trainer

    from transformers import DataCollatorWithPadding
    trainer = Trainer(model=model,     # 预训练模型
                      args=train_args, # 训练参数
                      train_dataset=tokenized_datasets["train"], # 训练集
                      eval_dataset=tokenized_datasets["test"],   # 验证集
                      data_collator=DataCollatorWithPadding(tokenizer=tokenizer),# DataCollator,填充到一个批次中最大长度,加快填充的速度
                      compute_metrics=eval_metric  # 指标评估的方法
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.5 模型训练及预测

    trainer.train()
    
    • 1

    在这里插入图片描述

    from transformers import pipeline
    
    
    id2_label = {0: "差评!", 1: "好评!"}
    model.config.id2label = id2_label
    pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)
    
    pipe('这家酒店隔音不错、位置离地铁站近、去西湖很方便。')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    [{'label': '好评!', 'score': 0.9933173656463623}]
    
    • 1
  • 相关阅读:
    Nginx安装与配置
    Shiro和Zuul权限管理整合方案
    新闻月刊 | GBASE 7月市场动态一览
    OpenHD改造实现廉价高清数字图传(树莓派zero + ubuntu PC )——(一)概述
    How to config secured and stable Jenkins connection
    Java常用类(一)
    模型分类model
    在MDK-Keil中开发S32K144
    备战无人机配送:互联网派To C、技术派To B
    基于springboot的医护人员排班系统 全套代码 全套文档
  • 原文地址:https://blog.csdn.net/qq_44665283/article/details/133967426