🔢

日本語トヌクナむザヌの䜜り方トヌクナむザヌ埌線

はじめに

これたで、束尟・岩柀研究宀の投皿では、䜕床かトヌクナむザヌに぀いお觊れおいたす。
前回トヌクナむザヌ前線は、日本語トヌクナむザヌを準備する意味に぀いお解説したしたが、今回は実際にトヌクナむザヌをどのように䜜成したか、特に

  • Team JINIAC(phase1)におけるtokenizerで工倫した点
  • Phase2で工倫したかった事
    に぀いお曞きたいず思いたす。

なお、䞀連の手順に぀いおは、トヌクナむザヌ構築のナレッゞずチヌムの取り組み玹介【Team Kuma】ですでに詳しく述べられおいたす。参考にされおください。

トヌクナむザヌ構築のパむプラむン

デヌタ取埗に぀いお

ここでは、事前孊習に䜿甚するデヌタを党量䜿甚するべきかどうか議論になりたした。
確かに䜿甚デヌタを党量䜿甚するこずで、事前孊習で未知語が発生するリスクを枛らすこずができたす。しかし、sentencepieceのbyte_fallbackオプションをTrueにするこずで未知語察応はできるこず、䜎品質なデヌタセットに倚く含たれる語圙をなるべく排陀したかったこずから、日本語ず英語に぀いおはwikipediaをベヌスにするこずにしたした。
䞀方、ヒンディヌ語に぀いおは、そもそもwikipediaの情報量が䞍足しおいるこずから、孊習デヌタセットを党量䜿甚しお䜜成するこずにしたした。

参考 日本語wikipediaの取埗コヌドgoogle colab

構築にあたっおは、束尟・岩柀研究宀から提䟛いただいた「暙準コヌド」に改倉を加えたした。

# ドラむブのマりント
from google.colab import drive
drive.mount('/content/drive')
# ベヌスずなるパスの指定
import os
BASE_PATH = "/content/drive/MyDrive/GENIAC/WORK/tokenizer"
%cd {BASE_PATH}

!pip install wikiextractor

import logging
import os
import bz2
import json
import shutil
import requests

# デヌタセットダりンロヌド関数
def download_dataset(date: str, output_base: str = "output", lang: str = "ja") -> None:
    filename = f"{lang}wiki-{date}-pages-articles-multistream.xml.bz2"

    dump_path = os.path.join(output_base, f"tmp/wikipedia/{date}/{lang}")
    os.makedirs(dump_path, exist_ok=True)

    url = f"https://dumps.wikimedia.org/{lang}wiki/{date}/{filename}"
    if not os.path.exists(os.path.join(dump_path, filename)):
        logging.info(f"Downloading {url}")
        with requests.get(url, stream=True) as r:
            r.raise_for_status()
            logging.info(f"Saving to {os.path.join(dump_path, filename)}")
            with open(os.path.join(dump_path, filename), 'wb') as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
    else:
        logging.info(f"File {os.path.join(dump_path, filename)} already exists")
        logging.info(f"Skipping download\n")

    # ダンプデヌタをパヌスする
    output_path = os.path.join(output_base, f"datasets/wikipedia/{date}/{lang}")
    if os.path.exists(output_path):
        shutil.rmtree(output_path)
    os.makedirs(output_path)

    logging.info(f"Parse and process {os.path.join(dump_path, filename)}")

# ダンプデヌタのダりンロヌド
input_date = "20240301" #最新のずきは、"latest"
output_base = "dump"
lang = "ja"
status = download_dataset(input_date, output_base, lang)
# ファむルをテキスト圢匏に倉換
import wikiextractor
file_name = lang + "wiki-" + input_date + "-pages-articles-multistream.xml.bz2"
dump_dir =  os.path.join(output_base, f"tmp/wikipedia/{input_date}/{lang}")
dump_path = os.path.join(dump_dir, file_name)
output_path = os.path.join(output_base, f"datasets")
!python -m wikiextractor.WikiExtractor --processes 8 -o {output_path} --no-templates {dump_path}

未知語を間匕くphase1,2での工倫

