4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

BERTに文のペアを入力するときのtoken_type_idsについて

Last updated at Posted at 2022-02-01

概要

BERTを使うとき,2つの文を入力したいときありますよね.別々のデータではなく,文のペアを一つ入力するということです.
質問応答や含意関係認識(NLI)等のタスクでやると思います.
そんなときに,データの前処理がめんどくさかったりします.それをより簡単にやっちゃいましょう.

「そんなの知っとるわ」と思われる方法かもしれませんが,ご了承ください.

(BERTに関する簡単な解説は付け加えていますが,より詳しく知りたい方は自分で調べてください.)

BERTに文のペアを入力する

本題の前に少し.

BERTはその事前学習において Next Sentence Predictoin(NSP) というタスクを行います.
日本語でいうと次文予測です.文字通り文Aに続く文Bを予測するというものです.

その名残(?)なのかは分からないですけど,BertTokenizerで文をエンコードすると,input_idsなどの他にtoken_type_idsと呼ばれる配列が得られます.
これは,2つの文を判別するためにトークンごとに0と1で表したものです.

example
'token_type_ids': [0, 0, 0, 1, 1, 1]

この場合,トークンの3つ目までか前半,それ以降が後半の文みたいな感じです .

これを使えば,BERTに文のペアを入力させられるわけです.

条件

Tokenizer = BertJapaneseTokenizer
model = cl-tohoku/bert-base-japanese-whole-word-masking

よくやるやつ

上で書いた実際のコードがこんな感じ

tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')

text = "私の名前は太郎です。 [SEP] 三時のおやつの時間です。"
inputs = tokenizer(text)

文の間にある[SEP]は,特殊トークンと呼ばれるものの一つで,文字通りSeparation 文の区切りを表すためのものです.
これにより,token_type_idsがうまく生成できるはずです.

そしてエンコードして出てくる結果がこれ

inputs
{
'input_ids': [2, 1325, 5, 1381, 9, 5250, 2992, 8, 3, 240, 72, 5, 73, 18874, 5, 640, 2992, 8, 3], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

あれ?

[SEP]を挿入したにも関わらず,token_type_idsがなぜか全部0です.
これではモデルが文のペアであることを認識できません.

どうやら自分で[SEP]トークンを自分で入れ込んだとしても,tokenizer側はそれを認識はするものの,token_type_idsに反映することはしてくれないそうです.

そんなわけで,こういうことをやるわけです

token_type_ids = [0 if i <= input_ids.index(3) else 1 for i in range(len(input_ids))]
#[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

これは何をしているかというと,[SEP]の場所より前を0,あとを1になるような長さがtoken数のリストを作っています.
要するに,token_type_idsを自作しています.

なんでこんな面倒なことをしなければならないのでしょう?

そもそもtoken_type_idsがちゃんとできるように[SEP]をわざわざ自分で挿入しているわけです.
なのに,encodeしても肝心のtoken_type_idsが全部0になるのはおかしいですよね.

これで万事解決

解決策は以下のとおりです

text1, text2 = "私の名前は太郎です", "三時のおやつの時間です。"
inputs = tokenizer(text1, text2)

実はBertTokenizerは入力文のペアを別々で与えられます.
[SEP]の挿入も必要ありません

そうすると,

inputs
{
'input_ids': [2, 1325, 5, 1381, 9, 5250, 2992, 3, 240, 72, 5, 73, 18874, 5, 640, 2992, 8, 3], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

今度はしっかりtoken_type_idsが出力されています.

中身を確認してみます

tokenizer.convert_ids_to_tokens(inputs['input_ids'])
['[CLS]',
 '',
 '',
 '名前',
 '',
 '太郎',
 'です',
 '[SEP]',
 '',
 '',
 '',
 '',
 'やつ',
 '',
 '時間',
 'です',
 '',
 '[SEP]']

ちゃんと[SEP]が文の間に入っていることも確認できます.

これのおかげで,わざわざ自分で[SEP]を間に入れたり,token_type_idsを自作する必要もなくなりました.
かなり楽になりましたね.

文のペアそれぞれを,別々の変数で与えているので間違いも防げそうです.

おわりに

そもそもこれが正しい方法なんでしょうね.
Huggingfaceにあるドキュメントを見てみると,

image.png

ちゃんと例として示されてました.
これはしっかり目を通してなかった私が悪いですね.(BertTokenizerの説明にもペアを与えられることを書いていてほしいものですが)

ちなみに,encode()やbatch_encode()では確認していません.

これが初投稿です.見にくい記事ですいません.

参考

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?