vectorizer = TfidfVectorizer(
tokenizer=tokenizer_func,
ngram_range=(1,2),
# max_features = 50000
)
vectorizer.fit(corpus)
sp_matrix = vectorizer.transform(corpus) # (num_passage, num_vocab)
ngram_range
default는 (1, 1)로 uni-gram만 사용
(1, 1)로 설정했을 땐 num_vocab이 26,797이며,
(1, 2)로 설정하면 684,272로 훨씬 증가
max_features
default는 None으로 모든 feature가 사용
숫자를 지정해주면 (num_passage, num_vocab) → (num_passages, max_features)
fit & transform
sp_matrix = vectorizer.fit_transform(corpus) 로 위 두 줄의 코드와 동일하게 가능query_vec = vectorizer.transform([query]) # (num_sentence, num_vocab)
{'text':
'이 문서는 나라 목록이며, 전 세계 206개 나라의 각 현황과 주권 승인 정보를 개요 형태로 나열하고 있다.
\n\n이 목록은 명료화를 위해 두 부분으로 나뉘어 있다.
\n\n# 첫 번째 부분은 바티칸 시국과 팔레스타인을 포함하여 유엔 등 국제 기구에 가입되어 국제적인 승인을 널리 받았다고 여기는 195개 나라를 나열하고 있다.
\n# 두 번째 부분은 일부 지역의 주권을 사실상 (데 팍토) 행사하고 있지만, 아직 국제적인 승인을 널리 받지 않았다고 여기는 11개 나라를 나열하고 있다.
\n\n두 목록은 모두 가나다 순이다.
\n\n일부 국가의 경우 국가로서의 자격에 논쟁의 여부가 있으며, 이 때문에 이러한 목록을 엮는 것은 매우 어렵고 논란이 생길 수 있는 과정이다.
이 목록을 구성하고 있는 국가를 선정하는 기준에 대한 정보는 "포함 기준" 단락을 통해 설명하였다. 나라에 대한 일반적인 정보는 "국가" 문서에서 설명하고 있다.',
'corpus_source': '위키피디아',
'url': 'TODO',
'domain': None,
'title': '나라 목록',
'author': None,
'html': None,
'document_id': 0}
Flow (데이터가 어떻게 들어가서 어떤 흐름으로 학습이 되는가?)

training_dataset은 question, context, answer이 포함된 dictionary
이를 TensorDataset을 이용해 passage와 query의 tokenize 결과를 모두 저장

