データナード

機械学習と自然言語処理についての備忘録 (旧ナード戦隊データマン)

langstat_generatorの高速化

langstat_generatorとは、cc_netの手法に基づいてlangstat (各ドメインの各言語のテキストがどのぐらいあるかの統計) を生成する自作スクリプトです。slurm上で実行することが目標でしたが、いくつか高速化したため、高速化した点を書きます。

github.com

3つの部分を高速化

hash_creatorの高速化

cc_netは、重複するパラグラフを除外するためにハッシュ値を計算します。

高速化前は、ハッシュを辞書として保持しており、マルチプロセッシングによって並列に辞書を生成しました。しかし、この方法だと辞書の結合に時間がかかります。そこで、以下のように変更しました:

def _create_hash(fname, logby=500000):
    hashes = defaultdict(int)
    arrived = set()
    out = []
    for i, (line, mode) in tqdm(enumerate(
            wet_loader.corpus_loader(wet_loader.file_loader(fname))),
                                miniters=logby, bar_format="{r_bar}"):
        if mode is not None and not mode:
            try:
                h = hashlib.sha1(
                    bytes(line.lower(), encoding="utf-8")).digest()
            except Exception:
                print("hash_creator: Error {} {} {}".format(fname, i, line))
            if h in arrived:
                continue
            else:
                hashes[h] += 1
                if hashes[h] > 1:
                    arrived.add(h)
                    out.extend([h])
                    del hashes[h]
    return out


def create_hashes(files, num_cpus=DEFAULT_CPUS):
    pool = Pool(num_cpus)
    hashes_list = pool.map(_create_hash, files)
    hashes = []
    for h in tqdm(hashes_list):
        hashes.extend(h)
    pool.close()
    return set(hashes)

以前は、ハッシュをキー、出現頻度をバリューとした辞書を作成しましたが、これを「二回以上出現したパラグラフのハッシュ値のリスト」をリターンするようにします。すると、リストをextendによって結合してからset化する処理は高速に行えるので、結合が高速化されます。

wet読み込み時の重複排除の高速化

取得されるハッシュ値は「重複したもの」のみになります。つまり、wetのロード時にこのハッシュ値の集合に含まれるパラグラフを見つけた場合は除外します。これは、辞書を使う方法よりも効率的です。

def corpus_loader_dedup(line_generator, hashes):
    wstr = "WARC/1.0"
    ustr = "WARC-Target-URI"
    header_mode = None
    out = []
    url = None
    domain = None
    for line in line_generator:
        line = line.strip()
        if line == wstr:
            header_mode = True
            if out:
                if domain is not None and url is not None:
                    yield {"url": url, "domain": domain, "data": out}
                url = None
                domain = None
                out = []
            continue
        if header_mode:
            if line.startswith(ustr):
                url = line.split(ustr + ":")[1].strip()
                domain = url.split("//")[1].split("/")[0]
            if line:
                continue
            else:
                header_mode = False
        else:
            h = hashlib.sha1(bytes(line.lower(), encoding="utf-8")).digest()
            if h in hashes:
                continue
            out.append(line)

CommonCrawlをgoofysによってマウント

もっとも高速化の効果が出るのは、CommonCrawlのデータの取得方法を変更することです。以前のコードは、httpを通じてrequestsでデータ取得しましたが、同一リージョン内でgoofysを使えばCommonCrawlをaws内でマウントできます。

Common Crawl - Registry of Open Data on AWS

goofysとは、s3バケットをマウントするために使うことができるツールで、s3fsよりも高速です。

GitHub - kahing/goofys: a high-performance, POSIX-ish Amazon S3 file system written in Go

同一リージョン内でマウントを行うことにより、データのダウンロードにかかる時間が節約されます。

実行時間計測

以下は、1 shard=50 wet に対する実行時間です。

2020-01-07 03:31:30.000448 0:00:00.000008 nid:0, sid:0, msg:TASK START
2020-01-07 03:31:30.000609 0:00:00.000169 nid:0, sid:0, msg:start shard
2020-01-07 03:31:30.000760 0:00:00.000319 nid:0, sid:0, msg:hash_creator: start
2020-01-07 03:36:15.778244 0:04:45.777810 nid:0, sid:0, msg:hash_creator: done
2020-01-07 03:36:15.778280 0:04:45.777838 nid:0, sid:0, msg:lang_separator: start
2020-01-07 03:47:30.263067 0:16:00.262632 nid:0, sid:0, msg:lang_separator: done
2020-01-07 03:47:30.263116 0:16:00.262676 nid:0, sid:0, msg:hash_creaning: start
2020-01-07 03:47:31.071801 0:16:01.071363 nid:0, sid:0, msg:hash_creaning: done
2020-01-07 03:47:31.071830 0:16:01.071388 nid:0, sid:0, msg:lm_scoring: start
2020-01-07 04:18:08.922997 0:46:38.922596 nid:0, sid:0, msg:lm_scoring: done
2020-01-07 04:18:09.417127 0:46:39.416689 nid:0, sid:0, msg:TASK DONE

多くの時間はlm_scoringに費やされています。「言語モデルスコアがほしい」という要件自体がなければ16分程度で実行できます。