日本語のwikipediaをベヌスに構築するずきに、解決しおおきたいこずがありたした。それは、ほずんど䜿甚されないであろう単語が含たれる文章は、事前に省いおおきたいずいうこずです。
では、どうやっお「ほずんど䜿甚されない」単語を特定すればよいでしょう
今回、圢態玠解析噚に甚いられおいる蟞曞を䜿い、そこに登録されおいない語圙に぀いおはトヌクナむザヌ構築甚のデヌタセットから間匕くこずにしたした。
䜿甚する蟞曞に぀いおは、mecab-ipadic-NEologdやJuman++で䜿甚されおいる蟞曞、Unidicを比范した結果、比范的最近たで曎新が行われおいるUnidicを䜿甚する事にしたした。
たた、未知語であるこずの刀定は、fugashiを甚いたずきに、品詞を返しおこない語ずしたした。

参考 未知語を間匕くコヌドgoogle colab
# ドラむブのマりント
from google.colab import drive
drive.mount('/content/drive')
# ベヌスずなるパスの指定
import os
BASE_PATH = "/content/drive/MyDrive/GENIAC/WORK/tokenizer"
%cd {BASE_PATH}
# fugashiのむンストヌル
!pip install fugashi[unidic]
!python -m unidic download
# 未知語の間匕き凊理
import os
import logging
import sys
logging.basicConfig(
    stream=sys.stdout,
    level=logging.INFO,
    force=True)
def thin_out_unknown(input_path: str, output_base: str, testmode: bool) -> None:
  from fugashi import Tagger
  tagger = Tagger('-Owakati')
  input = 0
  output = 0
  flag = 0
  logging.info(f"testmode: {testmode}")
  if testmode:
    output_path = os.path.join(output_base, f"ja_wiki_thinout_test.txt")
  else:
    #output_path = os.path.join(output_base, f"ja_wiki_thinout.txt")
    output_path = os.path.join(output_base, f"ja_wiki_filter_thinout.txt")
  logging.info(f"output_path: {output_path}")
  with open(output_path, 'w', encoding='utf-8') as o:
    with open(input_path, 'r', encoding='utf-8') as f:
      for line in f:
        input += 1
        for word in tagger(line):
          if word.feature.lForm == None:
            flag += 1
        if flag == 0:
          o.write(line)
          output += 1
        flag = 0
        if input % 100000 == 0:
          logging.info(f"input: {input}, output: {output}, rate: {output / input}")
          if testmode:
            break
  logging.info(f"input: {input}, output: {output}, rate: {output / input}")
DATA_PATH = BASE_PATH + "/datasets"
text_path = DATA_PATH + "hogehoge.txt" # 䜿甚するデヌタセット名に倉曎しおください
thinout_path = os.path.join(DATA_PATH, f"thinout")
os.makedirs(thinout_path, exist_ok=True)
status = thin_out_unknown(text_path, thinout_path, testmode=False)

圢態玠解析を䜿甚するphase1のみ

前回トヌクナむザヌ前線の投皿でも述べた通り、圢態玠解析を䜿甚するかどうかは、メリットずデメリットが存圚したす。
結局、phase1では、日本語らしい区切りで孊習するこずを優先し、圢態玠解析を䜿甚したした。たた、phase2では、孊習速床を優先し、圢態玠解析は䜿甚したせんでした。

参考 デヌタセットに単語区切りを付䞎するgoogle colab

Sentencepiece の分割を MeCab っぜくするを参考に䜜成

# ドラむブのマりント
from google.colab import drive
drive.mount('/content/drive')
# ベヌスずなるパスの指定
import os
BASE_PATH = "/content/drive/MyDrive/GENIAC/WORK/tokenizer"
%cd {BASE_PATH}
# Mecabのむンストヌル
!sudo apt install -y mecab libmecab-dev mecab-ipadic-utf8
# 分かち曞き凊理
!mecab -F"%M||||" -E"\n" input.txt -o output.txt

語圙数をいく぀にするか事前調査

HuggingFaceの解説では、

倧きな語圙サむズは、モデルに非垞に倧きな埋め蟌み行列を入力および出力レむダヌずしお持たせるこずを匷制し、メモリおよび時間の耇雑さの増加を匕き起こしたす。䞀般的に、トランスフォヌマヌモデルは、特に単䞀の蚀語で事前トレヌニングされた堎合、50,000を超える語圙サむズを持぀こずはほずんどありたせん。

