2.1 BERT의 기본 개념
BERT(Bidirectional Encoder Representation from Transformer)
- 구글에서 발표한 최신 임베딩 모델
* 임베딩 모델 : 정보를 다차원 공간의 조밀한 표현으로 캡슐화하도록 훈련된 알고리즘 - 질문 대답, 텍스트 생성, 문장분류 등의 태스크에서 좋은 성능을 도출해 자연어 처리 분야에 크게 기여
- 문맥을 고려한 임베딩 모델 (성공 요인)
- 문맥(context)이 없는 워드투벡터(word2vec)와 차별점
문맥 기반(context-based) 임베딩 모델 VS. 문맥 독립(context-free) 임베딩 모델
ex)
A 문장 : He got bit by Python.
B 문장 : Python is my favorite programming language.
- 두 문장에서 '파이썬'이라는 단어의 의미가 서로 다름(A: 뱀, B: 프로그래밍 언어)
- 문맥 기반 : 문자의 문맥을 이해한 다음 문맥에 따라 단어 임베딩 생성 (문맥 기반으로 동적 임베딩 생성)
- 문맥 독립 : 두 문장에서 동일한 단어가 쓰였음로 도일하게 표현, '파이썬' 단어에 대해 항상 동일한 임베딩 제공 (문맥과 관계없이 정적 임베딩 생성, ex.워드투벡터)
BERT 작동 원리(요약)
모든 단어의 문맥상 의미를 이해하기 위해 문장의 각 단어를 문장의 다른 모든 단어와 연결시켜 이해
ex) BERT는 '파이썬' 단어를 가져와 문장의 다른 모든 단어와의 관계를 기반으로 이해하려 시도
- A문장: 'Python'과 'bit'의 강한 연결 관계를 파악해, 파이썬이 뱀의 한 종류를 의미함을 파악
- B문장: 'Python'과 'programming'의 관련성을 인지
2.2 BERT의 동작 방식
BERT는 트랜스포머 모델을 기반
- 인코더-디코더가 있는 트랜스포머 모델과 달리 인코더만 사용
- 트랜스포머 인코더에 문장을 입력하여, 문장의 각 단어에 대한 표현 벡터를 출력으로 반환 = BERT의 표현 벡터
BERT의 '양방향(bidirectional)'
- 트랜스포머 인코더는 원래 양방향으로 문장을 읽을 수 있기에 양방향
- BERT는 기본적으로 트랜스포머에서 얻은 양방향 인코더 표현
BERT가 트랜스포머에서 어떻게 양방향 인코더 표현을 하나?
- A 문장을 트랜스포머의 인코더에 입력으로 제공
→ 문장의 각 단어에 대한 문맥표현(임베딩)을 출력으로 가져옴 - 인코더는 멀티 헤드 어텐션 메커니즘(multi-head attention mechanism)을 사용해 문장의 각 단어의 문맥 이해
→ 문장에 있는 각 단어의 문맥 표현을 출력으로 반환
- 문장을 트랜스포머의 인코더에 입력 & 문장의 각 단어에 대한 문맥 표현 출력
- N개의 인코더 쌓을 수 있음
- 각 토큰의 표현 크기는 인코더 레이어의 출력의 차원
ex) 인코더 레이어의 차원 768이라 가정하면 각 토큰의 표현 크기 768
2.3 BERT 구조
BERT 논문 저자들은 두 가지 구성의 모델 제시
2.3.1 BERT-base
- 12개의 인코더 레이어가 스택처럼 쌓인 형태로 구성
- 모든 인코더는 12개의 어텐션 헤드 사용
- 인코더의 피드포워드 네트워크는 768개 차원의 은닉(hidden) 유닛으로 구성
- BERT-base에서 얻은 표현의 크기는 768
사용할 표기법
- 인코더 레이어 수 L
- 어텐션 헤드 A
- 은닉 유닛 H
BERT-base 모델 : L = 12, A = 12, H = 768, 총 변수의 수는 1억 1천만 개
2.3.2 BERT-large
- 24개의 인코더 레이어가 스택처럼 쌓인 형태로 구성
- 모든 인코더는 16개의 어텐션 헤드 사용
- 인코더의 피드포워드 네트워크는 1,024개 차원의 은닉(hidden) 유닛으로 구성
- BERT-base에서 얻은 표현의 크기는 1,024
사용할 표기법
- 인코더 레이어 수 L
- 어텐션 헤드 A
- 은닉 유닛 H
BERT-base 모델 : L = 24, A = 16, H = 1024, 총 변수의 수는 3억 4천만 개
2.3.3 그 밖의 여러 BERT 구조
표준 구조외에도 다른 조합으로 BERT를 구축할 수 있음
- Bert-tiny, with L = 2, H = 128
- Bert-mini, with L = 4, H = 256
- Bert-small, with L = 4, H = 512
- Bert-medium, with L = 8, H = 512
- 컴퓨팅 리소스가 제한된 환경 → 작은 BERT가 적합
- 표준 구조가 더 정확한 결과를 제공하기에 가장 널리 사용 → BERT-base, BERT-large
남아있는 의문
- 입력 문장에 대한 적절한 표현을 생성하게 하려면 BERT를 어떻게 학습시켜야 할까?
- 학습에 어떤 데이터셋을 사용해야 할까?
- 학습 방법은 무엇일까?
(다음에서 알아보자)
2.4 BERT 사전 학습
사전 학습
- 새 태스크를 위해 새로운 모델로 처음부터 학습시키는 대신 사전 학습된 모델을 사용하고 새로운 태스크에 따라 가중치를 조정(파인튜닝)
- 특정 태스크에 대한 방대한 데이터셋으로 모델 학습 & 모델 저장
→ 새 태스크가 주어지면 임의 가중치로 모델을 초기화하는 대신 이미 학습된 모델(사전 학습 모델)의 가중치로 모델 초기화 - BERT는 MLM과 NSP라는 태스크를 이용해 거대한 망뭉치를 기반으로 사전 학습
= 사전 학습 후 사전 학습된 BERT를 저장해두고, 새로운 태스크가 주어질 경우 사전 학습된 BERT 사용
= 사전 학습된 BERT를 기반으로 새 태스크에 대한 가중치를 조정(파인 튜닝)
BERT의 사전 학습에 대해 알아보자
그전에 BERT가 허용하는 방식으로 입력 데이터를 구조화하는 방법부터 알아보자
2.4.1 BERT의 입력 표현
BERT에 데이터 입력하기 전, 다음 세 가지 임베딩 레이어를 기반으로 입력 데이터를 임베딩으로 변환해야 함
- 토큰 임베딩(token embedding)
- 세그먼트 임베딩(segment embedding)
- 위치 임베딩(position embedding)
토큰 임베딩 (token embedding)
ex)
문장 A: Paris is a beautiful city.
문장 B: I love Paris.
tokens = [Paris, is, a, beautiful, city, I, love, Paris]
- 두 문장을 모두 토큰화해 토큰들을 추출(이 예에서는 토큰 소문자 변환 x)
tokens = [ [CLS], Paris, is, a, beautiful, city, I, love, Paris]
- 첫 번째 문장의 시작 부분에만 [CLS] 토큰이라는 새 토큰 추가
- 분류 작업에 사용
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP]]
- 모든 문장 끝에 [SEP]라는 새 토큰 추가
- 모든 문장의 끝을 나타내는 데 사용
모든 토큰을 BERT에 입력하기 전, 토큰 임베딩이라는 임베딩 레이어를 사용해 토큰을 임베딩으로 변환
- 토큰 임베딩의 변수들은 사전 학습이 진행되면서 학습
- 모든 토큰에 대한 임베딩 존재
- E[cls]는 [CLS] 토큰의 임베딩, E[paris]는 Paris 토큰의 임베딩 나타냄
세그먼트 임베딩 (segment embedding)
주어진 두 문장을 구변하는 데 사용
ex)
문장 A: Paris is a beautiful city.
문장 B: I love Paris.
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP]]
- 두 문장을 토큰화하여 얻은 결과
[SEP] 토큰과 별도로 두 문장을 구분하기 위해 모델에 일종의 지표 제공해야 함
- 세그먼트 임베딩 레이어에 입력 토큰 제공
→ 출력으로 EA, EB만 반환
- 문장이 하나만 있는 경우, 문장의 모든 토큰이 EA에 매핑
위치 임베딩 (position embedding)
- (복습) 트랜스포머는 어떤 반복 메커니즘도 사용하지 않고 모든 단어를 병렬로 처리
→ 단어 순서와 관련된 정보 제공해야 함 = 위치 인코딩 사용 - BERT도 본질적으로 트랜스포머의 인코더
→ BERT에 데이터를 직접 입력하기 전에 문장에서 단어(토큰)의 위치에 대한 정보 제공해야 함
- 위치 임베딩이라는 레이어를 사용해 문장의 각 토큰에 대한 위치 임베딩 출력 얻게 됨
- [CLS]의 위치 임베딩인 E0와 Paris 토큰의 위치 임베딩인 E1 등 확인할 수 있음
최종 입력 데이터 표현
- 주어진 임력 문장을 토큰으로 변환
- 토큰을 토큰 임베딩, 세그먼트 임베딩, 위치 임베딩 레이어에 공급하고 임베딩 얻음
- 모든 임베딩을 합산해 BERT에 입력으로 제공
다음으로 BERT에서 사용하는 워드피스 토크나이저에 대해 알아보자
워드피스 토크나이저
BERT는 워드피스 토크나이저라는 특별한 유형의 토크나이저 사용
→ 하위 단어 토큰화 알고리즘을 기반으로 함
* 토크나이저 : 입력된 텍스트를 모델에서 처리할 수 있는 데이터로 변환
ex)
Let us start pretraining the model. (모델 사전 학습을 시작하자)
워드피스 토크나이저를 사용해 문장 토큰화
tokens = [let, us, start, pre, ##train, ##ing, the, model]
- 개별 단어가 pre, ##train, ##ing와 같은 하위 단어(subword)로 분할
왜 하위 단어로 분할할까?
- BERT는 워드피스 토크나이저를 사용해 토큰화할 때 단어가 어휘 사전에 있는지 확인
- 단어가 어휘 사전에 있으면 그 단어 토큰으로 사용
- 단어가 어휘 사전에 없으면 그 단어를 하위 단어로 분할해 어휘 사전에 있는지 확인
- 하위 단어가 어휘 사전에 있으면 이를 토큰으로 사용 - 어휘 사전에 있으면 토큰으로 사용하고 그렇지 않으면 분할
- 개별 문자에 도달할 때까지 어휘 사전을 기반으로 하위 단어를 계속 분할하고 확인
→ 어휘 사전 이외(out-of-vocabulary, OOV) 단어를 처리하는데 효과적
BERT 어휘 사전 크기는 3만 토큰
- 입력 단어가 3만 토큰에 속하면 이를 토큰으로 사용
- 그렇지 않으면 해당 단어를 하위 단어로 분할하여 하위 단어가 3만 토큰에 속하는지 확인
- 개별 문자에 도달할 때까지 어휘 사전(3만 토큰)으로 하위 단어를 계속 분할하고 확인
ex)
* 토큰 앞의 해시 기호: 하위 단어임을 나타내고 앞에 다른 단어가 있음을 의미
- pretraining이라는 단어는 BERT 어휘 사전에 없음
- pre, ##train, ##ing와 같은 하위 단어로 나눔
- 어휘 사전에 ##train, ##ing 있는지 확인
- 어휘 사전에 존재하므로 다시 나누지 않고 토큰으로 사용
tokens = [let, us, start, pre, ##train, ##ing, the, model]
- 워드피스 토크나이저를 사용해 위의 토큰들 얻음
tokens = [ [CLS], let, us, start, pre, ##train, ##ing, the model, [SEP] ]
- 이후 문장 시작 부분에 [CLS] 토큰 추가, 문장 끝부분에 [SEP] 토큰 추가
입력 토큰을 토큰, 세그먼트, 위치 임베딩 레이어에 입력해 각 임베딩을 앋고, 이들 임베딩을 합한 다음 BERT에 입력
이제 BERT 모델을 사전 학습시키는 방법을 확인해보자
2.4.2 사전 학습 전략
BERT는 다음 두 가지 태스크에 대해 사전학습
- 마스크 언어 모델링(masked language modeling, MLM)
- 다음 문자 예측(next sentence prediction, NSP)
언어 모델링
언어 모델링(language modeling)
- 임의의 문장이 주어지고 단어를 순서대로 보면서 다음 단어를 예측하도록 모델 학습
- 두 가지 분류
- 자동 회귀 언어 모델링(auto-regressive language modeling)
- 자동 인코딩 언어 모델링(auto-encoding language modeling)
자동 회귀 언어 모델링 (auto-regressive language modeling)
- 전방(왼쪽에서 오른쪽으로) 예측 (forward prediction)
- 후방(오른쪽에서 왼쪽으로) 예측 (backward prediction)
ex)
Paris is a beautiful city. I love Paris
- 'city' 단어 제거 & 공백 추가
Paris is a beautiful __. I love Paris
- 모델은 공백 예측해야 함
Paris is a beautiful __.
- 전방 예측: 예측을 수행하기 위해 왼쪽에서 오른쪽으로 공백까지 모든 단어 읽음
__. I love Paris
- 후방 예측: 오른쪽에서 왼쪽으로 공백까지 모든 단어를 읽음
자동 회귀 언어 모델은 원래 단방향이므로 한 방향으로만 문장 읽음
자동 인코딩 언어 모델링 (auto-encoding language modeling)
- 전방 & 후방 예측을 모두 활용. 즉, 예측을 하면서 양방향으로 문장을 읽음
ex)
Paris is a beautiful __. I love Paris
- 양방향으로 문장을 읽으면 문장 이해 측면에서 더 명확해지므로 더 정확한 결과
마스크 언어 모델링(MLM)
- BERT는 자동 인코딩 언어 모델, 예측을 위해 문장을 양방향으로 읽음
마스크 언어 모델링(masked language modeling, MLM)
- 주어진 입력 문장에서 전체 단어의 15%를 무작위로 마스킹
- 마스크된 단어를 예측하도록 모델 학습
- 마스크된 단어를 예측하기 위해 모델은 양방향으로 문장을 읽고 마스크된 단어 예측 시도
ex)
'Paris is a beautiful city', and 'I love Paris'
tokens = [Paris, is, a beautiful, city, I, love, Paris]
- 문장을 토큰화
tokens = [ [CLS], Paris, is, a beautiful, city, [SEP], I, love, Paris, [SEP] ]
- 첫 번째 문장의 시작 부분에 [CLS] 토큰 추가, 문장 끝에 [SEP] 토큰 추가
tokens = [ [CLS], Paris, is, a beautiful, [MASK], [SEP], I, love, Paris, [SEP] ]
- 토큰(단어)의 15%를 무작위로 마스킹
- city라는 단어를 마스킹한 다음 city를 [MASK] 토큰으로 바꿈
이제 마스크된 토큰을 에측하기 위한 BERT를 학습
작은 문제
: 위 방식으로 토큰을 마스킹하면 사전 학습과 파인 튜닝 사이에 불일치
- [MASK] 토큰을 예측해 BERT를 사전 학습시키고, 학습시킨 후에는 감정 분석과 같은 다운스트림 태스크를 위해 사전 학습된 BERT를 파인 튜닝
- 파인 튜닝에는 입력 [MASK] 토큰이 없음
→ BERT가 사전 학습되는 방식과 파인 튜닝에 사용되는 방식 간에 불일치
문제 극복
: 80-10-10% 규칙
문장에서 토큰에서 무작위로 마스킹한 15%에 다음을 수행
tokens = [ [CLS], Paris, is, a beautiful, [MASK], [SEP], I, love, Paris, [SEP] ]
- 15% 중 80%의 토큰(실제 단어)를 [MASK] 토큰으로 교체
tokens = [ [CLS], Paris, is, a beautiful, love, [SEP], I, love, Paris, [SEP] ]
- 15% 중 10%의 토큰(실제 단어)을 임의의 토큰(임의 단어)으로 교체
tokens = [ [CLS], Paris, is, a beautiful, love, [SEP], I, love, Paris, [SEP] ]
- 15% 중 나머지 10%의 토큰은 어떤 변경도 하지 않음
토큰화 및 마스킹 후에 입력 토큰을 토큰, 세그먼트, 위치 임베딩 레이어에 입력해 입력 임베딩을 얻음
→ 입력 임베딩을 BERT에 제공
- BERT는 입력을 받은 다음 각 토큰의 표현 벡터를 출력으로 반환
- R[CLS]는 [CLS] 토큰의 표현 벡터 ...
- 이 예에서 BERT-base 사용, 각 토큰의 표현 벡터 크기는 768
- 각 토큰의 표현 R을 얻음
표현 R로 마스크된 토큰을 어떻게 예측하게 될까?
- BERT에서 반환된 마스크된 토큰 R[MASK]의 표현을 소프트맥스 활성화를 통해 피드포워드 네트워크에 입력
- 피드포워드 네트워크는 다음 그림과 같이 R[MASK] 단어가 마스크된 단어가 될 확률 반환
- 그림에서 'city'라는 단어가 마스크된 단어일 확률 높음 → 마스크된 단어 'city'로 예측
- 학습 초기에는 BERT의 피드포워드 네트워크 및 인코더 계층의 가중치가 최적이 아니므로 모델이 올바른 확률 반환하지 않음
- 역전파를 통한 일련의 반복 학습을 거치며 BERT 피드포워드 네트워 및 인코더 계츠으이 가중치 업데이트가 반복되며 최적의 가중치 학습
- (복잡함을 줄이기 위해 입력 임베딩 레이어(토큰, 세그먼트, 위치)를 표시하지 않음)
- 마스크 언어 모델링 태스크 = 빈칸 채우기 태스크(cloze task)
전체 단어 마스킹(WWM)
전체 단어 마스킹(Whole Word Masking, WWM) 동작
ex)
Let us start pretraining the model (모델 사전 학습을 시작한다)
tokens = [let, us, start, pre, ##train, ##ing, the, model]
- BERT는 워드피스 토크나이저 사용, 문장 토큰화
tokens = [[CLS], let, us, start, pre, ##train, ##ing, the, model, [SEP]]
- 문장 시작 부분에 [CLS] 토큰을 추가, 문장 끝부분에 [SEP] 토큰 추가
tokens = [[CLS], [MASK], us, start, pre, [MASK], ##ing, the, model, [SEP]]
- 마지막으로 단어의 15%를 무작위로 마스킹
tokens = [[CLS], [MASK], us, start, [MASK], [MASK], [MASK], the, model, [SEP]]
- let, ##train이라는 단어 마스킹
- ##train이라는 단어는 pretraining(사전학습) 단어의 하위 단어
- WWM 방법에서는 하위 단어가 마스킹되면 해당 하위 단어와 관련된 모든 단어를 마스킹
- ##train 하위 단어와 관련된 모든 토큰들이 마스크
tokens = [[CLS], let, us, start, [MASK], [MASK], [MASK], the, model, [SEP]]
- WWM의 경우 하위 단어가 마스크되면 , 관련된 모든 단어를 마스킹하면서 마스크 비율(15%)을 유지하려 함
→ 하위 단어와 관련된 모든 단어를 마스킹하는 동안 마스크 비율이 15%를 초과하면 다른 단어의 마스킹을 무시 - 마스크 비율을 유지하기 위해 let이라는 단어의 마스킹 무시
이러한 방식으로 WWM을 기반으로 토큰을 마스킹
마스킹한 후 토큰을 BERT에 입력하여 마스크된 토큰을 예측하도록 모델 학습
다음 문장 예측(NSP)
다음 문장 예측(next sentence prediction, NSP)
- BERT에 두 문장을 입력하고 두 번째 문장이 첫 번째 문장의 다음 문장인지 예측
- BERT 학습에 사용되는 또 다른 태스크
- 이진 분류 테스트
ex)
문장 A: She cooked pasta.
문장 B: It was delicious.
- B 문장은 A 문장의 후속 문장
- 이 문장 쌍을 isnNext로 표시해 B문장이 A문장의 다음 문장임을 알 수 있게 함
문장 A: Turn the radio on.
문장 B: She bought a new hat.
- 이 문장 쌍에서 B 문장은 A 문장의 후속 문장이 아님
- notNext로 표시해 B 문장이 A 문장의 다음 문장이 아님을 알 수 있게 함
NSP 태스트에서 모델의 목표
- 문장 쌍이 isNext 범주에 속하는지 여부를 예측하는 것
- 문장 쌍(문장 A 및 B)을 BERT에 입력
- B 문장이 A 문장 다음에 오는지 여부 예측하도록 학습
- 모델은 B 문장이 A 문장에 이어지면 isNext 반환
- 그렇지 않으면 notNext 반환
=> NSP는 본질적으로 이진 분류 태스크
NSP 태스크의 목적
- NSP 태스크를 수행함으로써 모델은 두 문장 사이의 관계를 파악할 수 있음
- 두 문장 간의 관계를 이해하는 것은 질문-응답 및 유사문장탐지와 같은 다운스트림(downstream) 태스크에서 유용
* 질문-응답, 감정 분류, 유사문장탐지 등의 태스크에서 나타나는 두 문장은 거대한 사전 학습셋에서 서로 인접해 존재하는 경향이 있음. 이런 태스크를 NSP의 다운스트림 태스크라고 함
NSP 태스크를 위한 데이터셋
- 어떠한 말뭉치에서도 데이터셋 획득 가능
- 2개의 문서가 있다고 가정
- isNext 클래스의 경우 한 문서에서 연속된 두 문장을 isNext로 표시
- notNext 클래스의 경우 한 문서에서 한 문장을, 임의의 문서에서 다른 문장을 가져와 notNext로 표시 - isNext 클래스를 전체의 50% 비율로 유지, notNext 클래스에서 나머지 50%를 유지해 클래스가 균형을 이루도록 함
NSP 태스크를 수행하기 위해 BERT를 학습시키는 방법
- 데이터셋을 다음과 같이 가정
tokens = [She, cooked, pasta, It, was, delicious]
- 첫 번째 데이터
- 문장 쌍을 토큰화
tokens = [[CLS], She, cooked, pasta, [SEP], It, was, delicious, [SEP]]
- 토큰들을 토큰, 세그먼트, 위치 임베딩 레이어에 입력 & 입력 임베딩 뱐환
- 입력 임베딩을 BERT에 넣어 각 토큰의 표현을 얻음
- 토큰 R[CLS]는 [CLS]의 표현을 나타내고 Rshe는 She 토큰의 표현을 나타냄
NSP는 이진 분류 작업
지금 우리는 문장 쌍에서 각 토큰의 표현만 가짐
표현을 기반으로 문장 쌍을 어떻게 분류할 수 있을까?
- 분류를 수행하려면 간단히 [CLS] 토큰 표현을 가져와 소프트맥스 함수를 사용해 피드포워드 네트워크에 입력
- 그러면 문장 쌍이 isNext인지, notNext인지에 대한 확률값이 반환
왜 [CLS] 토큰만 포함? 다른 토큰의 임베딩이 아닌 이유는?
- [CLS] 토큰은 기본적으로 모든 토큰의 집계 표현 보유 → 문장 전체에 대한 표현 담고 있음
- 다른 모든 토큰의 표현은 무시하고, [CLS] 토큰 표현 R[CLS]를 가져와 확률을 반환하는 소프트맥스 함수를 사용해 피드포워드 네트워크에 공급
- 여기서는 복잡함을 줄이기 위해 입력 임베딩 레이어(토큰, 세그먼트, 위치 임베딩 레이어)를 표시하지 않음
- [그림]은 피드포워드 네트워크는 입력 문장 쌍이 isNext 클래스에 속할 확률이 높다는 것 보여줌
- 학습 초기에는 피드포워드 네트워크 및 인코더 계층의 가중치가 최적이 아니기 때문에 모델이 올바른 확률을 반환하지 못할 것
- 역전파를 기반으로 한 일련의 반복 학습을 통해 피드포워드 네트워크의 가중치와 BERT 인코더 계층의 가중치를 업데이트하고 최적의 가중치를 학습하게 됨
2.4.3 사전 학습 절차
- BERT의 사전 학습에는 토론토 책 말뭉치(Toronto BookCorpus) 및 위키피디아 데이터셋 사용
- BERT는 MLM(빈칸 채우기 태스크) 및 NSP 태스크를 사용해 사전 학습
두 태스크를 사용해 BERT를 학습시키기 위한 데이터셋을 어떻게 준비할까?
- 말뭉치에서 두 문장을 샘플링
ex) A와 B 문장을 샘플링
- A와 B 문장의 총 토큰 수의 합은 512보다 작거나 같아야 함
- 두 문장을 샘플링할 때 전체의 50%는 B 문장이 A 문장의 후속 문장이 되도록 샘플링
- 나머지 50%는 B 문장을 A 문장의 후속 문장이 아닌 것으로 샘플링
ex) 다음의 두 문장을 샘플링했다고 가정
- 문장 A: We enjoyed the game (우리는 게임을 즐겼다)
- 문장 B: Turn the radio on (라디오 켜줘)
tokens = [[CLS], we, enjoyed, the, game, [SEP], turn, the radio, on, [SEP]]
- 먼저 워드피스 토크나이저를 사용해 문장을 토큰화
- 첫 번째 문장의 시작 부분에 [CLS] 토큰을 추가 & 모든 문장의 끝에 [SEP] 토큰을 추가 → 토큰 리스트 얻음
tokens = [[CLS], we, enjoyed, the, [MASK], [SEP], turn, the radio, on, [SEP]]
- 80-10-10% 규칙에 따라 토큰의 15%를 무작위로 마스킹
- game 토큰을 마스킹했다고 가정
이제 토큰을 BERT에 입력 & 마스크된 토큰을 예측하기 위해 모델을 학습
동시에 B 문장이 A 문장의 후속 문장인지 여부를 분류
=> MLM과 NSP 작업을 동시에 사용해 BERT 학습
- BERT는 총 100만 스탭을 학습
- 각 스탭당 배치 크기 256 입력 시퀀스에 대해 학습
- 학습률은 lr = 1e-4, beta1 = 0.9, beta2 = 0.999로 설정
- 아담 옵티마이저 사용
- 웜업(warmup)은 1만 스텝으로 학습 진행
웜업 스텝은 무엇일까?
- 학습률 스케줄링 = 학습 초기에 학습률 값을 높게 설정한 다음 학습이 진행되면서 학습률을 감소시키는 것
* 웜업 스텝은 학습률 스케줄링의 일부 - 학습이 진행되면, 높은 학습률을 설정해 학습 초기에 모델의 큰 변화를 유도
학습 후반에는 낮은 학습률을 설정해 모델에 작은 변화를 주어 최적화 - 학습 초기에는 수렴과 거리가 멀기 때문에 모델에 과감한 변화
이후 수렴에 가까워지기 때문에 큰 변화보다 작은 변화를 주어 모델 최적화
ex) 학습률 1e - 4, 웜업 스텝 총 1만 스텝이라 가정
- 초기 1만 스텝은 학습률이 0에서 1e - 4로 선형적으로 증가
- 1만 스텝 후에는 수렴에 가까워짐에 따라 학습률을 선형적으로 감소
드롭아웃(dropout)
- 확률이 0.1인 모든 레이어에 드롭아웃을 적용
활성화 함수 (간단한 설명)
- BERT에서는 갤루(GELU)라는 활성화 함수 사용 → 가우시안 오차 선형 유닛(Gaussian Error Linear Unit) 의미
- 갤루 함수
- Φ(x)는 표준 가우시안 누적 분포(standard Gaussian cumulative distribution) 함수
- 갤루 함수를 다음과 같이 근사할 수 있음
- 사전 학습된 BERT는 다양한 태스크에 사용할 수 있음
2.5 하위 단어 토큰화 알고리즘
하위 단어 토큰화
- BERT 및 HPT-3을 포함한 최신 자연어 모델에서 널리 사용
→ 이 방식이 OOV 단어 처리에 매우 효과적이기 때문
(그 전에 먼저 단어 수준 토큰화부터 봐보자)
단어 수준 토큰화
- 학습 데이터셋에서 다음 단어로 구성된 어취 사전을 구축했다고 가정
(데이터셋에 있는 텍스트를 공백으로 분할, 모든 고유 단어를 어휘 사전에 추가)
vocabulary = [game, the, I, played, walked, enjoy]
- 위의 단어로 구성된 어휘 사전 가정
- 입력을 토큰화하는데 이 어휘 사전 사용
tokens = [I, played, the, game]
- 'I played the game'이라는 입력 문장
- 문장에서 토큰을 생성하기 위해 문장을 공백으로 분할해 문장의 모든 단어 얻음
- 어휘 사전에 모든 단어가 있는지 확인
- enjoyed를 제외하고 어휘 사전에 모든 단어가 있음
- 어휘 사전에 enjoy가 있지만
- enjoyed라는 단어가 어휘 사전에 존재하지 않으므로 <UNK>(unknown)으로 대체
tokens = [ I, <UNK>, the, game]
- 최종 토큰
- enjoyed는 <UNK> 토큰으로 표시해 알 수 없는 단어로 정의
한계
- 어휘 사전을 더 크게 할 수 있으나, 모델의 메모리 부족 문제와 성능 문제...
- 여전히 알 수 없는 단어(어휘 사전에 없는 단어)는 존재하므로 문제 발생의 여지
* 어휘 사전이 커지면 모델의 총 변수의 수가 늘어남. 이는 토큰 임베딩 변수의 수가 늘어나는 것에 기인. 총변수의 수가 늘어나면 모델이 점유하는 메모리 크기가 커지며, 메모리 크기가 커지면 모델의 처리량(throughput)이 작아지는 결과 초래
해결책
: 하위 단어 토큰화 알고리즘
vocabulary = [game, the, I, played, walked, enjoy]
- 어휘 사전
vocabulary = [game, the, I, play, walk, ed, enjoy]
- 하위 단어 토큰화에서는 단어를 하위 단어로 분활
- played → [play, ed]
- walked → [walk, ed]
- 하위 단어를 분리한 후 어휘 사전에 추가
- 어휘 사전은 고유한 단어로만 구성
tokens = [ I, enjoy, ##ed, the, game]
- 어휘 사전에 존재하지 않는 enjoyed 단어를 하위 단어 [enjoy, ed]로 쪼갬
- 어휘 사전에 하위 단어가 있는지 왁인
- 존재하므로 토큰 리스트는 다음과 같이 구성
enjoy 하위 단어에는 ## 기호가 없음
- 단어의 시작 부분에 해당하는 하위 단어에는 ## 기호를 추가하지 않음
- ## 기호는 하위 단어이고 앞에 다른 단어가 있음을 나타내기 위해 추가
→ 이러한 방식으로 하위 단어 토큰화는 알 수 없는 단어, 즉 어휘 사전에 없는 단어를 처리
어휘 사전에서 분할할 단어와 분할하지 않을 단어를 어떻게 결정할까?
→ 하위 단어 토큰화 알고리즘
- 바이트 쌍 인코딩(byte pair encoding)
- 바이트 수준 바이트 쌍 인코딩(byte-level byte pair encoding)
- 워드피스(WordPiece)
2.5.1 바이트 쌍 인코딩
바이트 쌍 인코딩(byte pair encoding, BPE) 동작 방식
- 데이터셋 가정 : 모든 단어를 빈도수와 함께 추출
ex) (cost, 2), (best, 2), (menu, 1), (men, 1), (camel, 1) 추출 단어라고 가정 - 모든 단어를 문자로 나누고 문자 시퀀스로 만듦
- 어휘 사전 크기 정의
- 크기가 14인 어휘 사전 구축한다고 가정 = 14개의 토큰으로만 어휘 사전을 생성
- 이후 BPE를 사용해 어휘 사전 만듦
- 문자 시퀀스에 있는 모든 고유 문자를 어휘 사전에 추가 (어휘 사전 크기 11) - 어휘 사전에 새로운 토큰 추가
어휘 사전에 새 토큰을 추가하는 방법
- 먼저 가장 빈도수가 큰 기호 쌍을 식별
- 가장 빈번한 기호 쌍을 병합해 어휘 사전에 추가
- 이 작업은 어휘 사전 크기에 도달할 때까지 반복적으로 수행
ex)
- 문자 시퀀스를 살펴보면 기호 쌍 s와 t가 4번 발생했기 때문에 가장 빈번한 기호 쌍이 s와 t임을 알 수 있음
(cost에서 2번, best에서 2번)
- 위와 같이 기호 s, t를 병합해 어휘 사전에 추가
- 같은 단계 반복, 가장 빈번한 기호 쌍을 다시 확인
- 현재 가장 빈번한 기호 쌍 m과 e (총 3번)
- m과 e 기호를 병합하고 어휘 사전에 추가
- 다시 한번 가장 빈번한 기호 쌍을 확인
- me와 n 기호를 병합하고 다음과 같이 어휘 사전에 추가
이런 식으로 어휘 사전 크기에 도달할 대까지 이 단계를 여러 번 반복
- 앞의 그림에서 어휘 사전에 14개의 토큰이 있음을 알 수 있음
- 주어진 데이터셋을 기반으로 총 14개의 토큰을 포함하는 어휘 사전 구축
- vocabulary = {a,b,c,e,l,m,n,o,s,t,u,st,me,men}
BPE와 관련된 단계
- 빈도수와 함께 주어진 데이터셋에서 단어 추출
- 어휘 사전 크기 정의
- 단어를 문자 시퀀스로 분할
- 문자 시퀀스의 모든 고유 문자를 어휘 사전에 추가
- 빈도가 높은 기호 쌍을 선택하고 병합
- 어휘 사전 크기에 도달할 때가지 앞 다섯 단계 반복
그렇다면 어휘 사전을 어떻게 사용할 수 있을까? → 주어진 입력 문장 토큰화에 사용 (다음 절)
BPE로 토큰화하기
어휘 사전을 어떻게 사용할 수 있는지 확인해보자
ex)
vocabulary = {a,b,c,e,l,m,n,o,s,t,u,st,me,men}
- 입력 텍스트 mean 한 단어로 구성
- 어휘 사전에 mean이라는 단어가 있는지 확인 → 존재하지 않음
- mean 단어를 하위 단어 [me, an]으로 나눔
- 다시 하위 단어가 어휘 사전에 있는지 확인 → me는 존재, an은 없음
- an을 분할 → 하위 단어는 [be, a, n]으로 구성
- 문자 a, n이 어휘 사전에 있는지 확인 → 존재
- 최종 토큰
tokens = [me,a,n]
- bear 입력 단어
- 어휘 사전에 bear 단어 없음 → 하위 단어 be와 ar로 나눔
- be와 ar이 어휘 사전에 있는지 확인 → be 존재, ar 없음
- 하위 단어 ar을 분할 → 하위 단어 [be, a, r]로 구성
- 문자 a, r이 어휘 사전에 있는지 확인 → a 존재, r 없음
- 개별 문자만 있으므로 다른 분할 수행할 수 없게 됨 → r은 <UNK> 토큰으로 교체
- 최종 토큰
tokens = [be,a, <UNK>]
BPE로 드문 단어를 잘 처리한다고 했는데, 입력 문장이 처리 결과로 <UNK> 토큰 포함
- r이 우리 어휘 사전에 존재하지 않는 건 작은 예시를 보여주기 위함
- 대부분의 경우 거대 말뭉치로 어휘 사전을 만들면 모든 문자가 포함됨
2.5.2 바이트 수준 바이트 쌍 인코딩
바이트 수준 바이트 쌍 인코딩(byte-level byte pair encoding, BBPE)
- BPE와 매우 유사하게 작동하지만 바이트 수준 시퀀스 사용 ↔ 문자 수준 시퀀스
BBPE의 작동
ex)
입력 텍스트가 best 단어로만 구성
- BPE
- 문자 시퀀스 반환: b e s t - BBPE
- 바이트 시퀀스 반환 : 62 65 73 74
각 유니코드는 바이트로 변환
- 단일 문자 크기는 1~4바이트가 될 수 있음
- 한자 단어도 문자 시퀀스 대신 바이트 수준 시퀀스로 변환
바이트 시퀀스: e4 bd a0 e5 a5 bd
BBPE 수행 목적
- 바이트 수준 BPE가 다국어 설정에서 매우 유용
- OOV(단어 사전에 없는 단어) 처리에 매우 효과적 → 여러 언어로 어휘 사전을 공유하기 좋음
2.5.3 워드피스
워드피스(WordPiece)
- BPE와 유사하지만 사소한 차이
- 빈도에 따라 심볼 쌍을 병합하지 않음
- 대신, 가능도(likelihood)를 기준으로 기호 쌍을 병합
- 주어진 학습 데이터에 대해 학습된 언어 모델 가능도가 높은 기호 쌍을 병합
ex)
- BPE : 가장 빈번한 기호 쌍 병합
- BPE에서는 심볼 쌍 s, t가 4번 발생했기 때문에 병합 - 워드피스에서는 가능도가 가장 높은 기호 쌍 병합
- 기호 쌍 s 및 t의 가능도 계산
- 가능도가 높으면 기호 쌍을 병합하고 어휘 사전에 추가
- 모든 기호 쌍의 가능도를 계산하고 최대 가능도를 가진 것을 병합해 어휘에 추가
알고리즘
- 빈도수와 함께 주어진 데이터셋에서 단어 추출
- 어휘 사전 크기 설정
- 단어를 문자 시퀀스로 분할
- 문자 시퀀스의 모든 고유 문자를 어휘 사전에 추가
- 주어진 데이터셋(학습셋)에서 언어 모델 빌드
- 학습셋에서 학습된 언어 모델의 최대 가능도를 가진 기호 쌍을 선택하고 병합
- 어휘 사전 크기에 도달할 때까지 앞의 여섯 단계 반복
→ 이렇게 구축된 어휘 사전은 입력 문장 토큰화에 사용
ex)
워드피스 방법을 사용해 만든 어휘 사전
: vocabulary = {a,b,c,e,l,m,n,o,s,t,u,st,me}
- 입력 텍스트가 하나의 단어 stem으로만 구성
- 어휘 사전에 stem이라는 단어가 없음 → 하위 단어 [st, ##em]으로 분할
- 어휘 사전에 하위 단어 st와 em이 있는지 확인 → st 존재, em 없음
- 하위 단어 em 분할 → 하위 단어는 [be, ##e, ##m]으로 구성
- 문자 e와 m이 어휘 사전에 있는지 확인 → 존재
- 최종 토큰
tokens = [st, ##e, ##m]