资讯 文档
技术能力
语音技术
文字识别
人脸与人体
图像技术
语言与知识
视频技术

数据模块使用说明

模块综述

Data模块主要作用是对明文数据进行处理,处理成paddle框架可以使用的tensor类型数据。处理过程的主要步骤为:

(1)按样本格式和任务类型定义好py_reader OP需要的shape、level、type。

(2)从文件(文件夹)中按行读取明文数据,每行数据按列切分各个域,一行数据为一个样本,多个样本组成一个batch,用迭代器方式输出为一个个batch。

(3)对每个batch中的明文数据进行切词、按词表转成id序列。

(4)转成id序列的数据按(1)中定义好的shape、type 输入到 py_reader,生成tensor类型的数据。

(5)以样本中各个域的名称为key(如单句分类样本为text 、label ),以步骤(4)中生成的对应列的tensor数据为value,按dict形式输出给组网部分,组网部分就可以按域名称获取到对应的tensor数据,进而开始后续的组网操作。

概念解释

Vocabulary

词表的封装对象,读入一个词表文件,生成两个dict对象:vocab_dict和id_dict ,vocab_dict中key为明文词汇(token),value为词id,id从0开始编号,不可重复。id_dict中key为id,value为名为词汇。主要方法为convert_ids_to_tokens(id转明文token),convert_tokens_to_ids(明文token转id)。

    ...
    class Vocabulary(object):
    """Vocabulary"""
    def __init__(self, vocab_path, unk_token):
        """
        :param vocab_path: 词表地址,必填
        :param unk_token: unk默认的token,必填
        """
        if not vocab_path:
            raise ValueError("vocab_path can't be None")

        self.vocab_path = vocab_path
        self.unk_token = unk_token
        self.vocab_dict, self.id_dict = self.load_vocab()
        self.vocab_size = len(self.id_dict)

    def load_vocab(self):
        """
        :return:
        """
        vocab_dict = collections.OrderedDict()
        id_dict = collections.OrderedDict()
        file_vocab = open(self.vocab_path)
        for num, line in enumerate(file_vocab):
            items = convert_to_unicode(line.strip()).split("\t")
            if len(items) > 2:
                break
            token = items[0]
            if len(items) == 2:
                index = items[1]
            else:
                index = num
            token = token.strip()

            vocab_dict[token] = int(index)
            id_dict[index] = token

        return vocab_dict, id_dict

    def add_reserve_id(self):
        """添加预留的一些id
        :return:
        """
        pass

    def convert_tokens_to_ids(self, tokens):
        """
        :param tokens:
        :return:
        """
        output = []
        UNK = self.vocab_dict[self.unk_token]
        for item in tokens:
            output.append(self.vocab_dict.get(item, UNK))
        return output

    def convert_ids_to_tokens(self, ids):
        """
        :param ids:
        :return:
        """
        output = []
        for item in ids:
            output.append(self.id_dict.get(item, self.unk_token))
        return output

    def get_vocab_size(self):
        """获取词表大小
        :return:
        """
        return len(self.id_dict)

    def covert_id_to_token(self, id):
        """
        :param id:
        :return: token
        """
        return self.id_dict.get(id, self.unk_token)

    def covert_token_to_id(self, token):
        """
        :param token:
        :return: id
        """
        UNK = self.vocab_dict[self.unk_token]
        return self.vocab_dict.get(token, UNK)
    ...

Tokenizer

分词工具的封装,主要成员变量为一个Vocabulary对象,输入一串明文,输出一个token数组。同时提供token转id,和id转token的功能。备注:一个Tokenizer对象只能有一个Vocabulary成员。

    ...
