Browse Source

注释增加

tags/v0.4.10
yh 6 years ago
parent
commit
361a090c26
2 changed files with 172 additions and 115 deletions
  1. +37
    -9
      fastNLP/core/dataset.py
  2. +135
    -106
      fastNLP/core/fieldarray.py

+ 37
- 9
fastNLP/core/dataset.py View File

@@ -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:


+ 135
- 106
fastNLP/core/fieldarray.py View File

@@ -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或截
取到该长度.
"""


Loading…
Cancel
Save