참고 - 12-09 파이토치(PyTorch)의 nn.Embedding()
https://wikidocs.net/64779🫡
12-09 파이토치(PyTorch)의 nn.Embedding()
파이토치에서는 임베딩 벡터를 사용하는 방법이 크게 두 가지가 있습니다. 바로 임베딩 층(embedding layer)을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습하는 …
wikidocs.net
🔻파이토치의 임베딩 방식
파이토치에는 크게 2가지 방식으로 임베딩을 할 수 있다.
- embedding layer를 만들어 처음부터 train dataset을 활용해 학습하는 방식
torch.nn.Embedding
클래스는 1번 방식을 위한 모듈이다.- pre-trained model을 활용해 임베딩하는 방식
🔻임베딩 원리 및 과정
🔸기본 원리
임베딩은 임베딩 벡터로 이루어진 lookup 테이블에서 특정 단어에 해당하는 벡터를 찾아서 반환하는 작업이다.
- 우선 입력 시퀀스를 토큰화한다. 하나의 토큰은 하나의 단어일 수도 있고, 한글의 경우 형태소 단위가 될 수도 있다.
- 토큰들을 정수로 인코딩한다. (마치 LabelEncoding을 하는 것과 같이 unique한 토큰들을 정수로 변환한다.)
- 원하는 임베딩 벡터의 차원을 설정한다.
- 임베딩 벡터 : 임베딩의 결과물이다.
- 설정한 차원은 lookup 테이블의 column 개수이다.
- Embedding Layer는 정수 인덱스에 해당하는 row를 lookup 테이블에서 찾아 반환한다.
- lookup 테이블 : 이 lookup 테이블의 값들이 훈련 과정에서 학습되는 것이다.
[https://wikidocs.net/64779 - embedding image]
🔸nn.Embedding의 주요 파라미터
num_embeddings
: lookup 테이블의 행수, unique한 토큰의 개수이다.embedding_dim
: lookup 테이블의 열수, 임베딩 벡터의 차원이다.padding_idx
: <pad> 토큰을 사용할 경우 해당 토큰의 정수 인덱스- tokenizer를 사용하지 않고 수동으로 임베딩할 경우 주로 padding_idx를 자신이 설정한 <pad> 토큰의 정수값으로 한다.
BertTokenizer
와 같이 special_token을 자동으로 처리하는 tokenizer를 사용할 경우,tokenizer.pad_token_type_id
와 같은 속성값을 파라미터에 전달해야 한다.max_norm
: 정규화 값 설정norm_type
(default=2) : p-norm에서 p의 값이다.
🔻토큰화 Tokenizing
🔸tokenizing 미사용 코드
- 우선 입력 시퀀스의 모든 단어들 중 unique한 값들만 추출해 단어 집합을 생성하고 이에 대해 integer index를 매핑한다.
import pandas as pd
train_data = pd.DataFrame({
'a' : ['hello i am teacher'],
'b' : ['hi glad to see'],
'c' : ['i love dog and cat !']
}).transpose()
# unique한 단어 집합을 생성
word_set = list(set(train_data.values.flatten()))
print(word_set)
# 단어 집합의 각 단어에 고유한 정수 인덱스를 매핑
vocab = {tkn : i+2 for i, tkn in enumerate(word_set)}
vocab['<unk>'] = 0 # 모르는 단어에 대한 토큰
vocab['<pad>'] = 1 # 시퀀스 길이를 맞추기 위한 패딩(padding) 토큰
- 토큰에는 <unk>나 <pad> 처럼
특별한 기능을 하는 토큰들이 있다. 이들을 Special Token 이라 부른다.
- 위의 코드 예제에서 <unk>는 train-dataset에 없는 단어 즉 모르는 단어를 나타내는 토큰이다.
- <pad>은 padding의 줄임말이다. 위의 예제에서 c문장은 느낌표까지 총 7개 단어이지만, a와 b문장은 4개 단어로 구성되어 있다. 위 데이터프레임을 토큰화하면 아래처럼 나온다.
[
[['hello'], ['i'], ['am'], ['teacher']],
[['hi'], ['glad'], ['to'], ['see']],
[['i'], ['love'], ['dog'], ['and'], ['cat'], ['!']]
]
- 모델에 이런 shape의 입력 데이터는 전달할 수 없으므로 a와 b문장의 토큰화 결과를 c의 길이에 맞춰줘야 한다. 이럴 때 <pad> 토큰을 사용하여 길이를 맞춰준다.
[
[['hello'], ['i'], ['am'], ['teacher'], ['<pad>'], ['<pad>'], ['<pad>']],
[['hi'], ['glad'], ['to'], ['see'], ['<pad>'], ['<pad>'], ['<pad>']],
[['i'], ['love'], ['dog'], ['and'], ['cat'], ['!']]
]
- 원하는 차원의 임베딩 레이어를 생성한다.
embedding_layer = nn.Embedding(num_embedding=(len(vocab)),
embedding_dim=3,
padding_idx=1) # <pad> token을 사용할 경우 <pad>의 정수 인덱스를 알려준다.
- embedding_layer의 output.shape은
(len(vocab), embedding_dim)
이 된다. - 모델을 훈련시킬 때, 입력 데이터를 먼저 tokenizing한 후 임베딩 레이어로 입력하게 된다.
🔸BertTokenizer 사용 코드
[ BertTokenizer의 주요 함수 ]
tokenize()
: 함수를 통해 문장에서 각 단어들을 하나의 토큰으로 만든다.convert_tokens_to_ids()
각 토큰들을 정수 인덱스와 매핑한다. 그러나 special-token을 자동으로 추가해주지 않는다.encode_plus()
: tokenizing과 integer encoding을 모두 수행해주는 함수이다. special-token도 자동으로 추가해준다.padding
: <pad> 토큰을 추가할 방식을 설정- 'longest' : tokenizer에 입력된 시퀀스 중 가장 긴 길이의 시퀀스를 기준으로 padding 추가
- 'max_length' :
max_length
파라미터에 설정한 값을 기준으로 padding 추가 (longest 파라미터도 설정해서 예제 코드를 실행해봤는데 그냥 max_length 설정하는 게 가장 간단하고 편한 것 같다.) - 'do_not_pad' : padding 미설정
max_length
: padding을 'max_length'로 설정한 경우 max_length 파라미터를 설정해줘야 한다.return_tensors
: 'pt'로 설정해야 반환값이 torch.tensor 타입으로 된다.
tokenize()함수과 convert_tokens_to_ids() 함수를 사용한 토큰화는 special_token을 자동으로 추가해주지 않기 때문에 코드에 대한 설명은 생략한다.
import pandas as pd
from transformers import BertTokenizer
train_data = pd.DataFrame({
'a' :['hello i am teacher'],
'b' :[ 'hi glad to see'],
'c' : ['i love dogs and cats!']
}).transpose()
texts = train_data.values.flatten()
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
tokenized_texts = [tokenizer.tokenize(text) for text in texts]
encoded_texts = [tokenizer.convert_tokens_to_ids(tokens) for tokens in tokenized_texts]
print(encoded_texts)
아마 실전에서는 encode_plus()
와 같이 special_token을 자동으로 추가해주는 함수를 더 많이 사용할 것 같다.
import pandas as pd
from transformers import BertTokenizer
train_data = pd.DataFrame({
'a' :['hello i not am teacher'],
'b' :['hi glad to see'],
'c' : ['i love dog !']
}).transpose()
texts = train_data.values.flatten()
### Tokenizing
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
max_len = max(len(text.split()) for text in texts)
### Mapping Integer Index
encoded_texts = [tokenizer.encode_plus(tokens,
add_special_token=True,
padding='max_length',
max_length=max_len+3, # special_token을 고려해 max_length를 넉넉하게 잡아준다.
return_tensors='pt') for text in texts]
input_ids = torch.cat([item['input_ids'] for item in encoded_texts], dim=0)
print(input_ids)
결과는 아래와 같다.
tensor([[ 101, 7592, 1045, 2025, 2572, 3836, 102, 0],
[ 101, 7632, 5580, 2000, 2156, 102, 0, 0],
[ 101, 1045, 2293, 3899, 999, 102, 0, 0]])
🔻BertTokenizer와 nn.Embedding을 활용한 간단한 예제
주어진 문장들을 2개의 class('positive','negative')로 분류하는 아주 쉬운 이진분류 예제를 통해 tokenizer와 embedding 방법을 익혀보고자 한다.
train 데이터로는 3개의 문장을 사용한다.
target class를 one-hot 인코딩했다는 가정 하에 negative는 [0,1], positive는 [1,0]이다.
[ Train Dataset ]
- 'hate bad sad angry !'
- 'hi glad to see'
- 'i love dog !'
[ Test Dataset ]
- 'it is bloody disgusting !'
- 'wow great job !'
- train 전체 코드
import pandas as pd
from transformers import BertTokenizer
train_data = pd.DataFrame({
'a' :['hate bad sad angry !'],
'b' :['hi glad to see'],
'c' : ['i love dog !']
}).transpose()
texts = train_data.values.flatten()
### Tokenizing
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
max_len = max(len(text.split()) for text in texts) + 4
### Mapping Integer Index
encoded_texts = [tokenizer.encode_plus(text,
padding='max_length',
max_length=max_len, # special_token을 고려해 max_length를 넉넉하게 잡아준다.
return_tensors='pt') for text in texts]
input_ids = torch.cat([item['input_ids'] for item in encoded_texts], dim=0)
### Define Model
class MyModel(nn.Module):
def __init__(self, max_len, num_embeddings, embedding_dim, pad_idx):
super(MyModel, self).__init__()
self.embedding = nn.Embedding(num_embeddings=num_embeddings,
embedding_dim=embedding_dim,
padding_idx=pad_idx)
nn.init.xavier_uniform_(self.embedding .weight) # initialize weights of Embedding layer
self.fc = nn.Linear(max_len*embedding_dim, 2)
self.softmax = nn.Softmax(dim=1) # row별로 softmax 계산
def forward(self, x):
outputs = self.embedding(x)
# outputs.size() = torch.size([입력시퀀스개수, max_len, embedding_dim])
outputs = torch.flatten(outputs,start_dim=1) # linear 레이어에 입력하기 위해 [입력시퀀스개수, max_len*embedding_dim]으로 flatten
outputs = self.fc(outputs)
outputs = self.softmax(outputs)
return outputs
model = MyModel(max_len=max_len,
num_embeddings=tokenizer.vocab_size,
embedding_dim=32,
pad_idx=tokenizer.pad_token_type_id)
# loss function & optimizer
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
### Train-Test dataset
X_train = input_ids
y_train = torch.tensor([[0,1],[1,0],[1,0]],dtype=torch.float) # 실제로는 여기에 target 데이터가 들어가야 함.
### train
epochs = 500
for epoch in range(epochs):
model.train()
optimizer.zero_grad()
outputs = model(X_train).squeeze()
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()
if epoch % 50 == 0 :
print(f"Epoch:{epoch}/{epochs}, Loss: {loss.item():.4f}")
- test 전체 코드
test_df = pd.DataFrame({
'a' :['it is bloody disgusting !'],
'b' :['wow great job !']
}).transpose()
tts = test_df.values.flatten()
encoded_test = [
tokenizer.encode_plus(text,
padding='max_length',
max_length=max_len, # special_token을 고려해 max_length를 넉넉하게 잡아준다.
return_tensors='pt') for text in tts
]
input_ids_test = torch.cat([item['input_ids'] for item in encoded_test], dim=0)
pred = model(input_ids_test)
print(pred)
간단한 예제라 데이터 수가 적어 train, test 문장을 바꿀 때마다 예측값이 천차만별이긴 하다.
여기까지 tokenizer와 embedding layer를 활용해 문자열을 임베딩하는 과정을 전체적으로 훑어보았다.
'데이터 > ML & DL' 카테고리의 다른 글
[CV] VGG19 fine-tuning - Image Classifiaction for chart, diagram, table (0) | 2024.07.30 |
---|---|
[교육] LG Aimers 5기 ML/DL 교육 (0) | 2024.07.03 |
[금융 데이터] 금융 데이터 분석 방법과 포트폴리오 (1) | 2024.04.01 |
[강화학습] Monte Carlo Method (0) | 2024.03.20 |
[강화학습] Markov Chain (Markov Decision Process) (0) | 2024.03.18 |