ELMo解讀(論文 + PyTorch源碼)


ELMo的概念也是很早就出了,應該是18年初的事情了。但我仍然是后知后覺,居然還是等BERT出來很久之后,才知道有這么個東西。這兩天才仔細看了下論文和源碼,在這里做一些記錄,如果有不詳實的地方,歡迎指出~

文章目錄
前言
一. ELMo原理
1. ELMo整體模型結構
2. 字符編碼層
3. biLMs原理
4. 生成ELMo詞向量
5. 結合下游NLP任務
二. PyTorch實現
1. 字符編碼層
2. biLMs層
3. 生成ELMo詞向量
三. 實驗
四. 一些分析
1. 使用哪些層的輸出?
2. 在哪里加入ELMo?
3. 每層輸出的側重點是什么?
4. 效率分析
五. 總結
傳送門
前言
ELMo出自Allen研究所在NAACL2018會議上發表的一篇論文《Deep contextualized word representations》,從論文名稱看,應該是提出了一個新的詞表征的方法。據他們自己的介紹:ELMo是一個深度帶上下文的詞表征模型,能同時建模(1)單詞使用的復雜特征(例如,語法和語義);(2)這些特征在上下文中會有何變化(如歧義等)。這些詞向量從深度雙向語言模型(biLM)的隱層狀態中衍生出來,biLM是在大規模的語料上面Pretrain的。它們可以靈活輕松地加入到現有的模型中,並且能在很多NLP任務中顯著提升現有的表現,比如問答、文本蘊含和情感分析等。聽起來非常的exciting,它的原理也十分reasonable!下面就將針對論文及其PyTorch源碼進行剖析,具體的資料參見文末的傳送門。

這里先聲明一點:筆者認為“ELMo”這個名稱既可以代表得到詞向量的模型,也可以是得出的詞向量本身,就像Word2Vec、GloVe這些名稱一樣,都是可以代表兩個含義的。下面提到ELMo時,一般帶有“模型”相關字眼的就是指的訓練出詞向量的模型,而帶有“詞向量”相關字眼的就是指的得出的詞向量。

一. ELMo原理
之前我們一般比較常用的詞嵌入的方法是諸如Word2Vec和GloVe這種,但這些詞嵌入的訓練方式一般都是上下文無關的,並且對於同一個詞,不管它處於什么樣的語境,它的詞向量都是一樣的,這樣對於那些有歧義的詞非常不友好。因此,論文就考慮到了要根據輸入的句子作為上下文,來具體計算每個詞的表征,提出了ELMo(Embeddings from Language Model)。它的基本思想,用大白話來說就是,還是用訓練語言模型的套路,然后把語言模型中間隱含層的輸出提取出來,作為這個詞在當前上下文情境下的表征,簡單但很有用!

1. ELMo整體模型結構
對於ELMo的模型結構,其實論文中並沒有給出具體的圖(這點對於筆者這種想象力極差的人來說很痛苦),筆者通過整合論文里面的蛛絲馬跡以及PyTorch的源碼,得出它大概是下面這么個東西(手殘黨畫的丑,勿怪):


假設輸入的句子維度為B∗W∗C B * W * CB∗W∗C,這里的 B BB 表示batch_size,W WW 表示num_words,即一句話中的單詞數目,在一個batch中可能需要padding,C CC 表示max_characters_per_token,即每個單詞的字符數目,這里論文里面用了固定值50,不根據每個batch的不同而動態設置,D DD 表示projection_dim,即單詞輸入biLMs的embedding_size,或者理解為最終生成的ELMo詞向量維度的1/2 1 / 21/2。

從圖里面看,輸入的句子會經過:

Char Encode Layer: 即首先經過一個字符編碼層,因為ELMo實際上是基於char的,所以它會先對每個單詞中的所有char進行編碼,從而得到這個單詞的表示。因此經過字符編碼層出來之后的維度為B∗W∗D B * W * DB∗W∗D,這就是我們熟知的對於一個句子在單詞級別上的編碼維度。
biLMs:隨后該句子表示會經過biLMs,即雙向語言模型的建模,內部其實是分開訓練了兩個正向和反向的語言模型,而后將其表征進行拼接,最終得到的輸出維度為(L+1)∗B∗W∗2D (L+1) * B * W * 2D(L+1)∗B∗W∗2D,+1實際上是加上了最初的embedding層,有點兒像residual,后面在“biLMs”部分會詳細提到。
Scalar Mixer:緊接着,得到了biLMs各個層的表征之后,會經過一個混合層,它會將前面這些層的表示進行線性融合(后面在“生成ELMo詞向量”部分會進行詳細說明),得出最終的ELMo向量,維度為B∗W∗2D B * W * 2DB∗W∗2D。
這里只是對ELMo模型從全局上進行的一個統觀,對每個模塊里面的結構還是很懵逼?沒關系,下面我們逐一來進行剖析:

