90 lines
3.9 KiB
Python
90 lines
3.9 KiB
Python
# -*- coding:utf-8 -*-
|
|
# Author: hankcs
|
|
# Date: 2019-11-10 21:00
|
|
from abc import ABC
|
|
from typing import Tuple, Union
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
from hanlp_common.structure import SerializableDict
|
|
from hanlp.common.transform_tf import Transform
|
|
from hanlp_common.constant import PAD
|
|
from hanlp.common.vocab_tf import create_label_vocab
|
|
from hanlp.utils.io_util import read_cells
|
|
from hanlp.utils.log_util import logger
|
|
|
|
|
|
class TableTransform(Transform, ABC):
|
|
def __init__(self, config: SerializableDict = None, map_x=False, map_y=True, x_columns=None,
|
|
y_column=-1, multi_label=False,
|
|
skip_header=True, delimiter='auto', **kwargs) -> None:
|
|
super().__init__(config, map_x, map_y, x_columns=x_columns, y_column=y_column, multi_label=multi_label,
|
|
skip_header=skip_header,
|
|
delimiter=delimiter, **kwargs)
|
|
self.label_vocab = create_label_vocab()
|
|
|
|
def file_to_inputs(self, filepath: str, gold=True):
|
|
x_columns = self.config.x_columns
|
|
y_column = self.config.y_column
|
|
num_features = self.config.get('num_features', None)
|
|
for cells in read_cells(filepath, skip_header=self.config.skip_header, delimiter=self.config.delimiter):
|
|
#multi-label: Dataset in .tsv format: x_columns: at most 2 columns being a sentence pair while in most
|
|
# cases just one column being the doc content. y_column being the single label, which shall be modified
|
|
# to load a list of labels.
|
|
if x_columns:
|
|
inputs = tuple(c for i, c in enumerate(cells) if i in x_columns), cells[y_column]
|
|
else:
|
|
if y_column != -1:
|
|
cells[-1], cells[y_column] = cells[y_column], cells[-1]
|
|
inputs = tuple(cells[:-1]), cells[-1]
|
|
if num_features is None:
|
|
num_features = len(inputs[0])
|
|
self.config.num_features = num_features
|
|
# multi-label support
|
|
if self.config.get('multi_label', None):
|
|
assert type(inputs[1]) is str, 'Y value has to be string'
|
|
if inputs[1][0] == '[':
|
|
# multi-label is in literal form of a list
|
|
labels = eval(inputs[1])
|
|
else:
|
|
labels = inputs[1].strip().split(',')
|
|
inputs = inputs[0], labels
|
|
else:
|
|
assert num_features == len(inputs[0]), f'Numbers of columns {num_features} ' \
|
|
f'inconsistent with current {len(inputs[0])}'
|
|
yield inputs
|
|
|
|
def inputs_to_samples(self, inputs, gold=False):
|
|
pad = self.label_vocab.safe_pad_token
|
|
for cells in inputs:
|
|
if gold:
|
|
yield cells
|
|
else:
|
|
yield cells, pad
|
|
|
|
def y_to_idx(self, y) -> tf.Tensor:
|
|
return self.label_vocab.lookup(y)
|
|
|
|
def fit(self, trn_path: str, **kwargs):
|
|
samples = 0
|
|
for t in self.file_to_samples(trn_path, gold=True):
|
|
if self.config.get('multi_label', None):
|
|
for l in t[1]:
|
|
self.label_vocab.add(l)
|
|
else:
|
|
self.label_vocab.add(t[1]) # the second one regardless of t is pair or triple
|
|
samples += 1
|
|
return samples
|
|
|
|
def create_types_shapes_values(self) -> Tuple[Tuple, Tuple, Tuple]:
|
|
num_features = self.config.num_features
|
|
# It's crucial to use tuple instead of list for all the three
|
|
types = tuple([tf.string] * num_features), tf.string
|
|
shapes = tuple([[]] * num_features), []
|
|
values = tuple([PAD] * num_features), self.label_vocab.safe_pad_token
|
|
return types, shapes, values
|
|
|
|
def x_to_idx(self, x) -> Union[tf.Tensor, Tuple]:
|
|
logger.warning('TableTransform can not map x to idx. Please override x_to_idx')
|
|
return x
|