elevne's Study Note

Huggingface 공부 - (3-1: Huggingface 다중언어 개체명 인식) 본문

Machine Learning/NLP

Huggingface 공부 - (3-1: Huggingface 다중언어 개체명 인식)

elevne 2022. 12. 26. 22:50

지금껏 NLP 모델을 만드는 작업을 진행할 때, 항상 하나의 언어에 대한 데이터를 구한 후 이를 통해 전체 모델을 학습시키는 방법을 사용하였다. 하지만 하나의 문서가 여러 언어로 이루어져 있는 경우도 있을 것이다. 그러한 경우에는 다중 언어 트랜스포머 모델을 사용할 수 있다.

 

 

 

다중언어 트랜스포머 모델들은 BERT 와 같이 사전훈련 목표로 마스크트 언어 모델링을 사용하지만, 100 개 이상의 언어로 된 텍스트에서 동시에 훈련을 진행한다고 한다. 다중 언어 트랜스포머는 많은 언어로 된 대규모 말뭉치에서 사전 훈련하여 Zero-shot Cross-lingual transfer 이 가능하다고 한다. (한 언어에서 Fine Tuning 된 모델이 추가훈련 없이 다른 언어에 적용될 수 있다는 것.)

 

 

 

이번 실습에서는 XLM_RoBERTa 모델을 사용하여 개체명 인식(NER)을 수행하도록 여러 언어에서 Fine Tune 하는 방법에 대해 알아보았다.

 

 

 

NER(Named Entity Recognition) 이란 Text 내에서 사람, 위치, 조직 등과 같은 개체 명을 식별하는 NLP Subtask 중 하나로 다양한 Application 에서 사용되는데, 특히 검색 엔진의 품질을 높이거나 말뭉치에서 구조적인 데이터베이스를 만드는데 사용될 수 있다고 한다.

 

 

 

이번 실습에서 사용한 데이터셋은 WikiANN 또는 PAN-X 라고 불리는 교차 언어 전이 평가 벤치마크의 데이터이다. 우선 XTREME 내의 데이터들을 아래와 같은 코드로 확인해볼 수 있다.

 

 

 

from datasets import get_dataset_config_names

xtreme_subsets = get_dataset_config_names("xtreme")
print(f"XTREM 서브셋 개수: {len(xtreme_subsets)}")

 

 

result

 

 

panx_subsets = [s for s in xtreme_subsets if s.startswith("PAN")]
panx_subsets

 

 

result

 

 

 

XTREME 에는 총 183 개에 달하는 서브셋이 있는 것을 확인할 수 있으며, PAN 으로 또 확인해보았을 때에도 다양한 언어로 이루어진 데이터(뒤에 .de 등과 같이 접미사로 붙어있는 것이 언어 표시) 를 볼 수 있다. 이제 위 PAN 데이터들 중 원하는 데이터를 골라 load_dataset 함수를 이용하여 Dataset 을 불러올 수 있다. 실제 상황에서 흔히 있는, 불균형한 데이터셋의 경우를 가정하여 데이터셋을 불러와보았다. (이렇게 불균형한 데이터셋을 사용하여도 모든 언어에서 작동할 수 있는 언어모델을 만들 수 있다.)

 

 

 

from collections import defaultdict
from datasets import DatasetDict

langs = ["de", "fr", "it", "en", "ko"]
fracs = [0.7, 0.3, 0.3, 0.2, 0.1]
panx_ch = defaultdict(DatasetDict)

for lang, frac in zip(langs, fracs):
  ds = load_dataset("xtreme", name=f"PAN-X.{lang}")
  for split in ds:
    panx_ch[lang][split] = (
        ds[split].shuffle(seed=0).select(range(int(frac*ds[split].num_rows)))
    )

 

 

 