2. 字符編碼層
這一層即“Char Encode Layer”,它的輸入維度是B∗W∗C B * W * CB∗W∗C,輸出維度是B∗W∗D B * W * DB∗W∗D,經查看源碼,它的結構圖長這樣:


畫的有點兒亂,大家將就着看~

首先,輸入的句子會被reshape成BW∗C BW * CBW∗C,因其是針對所有的char進行處理。然后會分別經過如下幾個層:

Char Embedding:這就是正常的embedding層,針對每個char進行編碼,實際上所有char的詞表大概是262,其中0-255是char的unicode編碼,256-261這6個分別是<bow>(單詞的開始)、<eow>(單詞的結束)、 <bos>(句子的開始)、<eos>(句子的結束)、<pow>(單詞補齊符)和<pos>(句子補齊符)。可見詞表還是比較小的,而且沒有OOV的情況出現。這里的Embedding參數維度為262(num_characters)∗d(char_embed_dim) 262(num\_characters) * d(char\_embed\_dim)262(num_characters)∗d(char_embed_dim)。注意這里的 d dd 與上一節提到的 D DD 是兩個概念,d dd 表示的是字符的embedding維度,而 D DD 表示的是單詞的embedding維度,后面會看到它們之間的映射關系。這部分的輸出維度為BW∗C∗d BW * C * dBW∗C∗d。
Multi-Scale卷積層:這里用的是不同scale的卷積層,注意是在寬度上擴展,而不是深度上,即輸入都是一樣的,卷積之間的不同在於其kernel_size和channel_size的大小不同,用於捕捉不同n-grams之間的信息,這點其實是仿照 TextCNN 的模型結構。假設有m mm個這樣的卷積層,其kernel_size從 k1,k2,...,km k1, k2, ..., kmk1,k2,...,km,比如1,2,3,4,5,6,7這種,其channel_size從 d1,d2,...,dm d1, d2, ..., dmd1,d2,...,dm,比如32,64,128,256,512,1024這種。注意:這里的卷積都是1維卷積,即只在序列長度上做卷積。與圖像中的處理類似,在卷積之后,會經過MaxPooling進行池化,這里的目的主要在於經過前面卷積出的序列長度往往不一致,后期沒辦法進行合並,所以這里在序列維度上進行MaxPooling,其實就是取一個單詞中最大的那個char的表示作為整個單詞的表示。最后再經過激活層,這一步就算結束了。根據不同的channel_size的大小,這一步的輸出維度分別為BW∗d1,BW∗d2,...,BW∗dm BW * d1, BW * d2, ..., BW * dmBW∗d1,BW∗d2,...,BW∗dm。
Concat層:上一步得出的是m個不同維度的矩陣,為了方便后期處理,這里將其在最后一維上進行拼接,而后將其reshape回單詞級別的維度B∗W∗(d1+d2+...+dm) B * W * (d1+d2+...+dm)B∗W∗(d1+d2+...+dm)。
Highway層:Highway(參見:https://arxiv.org/abs/1505.00387 )是仿照圖像中residual的做法,在NLP領域中常有應用,看代碼里面的實現,這一層實現的公式見下面:其實就是一種全連接+殘差的實現方式,只不過這里還需要一個element-wise的gate矩陣對x xx和f(A(x)) f(A(x))f(A(x))進行變換。這里需要經過H HH 層這樣的Highway層,輸出維度仍為B∗W∗(d1+d2+...+dm) B * W * (d1+d2+...+dm)B∗W∗(d1+d2+...+dm)。
y=g∗x+(1−g)∗f(A(x)),g=Sigmoid(B(x)) y = g * x + (1 - g) * f(A(x)), g = Sigmoid(B(x))
y=g∗x+(1−g)∗f(A(x)),g=Sigmoid(B(x))

Linear映射層:經過前面的計算,得到的向量維度d1+d2+...+dm d1+d2+...+dmd1+d2+...+dm往往比較長,這里額外加了一層的Linear進行映射,將維度映射到D DD,作為詞的embedding送入后續的層中,這里輸出的維度為B∗W∗D B * W * DB∗W∗D。
3. biLMs原理
ELMo主要是建立在biLMs(雙向語言模型)上的,下面先從數學上介紹一下什么是biLMs。

具體來說,給定一個有N NN個token的序列(t1,t2,...,tN) (t_1, t_2, ..., t_N)(t
1

,t
2

,...,t
N

),前向的語言模型(一般是多層的LSTM之類的)用於計算給定前面tokens的情況下當前token的概率,即:

p(t1,t2,...,tN)=∏Nk=1p(tk∣t1,t2,...,tk−1) p(t_1, t_2, ..., t_N) = \prod_{k=1}^{N} p(t_k | t_1, t_2, ..., t_{k-1})
p(t
1

,t
2

,...,t
N

)=
k=1

N

p(t
k

∣t
1

,t
2

,...,t
k−1

)

在每一個位置k kk,模型都會在每一層輸出一個上下文相關的表征h→LMk,j \overrightarrow{h}_{k, j}^{LM}
h

k,j
LM

,這里的j=1,...,L j = 1, ..., Lj=1,...,L表示第幾層。頂層的輸出h→LMk,L \overrightarrow{h}_{k, L}^{LM}
h

k,L
LM

用於預測下一個token:tk+1 t_{k+1}t
k+1

同樣地,反向的語言模型訓練與正向的一樣,只不過輸入是反過來的,即計算給定后面tokens的情況下當前token的概率:

p(t1,t2,...,tN)=∏Nk=1p(tk∣tk+1,tk+2,...,tN) p(t_1, t_2, ..., t_N) = \prod_{k=1}^{N} p(t_k | t_{k+1}, t_{k+2}, ..., t_N)
p(t
1

,t
2

,...,t
N

)=
k=1

N

p(t
k

∣t
k+1

,t
k+2

,...,t
N

)

同樣,反向的LM在每個位置k kk,也會在每一層生成一個上下文相關的表征h←LMk,j \overleftarrow{h}_{k, j}^{LM}
h

k,j
LM

ELMo用的biLMs就是同時結合正向和反向的語言模型,其目標是最大化如下的似然值:

∑Nk=1(logp(tk∣t1,...,tk−1;Θx,Θ→LSTM,Θs)+(logp(tk∣tk+1,...,tN;Θx,Θ←LSTM,Θs)) \sum_{k=1}^N(\log p(t_k | t_1, ..., t_{k-1}; \Theta_x, \overrightarrow \Theta _{LSTM}, \Theta_s) + (\log p(t_k | t_{k+1}, ..., t_N; \Theta_x, \overleftarrow \Theta _{LSTM}, \Theta_s))
k=1

N

(logp(t
k

∣t
1

,...,t
k−1


x

,
Θ

LSTM


s

)+(logp(t
k

∣t
k+1

,...,t
N


x

,
Θ

LSTM


s

))

里面的Θx \Theta_xΘ
x

、Θs \Theta_sΘ
s

和Θ→LSTM \overrightarrow \Theta _{LSTM}
Θ

LSTM

及Θ←LSTM \overleftarrow \Theta _{LSTM}
Θ

LSTM

分別是詞嵌入,輸出層(Softmax之前的)以及正反向LSTM的參數。

可以看出,其實就是相當於分別訓練了正向和反向的兩個LM。 好像也只能分開進行訓練,因為LM不能訓練雙向的。

示意圖的話,就是下面這種多層BiLSTM的樣子:

這里的 h hh 表示LSTM單元的hidden_size,可能會比較大,比如D=512,h=4096 D = 512, h = 4096D=512,h=4096這樣。所以在每一層結束后還需要一個Linear層將維度從 h hh 映射為 D DD,而后再輸入到下一層中。最后的輸出是將每一層的所有輸出以及embedding的輸出,進行stack,每一層的輸出里面又是對每個timestep的正向和反向的輸出進行concat,因而最后的輸出維度為(L+1)∗B∗W∗2D (L+1) * B * W * 2D(L+1)∗B∗W∗2D,這里的 L+1 L + 1L+1 中的 +1 +1+1 就代表着那一層embedding輸出,其會復制成兩份,以與biLMs每層的輸出維度保持一致。

4. 生成ELMo詞向量
在經過了biLMs層之后,得到的表征維度為(L+1)∗B∗W∗2D (L+1) * B * W * 2D(L+1)∗B∗W∗2D,接下來就需要生成最終的ELMo向量了!

對於每一個token tk t_kt
k

,L LL 層的biLMs,生成出來的表征有 2L+1 2L + 12L+1 個,如下公式:

Rk={xLMk,h→LMk,j,h←LMk,j∣j=1,...,L}={hLMk,j∣j=0,..,L} R_k = \{x_k^{LM}, \overrightarrow{h}_{k,j}^{LM}, \overleftarrow{h}_{k,j}^{LM} | j = 1, ..., L\} = \{h_{k,j}^{LM} | j = 0, .., L\}
R
k

={x
k
LM

,
h

k,j
LM

,
h

k,j
LM

∣j=1,...,L}={h
k,j
LM

∣j=0,..,L}

這里的hLMk,0 h_{k,0}^{LM}h
k,0
LM

是詞的embedding輸出,hLMk,j=[h→LMk,j;h←LMk,j] h_{k,j}^{LM} = [\overrightarrow{h}_{k,j}^{LM}; \overleftarrow{h}_{k,j}^{LM}]h
k,j
LM

=[
h

k,j
LM

;
h

k,j
LM

]表示每一層的正向和反向輸出拼接后的結果。

對於這些表征,論文用如下公式對它們做了一個scalar mixer:

ELMotaskk=E(Rk;Θtask)=γtask∑Lj=0staskjhLMk,j ELMo_{k}^{task} = E(R_k; \Theta^{task}) = \gamma^{task} \sum_{j=0}^L s_j^{task} h_{k,j}^{LM}
ELMo
k
task

=E(R
k


task
)=γ
task

j=0

L

s
j
task

h
k,j
LM

這里的staskj s_j^{task}s
j
task

是一個softmax后的概率值,標量參數γtask \gamma^{task}γ
task
是用於對整個ELMo向量進行scale上的縮放。這兩部分都是作為參數來學習的,針對不同任務會有不同的值。

同時論文里面還提到,每一層輸出的分布之間可能會有較大差別,所以有時也會在線性融合之前,為每層的輸出做一個Layer Normalization,這與Transformer里面一致。

經過Scalar Mixer之后的向量維度為B∗W∗2D B * W * 2DB∗W∗2D,即為生成的ELMo詞向量,可以用於后續的任務。

5. 結合下游NLP任務
一般ELMo模型會在一個超大的語料庫上進行預訓練,因為是訓練語言模型,不需要任何的標簽,純文本就可以,因而這里可以用超大的語料庫,這一點的優勢是十分明顯的。訓練完ELMo模型之后,就可以輸入一個新句子,得到其中每個單詞在當前這個句子上下文下的ELMo詞向量了。

論文中提到,在訓練的時候,發現使用合適的dropout和L2在ELMo模型上時會提升效果。

此時這個詞向量就可以接入到下游的NLP任務中,比如問答、情感分析等。從接入的位置來看,可以與下游NLP任務本身輸入的embedding拼接在一起,也可以與其輸出拼接在一起。而從模型是否固定來看,又可以將ELMo詞向量預先全部提取出來,即固定ELMo模型不讓其訓練,也可以在訓練下游NLP任務時順帶fine-tune這個ELMo模型。總之,使用起來非常的方便,可以插入到任何想插入的地方進行增補。

二. PyTorch實現
這里參考的主要是allennlp里面與ELMo本身有關的部分,涉及到biLMs的模型實現,以及ELMo推理部分,會只列出核心的部分,細枝末節的代碼就不列舉了。至於如何與下游的NLP任務結合以及fine-tune,還需要讀者自己去探索和實踐,這里不做說明!

1. 字符編碼層
這里實現的就是前面提到的Char Encode Layer。

首先是multi-scale CNN的實現:

# multi-scale CNN

# 網絡定義
for i, (width, num) in enumerate(filters):
conv = torch.nn.Conv1d(
in_channels=char_embed_dim,
out_channels=num,
kernel_size=width,
bias=True
)
self.add_module('char_conv_{}'.format(i), conv)

# forward函數
def forward(sef, character_embedding)
convs = []
for i in range(len(self._convolutions)):
conv = getattr(self, 'char_conv_{}'.format(i))
convolved = conv(character_embedding)
# (batch_size * sequence_length, n_filters for this width)
convolved, _ = torch.max(convolved, dim=-1)
convolved = activation(convolved)
convs.append(convolved)
# (batch_size * sequence_length, n_filters)
token_embedding = torch.cat(convs, dim=-1)
return token_embedding
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
然后是highway的實現:

# HighWay

# 網絡定義
self._layers = torch.nn.ModuleList([torch.nn.Linear(input_dim, input_dim * 2)
for _ in range(num_layers)])

# forward函數
def forward(self, inputs):
current_input = inputs
for layer in self._layers:
projected_input = layer(current_input)
linear_part = current_input
# NOTE: if you modify this, think about whether you should modify the initialization
# above, too.
nonlinear_part, gate = projected_input.chunk(2, dim=-1)
nonlinear_part = self._activation(nonlinear_part)
gate = torch.sigmoid(gate)
current_input = gate * linear_part + (1 - gate) * nonlinear_part
return current_input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2. biLMs層
這部分實際上是兩個不同方向的BiLSTM訓練,然后輸出經過映射后直接進行拼接即可,代碼如下:(以單向單層的為例)

# 網絡定義
# input_size:輸入embedding的維度
# hidden_size:輸入和輸出hidden state的維度
# cell_size:LSTMCell的內部維度。
# 一般input_size = hidden_size = D, hidden_size即為h。
self.input_linearity = torch.nn.Linear(input_size, 4 * cell_size, bias=False)
self.state_linearity = torch.nn.Linear(hidden_size, 4 * cell_size, bias=True)
self.state_projection = torch.nn.Linear(cell_size, hidden_size, bias=False)

# forward函數
def forward(self, inputs, batch_lengths, initial_state):
for timestep in range(total_timesteps):

# Do the projections for all the gates all at once.
# Both have shape (batch_size, 4 * cell_size)
projected_input = self.input_linearity(timestep_input)
projected_state = self.state_linearity(previous_state)

# Main LSTM equations using relevant chunks of the big linear
# projections of the hidden state and inputs.
input_gate = torch.sigmoid(projected_input[:, (0 * self.cell_size):(1 * self.cell_size)] +
projected_state[:, (0 * self.cell_size):(1 * self.cell_size)])
forget_gate = torch.sigmoid(projected_input[:, (1 * self.cell_size):(2 * self.cell_size)] +
projected_state[:, (1 * self.cell_size):(2 * self.cell_size)])
memory_init = torch.tanh(projected_input[:, (2 * self.cell_size):(3 * self.cell_size)] +
projected_state[:, (2 * self.cell_size):(3 * self.cell_size)])
output_gate = torch.sigmoid(projected_input[:, (3 * self.cell_size):(4 * self.cell_size)] +
projected_state[:, (3 * self.cell_size):(4 * self.cell_size)])
memory = input_gate * memory_init + forget_gate * previous_memory

# shape (current_length_index, cell_size)
pre_projection_timestep_output = output_gate * torch.tanh(memory)

# shape (current_length_index, hidden_size)
timestep_output = self.state_projection(pre_projection_timestep_output)

output_accumulator[0:current_length_index + 1, index] = timestep_output

# Mimic the pytorch API by returning state in the following shape:
# (num_layers * num_directions, batch_size, ...). As this
# LSTM cell cannot be stacked, the first dimension here is just 1.
final_state = (full_batch_previous_state.unsqueeze(0),
full_batch_previous_memory.unsqueeze(0))

return output_accumulator, final_state
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
3. 生成ELMo詞向量
這部分即為Scalar Mixer,其代碼如下:

# 參數定義
self.scalar_parameters = ParameterList(
[Parameter(torch.FloatTensor([initial_scalar_parameters[i]]),
requires_grad=trainable) for i
in range(mixture_size)])
self.gamma = Parameter(torch.FloatTensor([1.0]), requires_grad=trainable)

# forward函數
def forward(tensors, mask):

def _do_layer_norm(tensor, broadcast_mask, num_elements_not_masked):
tensor_masked = tensor * broadcast_mask
mean = torch.sum(tensor_masked) / num_elements_not_masked
variance = torch.sum(((tensor_masked - mean) * broadcast_mask)**2) / num_elements_not_masked
return (tensor - mean) / torch.sqrt(variance + 1E-12)

normed_weights = torch.nn.functional.softmax(torch.cat([parameter for parameter
in self.scalar_parameters]), dim=0)
normed_weights = torch.split(normed_weights, split_size_or_sections=1)

if not self.do_layer_norm:
pieces = []
for weight, tensor in zip(normed_weights, tensors):
pieces.append(weight * tensor)
return self.gamma * sum(pieces)

else:
mask_float = mask.float()
broadcast_mask = mask_float.unsqueeze(-1)
input_dim = tensors[0].size(-1)
num_elements_not_masked = torch.sum(mask_float) * input_dim

pieces = []
for weight, tensor in zip(normed_weights, tensors):
pieces.append(weight * _do_layer_norm(tensor,
broadcast_mask, num_elements_not_masked))
return self.gamma * sum(pieces)
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
三. 實驗
這里主要列舉一些在實際下游任務上結合ELMo的表現,分別是SQuAD(問答任務)、SNLI(文本蘊含)、SRL(語義角色標注)、Coref(共指消解)、NER(命名實體識別)以及SST-5(情感分析任務),其結果如下:

可見,基本都是在一個較低的baseline的情況下,用了ELMo后,達到了超越之前SoTA的效果!

四. 一些分析
論文中,作者也做了一些有趣的分析,從各個角度窺探ELMo的優勢和特性。比如:

1. 使用哪些層的輸出?
作者探索了使用不同biLMs層帶來的效果,以及使用不同的L2范數的權重,如下表所示:


這里面的Last Only指的是只是用biLM最頂層的輸出,λ \lambdaλ 指的是L2范數的權重,可見使用所有層的效果普遍比較好,並且較低的L2范數效果也較好,因其讓每一層的表示都趨於不同,當L2范數的權重較大時,會讓模型所有層的參數值趨於一致,導致模型每層的輸出也會趨於一致。

2. 在哪里加入ELMo?
前面提到過,可以在輸入和輸出的時候加入ELMo向量,作者比較了這兩者的不同:


在問答和文本蘊含任務上,是同時在輸入和輸出加入ELMo的效果較好,而在語義角色標注任務上,則是只在輸入加入比較好。論文猜測這個原因可能是因為,在前兩個任務上,都需要用到attention,而在輸出的時候加入ELMo,能讓attention直接看到ELMo的輸出,會對整個任務有利。而在語義角色標注上,與任務相關的上下文表征要比biLMs的通用輸出更重要一些。

3. 每層輸出的側重點是什么?
論文通過實驗得出,在biLMs的低層,表征更側重於諸如詞性等這種語法特征,而在高層的表征則更側重於語義特征。比如下面的實驗結果:


左邊的任務是語義消歧,右邊的任務是詞性標注,可見在語義消歧任務上面,使用第二層的效果比第一層的要好;而在詞性標注任務上面,使用第一層的效果反而比使用第二層的效果要好。

總體來看,還是使用所有層輸出的效果會更好,具體的weight讓模型自己去學就好了。

4. 效率分析
一般而言,用了預訓練模型的網絡往往收斂的會更快,同時也可以使用更少的數據集。論文通過實驗驗證了這一點:


比如在SRL任務中,使用了ELMo的模型僅使用1%的數據集就能達到不使用ELMo模型在使用10%數據集的效果!

五. 總結
ELMo具有如下的優良特性:

上下文相關:每個單詞的表示取決於使用它的整個上下文。
深度:單詞表示組合了深度預訓練神經網絡的所有層。
基於字符:ELMo表示純粹基於字符,然后經過CharCNN之后再作為詞的表示,解決了OOV問題,而且輸入的詞表也很小。
資源豐富:有完整的源碼、預訓練模型、參數以及詳盡的調用方式和例子,又是一個造福伸手黨的好項目!而且:還有人專門實現了多語的,好像是哈工大搞的,戳這里看項目。
傳送門
論文:https://arxiv.org/pdf/1802.05365.pdf
項目首頁:https://allennlp.org/elmo
源碼:https://github.com/allenai/allennlp (PyTorch,關於ELMo的部分戳這里)
https://github.com/allenai/bilm-tf (TensorFlow)
多語言:https://github.com/HIT-SCIR/ELMoForManyLangs (哈工大CoNLL評測的多國語言ELMo,還有繁體中文的)
---------------------
作者:MagicBubble
來源:CSDN
原文:https://blog.csdn.net/Magical_Bubble/article/details/89160032
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM