Closed hccho2 closed 4 years ago
제 생각에는 make_bert_vocab()가 수정되어야 할 것 같습니다.
원본:
def make_bert_vocab(input_fname, output_fname):
train = '--input=' + input_fname + ' --model_prefix=sentpiece --vocab_size=32000 --model_type=bpe --character_coverage=0.9995'
print('cmd: ', train)
spm.SentencePieceTrainer.Train(train)
with open('sentpiece.vocab', 'r', encoding='utf-8') as f1, \
open(output_fname, 'w', encoding='utf-8') as f2:
f2.writelines("[PAD]\n[UNK]\n[CLS]\n[SEP]\n[MASK]\n")
for line in f1:
word = line.replace('\n', '').split('\t')[0].replace('▁', '##') # '▁' != '_'(underscore)
if not word or word in ["##", "<unk>", "<s>", "</s>"]: continue
f2.writelines(word + "\n")
수정:
def make_bert_vocab(input_fname, output_fname):
train = '--input=' + input_fname + ' --model_prefix=sentpiece --vocab_size=32000 --model_type=bpe --character_coverage=0.9995'
print('cmd: ', train)
spm.SentencePieceTrainer.Train(train)
with open('sentpiece.vocab', 'r', encoding='utf-8') as f1, \
open(output_fname, 'w', encoding='utf-8') as f2:
f2.writelines("[PAD]\n[UNK]\n[CLS]\n[SEP]\n[MASK]\n")
for line in f1:
word = line.replace('\n', '').split('\t')[0]
if not word or word in ["▁", "<unk>", "<s>", "</s>"]:
continue
if word[0] == '▁':
word = word.replace('▁', '')
else:
word = '##' + word
f2.writelines(word + "\n")
안녕하세요, @hccho2 님! 매번 날카로운 질문 해주셔서 감사드립니다.
sentencepiece의 ▁
와 full tokenizer의 ##
은 둘 모두 어절(string을 공백으로 구분했을 경우 하나의 단위) 경계를 나누기 위해 도입된 구분자이지만, 실제로는 정반대 뜻으로 사용되고 있습니다. 다시 말해 sentencepiece 토크나이저에서 ▁
가 나타났을 경우 해당 토큰이 어절의 시작을, ▁
가 나타나지 않았을 경우 해당 토큰이 어절의 시작이 아님을 의미하는 것으로 알고요. 반면 FullTokenizer에서는 ##
가 나타났을 경우 해당 토큰이 어절의 시작이 아님을, ##
가 나타나지 않았을 경우 해당 토큰이 어절의 시작임을 나타내고 있습니다(이와 관련해 BERT 공식 리포의 FullTokenizer 분석 예시 참조 : 링크).
@hccho2 님께서 들어주신 예시를 제 나름대로 분석하면 다음과 같습니다. sentencepiece와 FullTokenizer가 일관된 분석을 하고 있음을 확인할 수 있습니다. 토큰화를 할 때 이 같이 어절 경계 구분자를 남겨놓으면 간단한 룰로 토큰 시퀀스를 원래 문장으로 복원 가능합니다(mecab 등 특정 언어에 의존적인 분석기는 토큰 시퀀스/List[str]를 원래 문장/str으로 복원하기가 상대적으로 까다롭습니다).
▁
가 나타난 ▁학교
는 학교
라는 어절의 시작이라는 의미, ▁
가 나타난 ▁자동차
는 자동차
라는 어절의 시작이라는 의미##
가 나타나지 않은 학교
는 학교
라는 어절의 시작이라는 의미, ##
가 나타나지 않은 자동차
는 자동차
라는 어절의 시작이라는 의미아울러 책의 예시를 좀 더 풀어 설명하면 다음과 같습니다.
▁
가 나타난 ▁학교
는 학교에서
라는 어절의 시작이라는 의미##
가 나타나지 않은 집에
는 집에좀
이라는 어절의 시작이라는 의미, ##
이 나타난 ##좀
은 집에좀
이라는 어절의 시작이 아니라는 의미@hccho2 님께서 의문점을 가지신 근본적인 원인은 make_bert_vocab 함수 때문인 것 같습니다. 이것은 전적으로 제 착오로 코드 수정과 관련한 @hccho2 님 의견이 맞다는 판단입니다. 다시 말해 sentencepiece의 ▁
와 full tokenizer의 ##
이 정반대 뜻으로 활용되고 있으므로 sentencepiece 도움을 받아 BPE 보캡을 만든 후 이를 BERT 저자들이 작성한 FullTokenizer에 적용하려면 sentencepiece의 ▁
를 ##
로 대치(replace)하는 것은 적절하지 않습니다. @hccho2 님께서 작성해주신 코드처럼 ▁
가 나타는 sentencepiece 보캡의 단어에서 ▁
를 없애고, ▁
가 나타나지 않는 단어 앞에 ##
를 붙여주는 것이 타당하다고 생각합니다.
이 이슈에 제시됐던 내용은 @hccho2 님과 조금 더 토론을 진행한 후에 정오표와 전자책, 코드, 그리고 4쇄 이후 종이책에 반영하도록 하겠습니다. 늘 신세만 지는 것 같습니다. 날카로운 의견 진심으로 감사드립니다.
검토해 주셔서 감사합니다. 저도 혼란스러움이 해소되었습니다.
다음 질문은 꼭 책에 국한된 질문은 아닐 수 있습니다. 마땅히 질문할 곳이 없어, 이곳에 남깁니다. 어떤 의견이든 감사히 받겠습니다.
============================================================== [질문]
책에서는 sentencepiece의 SentencePieceTrainer.Train()을 통해 sentpiece.model, sentpiece.vocab 2개의 파일을 생성 후,
sentpiece.vocab 파일로 부터 '_'를 '##'으로 변환하여 vocab.txt를 만들고 있습니다.
tokenizing을 2가지 방식으로 할 수 있을 것 같습니다.
sentnecepiece의 EncodeAsPieces() <------ 앞에서 만든 sentpiece.model 이용
FullTokenizer <---- 앞에서 만든 vocab.txt를 이용
=============== 제가 주목한 token이 `자동차' 입니다. vocab.txt, 에는 '자동차', '##자동차' 가 모두 포함되어 있습니다.
sentpiece.vocab 에도 '자동차', `▁자동차' 가 모두 포함되어 있습니다.
================= 이제 s = '학교 앞을 지나는 자동차` 를 tokenizing해보면 다음과 같습니다.
sentnecepiece 결과: ['▁학교', '▁앞', '을', '▁지나는', '▁자동차']
FullTokenizer 결과: ['학교', '앞', '##을', '지나', '##는', '자동차']
'자동차' 라는 token을 각각 '▁자동차', '자동차' 로 처리했습니다.
2가지 방법이 일관성이 있기 위해서는 모두 `자동차'가 되든지, 아니면 '▁자동차', '##자동차'가 되어야 할 것 같은데 다른 결과가 나왔습니다.
=================== 책 page 105에 보면 '학교에서 밥을 먹었다' ---> ▁학교, 에서, ▁밥, 을, ▁먹었, 다 로 설명하고 있습니다. '학교'가 아닌 '▁학교'로.
책 page 106에 보면 '집에 좀 가자' ---> '집에' '##좀', '가자' 로 설명하고 있습니다. 105페이지 내용과 일관성이 있으려면 '집에'가 아니고, '##집에'가 되어야 하지 않나요?
=================== 정리하면, sentencepiece의 tokenizing은 subword로 우선 처리하고, FullTokenizer는 단독 word를 우선 처리하고 있습니다.
2가지의 처리 방식이 분명히 다른 것 같은데, 특별한 이유가 있는 것인가요? 처리 방식 또한 모델의 일부라 보고, 다르게 했다고 하면, 할말은 없겠지만요.