@RegisterSet.tokenizer.register
class Tokenizer(object):
    """Tokenizer"""

    def __init__(self, vocab_file, split_char=" ", unk_token="[UNK]", params=None):
        """
        :param vocab_file: 词表文件路径
        :param split_char: 明文分隔符,默认是空格
        :param unk_token: unk 对应的token,默认是[UNK]
        :param params: 个别tokenizer自己用到的额外参数,dict类型
        """
        self.vocabulary = Vocabulary(vocab_file, unk_token)
        self.split_char = split_char
        self.unk_token = unk_token
        self.params = params

    def tokenize(self, text):
        """
        :param text:
        :return: tokens, list类型
        """
        raise NotImplementedError

    def convert_tokens_to_ids(self, tokens):
        """
        :param tokens:
        :return:
        """
        raise NotImplementedError

    def convert_ids_to_tokens(self, ids):
        """
        :param ids:
        :return:
        """
        raise NotImplementedError

    def covert_id_to_token(self, id):
        """
        :param id:
        :return: token
        """
        return self.vocabulary.covert_id_to_token(id)

    def covert_token_to_id(self, token):
        """
        :param token:
        :return: id
        """
        return self.vocabulary.covert_token_to_id(token)
    ...

FieldReader

针对于一个样本中的某个列(也称作域,或者field)的封装,主要成员变量是一个Tokenizer对象和一个config对象。 config对象定义这个field的名称、数据类型、字符串最大长度、词表路径等一些配置。 Tokenizer对象用来对field对应的明文进行切词、转id。 FieldReader的主要功能有:定义这个field对应的数据转成py_reader之后的格式,明文转id序列(通过tokenizer对象实现),明文id序列转成dict形式输出。 注:一个FieldReader只能有一个Tokenzier成员。若文本域为离散型,即不需要tokenizer的文本域,则可以参考categorical_field_reader.py来实现。

    ...
    
@RegisterSet.field_reader.register
class BaseFieldReader(object):
    """BaseFieldReader: 作用于field的reader,主要是定义py_reader的格式,完成id序列化和embedding的操作
    """
    def __init__(self, field_config):
        self.field_config = field_config
        self.tokenizer = None  # 用来分词,需要各个子类实现
        self.token_embedding = None  # 用来生成embedding向量,需要各个子类实现

    def init_reader(self):
        """ 初始化reader格式
        :return: reader的shape[]、type[]、level[]
        """
        raise NotImplementedError

    def convert_texts_to_ids(self, batch_text):
        """ 明文序列化
        :param:batch_text
        :return: id_list
        """
        raise NotImplementedError

    def get_field_length(self):
        """获取当前这个field在进行了序列化之后,在field_id_list中占多少长度
        :return:
        """
        raise NotImplementedError

    def structure_fields_dict(self, fields_id, start_index, need_emb=True):
        """静态图调用的方法,生成一个dict, dict有两个key:id , emb. id对应的是pyreader读出来的各个field产出的id,emb对应的是各个
        field对应的embedding
        :param fields_id: pyreader输出的完整的id序列
        :param start_index:当前需要处理的field在field_id_list中的起始位置
        :param need_emb:是否需要embedding(预测过程中是不需要embedding的)
        :return:
        """
        raise NotImplementedError
    ...

DataSetReader

针对一个数据集(文件/文件夹)中的数据,将数据读取、shuffle、组batch等操作统一封装起来,组装成一个py_reader,向外提供一个统一的接口。核心内容是读取明文数据集,通过迭代器将数据组成多个batch,然后转成id序列,灌给py_reader,通过fluid.layers.read_file(py_reader) 接口来获取出对应的tensor数据, 最后将这些tensor以各个field的name为key,转成一个dict类型输出给model(神经网络组网)模块。备注:一个DataSetReader可以有多个FieldReader,对于那种没有域的概念或者各个域之间不相互独立的情况,像阅读理解任务,就不需要使用field_reader, 按BaseDataSetReader接口自定义自己的DatasetReader即可。

    ...
    