위에서 우선 collections 모듈의 defaultdict 라는 클래스가 사용되었는데, 이는 dictionary 내에서 찾을 수 없는 키에 대한 기본값을 가질 수 있게끔 한다. 다시 말해, 딕셔너리 내에 키가 존재하지 않더라도 에러를 발생시키지 않을 수 있다는 것이다. 아래와 같은 코드로 이에 대해 조금 더 알아보았다.

 

 

 

from collections import defaultdict

# Create a defaultdict with a default value of 0 for all keys
counts = defaultdict(int)

# Increment the count for the key 'apple'
counts['apple'] += 1
print(counts['apple'])  # Output: 1

# Increment the count for the key 'banana'
counts['banana'] += 1
print(counts['banana'])  # Output: 1

# The count for the key 'pear' is not set, so it will use the default value of 0
print(counts['pear'])  # Output: 0

 

 

다시 위 코드로 돌아가서 살펴보자면, 데이터셋에 의도하지 않은 편향이 들어가지 않도록 .shuffle 메서드가 사용되었고, select() 메서드를 통해서 fracs 값에 따라서 각 말뭉치의 개수를 다르게 다운로드 할 수 있게 하였다. (Dataset.num_rows 속성으로 train data 개수를 알 수 있다.) 데이터를 전부 불러온 후, pandas dataframe 을 활용하여 간단하게 각 언어별 데이터 수를 조회해보았다.

 

 

 

import pandas as pd

pd.DataFrame({lang : [panx_ch[lang]["train"].num_rows] for lang in langs}, index=["Number of training examples"])

 

 

result

 

 

 

이제 데이터가 실제로 어떻게 이루어져 있는지 확인해보기 위해, 아래 코드를 실행시켜 보았다.

 

 

 

element = panx_ch["ko"]["train"][0]
for key, value in element.items():
  print(f"{key} : {value}")

 

 

result

 

 

 

element.items() 메서드를 활용하여 내부 데이터를 쉽게 확인할 수 있다. 위 items 중에서 ner_tags 는 각 개체명이 매핑된 클래스 ID 에 해당하는 것인데, 숫자만 보면 이해하기 어려우니 아래와 같은 코드로 더 보기 좋게 바꿔줄 수 있다.

 

 

 

for key, value in panx_ch["ko"]["train"].features.items():
  print(f"{key}:{value}")

 

 

result

 

 

 

이번에는 .features.items() 메서드를 활용하여 위와 같은 데이터를 확인할 수 있었다. 

 

 

 

마지막으로 각 태그가 불균형하게 부여되지 않았나 확인해보기 위해 각 분할에서 개체명의 빈도를 아래와 같은 코드로 확인해볼 수 있었다.

 

 

 

from collections import Counter

split2freqs = defaultdict(Counter)
for split, dataset in panx_de.items():
  for row in dataset["ner_tags_str"]:
    for tag in row:
      if tag.startswith("B"):
        tag_type = tag.split("-")[1]
        split2freqs[split][tag_type] += 1
pd.DataFrame.from_dict(split2freqs, orient="index")

 

 

result

 

 

 

Counter 클래스는 list 나 iterable 개체 내에 빈도 수를 알아보기 위해 사용할 수 있다. 아래와 같은 코드로 특징을 더 확인해볼 수 있다.

 

 

 

from collections import Counter

# Create a Counter from a list of items
c = Counter(['apple', 'banana', 'apple', 'pear', 'apple'])

# Count the number of occurrences of each item
print(c)  # Output: Counter({'apple': 3, 'banana': 1, 'pear': 1})

# Get the most common items
print(c.most_common())  # Output: [('apple', 3), ('banana', 1), ('pear', 1)]

# Get the count for a specific item
print(c['apple'])  # Output: 3

# Increment the count for an item
c['apple'] += 1
print(c['apple'])  # Output: 4

# Set the count for an item to a specific value
c['banana'] = 10
print(c['banana'])  # Output: 10

 

 

 

 

Reference:

Natural Language Processing with Transformers