passage encoder와 query encoder를 따로 생성
BertModelForSequenceClassification이 아닌, BertModel을 이용
del p_inputs, q_inputs
변수 자체를 삭제
반복문을 돌면서 새로 할당이 되는데 왜 삭제? 메모리 이슈?
Positive example과 Negative example을 합쳐서 학습 데이터셋을 구성합니다.
이 때 Negative example의 비율은 일반적으로 Positive example에 비해 매우 높게 설정됩니다.
이렇게 구성된 학습 데이터셋은 모델이 관련성을 판별하는 데 필요한 문맥과
관련 없는 문서를 구별하는 능력을 향상시킵니다.
# 20개의 sample 사용
sample_idx = np.random.choice(range(len(dataset['train'])), 20)
training_dataset = dataset['train'][sample_idx]
num_neg = 3 # 사용할 negative sample 개수
corpus = np.array(corpus)
p_with_neg = []
for c in training_dataset['context']:
while True:
neg_idxs = np.random.randint(len(corpus), size=num_neg)
if not c in corpus[neg_idxs]:
p_neg = corpus[neg_idxs]
p_with_neg.append(c)
p_with_neg.extend(p_neg)
break
20개의 query와, 80개의 passage_with_neg 사용
한 개의 positive passage와 num_neg의 negative passage
p_with_neg[0, 1, 2, 3] 는 0번 query에 대한 passage,
p_with_neg[4, 5, 6, 7] 은 1번 query에 대한 passage, …
from torch.utils.data import (DataLoader, RandomSampler, TensorDataset)
q_seqs = tokenizer(training_dataset['question'], padding="max_length", truncation=True, return_tensors='pt')
p_seqs = tokenizer(p_with_neg, padding="max_length", truncation=True, return_tensors='pt')
max_len = p_seqs['input_ids'].size(-1) # 512
p_seqs['input_ids'] = p_seqs['input_ids'].view(-1, num_neg+1, max_len)
p_seqs['attention_mask'] = p_seqs['attention_mask'].view(-1, num_neg+1, max_len)
p_seqs['token_type_ids'] = p_seqs['token_type_ids'].view(-1, num_neg+1, max_len)
q_seqs의 size는 [20, 512] = [num_example, max_len]
p_seqs의 size는 [20, 4, 512] = [num_example, pos+neg, max_len]
targets = torch.zeros(args.per_device_train_batch_size).long()
positive example은 모두 0번째에 위치하므로 torch.zeros(batch_size)
nll 손실함수를 이해할 필요가 있음 (아래)
p_outputs = p_model(**p_inputs) # (batch_size*(num_neg+1), emb_dim)
q_outputs = q_model(**q_inputs) # (batch_size*, emb_dim)
# Calculate similarity score & loss
p_outputs = p_outputs.view(args.per_device_train_batch_size, -1, num_neg+1)
# [batch_size, hidden_size, pos+neg]
q_outputs = q_outputs.view(args.per_device_train_batch_size, 1, -1)
# [batch_size, 1(num_query), hidden_size]
sim_scores = torch.bmm(q_outputs, p_outputs).squeeze()
# [batch_size, 1, pos+neg] ==squeeze==> [batch_size, pos+neg]
sim_scores = sim_scores.view(args.per_device_train_batch_size, -1)
# 위 코드와 size가 동일한데 왜 하는지 잘 모르겠음
sim_scores = F.log_softmax(sim_scores, dim=1)
loss = F.nll_loss(sim_scores, targets)
loss.backward()
optimizer.step()
scheduler.step()
q_model.zero_grad()
p_model.zero_grad()
torch.bmm
bmm = batch matrix multiplication
bmm( [B, N, M] * [B, M, P] ) ⇒ [B, N, P]
loss = F.nll_loss(sim_scores, targets)
sim_scores
# sim_scores
tensor([[-4.8033, -0.3004, -1.8040, -2.4461],
[-7.4634, -0.3286, -2.2671, -1.7379]]
# loss
tensor(4.3046, grad_fn=<NllLossBackward0>)
Log softmax 결과 값인 [-4.8033, -0.3004, -1.8040, -2.4461]은 다음과 같이 해석할 수 있습니다.
주어진 결과 값 [-4.8033, -0.3004, -1.8040, -2.4461]는 각 클래스에 속할 확률에 대한 로그값을 나타냅니다.
주어진 결과 값을 exponential 함수에 적용하여 원래의 확률 값을 얻습니다.
[exp(-4.8033), exp(-0.3004), exp(-1.8040), exp(-2.4461)] ≈ [0.008, 0.741, 0.164, 0.086]
얻어진 값들을 총 합이 1이 되도록 정규화(normalize)합니다.
[0.008, 0.741, 0.164, 0.086] / (0.008 + 0.741 + 0.164 + 0.086) ≈ [0.008, 0.679, 0.150, 0.079]
해석 결과, 주어진 값 [-4.8033, -0.3004, -1.8040, -2.4461]의
로그 softmax 결과는 다음과 같이 해석할 수 있습니다:
첫 번째 클래스: 약 0.8%의 확률
두 번째 클래스: 약 67.9%의 확률
세 번째 클래스: 약 15.0%의 확률
네 번째 클래스: 약 7.9%의 확률
nll loss function
Negative log likelihood 손실 함수는 다음과 같이 계산됩니다:
L = -log(p),
여기서,
L: 손실 값
p: 실제 타깃 값에 해당하는 클래스의 확률 값
실제 타깃 값이 0인 경우, 해당 클래스의 확률 값은 0.008입니다.
따라서, negative log likelihood 손실 함수를 적용하면 다음과 같이 계산됩니다:
L = -log(0.008) ≈ 4.828
nll의 default reduction은 mean으로, batch size가 2이므로 각각 로그 우도를 구한 뒤 평균
즉, target은 항상 0번 째 index이므로 target=torch.zeros 로 선언하고,
그 0번 째 index를 nll 손실 함수에서 사용
※ 모든 이미지 및 코드 출처는 네이버 커넥트재단 부스트캠프 AI Tech 5기입니다. ※