🐈

LLM データセット向け 日本語文章の繰り返しを判定するメモ

2023/09/16に公開

背景

LLM 向けの高品質なデータセットを web などから構築したい.
繰り返しのある文章を取り除きたい!

ぺろっとやってくれるライブラリは無いっぽ?

https://github.com/shjwudp/c4-dataset-script/blob/master/c4_dataset_script/Chinese/repetition_removal.py

を参考に作ります!

しくみ

大まかには

  • 入力文をトークナイズ(日本語だと分かち書きでとりま事足りるでしょうか)
  • n-gram 作る
  • ハッシュ計算
  • 判定

のようです.

詳細は

Scaling Language Models: Methods, Analysis & Insights from Training Gopher
https://arxiv.org/abs/2112.11446

を読んでね.

実装

とりま Fugashi で分かち書きして処理するようにしてみました.

# based on https://github.com/shjwudp/c4-dataset-script

import argparse
import nltk
import hashlib
import os
import json

#from pyspark.sql import SparkSession
from fugashi import Tagger
import fugashi

tagger = Tagger('-Owakati')


def parse_args():
    parser = argparse.ArgumentParser("Filter out bad docs.")
    #parser.add_argument("--output_bad_docs", default="bad_docs.jsonl.zst",
    #    help="output file for bad lines")

    args = parser.parse_args()

    return args


def hash_text(text):
    return hashlib.md5(text.encode("utf-8")).hexdigest()


def is_repetition_removal(
    text, duplicate_line_fraction=0.3, duplicate_line_character_faction=0.2
):
    """Check if there is repeated content in the input text. Excessive
    repetition is often linked with uninformative content and can be used to
    determine whether it is low-quality text. This function implements
    "Repetition Removal" as described in Gopher_.

    .. _Gopher: https://arxiv.org/abs/2112.11446

    Args:
        text (str): input text.
        duplicate_line_fraction (float, optional): Duplicate row deduplication
            threshold. Defaults to 0.3.
        duplicate_line_character_faction (float, optional): Threshold for the
            proportion of repeated line characters. Defaults to 0.2.

    Returns:
        bool: If there is repeated content in the input text.
    """
    line_count = 0
    dup_line = 0
    dup_line_chars = 0
    visit_lines = {}
    for line in text.split("\n"):
        line_hash = hash_text(line)
        if line_hash in visit_lines:
            dup_line += 1
            dup_line_chars += len(line)
        visit_lines[line_hash] = True

        line_count += 1

    if float(dup_line) / line_count > duplicate_line_fraction:
        return True

    if float(dup_line_chars) / len(text) > duplicate_line_character_faction:
        return True

    top_ngram_character_fractions = [
        (2, 0.2),
        (3, 0.18),
        (4, 0.16),
    ]
    for ngram, threshold in top_ngram_character_fractions:
        #word_list = list(jieba.cut(text))
        # wakachi-gaki
        word_list = tagger.parse(text).split()
        bgs = nltk.ngrams(word_list, ngram)
        fdist = nltk.FreqDist(bgs)
        for word_list, repeat in fdist.items():
            char_count = sum([len(word) for word in word_list])
            if char_count * (repeat - 1) / len(text) > threshold:
                return True

    duplicate_ngram_character_fractions = [
        (5, 0.15),
        (6, 0.14),
        (7, 0.13),
        (8, 0.12),
        (9, 0.11),
        (10, 0.10),
    ]
    for ngram, threshold in duplicate_ngram_character_fractions:
        fdist = {}
        word_list = tagger.parse(text).split()
        mark = [0] * len(word_list)
        for i in range(len(word_list) - ngram + 1):
            bag = tuple(word_list[i: i + ngram])
            if bag in fdist:
                for j in range(i, i + ngram):
                    mark[j] = len(word_list[j])
                fdist[bag] += 1
            else:
                fdist[bag] = 1

        if sum(mark) / float(len(text)) > threshold:
            return True

    return False


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True)
    #parser.add_argument("--output", default="./rr_output")
    args = parser.parse_args()

    # assume plain-text
    with open(args.input, 'r') as f:
        lines = f.readlines()

        for line in lines:
            print(line)
            print('repeat', is_repetition_removal(line))


if __name__ == "__main__":
    main()

ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.\nダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.\nダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.\nダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.\nダミーテキストです.ダミーテキストです. ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.ダミーテキストです.
=> repeat True


こんにちは, こんにちの天気はこんにちです.
=> repeat False


こんにちは, こんにちの天気は晴れです.
=> repeat False


無駄無駄無駄無駄ァ
=> repeat True

Voila!

↑の ダミーテキストです は実際に OSCAR2301 のデータセットにあったものです.

TODO

  • 文が重複している場合は, ひとつだけ文を残すようにしてみる.
  • Rinna トークナイザなど使ってみる.
  • 日本語向けにちょうどよい threshold を求めてみる.
  • blog フッターなど, web データセットの中には日付の羅列などもあったりします.

2020 年 1 月 2020 年 2 月 ...

数字はゼロにするなどして判定できるようにするとさらによいでしょうか.

Discussion