跳转至

2.1 数据处理

基于BERT模型的数据处理


学习目标

  • 掌握BERT模型的相关细节.
  • 掌握数据处理的工具函数代码实现.

投满分项目数据预处理

  • 本项目中对数据部分的预处理步骤如下:
    • 第一步: 查看项目数据集
    • 第二步: 查看预训练模型相关数据
    • 第三步: 编写工具类函数

第一步: 查看项目数据集

  • 数据集的路径: /home/ec2-user/toutiao/data/data/

  • 项目的数据集包括5个文件, 依次来看一下:

  • 标签文件/home/ec2-user/toutiao/data/data/class.txt


finance
realty
stocks
education
science
society
politics
sports
game
entertainment

  • class.txt中包含10个类别标签, 每行一个标签, 为英文单词的展示格式.

  • 训练数据集/home/ec2-user/toutiao/data/data/train.txt

中华女子学院:本科层次仅1专业招男生     3
两天价网站背后重重迷雾:做个网站究竟要多少钱    4
东5环海棠公社230-290平2居准现房98折优惠 1
卡佩罗:告诉你德国脚生猛的原因 不希望英德战踢点球       7
82岁老太为学生做饭扫地44年获授港大荣誉院士      5
记者回访地震中可乐男孩:将受邀赴美国参观        5
冯德伦徐若�隔空传情 默认其是女友        9
传郭晶晶欲落户香港战伦敦奥运 装修别墅当婚房     1
《赤壁OL》攻城战诸侯战硝烟又起  8
“手机钱包”亮相科博会    4

  • train.txt中包含180000行样本, 每行包括两列, 第一列为待分类的中文文本, 第二列是数字化标签, 中间用\t作为分隔符.

  • 验证数据集/home/ec2-user/toutiao/data/data/dev.txt

体验2D巅峰 倚天屠龙记十大创新概览       8
60年铁树开花形状似玉米芯(组图)  5
同步A股首秀:港股缩量回调       2
中青宝sg现场抓拍 兔子舞热辣表演 8
锌价难续去年辉煌        0
2岁男童爬窗台不慎7楼坠下获救(图)        5
布拉特:放球员一条生路吧 FIFA能消化俱乐部的攻击 7
金科西府 名墅天成       1
状元心经:考前一周重点是回顾和整理      3
发改委治理涉企收费每年为企业减负超百亿  6

  • dev.txt中包含10000行样本, 每行包括两列, 第一列为待分类的中文文本, 第二列是数字化标签, 中 间用\t作为分隔符.

  • 测试数据集/home/ec2-user/toutiao/data/data/test.txt

词汇阅读是关键 08年考研暑期英语复习全指南       3
中国人民公安大学2012年硕士研究生目录及书目      3
日本地震:金吉列关注在日学子系列报道    3
名师辅导:2012考研英语虚拟语气三种用法  3
自考经验谈:自考生毕业论文选题技巧      3
本科未录取还有这些路可以走      3
2009年成人高考招生统一考试时间表        3
去新西兰体验舌尖上的饕餮之旅(组图)      3
四级阅读与考研阅读比较分析与应试策略    3
备考2012高考作文必读美文50篇(一)        3

  • test.txt中包含10000行样本, 每行包括两列, 第一列为待分类的中文文本, 第二列是数字化标签, 中 间用\t作为分隔符.

  • 词典文件/home/ec2-user/toutiao/data/data/vocab.pkl为不可读文件, 训练模型中使用.


第二步: 查看预训练模型相关数据

  • 预训练模型相关数据的文件夹路径为/home/ec2-user/toutiao/data/bert_pretrain/

  • 预训练模型相关数据共包含3个文件:

  • BERT模型的超参数配置文件/home/ec2-user/toutiao/data/bert_pretrain/bert_config.json


{
  "attention_probs_dropout_prob": 0.1,
  "directionality": "bidi",
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pooler_fc_size": 768,
  "pooler_num_attention_heads": 12,
  "pooler_num_fc_layers": 3,
  "pooler_size_per_head": 128,
  "pooler_type": "first_token_transform",
  "type_vocab_size": 2,
  "vocab_size": 21128
}

  • BERT预训练模型文件/home/ec2-user/toutiao/data/bert_pretrain/pytorch_model.bin

-rw-r--r-- 1 root root 411578458 1月   9 11:50 pytorch_model.bin

  • BERT预训练模型词典文件/home/ec2-user/toutiao/data/bert_pretrain/vocab.txt

