168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
# -*- coding:utf-8 -*-
|
|
# Author: hankcs
|
|
# Date: 2020-05-27 15:06
|
|
import logging
|
|
import os
|
|
import sys
|
|
from typing import Optional, Callable
|
|
|
|
import fasttext
|
|
import torch
|
|
from torch import nn
|
|
from torch.nn.utils.rnn import pad_sequence
|
|
|
|
from hanlp_common.configurable import AutoConfigurable
|
|
from torch.utils.data import DataLoader
|
|
|
|
from hanlp.common.dataset import PadSequenceDataLoader, TransformableDataset
|
|
from hanlp.common.torch_component import TorchComponent
|
|
from hanlp.common.transform import EmbeddingNamedTransform
|
|
from hanlp.common.vocab import Vocab
|
|
from hanlp.layers.embeddings.embedding import Embedding
|
|
from hanlp.utils.io_util import get_resource, stdout_redirected
|
|
from hanlp.utils.log_util import flash
|
|
|
|
|
|
class FastTextTransform(EmbeddingNamedTransform):
|
|
def __init__(self, filepath: str, src, dst=None, **kwargs) -> None:
|
|
if not dst:
|
|
dst = src + '_fasttext'
|
|
self.filepath = filepath
|
|
flash(f'Loading fasttext model {filepath} [blink][yellow]...[/yellow][/blink]')
|
|
filepath = get_resource(filepath)
|
|
with stdout_redirected(to=os.devnull, stdout=sys.stderr):
|
|
self._model = fasttext.load_model(filepath)
|
|
flash('')
|
|
output_dim = self._model['king'].size
|
|
super().__init__(output_dim, src, dst)
|
|
|
|
def __call__(self, sample: dict):
|
|
word = sample[self.src]
|
|
if isinstance(word, str):
|
|
vector = self.embed(word)
|
|
else:
|
|
vector = torch.stack([self.embed(each) for each in word])
|
|
sample[self.dst] = vector
|
|
return sample
|
|
|
|
def embed(self, word: str):
|
|
return torch.tensor(self._model[word])
|
|
|
|
|
|
class SelectFromBatchModule(torch.nn.Module):
|
|
def __init__(self, key) -> None:
|
|
super().__init__()
|
|
self.key = key
|
|
|
|
def __call__(self, batch: dict, mask=None, **kwargs):
|
|
return batch[self.key]
|
|
|
|
|
|
class FastTextEmbeddingModule(SelectFromBatchModule):
|
|
|
|
def __init__(self, key, embedding_dim: int) -> None:
|
|
"""An embedding layer for fastText (:cite:`bojanowski2017enriching`).
|
|
|
|
Args:
|
|
key: Field name.
|
|
embedding_dim: Size of this embedding layer
|
|
"""
|
|
super().__init__(key)
|
|
self.embedding_dim = embedding_dim
|
|
|
|
def __call__(self, batch: dict, mask=None, **kwargs):
|
|
outputs = super().__call__(batch, **kwargs)
|
|
outputs = pad_sequence(outputs, True, 0)
|
|
if mask is not None:
|
|
outputs = outputs.to(mask.device)
|
|
return outputs
|
|
|
|
def __repr__(self):
|
|
s = self.__class__.__name__ + '('
|
|
s += f'key={self.key}, embedding_dim={self.embedding_dim}'
|
|
s += ')'
|
|
return s
|
|
|
|
def get_output_dim(self):
|
|
return self.embedding_dim
|
|
|
|
|
|
class FastTextEmbedding(Embedding, AutoConfigurable):
|
|
def __init__(self, src: str, filepath: str) -> None:
|
|
"""An embedding layer builder for fastText (:cite:`bojanowski2017enriching`).
|
|
|
|
Args:
|
|
src: Field name.
|
|
filepath: Filepath to pretrained fastText embeddings.
|
|
"""
|
|
super().__init__()
|
|
self.src = src
|
|
self.filepath = filepath
|
|
self._fasttext = FastTextTransform(self.filepath, self.src)
|
|
|
|
def transform(self, **kwargs) -> Optional[Callable]:
|
|
return self._fasttext
|
|
|
|
def module(self, **kwargs) -> Optional[nn.Module]:
|
|
return FastTextEmbeddingModule(self._fasttext.dst, self._fasttext.output_dim)
|
|
|
|
|
|
class FastTextDataset(TransformableDataset):
|
|
|
|
def load_file(self, filepath: str):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
|
|
class FastTextEmbeddingComponent(TorchComponent):
|
|
def __init__(self, **kwargs) -> None:
|
|
""" Toy example of Word2VecEmbedding. It simply returns the embedding of a given word
|
|
|
|
Args:
|
|
**kwargs:
|
|
"""
|
|
super().__init__(**kwargs)
|
|
|
|
def build_dataloader(self, data, shuffle=False, device=None, logger: logging.Logger = None,
|
|
**kwargs) -> DataLoader:
|
|
embed: FastTextEmbedding = self.config.embed
|
|
dataset = FastTextDataset([{'token': data}], transform=embed.transform())
|
|
return PadSequenceDataLoader(dataset, device=device)
|
|
|
|
def build_optimizer(self, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def build_criterion(self, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def build_metric(self, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def execute_training_loop(self, trn: DataLoader, dev: DataLoader, epochs, criterion, optimizer, metric, save_dir,
|
|
logger: logging.Logger, devices, ratio_width=None, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def fit_dataloader(self, trn: DataLoader, criterion, optimizer, metric, logger: logging.Logger, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def evaluate_dataloader(self, data: DataLoader, criterion: Callable, metric=None, output=False, **kwargs):
|
|
raise NotImplementedError('Not supported.')
|
|
|
|
def load_vocabs(self, save_dir, filename='vocabs.json'):
|
|
pass
|
|
|
|
def load_weights(self, save_dir, filename='model.pt', **kwargs):
|
|
pass
|
|
|
|
def build_model(self, training=True, **kwargs) -> torch.nn.Module:
|
|
embed: FastTextEmbedding = self.config.embed
|
|
return embed.module()
|
|
|
|
def predict(self, data: str, **kwargs):
|
|
dataloader = self.build_dataloader(data, device=self.device)
|
|
for batch in dataloader: # It's a toy so doesn't really do batching
|
|
return self.model(batch)[0]
|
|
|
|
@property
|
|
def devices(self):
|
|
return [torch.device('cpu')]
|