fastai--fastai
175 行
8.3 KiB
Python
175 行
8.3 KiB
Python
"""Contain the modules common between different architectures and the generic functions to get models
|
|
|
|
Docs: https://docs.fast.ai/text.models.core.html.md"""
|
|
|
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../nbs/33_text.models.core.ipynb.
|
|
|
|
# %% auto #0
|
|
__all__ = ['LinearDecoder', 'SequentialRNN', 'get_language_model', 'SentenceEncoder', 'masked_concat_pool',
|
|
'PoolingLinearClassifier', 'get_text_classifier']
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #6c02fbd6
|
|
from ...data.all import *
|
|
from ..core import *
|
|
from .awdlstm import *
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #68c3433a
|
|
_model_meta = {AWD_LSTM: {'hid_name':'emb_sz', 'url':URLs.WT103_FWD, 'url_bwd':URLs.WT103_BWD,
|
|
'config_lm':awd_lstm_lm_config, 'split_lm': awd_lstm_lm_split,
|
|
'config_clas':awd_lstm_clas_config, 'split_clas': awd_lstm_clas_split},}
|
|
# Transformer: {'hid_name':'d_model', 'url':URLs.OPENAI_TRANSFORMER,
|
|
# 'config_lm':tfmer_lm_config, 'split_lm': tfmer_lm_split,
|
|
# 'config_clas':tfmer_clas_config, 'split_clas': tfmer_clas_split},
|
|
# TransformerXL: {'hid_name':'d_model',
|
|
# 'config_lm':tfmerXL_lm_config, 'split_lm': tfmerXL_lm_split,
|
|
# 'config_clas':tfmerXL_clas_config, 'split_clas': tfmerXL_clas_split}}
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #0228d0c1
|
|
class LinearDecoder(Module):
|
|
"To go on top of a RNNCore module and create a Language Model."
|
|
initrange=0.1
|
|
|
|
def __init__(self,
|
|
n_out:int, # Number of output channels
|
|
n_hid:int, # Number of features in encoder last layer output
|
|
output_p:float=0.1, # Input dropout probability
|
|
tie_encoder:nn.Module=None, # If module is supplied will tie decoder weight to `tie_encoder.weight`
|
|
bias:bool=True # If `False` the layer will not learn additive bias
|
|
):
|
|
self.decoder = nn.Linear(n_hid, n_out, bias=bias)
|
|
self.decoder.weight.data.uniform_(-self.initrange, self.initrange)
|
|
self.output_dp = RNNDropout(output_p)
|
|
if bias: self.decoder.bias.data.zero_()
|
|
if tie_encoder: self.decoder.weight = tie_encoder.weight
|
|
|
|
def forward(self, input):
|
|
dp_inp = self.output_dp(input)
|
|
return self.decoder(dp_inp), input, dp_inp
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #5f62903f
|
|
class SequentialRNN(nn.Sequential):
|
|
"A sequential module that passes the reset call to its children."
|
|
def reset(self):
|
|
for c in self.children(): getcallable(c, 'reset')()
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #fe7a2e72
|
|
def get_language_model(
|
|
arch, # Function or class that can generate a language model architecture
|
|
vocab_sz:int, # Size of the vocabulary
|
|
config:dict=None, # Model configuration dictionary
|
|
drop_mult:float=1. # Multiplicative factor to scale all dropout probabilities in `config`
|
|
) -> SequentialRNN: # Language model with `arch` encoder and linear decoder
|
|
"Create a language model from `arch` and its `config`."
|
|
meta = _model_meta[arch]
|
|
config = ifnone(config, meta['config_lm']).copy()
|
|
for k in config.keys():
|
|
if k.endswith('_p'): config[k] *= drop_mult
|
|
tie_weights,output_p,out_bias = map(config.pop, ['tie_weights', 'output_p', 'out_bias'])
|
|
init = config.pop('init') if 'init' in config else None
|
|
encoder = arch(vocab_sz, **config)
|
|
enc = encoder.encoder if tie_weights else None
|
|
decoder = LinearDecoder(vocab_sz, config[meta['hid_name']], output_p, tie_encoder=enc, bias=out_bias)
|
|
model = SequentialRNN(encoder, decoder)
|
|
return model if init is None else model.apply(init)
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #04529106
|
|
def _pad_tensor(t:Tensor, bs:int) -> Tensor:
|
|
if t.size(0) < bs: return torch.cat([t, t.new_zeros(bs-t.size(0), *t.shape[1:])])
|
|
return t
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #e4b3f1cb
|
|
class SentenceEncoder(Module):
|
|
"Create an encoder over `module` that can process a full sentence."
|
|
def __init__(self,
|
|
bptt:int, # Backpropagation through time
|
|
module:nn.Module, # A module that can process up to [`bs`, `bptt`] tokens
|
|
pad_idx:int=1, # Padding token id
|
|
max_len:int=None # Maximal output length
|
|
):
|
|
store_attr('bptt,module,pad_idx,max_len')
|
|
|
|
def reset(self): getcallable(self.module, 'reset')()
|
|
|
|
def forward(self, input):
|
|
bs,sl = input.size()
|
|
self.reset()
|
|
mask = input == self.pad_idx
|
|
outs,masks = [],[]
|
|
for i in range(0, sl, self.bptt):
|
|
#Note: this expects that sequence really begins on a round multiple of bptt
|
|
real_bs = (input[:,i] != self.pad_idx).long().sum()
|
|
o = self.module(input[:real_bs,i: min(i+self.bptt, sl)])
|
|
if self.max_len is None or sl-i <= self.max_len:
|
|
outs.append(o)
|
|
masks.append(mask[:,i: min(i+self.bptt, sl)])
|
|
outs = torch.cat([_pad_tensor(o, bs) for o in outs], dim=1)
|
|
mask = torch.cat(masks, dim=1)
|
|
return outs,mask
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #40c5276a
|
|
def masked_concat_pool(
|
|
output:Tensor, # Output of sentence encoder
|
|
mask:Tensor, # Boolean mask as returned by sentence encoder
|
|
bptt:int # Backpropagation through time
|
|
) -> Tensor: # Concatenation of [last_hidden, max_pool, avg_pool]
|
|
"Pool `MultiBatchEncoder` outputs into one vector [last_hidden, max_pool, avg_pool]"
|
|
lens = output.shape[1] - mask.long().sum(dim=1)
|
|
last_lens = mask[:,-bptt:].long().sum(dim=1)
|
|
avg_pool = output.masked_fill(mask[:, :, None], 0).sum(dim=1)
|
|
avg_pool.div_(lens.type(avg_pool.dtype)[:,None])
|
|
max_pool = output.masked_fill(mask[:,:,None], -float('inf')).max(dim=1)[0]
|
|
x = torch.cat([output[torch.arange(0, output.size(0)),-last_lens-1], max_pool, avg_pool], 1) #Concat pooling.
|
|
return x
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #1c4c7478
|
|
class PoolingLinearClassifier(Module):
|
|
"Create a linear classifier with pooling"
|
|
def __init__(self,
|
|
dims:list, # List of hidden sizes for MLP as `int`s
|
|
ps:list, # List of dropout probabilities as `float`s
|
|
bptt:int, # Backpropagation through time
|
|
y_range:tuple=None # Tuple of (low, high) output value bounds
|
|
):
|
|
if len(ps) != len(dims)-1: raise ValueError("Number of layers and dropout values do not match.")
|
|
acts = [nn.ReLU(inplace=True)] * (len(dims) - 2) + [None]
|
|
layers = [LinBnDrop(i, o, p=p, act=a) for i,o,p,a in zip(dims[:-1], dims[1:], ps, acts)]
|
|
if y_range is not None: layers.append(SigmoidRange(*y_range))
|
|
self.layers = nn.Sequential(*layers)
|
|
self.bptt = bptt
|
|
|
|
def forward(self, input):
|
|
out,mask = input
|
|
x = masked_concat_pool(out, mask, self.bptt)
|
|
x = self.layers(x)
|
|
return x, out, out
|
|
|
|
# %% ../../../nbs/33_text.models.core.ipynb #24063927
|
|
def get_text_classifier(
|
|
arch:Callable, # Function or class that can generate a language model architecture
|
|
vocab_sz:int, # Size of the vocabulary
|
|
n_class:int, # Number of classes
|
|
seq_len:int=72, # Backpropagation through time
|
|
config:dict=None, # Encoder configuration dictionary
|
|
drop_mult:float=1., # Multiplicative factor to scale all dropout probabilities in `config`
|
|
lin_ftrs:list=None, # List of hidden sizes for classifier head as `int`s
|
|
ps:list=None, # List of dropout probabilities for classifier head as `float`s
|
|
pad_idx:int=1, # Padding token id
|
|
max_len:int=72*20, # Maximal output length for `SentenceEncoder`
|
|
y_range:tuple=None # Tuple of (low, high) output value bounds
|
|
):
|
|
"Create a text classifier from `arch` and its `config`, maybe `pretrained`"
|
|
meta = _model_meta[arch]
|
|
cfg = meta['config_clas'].copy()
|
|
cfg.update(ifnone(config, {}))
|
|
config = cfg
|
|
for k in config.keys():
|
|
if k.endswith('_p'): config[k] *= drop_mult
|
|
if lin_ftrs is None: lin_ftrs = [50]
|
|
if ps is None: ps = [0.1]*len(lin_ftrs)
|
|
layers = [config[meta['hid_name']] * 3] + lin_ftrs + [n_class]
|
|
ps = [config.pop('output_p')] + ps
|
|
init = config.pop('init') if 'init' in config else None
|
|
encoder = SentenceEncoder(seq_len, arch(vocab_sz, **config), pad_idx=pad_idx, max_len=max_len)
|
|
model = SequentialRNN(encoder, PoolingLinearClassifier(layers, ps, bptt=seq_len, y_range=y_range))
|
|
return model if init is None else model.apply(init)
|