@RegisterSet.data_set_reader.register
class BaseDataSetReader(object):
    """BaseDataSetReader:将样本中数据组装成一个py_reader, 向外提供一个统一的接口。
    核心内容是读取明文文件,转换成id,按py_reader需要的tensor格式灌进去,然后通过调用run方法让整个循环跑起来。
    py_reader拿出的来的是lod-tensor形式的id,这些id可以用来做后面的embedding等计算。
    """
    def __init__(self, name, fields, config):
        self.name = name
        self.fields = fields
        self.config = config  # 常用参数,batch_size等,ReaderConfig类型变量
        self.paddle_py_reader = None
        self.current_example = 0
        self.current_epoch = 0
        self.num_examples = 0

    def create_reader(self):
        """ 
        必须选项,否则会抛出异常。
        用于初始化self.paddle_py_reader。
        ``self.paddle_py_reader = fluid.layers.py_reader(
            capacity=capacity,
            shapes=shapes,
            name=self.name,
            dtypes=types,
            lod_levels=levels,
            use_double_buffer=True)
        ``
        :return:None
        """
        raise NotImplementedError

    def instance_fields_dict(self):
        """
        必须选项,否则会抛出异常。
        实例化fields_dict, 调用pyreader,得到fields_id, 视情况构造embedding,然后结构化成dict类型返回给组网部分。
        :return:dict
                {"field_name":
                    {"RECORD_ID":
                        {"SRC_IDS": [ids],
                         "MASK_IDS": [ids],
                         "SEQ_LENS": [ids]
                        }
                    }
                }
        实例化的dict,保存了各个field的id和embedding(可以没有,是情况而定), 给trainer用.
        
        """
        raise NotImplementedError

    def data_generator(self):
        """
        必须选项,否则会抛出异常。
        数据生成器:读取明文文件,生成batch化的id数据,绑定到py_reader中
        :return:list
                [[src_ids],
                 [mask_ids],
                 [seq_lens]
                ]
        """
        raise NotImplementedError


    def convert_fields_to_dict(self, field_list, need_emb=False):
        """instance_fields_dict一般调用本方法实例化fields_dict,保存各个field的id和embedding(可以没有,是情况而定),
        当need_emb=False的时候,可以直接给predictor调用
        :param field_list:
        :param need_emb:
        :return: dict
        """
        raise NotImplementedError

    def run(self):
        """
        配置py_reader对应的数据生成器,并启动
        :return:
        """
        if self.paddle_py_reader:
            self.paddle_py_reader.decorate_tensor_provider(self.data_generator())
            self.paddle_py_reader.start()
            logging.info("set data_generator and start.......")
        else:
            raise ValueError("paddle_py_reader is None")

    def stop(self):
        """
        :return:
        """
        if self.paddle_py_reader:
            self.paddle_py_reader.reset()
        else:
            raise ValueError("paddle_py_reader is None")

    def get_train_progress(self):
        """Gets progress for training phase."""
        return self.current_example, self.current_epoch

    def get_num_examples(self):
        """get number of example"""
        return self.num_examples

    ...

DataSet

一个NLP任务,可以在训练过程中对评估集、测试集进行效果验证,那也就意味着有多个数据集,一个DataSetReader可以对一个数据集进行操作,那么DataSet就是将多个DataSetReader集中管理起来。目前的DataSet的主要操作是根据Json配置进行多个DataSetReader的初始化,主要包含以下几个成员变量:训练集DataSetReader(train_reader)、测试集DataSetReader(test_reader)、评估集DataSetReader(dev_reader)、预测集DataSetReader(predict_reader).

    ...
    
class DataSet(object):
    """DataSet"""
    def __init__(self, params_dict):
        """"""
        self.train_reader = None
        self.test_reader = None
        self.dev_reader = None
        self.predict_reader = None

        self.params_dict = params_dict

    def build(self):
        """ init readers
        :return:
        """

    ...

设计思路

整体设计

Data模块的功能是将明文数据集处理成组网可用的tensor类型数据,并以dict形式传入给组网模块供其后续op使用。具体的处理步骤由DataSetReader控制,对用户透明。

输入:明文数据集

输出:以dict形式迭代出一个个batch,数据类型为tensor。

