관리 메뉴

개발자의 스터디 노트

MLP로 성씨 분류하기 (3) : 평가, 추론, 분석 하기 본문

파이썬/파이토치 자연어처리

MLP로 성씨 분류하기 (3) : 평가, 추론, 분석 하기

박개발씨 2022. 2. 19. 00:24

MLP로 학습한 모델을 평가해보도록 하겠습니다. 평가는 테스트 데이터 셋으로 진행합니다. 훈련 데이터의 정확도가 테스트 데이터의 정확도보다 높게 나옴을 알 수 있습니다. 모델은 항상 훈련하는 데이터에서 더 높은 성능이 나옵니다. 따라서 훈련 세트의 성능이 새로운 데이터에도 적용된다고 생각해서는 안됩니다.

 

1. 테스트 셋 데이터에서 평가하기

# 가장 좋은 모델을 사용해 테스트 세트의 손실과 정확도를 계산합니다
classifier.load_state_dict(torch.load(train_state['model_filename']))

classifier = classifier.to(args.device)
dataset.class_weights = dataset.class_weights.to(args.device)
loss_func = nn.CrossEntropyLoss(dataset.class_weights)

dataset.set_split('test')
batch_generator = generate_batches(dataset, 
                                   batch_size=args.batch_size, 
                                   device=args.device)
running_loss = 0.
running_acc = 0.
classifier.eval()

for batch_index, batch_dict in enumerate(batch_generator):
    # 출력을 계산합니다
    y_pred =  classifier(batch_dict['x_surname'])
    
    # 손실을 계산합니다
    loss = loss_func(y_pred, batch_dict['y_nationality'])
    loss_t = loss.item()
    running_loss += (loss_t - running_loss) / (batch_index + 1)

    # 정확도를 계산합니다
    acc_t = compute_accuracy(y_pred, batch_dict['y_nationality'])
    running_acc += (acc_t - running_acc) / (batch_index + 1)

train_state['test_loss'] = running_loss
train_state['test_acc'] = running_acc
print("테스트 손실: {};".format(train_state['test_loss']))
print("테스트 정확도: {}".format(train_state['test_acc']))
테스트 손실: 1.7831668853759768;
테스트 정확도: 46.31249999999999

 

 

2. 새로운 성씨 분류하기

새로운 성씨를 분류하는 코드입니다. 문자열로 성씨를 전달하면 이 함수는 먼저 벡터화 과정을 적용한 다음 모델 예측을 만듭니다. apply_softmax 플래그를 True로 설정해서 result에 확률을 담았습니다. 다중 분류임으로 모델 예측은 클래스 확률의 리스트입니다. 파이토치 텐서의 max() 메서를 사용해 확률이 가장 높은 클래스를 선택합니다.

def predict_nationality(surname, classifier, vectorizer):
    """새로운 성씨로 국적 예측하기
    
    매개변수:
        surname (str): 분류할 성씨
        classifier (SurnameClassifer): 분류기 객체
        vectorizer (SurnameVectorizer): SurnameVectorizer 객체
    반환값:
        가장 가능성이 높은 국적과 확률로 구성된 딕셔너리
    """
    vectorized_surname = vectorizer.vectorize(surname)
    vectorized_surname = torch.tensor(vectorized_surname).view(1, -1)
    result = classifier(vectorized_surname, apply_softmax=True)

    probability_values, indices = result.max(dim=1)
    index = indices.item()

    predicted_nationality = vectorizer.nationality_vocab.lookup_index(index)
    probability_value = probability_values.item()

    return {'nationality': predicted_nationality, 'probability': probability_value}
new_surname = input("분류하려는 성씨를 입력하세요: ")
classifier = classifier.to("cpu")
prediction = predict_nationality(new_surname, classifier, vectorizer)
print("{} -> {} (p={:0.2f})".format(new_surname,
                                    prediction['nationality'],
                                    prediction['probability']))
분류하려는 성씨를 입력하세요: McMahan
McMahan -> Irish (p=0.43)

 

 

3. 새로운 성씨에 대해 최상위 k 개 예측 만들기

def predict_topk_nationality(name, classifier, vectorizer, k=5):
    """새로운 성씨에 대한 최상위 K개 국적을 예측합니다
    
    매개변수:
        surname (str): 분류하려는 성씨
        classifier (SurnameClassifer): 분류기 객체
        vectorizer (SurnameVectorizer): SurnameVectorizer 객체
        k (int): the number of top nationalities to return
    반환값:
        딕셔너리 리스트, 각 딕셔너리는 국적과 확률로 구성됩니다.
    """
    vectorized_name = vectorizer.vectorize(name)
    vectorized_name = torch.tensor(vectorized_name).view(1, -1)
    prediction_vector = classifier(vectorized_name, apply_softmax=True)
    probability_values, indices = torch.topk(prediction_vector, k=k)
    
    # 반환되는 크기는 (1,k)입니다
    probability_values = probability_values.detach().numpy()[0]
    indices = indices.detach().numpy()[0]
    
    results = []
    for prob_value, index in zip(probability_values, indices):
        nationality = vectorizer.nationality_vocab.lookup_index(index)
        results.append({'nationality': nationality, 
                        'probability': prob_value})
    
    return results


new_surname = input("분류하려는 성씨를 입력하세요: ")
classifier = classifier.to("cpu")

k = int(input("얼마나 많은 예측을 보고 싶나요? "))
if k > len(vectorizer.nationality_vocab):
    print("앗! 전체 국적 개수보다 큰 값을 입력했습니다. 모든 국적에 대한 예측을 반환합니다. :)")
    k = len(vectorizer.nationality_vocab)
    
predictions = predict_topk_nationality(new_surname, classifier, vectorizer, k=k)

print("최상위 {}개 예측:".format(k))
print("===================")
for prediction in predictions:
    print("{} -> {} (p={:0.2f})".format(new_surname,
                                        prediction['nationality'],
                                        prediction['probability']))
분류하려는 성씨를 입력하세요: McMahan
얼마나 많은 예측을 보고 싶나요? 5
최상위 5개 예측:
===================
McMahan -> Irish (p=0.43)
McMahan -> Scottish (p=0.24)
McMahan -> Czech (p=0.07)
McMahan -> Vietnamese (p=0.07)
McMahan -> German (p=0.05)

 

 

최상위 예측을 여러 개 확인하면 종종 도움이 됩니다. 예를 들어 NLP에는 최상위 예측 k 개를 선택한 다음 다른 모델을 사용해 순위를 다시 매기는 관행이 있습니다. 파이토치는 이런 예측을 쉽게 얻도록 해주는 torch.topk() 함수를 제공합니다.