1. はじめに
多言語のデータセットを扱うとき、英語以外のデータセットには大体英語が混ざっているじゃないですか。
その影響を排除したい、でも目で見て判断するのは現実的じゃない・・・。
そこで「この文章って何語なの?」を判断してくれる Language Identification のモデルですぐに使えるものを比較します。
また、文章で学習されているモデルではありますが、単語単位でも精度が出るのかも検証します。
2. バージョンの確認
# Python
python==3.10.5
# Language Identification
fasttext-langdetect==1.0.5
langdetect==1.0.9
langid==1.1.6
stanza==1.4.2
transformers==4.26.1
# 前処理
ginza==5.1.2
ja-ginza==5.1.2
underthesea==6.2.0
3. 使用するライブラリ
3.1 langdetect
このライブラリ自体は2021年に公開されていますが、2014年の Java 実装を Python に移植したもののようです。
今回使用する5言語のうち、エスペラント語を除く4言語に対応しています。
import langdetect
def identify_by_langdetect(data: list[str]) -> list[str]:
return [langdetect.detect(item) for item in data]
3.2 langid
2014年に公開されたライブラリです。
今回使用する5言語すべてに対応しています。
import langid
def identify_by_langid(data: list[str]) -> list[str]:
return [langid.classify(item)[0] for item in data]
3.3 fasttext-langdetect
fastText の Language Identification 部分のラッパーです。
このライブラリは2023年に公開されていますが、 fastText 自体の公開は2016年です。
今回使用する5言語すべてに対応しています。
import ftlangdetect
def identify_by_fasttext(data: list[str]) -> list[str]:
return [ftlangdetect.detect(item, low_memory=False)['lang'] for item in data]
3.4 stanza
この機能の実装がいつかは分かりませんが、 stanza 自体は2020年に公開されています。
今回使用する5言語のうち、エスペラント語を除く4言語に対応しています。
from stanza.models.common.doc import Document
from stanza.pipeline.core import Pipeline
nlp_stanza = Pipeline(lang='multilingual', processors='langid')
def identify_by_stanza(data: list[str]) -> list[str]:
docs = [Document([], text=item) for item in data]
nlp_stanza(docs)
return [doc.lang for doc in docs]
3.5 xlm-roberta-base-language-detection
HuggingFace の xlm-roberta-base を fine-tuning したものです。
2021年に公開されています。
今回使用する5言語のうち、エスペラント語を除く4言語に対応しています。
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer_papluca = AutoTokenizer.from_pretrained('papluca/xlm-roberta-base-language-detection')
model_papluca = AutoModelForSequenceClassification.from_pretrained('papluca/xlm-roberta-base-language-detection')
def identify_by_papluca(data: list[str]) -> list[str]:
encoded_input = tokenizer_papluca(data, padding=True, truncation=True, max_length=128, return_tensors='pt')
logits = model_papluca(**encoded_input).logits.detach().numpy()
return [model_papluca.config.id2label[id_] for id_ in logits.argmax(1)]
3.6 xlm-roberta-base-finetuned-language-detection-new
上とほぼ同じに見えますが、こちらは2022年に公開されたものです。
今回使用する5言語のうち、エスペラント語を除く4言語に対応しています。
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer_dinalzein = AutoTokenizer.from_pretrained('dinalzein/xlm-roberta-base-finetuned-language-identification')
model_dinalzein = AutoModelForSequenceClassification.from_pretrained('dinalzein/xlm-roberta-base-finetuned-language-identification')
def identify_by_dinalzein(data: list[str]) -> list[str]:
encoded_input = tokenizer_dinalzein(data, padding=True, truncation=True, max_length=128, return_tensors='pt')
logits = model_dinalzein(**encoded_input).logits.detach().numpy()
return [model_dinalzein.config.id2label[id_] for id_ in logits.argmax(1)]
4. 検証方法
今回は、日本語、英語、ドイツ語、エスペラント語、ベトナム語の5言語について検証を行います。
データは Tatoeba から取得して、文章・単語それぞれランダムに100個抽出し、正解率を求めます。これを100回繰り返して、その平均と標準偏差を計算します。
import random
from collections.abc import Generator
from pathlib import Path
from typing import Literal, Optional
import underthesea
import spacy
nlp_ja = spacy.load('ja_ginza', enable=[''])
def tokenize(sent: str,
lang: str
) -> list[str]:
"""
言語に合わせて文章を単語に分ける。
"""
match lang:
case 'ja':
return [token.text for token in nlp_ja(sent)]
case 'vi':
return underthesea.word_tokenize(sent)
case _:
return sent.split(' ')
def extract_words(sents: list[str],
lang: Literal['de', 'en', 'eo', 'ja', 'vi']
) -> Generator[str, None, None]:
"""
文章群から単語を抽出する。
"""
for sent in sents:
for word in tokenize(sent, lang):
if not word.isalpha():
continue
yield word
def sample_sentences(data_path: Path,
num_sampling: Optional[int] = 100,
lower: bool = True,
) -> list[str]:
"""
指定した数の文章をサンプリングする。
"""
lines = Path(data_path).read_text(encoding='utf-8').splitlines()
sents = [
sent.lower() if lower else sent
for line in lines
if (sent := line.split('\t')[2])
]
random.shuffle(sents)
return sents if num_sampling is None else sents[:num_sampling]
def sample_words(data_path: Path,
lang: Literal['de', 'en', 'eo', 'ja', 'vi'],
num_sampling: Optional[int] = 100,
lower: bool = True,
) -> list[str]:
"""
指定した数の単語をサンプリングする。
"""
sents = sample_sentences(data_path, num_sampling=None, lower=lower)
words = set()
for word in extract_words(sents, lang):
words.add(word)
if len(words) == num_sampling:
break
return list(words)
5. 検証結果
5-1. 文章単位
まずは文章の言語を判別してみた結果です。
(文章単位) | de | en | eo | ja | vi |
---|---|---|---|---|---|
langdetect | 0.9131 (0.0270) | 0.9046 (0.0257) | - | 0.9989 (0.0031) | 0.9992 (0.0027) |
langid | 0.9713 (0.0171) | 0.9677 (0.0168) | 0.8525 (0.0351) | 0.9990 (0.0030) | 0.9980 (0.0047) |
fasttext | 0.9940 (0.0083) | 0.9974 (0.0046) | 0.9970 (0.0056) | 0.9995 (0.0022) | 0.9965 (0.0061) |
stanza | 0.9545 (0.0216) | 0.9925 (0.0083) | - | 0.9993 (0.0026) | 0.9996 (0.0020) |
papluca | 0.9908 (0.0099) | 0.9858 (0.0116) | - | 0.9991 (0.0032) | 0.9974 (0.0046) |
dinalzein | 0.9868 (0.0116) | 0.9623 (0.0206) | - | 0.9993 (0.0026) | 0.9995 (0.0022) |
全体的に精度は高く、特にfastText
はベトナム語以外で一位になっています。
エラーを確認すると、
- ドイツ語は英語と誤判定されることが多いが、その他ヨーロッパ言語の場合もある。
- 英語はスペイン語やイタリア語と誤判定されることが多い。
- エスペラント語もスペイン語やイタリア語と誤判定されることが多い。
- 日本語は中国語だと誤判定されることが多い。
- ベトナム語は色々な言語に誤判定される。
5-2. 単語単位
次に単語の言語を判別してみた結果です。
ちなみに、日本語をlangdetect
で判別しようとしたところ一部(15/100)エラーが出たため、その分は除外して計算しています。
(単語単位) | de | en | eo | ja | vi |
---|---|---|---|---|---|
langdetect | 0.4063 (0.0417) | 0.2568 (0.0372) | - | 0.6788 (0.0404) | 0.8645 (0.0276) |
langid | 0.5059 (0.0445) | 0.9278 (0.0265) | 0.0622 (0.0221) | 0.6901 (0.0395) | 0.6834 (0.0362) |
fasttext | 0.7829 (0.0381) | 0.8932 (0.0282) | 0.6655 (0.0377) | 0.8634 (0.0344) | 0.7954 (0.0300) |
stanza | 0.5916 (0.0441) | 0.4879 (0.0417) | - | 0.8447 (0.0342) | 0.7791 (0.0337) |
papluca | 0.6622 (0.0400) | 0.7556 (0.0331) | - | 0.3976 (0.0454) | 0.4152 (0.0368) |
dinalzein | 0.7122 (0.0351) | 0.4215 (0.0439) | - | 0.7459 (0.0416) | 0.8716 (0.0280) |
やはり文章単位と比べるとかなり精度が下がりました。
こちらもベトナム語以外ではfastText
の精度が最も高く、ベトナム語はdinalzein
さんのモデルが一位でした。
エラーを確認すると、
- ドイツ語、英語、エスペラント語、ベトナム語は色々な言語に誤判定される。
- 日本語は中国語の誤判定が多いが、それ以外の言語にも誤判定される。
6. 終わりに
XLM-RoBERTa
なども検証しましたが、全体としてfastText
が最も精度が高い、という結果になりました。
今回は最も確率の高い出力のみを使って正解率を出しましたが、例えばエスペラント語の単語はもともとロマンス系の言語に似ているので、もう少し幅を持って(出力の上位〇位以内とか上位〇%とか)検証すると、また新しい発見があるかもしれないです。