文心平台空间 > 「公共版」文心v1.6.0 Data模块使用说明 > image2020-3-17_19-18-21.png

主要类图

预置内容

预置FieldReader:

TextFieldReader:最基本的文本(String)类型的field_reader,只返回原始src_id(添加了padding)和length。在py_reader中对应的shape=[[batch_size, max_lens, 1], [batch_size,1]]。 其中第一个元素为src_ids, 第二个元素为length

CustomTextFieldReader:通用文本(String)类型的field_reader,文本处理规则是:文本类型的数据会自动添加padding和mask,并返回length。在py_reader中对应的shape=[[batch_size, max_lens, 1], [batch_size, max_lens, 1],[batch_size,1]]。 其中第一个元素为src_ids, 第二个元素为mask_ids,第三个元素为length

ErnieTextFieldReader:使用ernie的文本类型的field_reader,用户不需要自己分词(搭配FullTokenizer使用),处理规则是:自动添加padding,mask,position,task,sentence,并返回length。在py_reader中对应的shape=[[batch_size, max_lens, 1], [batch_size, max_lens, 1], [batch_size, max_lens, 1], [batch_size, max_lens, 1], [batch_size, max_lens, 1],[batch_size,1]]。 其中第一个元素为src_ids, 第二个元素为sentence_ids,第三个元素为position_ids,第四个元素为mask_ids,第五个元素为task_ids, 第六个元素为length。

ErnieClassificationFieldReader:使用ernie的文本类型的field_reader,用户不需要自己分词(搭配FullTokenizer使用),处理规则是:自动添加padding,mask,position,task,sentence,并返回length。shape和ErnieTextFieldReader规则一样。

与ErnieTextFieldReader的区别是如果输入的明文是句对(text_a, text_b),会将两个text拼接成一个text处理,为CLS + text_a + SEP + text_b + SEP

ErnieSeqlabelLabelFieldReader:基于ernie的序列标注专用field_reader,处理规则和custom_text_field一样,自动添加padding和mask,并返回length。不同的地方在于ErnieSeqlabelLabelFieldReader会在序列首尾分别添加UNK_ID来占位,以保证和ernie序列化过程中的[CLS]和[SEP]对应

GenerateLabelFieldReader:seq2seq (文本生成)任务中 label域的专用field_reader,会用label对应的明文先生成src_id , start_id + src_id 来生成train_src _id, src_id + end_id来生成infer_src_id ,接着对train_src _id, infer_src_id分别进行padding和mask,并返回length。

ScalarFieldReader:单个标量的field_reader,直接返回数据本身(数据可以是单个数字,也可以是单个的明文字符,明文通过json文件中配置的vocab_path去进行转换),支持int64和float32两种类型,shape= [batch_size,1]

ScalarArrayFieldReader:标量数组的field_reader,直接返回数据本身(数据可以是明文字符串,但必须是已经分词之后的,且以空格分隔,明文通过json文件中配置的vocab_path去进行转换)和数据长度,支持int64和float32两种类型。备注:数据是加了padding的。

CategoricalField:通用文本(string)类型的field_reader, 不进行分词,直接通过词表将明文转成id,文本处理规则是,文本类型的数据会自动添加padding和mask,并返回length. 比较特殊的一点是,这个field_reader中的length全是1。

预置DataSetReader:

BasicDataSetReader:一个最基础的DataSetReader,实现了文件读取,id序列化,token embedding化等基本操作。

ErnieClassificationDataSetReader:基于Ernie的、拼接方式的句对匹配任务使用的DataSetReader ,需要配合ErnieClassificationFieldReader使用,实现了文件读取,id序列化,token embedding化等基本操作。

预置Tokenizer:

CustomTokenizer:非Ernie任务使用的Tokenizer,将用户输入的明文数据按配置的分割符分隔成多个tokens,然后按配置的词表路径构造Vocabulary,将tokens转成ids。CustomTokenizer不提供分词功能,

需要用户自己先分好词再输入。词表文件也需要用户自己提供。

