일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 삽입정렬
- BFS
- 재귀함수
- 큐
- 다이나믹 프로그래밍
- 그리디
- LSTM
- 선택정렬
- 인공지능
- 계수정렬
- 머신러닝
- 스택
- 퀵정렬
- 정렬
- 이진 탐색
- 딥러닝
- DFS
- AI
- 알고리즘
- RESNET
- 최단 경로
- pytorch
- 선형대수
- rnn
- GRU
- Machine Learning
- 캐치카페신촌점 #캐치카페 #카페대관 #대학생 #진학사 #취준생
- Today
- Total
hyeonzzz's Tech Blog
[딥러닝 파이토치 교과서] 10장. 임베딩 -(2) 본문
10.3 한국어 임베딩
<사전 훈련된 버트 모델을 사용한 한국어 임베딩 구현>
1. 라이브러리 불러오기
- 한국어를 위한 버트 토크나이저 'bert-base-multilingual-cased' 사용
2. 문장의 토크나이징
- 토크나이징한 결과 쪼개진 단어들이 정확하지 않다.
- 버트 토크나이저가 단어의 가장 작은 조각을 기준으로 쪼개도록 설계되었기 때문이다.
- 따라서 KoBert 같은 국내에서 개발한 모델들을 이용하는 것도 좋다.
3. 모델을 훈련시킬 텍스트 정의
- 버트는 문장이 바뀔 때마다 0에서 1로 바뀌고, 다시 문장이 바뀌면 1에서 0으로 바뀐다.
- [0, 0, 1, 1, 1, 0, 0, 0] 이라는 결과가 있다면 3개의 문장으로 구성된 것이다.
4. 문장 인식 단위 지정
- 하나의 문장으로 인식시키기 위해 33개의 토큰에 벡터 1을 부여한다.
5. 데이터를 텐서로 변환
6. 모델 생성
model = BertModel.from_pretrained('bert-base-multilingual-cased',
output_hidden_states = True,)
- from_pretrained : 사전 훈련된 모델을 내려받는다.
- 'bert-base-multilingual-cased' : 12개의 계층으로 구성된 심층 신경망. 다국어에 대한 임베딩을 처리
- output_hidden_states : 버트 모델에서 은닉 상태의 값을 가져오기 위해 사용
7. 모델 훈련
8. 모델의 은닉 상태 정보 확인
# 모델 훈련
with torch.no_grad(): # 모델을 평가할 때 기울기 사용 X
outputs = model(tokens_tensor, segments_tensors)
hidden_states = outputs[2] # 네트워크의 은닉 상태를 가져온다
계층 수: 13 (initial embeddings + 12 BERT layers)
배치 수: 1
토큰 수: 33
은닉층 유닛 수: 768
Hugging Face의 트랜스포머 라이브러리를 사용하는 BERT 모델의 경우, outputs는 기본적으로 다음 세 가지를 포함하는 튜플로 반환된다:
- last_hidden_state: 최종 레이어의 출력 (각 입력 토큰에 대한 은닉 상태)
- pooler_output: 분류 작업을 위해 풀링된 출력 (주로 [CLS] 토큰에 대응하는 은닉 상태)
- hidden_states: 모든 레이어의 은닉 상태 (옵션, 모델 초기화 시 output_hidden_states=True로 설정해야 함)
13개인 이유는 첫 번째 임베딩 계층이 포함되었기 때문이다.
(계층, 배치, 토큰, 은닉층 유닛) 차원 -> (토큰, 계층, 은닉층 유닛) 차원으로 변환한다.
print('은닉 상태의 유형: ', type(hidden_states))
print('각 계층에서의 텐서 형태: ', hidden_states[0].size())
은닉 상태의 유형: <class 'tuple'>
각 계층에서의 텐서 형태: torch.Size([1, 33, 768])
- 은닉 상태는 튜플로 구성되어 있다.
- 각 계층에서의 텐서는 [1, 33, 768] 형태를 갖는다. (배치, 토큰, 은닉층 유닛)
9. 텐서의 형태 변경
# 텐서의 형태 변경
token_embeddings = torch.stack(hidden_states, dim=0) # 각 계층의 텐서 결합은 stack을 사용
token_embeddings.size() # 최종 텐서의 형태를 출력
torch.Size([13, 1, 33, 768])
token_embeddings = torch.squeeze(token_embeddings, dim=1) # 배치 차원(1) 제거
token_embeddings.size() # 배치 차원 제거 후 최종 텐서의 형태를 출력
torch.Size([13, 33, 768])
- 배치 차원이 제거되었다.
10. 텐서 차원 변경
# 텐서 차원 변경
token_embeddings = token_embeddings.permute(1,0,2)
token_embeddings.size()
torch.Size([33, 13, 768])
- permute는 차원을 맞교환할 때 사용한다.
- (계층, 토큰, 은닉층 유닛) -> (토큰, 계층, 은닉층 유닛)
11. 각 단어에 대한 벡터 형태 확인
각 단어에 대해 BERT 모델의 마지막 4개 레이어에서 생성된 은닉 상태 벡터를 결합하여, 각 단어의 문맥 정보를 더 풍부하게 담고 있는 새로운 벡터를 생성한다. 이 과정은 각 단어에 대한 문맥을 보다 정교하게 분석하고자 할 때 유용할 수 있다.
# 각 단어에 대한 벡터 형태 확인
token_vecs_cat = [] # 형태가 [33 x (33 x 768)]인 벡터를 [33 x 25344]로 변경하여 저장
# token_embeddings는 [33 x 12 x 768] 형태의 텐서를 갖는다
for token in token_embeddings:
# 마지막 4개의 레이어의 은닉 상태 벡터를 이어붙인다
cat_vec = torch.cat((token[-1], token[-2], token[-3], token[-4]), dim=0)
token_vecs_cat.append(cat_vec)
# 결과 벡터의 형태를 출력
print('형태는: %d x %d' % (len(token_vecs_cat), len(token_vecs_cat[0])))
형태는: 33 x 3072
- token_vecs_cat = []:
- 각 단어에 대한 벡터를 저장할 리스트를 초기화합니다.
- for token in token_embeddings::
- token_embeddings 텐서의 각 단어에 대해 반복합니다. token_embeddings는 [33 x 12 x 768] 형태를 갖습니다.
- 여기서 33은 토큰, 12는 계층, 768은 은닉층 유닛입니다.
- cat_vec = torch.cat((token[-1], token[-2], token[-3], token[-4]), dim=0):
- 각 단어의 마지막 4개 레이어의 은닉 상태 벡터를 이어붙입니다.
- token[-1]은 마지막 레이어의 은닉 상태 벡터, token[-2]는 그 이전 레이어의 은닉 상태 벡터 등입니다.
- torch.cat 함수는 지정된 차원(dim=0)을 따라 텐서를 이어붙입니다. 결과는 [4 x 768] 크기의 텐서가 됩니다.
- token_vecs_cat.append(cat_vec):
- 이어붙인 벡터(cat_vec)를 token_vecs_cat 리스트에 추가합니다.
- print('형태는: %d x %d' % (len(token_vecs_cat), len(token_vecs_cat[0]))):
- token_vecs_cat 리스트의 첫 번째 차원(토큰 수)과 두 번째 차원(이어붙인 벡터의 길이)을 출력합니다.
- 결과는 형태는: 33 x 3072가 됩니다. [토큰 수 x 결합된 벡터의 길이]
12. 계층을 결합하여 최종 단어 벡터 생성
각 단어에 대해 BERT 모델의 마지막 4개 레이어에서 생성된 은닉 상태 벡터를 합산하여, 각 단어의 문맥 정보를 더 간결하게 담고 있는 벡터를 생성한다. 이 벡터는 각 단어를 768차원의 벡터로 표현하며, 문맥 정보를 포함하고 있다.
# 계층을 결합하여 최종 단어 벡터 생성
token_vecs_sum = [] # [33x768] 형태의 토큰을 벡터로 저장
for token in token_embeddings: # 'token_embeddings'는 [33x12x768] 형태의 토큰을 갖는다
sum_vec = torch.sum(token[-4:], dim=0) # 마지막 4개 계층의 벡터를 합산
token_vecs_sum.append(sum_vec) # sum_vec를 사용하여 토큰을 표현
print ('형태는: %d x %d' % (len(token_vecs_sum), len(token_vecs_sum[0])))
형태는: 33 x 768
- token은 [12 x 768] 형태의 텐서입니다.
- token[-4:]는 마지막 4개 레이어에 해당하는 [4 x 768] 크기의 텐서를 선택합니다.
- torch.sum(token[-4:], dim=0)은 이 4개 레이어의 벡터를 차원 0을 따라 합산하여 [768] 크기의 벡터를 만듭니다.
- 즉, 각 레이어의 은닉 상태 벡터의 요소들을 더합니다
13. 문장 벡터
이제 전체 문장에 대한 단일 벡터를 구해야 한다. 768 길이의 벡터를 생성하는 각 토큰의 두 번째에서 마지막 은닉 계층을 평균화하면 쉽게 구할 수 있다.
# 문장 벡터
token_vecs = hidden_states[-2][0] #[33x768]
sentence_embedding = torch.mean(token_vecs, dim=0)
print ("최종 임베딩 벡터의 형태:", sentence_embedding.size())
최종 임베딩 벡터의 형태: torch.Size([768])
14. 토큰과 인덱스 출력
15. 단어 벡터 확인
# 단어 벡터 확인
print("사과가 많았다", str(token_vecs_sum[6][:5]))
print("나에게 사과했다", str(token_vecs_sum[10][:5]))
print("사과를 먹었다", str(token_vecs_sum[19][:5]))
사과가 많았다 tensor([-0.5844, -4.0836, 0.4906, 0.8915, -1.8054])
나에게 사과했다 tensor([-0.8631, -3.4047, -0.7351, 0.9805, -2.6700])
사과를 먹었다 tensor([ 0.6756, -0.3618, 0.0586, 2.2050, -2.4193])
16. 코사인 유사도 계산
# 코사인 유사도 계산
from scipy.spatial.distance import cosine
diff_apple = 1 - cosine(token_vecs_sum[5], token_vecs_sum[27]) # '사과가 많았다'와 '나에게 사과했다'에서 단어 '사과' 사이의 코사인 유사도 계싼
same_apple = 1 - cosine(token_vecs_sum[5], token_vecs_sum[16]) # '사과가 많았다'와 '사과를 먹었다'에 있는 '사과'사이의 코사인 유사도를 계산
print('*유사한* 의미에 대한 벡터 유사성: %.2f' % same_apple)
print('*다른* 의미에 대한 벡터 유사성: %.2f' % diff_apple)
*유사한* 의미에 대한 벡터 유사성: 0.86
*다른* 의미에 대한 벡터 유사성: 0.91
- 한국어에 대한 정확한 판별이 어렵다.
- 사과라는 단어가 쪼개져 있기 때문에 정확한 결과라고 하기 어렵다.
'Deep Learning > Pytorch' 카테고리의 다른 글
[딥러닝 파이토치 교과서] 13장. 생성 모델 - (1) (2) | 2024.07.05 |
---|---|
[딥러닝 파이토치 교과서] 12장. 강화학습 (0) | 2024.06.26 |
[딥러닝 파이토치 교과서] 10장. 임베딩 -(1) (0) | 2024.05.31 |
[딥러닝 파이토치 교과서] 9장. 자연어 전처리 (0) | 2024.05.16 |
[딥러닝 파이토치 교과서] 8장. 성능 최적화 (0) | 2024.05.15 |