Open kyakuno opened 3 months ago
BERT Encoder
input_text To be or not to be, that is the question
txt_cleaned TO BE OR NOT TO BE THAT IS THE QUESTION
grapheme_encoded [ 0 22 17 30 4 7 30 17 20 30 16 17 22 30 22 17 30 4 7 30 22 10 3 22
30 11 21 30 22 10 7 30 19 23 7 21 22 11 17 16]
input_ids [[ 101 2000 2022 2030 2025 2000 2022 1010 2008 2003 1996 3160 102]]
attention_mask [[1 1 1 1 1 1 1 1 1 1 1 1 1]]
token_type_ids [[0 0 0 0 0 0 0 0 0 0 0 0 0]]
hidden_states [[[[ 0.1685791 -0.28588867 -0.32592773 ... -0.02757263 0.03826904
0.16394043]
hidden_states (13, 1, 13, 768)
word_emb [[ 5.42041016 2.01513672 -2.68774414 ... 0.62231445 0.51385498
1.50268555]
[ 5.42041016 2.01513672 -2.68774414 ... 0.62231445 0.51385498
1.50268555]
output.shape (11, 768)
clean_pipelineは2つ以上の空白を1つの空白に変換し、大文字にする。 grapheme_encodedは下記のテーブルで変換する。
lab2ind = {
# fmt: off
'<bos>': 0, '<eos>': 1, '<unk>': 2, 'A': 3, 'B': 4, 'C': 5, 'D': 6, 'E': 7, 'F': 8, 'G': 9, 'H': 10, 'I': 11, 'J': 12, 'K': 13, 'L': 14, 'M': 15, 'N': 16, 'O': 17, 'P': 18, 'Q': 19, 'R': 20, 'S': 21, 'T': 22, 'U': 23, 'V': 24, 'W': 25, 'X': 26, 'Y': 27, 'Z': 28, "'": 29, ' ': 30
# fmt: on
}
hidden_statesのshapeは(13, 1, 13, 768)になる。 13はトークン数。
word_ids [None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None]
token_ids_word [ 1 2 3 4 5 6 7 8 9 10 11]
下記のコードが不思議。
# get_hidden_states
layers = [-4, -3, -2, -1]
output = np.sum(hidden_states[layers], axis=0)
output = np.squeeze(output)
output = output[token_ids_word]
元コードだと、hidden_stateの末尾4つを参照する構造になっている。 https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/wordemb/transformer.py#L247-L253
論理としては、hidden_stateの末尾4つを加算した上で、Special Tokenを除いたEmbeddingだけ取得している。
expand_to_charsでは、入力されたテキストの文字列を、spaceに相当するトークンでword_boundariesを検出し、word単位のembeddingから、文字単位のembeddingに変換する。
seq [[ 0 22 17 30 4 7 30 17 20 30 16 17 22 30 22 17 30 4 7 30 22 10 3 22
30 11 21 30 22 10 7 30 19 23 7 21 22 11 17 16]]
word_boundaries [[False False False True False False True False False True False False
False True False False True False False True False False False False
True False False True False False False True False False False False
False False False False]]
emb.shape (1, 11, 768)
words.shape (1, 40)
char_word_emb.shape (1, 40, 768)
char_word_emb [[[ 5.42041016 2.01513672 -2.68774414 ... 0.62231445 0.51385498
1.50268555]
[ 5.42041016 2.01513672 -2.68774414 ... 0.62231445 0.51385498
1.50268555]
[ 5.42041016 2.01513672 -2.68774414 ... 0.62231445 0.51385498
1.50268555]
...
To be or not to be, that is the questionary にすると、word_idsが重複する。questionaryがwordpieceで分割されるため。
この場合、tokens_ids_wordは下記のようになる。 tokens [101, 2000, 2022, 2030, 2025, 2000, 2022, 1010, 2008, 2003, 1996, 3160, 5649, 102] word_ids [None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, None] token_ids_word [ 1 2 3 4 5 6 7 8 9 10 11 12]
emb.shape (1, 12, 768) となり、embeddingはsubword単位で格納される。
このデータに対して、expand_to_charsをすると、ワード数が一致しなくなる気がする。
元のコードは下記。numpyに移植した際に、token_ids_wordの定義が間違っている気がする。 https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/wordemb/transformer.py
def _get_word_vector(self, encoded, states, idx):
token_ids_word = torch.from_numpy(
np.where(np.array(encoded.word_ids()) == idx)[0]
).to(self.device)
return self._get_hidden_states(states, token_ids_word)
"To be or not to be, that is the questionary"のように","が存在する場合、grapheme_pipelineで","が削除されるものの、BERTのTokenizerでは","を単語として扱うため、token_ids_wordの数と、expand_to_charsの中のseqを分割した数も合わなくなる。これも移植で入った問題かも。
expand_to_charsのvalueのdump。空白にはその次のwordのembeddingが入る。
char_word_emb[idx] = emb[idx, item]
しかし、下記のロジックが何をしているかわからない。 item_lengthは49*49が入っており、そもそも、:49までしか値がないはずなのに、大きい値を代入している。 word_boundariesもTrue,Falseの配列で、それに代入している。
char_word_emb[idx, item_length:, :] = 0
char_word_emb[idx, word_boundaries[idx], :] = 0
オリジナルのコードは下記。 https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/wordemb/util.py
seq [[ 0 22 17 30 4 7 30 17 20 30 16 17 22 30 22 17 30 4 7 30 22 10 3 22
30 11 21 30 22 10 7 30 19 23 7 21 22 11 17 16 3 20 27]]
emb.shape (1, 11, 768)
word_boundaries [[False False False True False False True False False True False False
False True False False True False False True False False False False
True False False True False False False True False False False False
False False False False False False False]]
words.shape (1, 43)
words [[0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 6 6 6 6 6 7 7 7 8 8 8 8 9 9 9 9 9
9 9 9 9 9 9 9]]
char_word_emb.shape (1, 43, 768)
seq_len [43]
seq.shape[-1] 43
seq_len_idx [1849]
idx 0
item [0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 6 6 6 6 6 7 7 7 8 8 8 8 9 9 9 9 9 9
9 9 9 9 9 9]
item_length 1849
word_boundaries[idx] [False False False True False False True False False True False False
print("seq", seq)
print("emb.shape", emb.shape)
word_boundaries = seq == word_separator
words = np.cumsum(word_boundaries, axis=-1)
print("word_boundaries", word_boundaries)
print("words.shape", words.shape)
print("words", words)
char_word_emb = np.zeros((emb.shape[0], seq.shape[-1], emb.shape[-1]))
print("char_word_emb.shape", char_word_emb.shape)
seq_len_idx = (seq_len * seq.shape[-1]).astype(int)
print("seq_len", seq_len)
print("seq.shape[-1]", seq.shape[-1])
print("seq_len_idx", seq_len_idx)
for idx, (item, item_length) in enumerate(zip(words, seq_len_idx)):
print("idx", idx)
print("item", item)
print("item_length", item_length)
print("word_boundaries[idx]", word_boundaries[idx])
char_word_emb[idx] = emb[idx, item]
char_word_emb[idx, item_length:, :] = 0
char_word_emb[idx, word_boundaries[idx], :] = 0
print("char_word_emb", char_word_emb)
word_boundaries[idx]を代入しているのは、空白部分には0を入れることを意図している。
attention
p_seq.shape (1, 1, 43)
p_seq [[[-5.957753 -6.236804 -5.8820686 -5.653797 -6.1083875
-5.641102 -5.7392464 -6.0706687 -5.921131 -5.926258
-5.885243 -5.5038943 -5.98119 -5.712879 -6.114613
-6.3263593 -5.7158093 -5.697743 -5.784657 -5.2665906
-6.007314 -5.9040413 -5.0927625 -5.5248904 -5.73534
-5.4277234 -6.118397 -6.0178127 -5.7719617 -4.4199104
-5.14159 -4.757801 -5.825917 -0.16014495 -5.8332405
-5.837391 -5.4638557 -5.6127815 -5.728504 -5.7895393
-5.420399 -5.895008 -6.436634 ]]]
encoder_outputs.shape (1, 43, 1024)
encoder_outputs [[[-5.9692383e-02 -2.2064209e-02 -3.2318115e-02 ... -2.9687500e-01
-2.5768280e-03 -6.8740845e-03]
[ 1.4819336e-01 -6.0363770e-02 -7.7197266e-01 ... 4.4647217e-02
0.0000000e+00 -3.1530857e-05]
[ 7.2998047e-02 -7.1960449e-02 1.3769531e-01 ... -3.6694336e-01
1.1238098e-02 -8.0943108e-05]
...
[ 8.6669922e-02 5.4382324e-02 -5.2880859e-01 ... -5.2880859e-01
1.8322468e-04 -1.3113022e-06]
[ 5.7324219e-01 4.9609375e-01 3.4887695e-01 ... -1.0604858e-02
3.8290024e-04 -2.7418137e-04]
[ 7.0166016e-01 3.2788086e-01 5.5615234e-01 ... -4.1015625e-01
1.4219284e-03 3.2782555e-06]]]
OriginalのSpechBrainでのテスト。 想定通り、継続シンボルを持つトークンはスキップされる。 ただ、カンマを適切に扱えていないままな気はする。 textは,を入れて11シンボルあるのに、10シンボルとしてcharacterにコピーしてしまっている。
text = "To be or not to be, that is the question"
emb.shape torch.Size([1, 11, 768])
seq tensor([[ 0, 22, 17, 30, 4, 7, 30, 17, 20, 30, 16, 17, 22, 30, 22, 17, 30, 4,
7, 30, 22, 10, 3, 22, 30, 11, 21, 30, 22, 10, 7, 30, 19, 23, 7, 21,
22, 11, 17, 16]])
seq.shape torch.Size([1, 40])
words tensor([[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 6,
7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9]])
words.shape torch.Size([1, 40])
text = "To be or not to be, that is the questionary"
emb.shape torch.Size([1, 12, 768])
seq tensor([[ 0, 22, 17, 30, 4, 7, 30, 17, 20, 30, 16, 17, 22, 30, 22, 17, 30, 4,
7, 30, 22, 10, 3, 22, 30, 11, 21, 30, 22, 10, 7, 30, 19, 23, 7, 21,
22, 11, 17, 16, 3, 20, 27]])
seq.shape torch.Size([1, 43])
words tensor([[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 6,
7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]])
現状のシステムはPunctuationは扱えないので、事前に置換すべきように見える。 https://github.com/speechbrain/speechbrain/issues/2227 soundchoiceの公式のサンプルに与えてはいけない,を与えてしまっているだけのように見える。
推論の起点コード。 https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/inference/text.py
from speechbrain.inference.text import GraphemeToPhoneme
g2p = GraphemeToPhoneme.from_hparams("speechbrain/soundchoice-g2p", savedir="pretrained_models/soundchoice-g2p")
text = "To be or not to be, that is the question"
phonemes = g2p(text)
オリジナルのコードでも、emb.shapeが12になる。そもそも、元のコードの段階で、subword分割をうまく考慮できていない気がする。
To be or not to be, that is the questionary
emb.shape torch.Size([1, 12, 768])