diff --git a/fastNLP/core/dataset.py b/fastNLP/core/dataset.py index 013816a6..41858bdc 100644 --- a/fastNLP/core/dataset.py +++ b/fastNLP/core/dataset.py @@ -210,34 +210,62 @@ class DataSet(object): raise KeyError("DataSet has no field named {}.".format(old_name)) def set_target(self, *field_names, flag=True): - """Change the target flag of these fields. + """将field_names的target设置为flag状态 + Example:: - :param field_names: a sequence of str, indicating field names - :param bool flag: Set these fields as target if True. Unset them if False. + dataset.set_target('labels', 'seq_len') # 将labels和seq_len这两个field的target属性设置为True + dataset.set_target('labels', 'seq_lens', flag=False) # 将labels和seq_len的target属性设置为False + + :param field_names: str, field的名称 + :param flag: bool, 将field_name的target状态设置为flag """ + assert isinstance(flag, bool), "Only bool type supported." for name in field_names: if name in self.field_arrays: self.field_arrays[name].is_target = flag else: raise KeyError("{} is not a valid field name.".format(name)) - def set_input(self, *field_name, flag=True): - """Set the input flag of these fields. + def set_input(self, *field_names, flag=True): + """将field_name的input设置为flag状态 + Example:: + + dataset.set_input('words', 'seq_len') # 将words和seq_len这两个field的input属性设置为True + dataset.set_input('words', flag=False) # 将words这个field的input属性设置为False - :param field_name: a sequence of str, indicating field names. - :param bool flag: Set these fields as input if True. Unset them if False. + :param field_names: str, field的名称 + :param flag: bool, 将field_name的input状态设置为flag """ - for name in field_name: + for name in field_names: if name in self.field_arrays: self.field_arrays[name].is_input = flag else: raise KeyError("{} is not a valid field name.".format(name)) + def set_ignore_type(self, *field_names, flag=True): + """将field_names的ignore_type设置为flag状态 + + :param field_names: str, field的名称 + :param flag: bool, + :return: + """ + assert isinstance(flag, bool), "Only bool type supported." + for name in field_names: + if name in self.field_arrays: + self.field_arrays[name].ignore_type = flag + else: + raise KeyError("{} is not a valid field name.".format(name)) + def set_padder(self, field_name, padder): """为field_name设置padder + Example:: + + from fastNLP import EngChar2DPadder + padder = EngChar2DPadder() + dataset.set_padder('chars', padder) # 则chars这个field会使用EngChar2DPadder进行pad操作 :param field_name: str, 设置field的padding方式为padder - :param padder: PadderBase类型或None. 设置为None即删除padder。即对该field不进行padding操作. + :param padder: (None, PadderBase). 设置为None即删除padder, 即对该field不进行padding操作. :return: """ if field_name not in self.field_arrays: diff --git a/fastNLP/core/fieldarray.py b/fastNLP/core/fieldarray.py index c9caea56..3d0fb582 100644 --- a/fastNLP/core/fieldarray.py +++ b/fastNLP/core/fieldarray.py @@ -1,98 +1,6 @@ import numpy as np from copy import deepcopy -class PadderBase: - """ - 所有padder都需要继承这个类,并覆盖__call__()方法。 - 用于对batch进行padding操作。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前deepcopy一份。 - """ - def __init__(self, pad_val=0, **kwargs): - self.pad_val = pad_val - - def set_pad_val(self, pad_val): - self.pad_val = pad_val - - def __call__(self, contents, field_name, field_ele_dtype): - """ - 传入的是List内容。假设有以下的DataSet。 - from fastNLP import DataSet - from fastNLP import Instance - dataset = DataSet() - dataset.append(Instance(word='this is a demo', length=4, - chars=[['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']])) - dataset.append(Instance(word='another one', length=2, - chars=[['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']])) - # 如果batch_size=2, 下面只是用str的方式看起来更直观一点,但实际上可能word和chars在pad时都已经为index了。 - word这个field的pad_func会接收到的内容会是 - [ - 'this is a demo', - 'another one' - ] - length这个field的pad_func会接收到的内容会是 - [4, 2] - chars这个field的pad_func会接收到的内容会是 - [ - [['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']], - [['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']] - ] - 即把每个instance中某个field的内容合成一个List传入 - :param contents: List[element]。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 - deepcopy一份。 - :param field_name: str, field的名称,帮助定位错误 - :param field_ele_dtype: np.int64, np.float64, np.str. 该field的内层list元素的类型。辅助判断是否pad,大多数情况用不上 - :return: List[padded_element]或np.array([padded_element]) - """ - raise NotImplementedError - - -class AutoPadder(PadderBase): - """ - 根据contents的数据自动判定是否需要做padding。 - - 1 如果元素类型(元素类型是指field中最里层List的元素的数据类型, 可以通过FieldArray.dtype查看,比如['This', 'is', ...]的元素类 - 型为np.str, [[1,2], ...]的元素类型为np.int64)的数据不为(np.int64, np.float64)则不会进行padding - - 2 如果元素类型为(np.int64, np.float64), - - 2.1 如果该field的内容只有一个,比如为sequence_length, 则不进行padding - - 2.2 如果该field的内容为List, 那么会将Batch中的List pad为一样长。若该List下还有里层的List需要padding,请使用其它padder。 - 如果某个instance中field为[1, 2, 3],则可以pad; 若为[[1,2], [3,4, ...]]则不能进行pad - """ - def __init__(self, pad_val=0): - """ - :param pad_val: int, padding的位置使用该index - """ - super().__init__(pad_val=pad_val) - - def _is_two_dimension(self, contents): - """ - 判断contents是不是只有两个维度。[[1,2], [3]]是两个维度. [[[1,2], [3, 4, 5]], [[4,5]]]有三个维度 - :param contents: - :return: - """ - value = contents[0] - if isinstance(value , (np.ndarray, list)): - value = value[0] - if isinstance(value, (np.ndarray, list)): - return False - return True - return False - - def __call__(self, contents, field_name, field_ele_dtype): - if not is_iterable(contents[0]): - array = np.array([content for content in contents], dtype=field_ele_dtype) - elif field_ele_dtype in (np.int64, np.float64) and self._is_two_dimension(contents): - max_len = max([len(content) for content in contents]) - array = np.full((len(contents), max_len), self.pad_val, dtype=field_ele_dtype) - for i, content in enumerate(contents): - array[i][:len(content)] = content - elif field_ele_dtype is None: - array = np.array(contents) # 当ignore_type=True时,直接返回contents - else: # should only be str - array = np.array([content for content in contents]) - return array - class FieldArray(object): """``FieldArray`` is the collection of ``Instance``s of the same field. @@ -336,18 +244,18 @@ class FieldArray(object): self.content.append(val) def __getitem__(self, indices): - return self.get(indices) + return self.get(indices, pad=False) def __setitem__(self, idx, val): assert isinstance(idx, int) self.content[idx] = val def get(self, indices, pad=True): - """Fetch instances based on indices. + """根据给定的indices返回内容 - :param indices: an int, or a list of int. - :param pad: bool, 是否对返回的结果进行padding。 - :return: + :param indices: (int, List[int]), 获取indices对应的内容。 + :param pad: bool, 是否对返回的结果进行padding。仅对indices为List[int]时有效 + :return: (single, List) """ if isinstance(indices, int): return self.content[indices] @@ -362,14 +270,16 @@ class FieldArray(object): def set_padder(self, padder): """ - 设置padding方式 + 设置padder,在这个field进行pad的时候用这个padder进行pad,如果为None则不进行pad。 - :param padder: PadderBase类型或None. 设置为None即删除padder. + :param padder: (None, PadderBase). 设置为None即删除padder. :return: """ if padder is not None: assert isinstance(padder, PadderBase), "padder must be of type PadderBase." - self.padder = deepcopy(padder) + self.padder = deepcopy(padder) + else: + self.padder = None def set_pad_val(self, pad_val): """修改padder的pad_val. @@ -391,7 +301,7 @@ class FieldArray(object): def to(self, other): """ - 将other的属性复制给本FieldArray(other必须为FieldArray类型). 包含 is_input, is_target, padder, ignore_type + 将other的属性复制给本FieldArray(other必须为FieldArray类型).属性包括 is_input, is_target, padder, ignore_type :param other: FieldArray :return: @@ -413,17 +323,136 @@ def is_iterable(content): return True +class PadderBase: + """ + 所有padder都需要继承这个类,并覆盖__call__()方法。 + 用于对batch进行padding操作。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前deepcopy一份。 + """ + + def __init__(self, pad_val=0, **kwargs): + self.pad_val = pad_val + + def set_pad_val(self, pad_val): + self.pad_val = pad_val + + def __call__(self, contents, field_name, field_ele_dtype): + """ + 传入的是List内容。假设有以下的DataSet。 + + :param contents: List[element]。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 + deepcopy一份。 + :param field_name: str, field的名称。 + :param field_ele_dtype: (np.int64, np.float64, np.str, None), 该field的内层元素的类型。如果该field的ignore_type + 为True,该这个值为None。 + :return: np.array([padded_element]) + + Example:: + + from fastNLP import DataSet + from fastNLP import Instance + dataset = DataSet() + dataset.append(Instance(sent='this is a demo', length=4, + chars=[['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']])) + dataset.append(Instance(sent='another one', length=2, + chars=[['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']])) + 如果调用 + batch = dataset.get([0,1], pad=True) + sent这个field的padder的__call__会接收到的内容会是 + [ + 'this is a demo', + 'another one' + ] + + length这个field的padder的__call__会接收到的内容会是 + [4, 2] + + chars这个field的padder的__call__会接收到的内容会是 + [ + [['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']], + [['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']] + ] + + 即把每个instance中某个field的内容合成一个List传入 + + """ + raise NotImplementedError + + +class AutoPadder(PadderBase): + """ + 根据contents的数据自动判定是否需要做padding。 + + 1 如果元素类型(元素类型是指field中最里层元素的数据类型, 可以通过FieldArray.dtype查看,比如['This', 'is', ...]的元素类 + 型为np.str, [[1,2], ...]的元素类型为np.int64)的数据不为(np.int64, np.float64)则不会进行pad + + 2 如果元素类型为(np.int64, np.float64), + + 2.1 如果该field的内容为(np.int64, np.float64),比如为seq_len, 则不进行padding + + 2.2 如果该field的内容为List, 那么会将Batch中的List pad为一样长。若该List下还有里层的List需要padding,请使用其它padder。 + 如果某个instance中field为[1, 2, 3],则可以pad;若为[[1,2], [3,4, ...]]则不能进行pad + """ + + def __init__(self, pad_val=0): + """ + :param pad_val: int, padding的位置使用该index + """ + super().__init__(pad_val=pad_val) + + def _is_two_dimension(self, contents): + """ + 判断contents是不是只有两个维度。[[1,2], [3]]是两个维度. [[[1,2], [3, 4, 5]], [[4,5]]]有三个维度 + :param contents: + :return: + """ + value = contents[0] + if isinstance(value, (np.ndarray, list)): + value = value[0] + if isinstance(value, (np.ndarray, list)): + return False + return True + return False + + def __call__(self, contents, field_name, field_ele_dtype): + if not is_iterable(contents[0]): + array = np.array([content for content in contents], dtype=field_ele_dtype) + elif field_ele_dtype in (np.int64, np.float64) and self._is_two_dimension(contents): + max_len = max([len(content) for content in contents]) + array = np.full((len(contents), max_len), self.pad_val, dtype=field_ele_dtype) + for i, content in enumerate(contents): + array[i][:len(content)] = content + elif field_ele_dtype is None: + array = np.array(contents) # 当ignore_type=True时,直接返回contents + else: # should only be str + array = np.array([content for content in contents]) + return array + + class EngChar2DPadder(PadderBase): """ - 用于为英语执行character级别的2D padding操作。对应的field内容应该为[['T', 'h', 'i', 's'], ['a'], ['d', 'e', 'm', 'o']](这里为 - 了更直观,把它们写为str,但实际使用时它们应该是character的index)。 - padded过后的batch内容,形状为(batch_size, max_sentence_length, max_word_length). max_sentence_length最大句子长度。 - max_word_length最长的word的长度 + 用于为英语执行character级别的2D padding操作。对应的field内容应该类似[['T', 'h', 'i', 's'], ['a'], ['d', 'e', 'm', 'o']], + 但这个Padder只能处理index为int的情况。 + padded过后的batch内容,形状为(batch_size, max_sentence_length, max_word_length). max_sentence_length为这个batch中最大句 + 子长度;max_word_length为这个batch中最长的word的长度 + + Example:: + + from fastNLP import DataSet + from fastNLP import EnChar2DPadder + from fastNLP import Vocabulary + dataset = DataSet({'sent': ['This is the first demo', 'This is the second demo']}) + dataset.apply(lambda ins:[list(word) for word in ins['sent'].split()], new_field_name='chars') + vocab = Vocabulary() + vocab.from_dataset(dataset, field_name='chars') + vocab.index_dataset(dataset, field_name='chars') + dataset.set_input('chars') + padder = EnChar2DPadder() + dataset.set_padder('chars', padder) # chars这个field的设置为了EnChar2DPadder """ def __init__(self, pad_val=0, pad_length=0): """ - :param pad_val: int, padding的位置使用该index + :param pad_val: int, pad的位置使用该index :param pad_length: int, 如果为0则取一个batch中最大的单词长度作为padding长度。如果为大于0的数,则将所有单词的长度都pad或截 取到该长度. """