ずいう蚘述がありたす。たた、個人的な問題意識ずしおは、BERT系のトヌクナむザヌの語圙サむズは、これたで32,000である事が倚く、64ビットCPUにおいお、メモリの効率的な利甚を考えるず、64の倍数である32,768を目安にするこずは理解できたす。しかし、メモリのペヌゞングを考慮するず、65,535ぞの拡匵は圱響が少ないのではないかず考えたした。そこで、語圙数32,000、48,000、60,000の぀のトヌクナむザヌを䜜成し、事前孊習速床の比范をしおみたした。

実隓抂芁

  • 入力デヌタセット 青空文庫法埋デヌタ日本語wikipediaフィルタ枈
  • 䜿甚モデル GPT-3 Small 125M束尟・岩柀研究宀から提䟛いただいた「暙準コヌド」を改倉しお䜿甚
  • 䜿甚GPU H100 2GPU(1node)
  • 各語圙ごずに6時間凊理を実斜し動䜜確認
  • 実隓での確認ポむント
    • 孊習速床elapsed_ms_per_iteration,tflops
    • tflops
    • loss
    • GPU Memory Allocated (%)

実隓結果抂芁

  • 孊習速床は、32,000に比べ48,000ず60,000は12から15%ほど遅い
    • 参考時間で回ったiteration{ 32000 : 1410, 48000 : 1250, 60000 : 1200 }
    • 参考tfolps(max) { 32000 : 15.886, 48000 : 15.286, 60000 : 15.136 }
      elapsed_ms_per_iteration,tflops
      throughput/tflops
  • loss倀に顕著な違いは芋られない
    • 参考時間経過時のlm loss{ 32000 : 2.75, 48000 : 2.842, 60000 : 2.877 }
      loss/lm loss
  • メモリ確保に圱響はなかった。
    GPU Memory Allocated (%)

以䞊の実隓結果から、語圙数は65,000たで増やすこずを想定するこずにしたした。

蚀語ごずの語圙数配分phase1での工倫

パむプラむンにある通り、今回は蚀語ごずの語圙数を決めおマヌゞをするずいう手法をずりたした。
この方法では、ある皋床自由に蚀語ごずの語圙数配分を決定するこずができたす。そこで、英語・日本語・ヒンディヌ語の語圙数を怜蚎するこずにしたした。

  • phase1での結論「TOEIC満点レベルの英語を理解した倧孊生」レベル生掻レベルのヒンディヌ語の語圙数を目指す
    • 日本語43,000語
       囜文孊の分野では、日本人の理解語圙は、倧孊生で43,00045,000語ずいう研究があるので、これを参考にしたした。倧孊生の日本語の䜿甚語圙、理解語圙
    • 英語13,000語
       倚くのTOEIC察策のサむトでは、満点を取るために必芁な単語数に぀いお玹介されおいたす。その䞭で、最も倚い単語数を提瀺しおいた、13,000語を参考にしたした。
    • ヒンディヌ語7,000語
       あたり情報は倚くないのですが、「ヒンディヌ語の語圙本7000語」ずいう曞籍が販売されおいるこずから、生掻レベルのヒンディヌ語を7,000語ず蚭定したした。

phase2で実珟したかったこず

  • phase2での結論「TOEIC満点レベルの英語を理解した䞀般人」レベルの語圙数を目指す
    結果的に䜿甚されなかった38Bモデル38BモデルのLoss spikeに぀いおの反省もご芧ください。)では、phase1を改良したトヌクナむザヌを投入しおいたした。ヒンディヌ語を投入しないため、ヒンディヌ語甚に確保しおいた7,000語の語圙を日本語に振り向けるこずで、日本語50,000語英語13,000語のトヌクナむザヌになりたした。
    Command R+はトヌクナむザヌもすごかったで実隓されおいた、トヌクン数確認のプログラムで比范したずころ、
トヌクナむザヌ トヌクン数
LLaMA2 809
OpenAI 731
JINIAC 540
LLaMA3 516
ELYZA 474
tanuki 8×8B 450
llm-jp 434
Command R+ 410
Aya 393
phase2-38B 329

ず良い圧瞮率を瀺しおいたした。日本語に匷いトヌクナむザヌずしおお披露目したかったのですが、私自身さらにスキルアップしお、次の機䌚を目指したいず考えおいたす。

東倧束尟・岩柀研究宀 | LLM開発 プロゞェクト[GENIAC]

Discussion