[PAD]
[unused1]
[unused2]
[unused3]
[unused4]
[unused5]
[unused6]
[unused7]
[unused8]
[unused9]
[unused10]

......
......
......

[unused98]
[unused99]
[UNK]
[CLS]
[SEP]
[MASK]
<S>
<T>
!
......
......
......


第三步: 编写工具类函数

  • 工具类函数的路径为/home/ec2-user/toutiao/src/utils.py

  • 第一个工具类函数build_vocab(), 位于utils.py中的独立函数.

def build_vocab(file_path, tokenizer, max_size, min_freq):
    vocab_dic = {}
    with open(file_path, "r", encoding="UTF-8") as f:
        for line in tqdm(f):
            lin = line.strip()
            if not lin:
                continue
            content = lin.split("\t")[0]
            for word in tokenizer(content):
                vocab_dic[word] = vocab_dic.get(word, 0) + 1
        vocab_list = sorted(
                [_ for _ in vocab_dic.items() if _[1]>=min_freq],key=lambda x:x[1],reverse=True)[:max_size]
        vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}
        vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic) + 1})
    return vocab_dic

  • 第二个工具类函数build_dataset(), 位于utils.py中的独立函数.
def build_dataset(config):
    def load_dataset(path, pad_size=32):
        contents = []
        with open(path, "r", encoding="UTF-8") as f:
            for line in tqdm(f):
                line = line.strip()
                if not line:
                    continue
                content, label = line.split("\t")
                token = config.tokenizer.tokenize(content)
                token = [CLS] + token
                seq_len = len(token)
                mask = []
                token_ids = config.tokenizer.convert_tokens_to_ids(token)

                if pad_size:
                    if len(token) < pad_size:
                        mask = [1] * len(token_ids) + [0] * (pad_size - len(token))
                        token_ids += [0] * (pad_size - len(token))
                    else:
                        mask = [1] * pad_size
                        token_ids = token_ids[:pad_size]
                        seq_len = pad_size
                contents.append((token_ids, int(label), seq_len, mask))
        return contents

    train = load_dataset(config.train_path, config.pad_size)
    dev = load_dataset(config.dev_path, config.pad_size)
    test = load_dataset(config.test_path, config.pad_size)
    return train, dev, test

  • 第三个工具函数build_iterator(), 包括数据迭代器的类class DatasetIterater(), 位于utils.py中的独立函数和类.
class DatasetIterater(object):
    def __init__(self, batches, batch_size, device, model_name):
        self.batch_size = batch_size
        self.batches = batches
        self.model_name = model_name
        self.n_batches = len(batches) // batch_size
        self.residue = False  # 记录batch数量是否为整数
        if len(batches) % self.n_batches != 0:
            self.residue = True
        self.index = 0
        self.device = device

    def _to_tensor(self, datas):
        x = torch.LongTensor([_[0] for _ in datas]).to(self.device)
        y = torch.LongTensor([_[1] for _ in datas]).to(self.device)

        # pad前的长度(超过pad_size的设为pad_size)
        seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)
        if self.model_name == "bert" or self.model_name == "multi_task_bert":
            mask = torch.LongTensor([_[3] for _ in datas]).to(self.device)
            return (x, seq_len, mask), y

    def __next__(self):
        if self.residue and self.index == self.n_batches:
            batches = self.batches[self.index * self.batch_size : len(self.batches)]
            self.index += 1
            batches = self._to_tensor(batches)
            return batches

        elif self.index >= self.n_batches:
            self.index = 0
            raise StopIteration
        else:
            batches = self.batches[self.index * self.batch_size : (self.index + 1) * self.batch_size]
            self.index += 1
            batches = self._to_tensor(batches)
            return batches

    def __iter__(self):
        return self

    def __len__(self):
        if self.residue:
            return self.n_batches + 1
        else:
            return self.n_batches


def build_iterator(dataset, config):
    iter = DatasetIterater(dataset, config.batch_size, config.device, config.model_name)
    return iter

  • 第四个工具类函数get_time_dif(), 位于utils.py中的独立函数.
def get_time_dif(start_time):
    # 获取已使用时间
    end_time = time.time()
    time_dif = end_time - start_time
    return timedelta(seconds=int(round(time_dif)))


小节总结

  • 本小节讲解了BERT模型的相关技术细节, 并实现了若干工具函数, 这些工具函数会在未来的项目代码中应用.