FullTokenizer:基于Ernie任务的Tokenizer,输入为明文数据(不需要切词),输出为切字之后的ids。Ernie词表是内置好的,不需要用户提供。

WSSPTokenizer:Ernie-Tiny任务专用的Tokenizer,输入为明文数据(不需要切词),输出为切词(SentencePiece方式)之后的ids。Ernie词表是内置好的,不需要用户提供。

目录结构

.                                                        
├── data                                                                  ## 数据读取、嵌入部分
│   ├── __init__.py
│   ├── data_set.py                                                       ## 区分训练与评估的与不同域的reader                                                    
│   ├── data_set_reader
│   │   ├── __init__.py                                                 
│   │   ├── base_dataset_reader.py                                        ## 基类,读文件、组batch
│   │   ├── basic_dataset_reader.py                                     
│   │   └── ...
│   ├── field.py                                                          ## 域对象的定义
│   ├── field_reader
│   │   ├── __init__.py                                                 
│   │   ├── base_field_reader.py                                          ## 基类
│   │   ├── custom_text_field_reader.py                                   ## 通用文本reader basic + mask(用于区分padding), 非ernie网络通用
│   │   ├── ernie_classification_field_reader.py                          ## ernie句对分类reader
│   │   ├── ernie_seqlabel_label_field_reader.py                          ## ernie序列标注标签域reader                       
│   │   ├── ernie_text_field_reader.py                                    ## ernie双塔文本域reader
│   │   ├── generate_label_field_reader.py                                ## 文本生成标签域reader
│   │   ├── scalar_array_field_reader.py                                  ## 数值类型数组reader,需要指定整型还是浮点型,支持转为id的文本数据
│   │   ├── scalar_field_reader.py                                        ## 数值类型标量reader,需要指定整型还是浮点型
│   │   └── text_field_reader.py                                          ## 文本reader 只输出padding后的id length(区分非padding)
│   ├── reader_config.py                                                   ## 一些配置参数,如batch-size、epoch、shuffle等
│   ├── tokenizer
│   │   ├── __init__.py 
│   │   ├── tokenizer.py                                                  ## 基类
│   │   ├── custom_tokenizer.py                                           ## 分词用tokenizer, 对于已用空格分好词的文本                                           
│   │   ├── tokenization_spm.py											 ## 针对英文数据集的ernie pre-train任务的tokenizer, 用户不需要分词
│   │   ├── tokenization_utils.py                                         ## tokenizer过程中会用到的工具方法,如unicode转换、空格清理等
│   │   └── tokenization_wp.py                                            ## 针对中文数据集的ernie任务用到的tokenizer,包括FullTokenzier、WordPiece、SentencePiece等
│   ├── util_helper.py                                                    ## 数据处理过程中的一些工具方法,比如添加padding、添加mask。
│   ├── vocabulary.py                                                     ## 词表模块
├──

举个例子

句对匹配任务(非Ernie任务)

从数据来看,一条样本有三个域 : text_a , text_b, label ,域之间相互独立,各自生成对应的tensor, 那么就应该选择BasicDataSetReader。 text_a , text_b是文本(String)类型,且是分过词的,词之间以空格分隔,那么应该使用CustomTextFieldReader和CustomTokenizer 。label是数字(int)类型, 那么应该使用ScalarFieldReader,对应的类型为int64。详细任务配置可参考「公共版」文心v1.6.0 文本匹配(Text Matching)

基于Ernie的单句分类任务

从数据来看,一条样本有两个域 : text_a , label ,域之间相互独立,各自生成对应的tensor, 那么就应该选择BasicDataSetReader。text_a 是文本(String)类型,没有分词(Ernie任务不需要分词),那么应该使用ErnieTextFieldReader。

label是数字(int)类型, 那么应该使用ScalarFieldReader,对应的类型为int64。详细任务配置可参考「公共版」文心v1.6.0 文本分类(Text Classification)

其余任务的reader配置请参考 reader配置说明