fasttext

1、文本分类

1.1 n-gram

由于Bag of words不考虑词语的顺序,因此引入bag of n-gram。针对英文,词内的是char n-gram,用于词向量;词之间的是word n-gram,用于分类;对于中文,存在词粒度和字粒度。

举个例子,句子A为”今天天气真不错”,这里以词粒度举例,先分词为[“今天”,”天气”,”真“,”不错“]

uni-gram:今天 天气 真 不错

2-gram为:今天/天气 天气/真 真/不错

3-gram为:今天/天气/真 天气/真/不错

由于n-gram的量远比word大的多,完全存下所有的n-gram也不现实。FastText采用了hashing trick的方式,如下图所示:

用哈希的方式既能保证查找时O(1)的效率,又可能把内存消耗控制在O(buckets * dim)范围内。不过这种方法潜在的问题是存在哈希冲突,不同的n-gram可能会共享同一个embedding。如果buckets取的足够大,这种影响会很小。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def build_dataset(config, ues_word):
if ues_word:
tokenizer = lambda x: x.split(' ') # word-level
else:
tokenizer = lambda x: [y for y in x] # char-level
if os.path.exists(config.vocab_path):
vocab = pkl.load(open(config.vocab_path, 'rb'))
else:
vocab = build_vocab(config.train_path, tokenizer=tokenizer, max_size=MAX_VOCAB_SIZE, min_freq=1)
pkl.dump(vocab, open(config.vocab_path, 'wb'))
print(f"Vocab size: {len(vocab)}")

def biGramHash(sequence, t, buckets):
t1 = sequence[t - 1] if t - 1 >= 0 else 0
return (t1 * 14918087) % buckets

def triGramHash(sequence, t, buckets):
t1 = sequence[t - 1] if t - 1 >= 0 else 0
t2 = sequence[t - 2] if t - 2 >= 0 else 0
return (t2 * 14918087 * 18408749 + t1 * 14918087) % buckets

def load_dataset(path, pad_size=32):
contents = []
with open(path, 'r', encoding='UTF-8') as f:
for line in tqdm(f):
lin = line.strip()
if not lin:
continue
content, label = lin.split('\t')
words_line = []
token = tokenizer(content)
seq_len = len(token)
if pad_size:
if len(token) < pad_size:
token.extend([PAD] * (pad_size - len(token)))
else:
token = token[:pad_size]
seq_len = pad_size
# word to id
for word in token:
words_line.append(vocab.get(word, vocab.get(UNK)))

# fasttext ngram
buckets = config.n_gram_vocab
bigram = []
trigram = []
# ------ngram------
for i in range(pad_size):
bigram.append(biGramHash(words_line, i, buckets))
trigram.append(triGramHash(words_line, i, buckets))
# -----------------
contents.append((words_line, int(label), seq_len, bigram, trigram))
return contents # [([...], 0), ([...], 1), ...]

train = load_dataset(config.train_path, config.pad_size)
dev = load_dataset(config.dev_path, config.pad_size)
test = load_dataset(config.test_path, config.pad_size)
return vocab, train, dev, test

1.2 网络结构

fasttext

模型结构上word2vec的cbow模型很像

输入层:举个例子,输入文本”今天天气真不错”,词粒度的2-gram为

中间层:线形层+relu作为激活函数

输出层:为简单的线形层

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__()
if config.embedding_pretrained is not None:
self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)
else:
self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
self.embedding_ngram2 = nn.Embedding(config.n_gram_vocab, config.embed)
self.embedding_ngram3 = nn.Embedding(config.n_gram_vocab, config.embed)
self.dropout = nn.Dropout(config.dropout)
self.fc1 = nn.Linear(config.embed * 3, config.hidden_size)
# self.dropout2 = nn.Dropout(config.dropout)
self.fc2 = nn.Linear(config.hidden_size, config.num_classes)

def forward(self, x):

out_word = self.embedding(x[0])
out_bigram = self.embedding_ngram2(x[2])
out_trigram = self.embedding_ngram3(x[3])
out = torch.cat((out_word, out_bigram, out_trigram), -1)

out = out.mean(dim=1)
out = self.dropout(out)
out = self.fc1(out)
out = F.relu(out)
out = self.fc2(out)
return out

1.3 分层softmax

对于分类问题,神经网络的输出结果需要经过softmax将其转为概率分布后才可以利用交叉熵计算loss

由于普通softmax的计算效率比较低,计算效率为$O(Kd)$使用分层的softmax时间复杂度可以达到$dlogK$,$K$为分类的数量,$d$为向量的维度

1.3.1 普通softmax

假设输出为$Y_{pred}=[y_1,y_2,…,y_K]$,则$P_{y_i}$为

其中$y_i$的维度为$d$,从公式可以看出计算效率为$O(Kd)$

1.3.2 分层softmax

霍夫曼树可以参考 https://zhuanlan.zhihu.com/p/154356949

为什么要霍夫曼,普通的不行?

分层softmax核心思想为利用训练样本构建霍夫曼树,如下

fasttext

树的结构是根据不同类在样本中出现的频次构造的,即频次越大的节点距离根节点越近。$K$个不同的类组成所有的叶子节点,$K-1个$内部节点作为参数。从根节点到某个叶子节点$y_i$经过的节点和边形成一条路径,路径长度表示为 $L_{y_i}$,$n_{(y_i,j)}$表示路径上的节点,那么

从公式可以看出时间复杂度降低至$dlogK$。

以图中$y_2$为例:

从根节点走到叶子节点 $y_2$ ,实际上是在做了3次逻辑回归。

2.训练词向量

https://arxiv.org/abs/1607.04606

参考

https://arxiv.org/abs/1607.01759

https://zhuanlan.zhihu.com/p/32965521

https://blog.csdn.net/qq_27009517/article/details/80676022

http://alex.smola.org/papers/2009/Weinbergeretal09.pdf

https://arxiv.org/abs/1607.04606

fasttext工具 https://github.com/facebookresearch/fastText

Author

Lavine Hu

Posted on

2021-07-19

Updated on

2022-05-29

Licensed under

Comments

:D 一言句子获取中...