ナード戦隊データマン

機械学習, 自然言語処理, データサイエンスについてのブログ

字幕コーパスJESCのコードを理解し、パラレルコーパスを作成する

JESCとは、ネット上からクロールされた字幕データを用いた日英パラレルコーパスです。

今回は、JESCのクロール, アラインメント, クリーニングの仕組みを理解し、2年間更新されてないコードのリファクタリングを試みます。

コード: GitHub - rpryzant/JESC: JESC code release

概要

JESCのコードの配置を見ると、以下のようになっています。

JESC/
├── README.md
├── corpus_cleaning
│   ├── all_to_utf8.py
│   ├── postprocessing.py
│   ├── ratio_thresholder.py
│   └── spellchecker
│       ├── leo-will.txt
│       └── reconstruct.py
├── corpus_generation
│   ├── parser_v4.py
│   ├── subfile_aligner.py
│   ├── subscene_crawler.py
│   ├── tf_idf.py
│   └── utils.py
├── corpus_processing
│   ├── apply_bpe.sh
│   ├── generate_splits.sh
│   ├── prepare_corpus.sh
│   └── train_bpe.sh
└── requirements.txt

処理の流れは以下です。

  1. corpus_generation
  2. corpus_cleaning
  3. corpus_processing

corpus_processingは本質的な部分ではないため、割愛します。

さらに深いレベルで見ると、

[corpus_generation]

  1. subscene_crawler.py
  2. parser_v4.py

[corpus_cleaning]

  1. all_to_utf8.py
  2. spellchecker
  3. ratio_thresholder.py
  4. postprocessing.py

のように実行していきます。corpus_cleaningの部分は動作確認がとれましたが、corpus_generationの方はいくつか問題があるため、それらを修正していきます。

corpus_generation

corpus_generationフェーズは、クロールとアラインメントのフェーズです。基本的なフェーズは以下のように進行します:

  1. クロール
  2. ドキュメントアラインメント
  3. 文アラインメント

parser_v4.pyの中では、ドキュメントアラインメントと文アラインメントを両方実行しています。そのため、コードの直交性は低いと言えます。今回は、ドキュメントアラインメントと文アラインメントを分離します。

subscene_crawler.py

このクローラは現在、動作確認が取れておらず、"subscene.com内の日本語字幕ページのURL一覧を予め持っている"という前提がなされています。そのため、URL一覧を取得するコードを自前で書く必要があります。

parser_v4.py

ドキュメントアラインメント

parser_v4.pyでは、ドキュメントアラインメントを以下の方法で行います。

  1. guessitモジュールを用いて英語と日本語字幕からepisodeまたはtitle名を取得する。
  2. 1で取得したタイトルをSequenceMatcherのratio()関数によって類似度を取得。
  3. 類似度が一定以上のドキュメントペアを、次のフェーズ(文アラインメント)にわたす。

文アラインメント

parser_v4.py内では、subfile_aligner.pyが用いられており、これを用いて文アラインメントを実行します。

subfile_aligner.pyは以下の流れで文をアラインメントします。

  1. 英語と日本語のsrtファイルをロードする。
  2. ロード時に、日本語を英語に翻訳(Google Translate API)して保持する。
  3. ロード時に、英語のTF-IDFを保持する。
  4. 日本語字幕の英語翻訳と英語字幕のTF-IDF類似度を順に保持する。
  5. 日本語字幕の英語翻訳と英語字幕の長さの比率を順に保持する。
  6. 類似度の平均と標準偏差を使い、sim_cutoff = sim_mean + 0.50*sim_std を保持する。
  7. 長さ比率の平均と標準偏差の和、ratio_cutoff = ratio_mean + ratio_std を保持する。
  8. 類似度がsim_cutoffより大きいかつ長さ比率がratio_cutoffより小さい文ペアを出力する。

subscene_crawlerの機能追加とリファクタリング

追加したい機能

  • subscene.comから日本語と英語の字幕が存在するURL一覧を取得する。
  • 取得したURL一覧からターゲットのzipファイルをダウンロード&展開するコード。

browseページの問題点

subscene.comのbrowseページは、最大ページ数66件までしか取得できないという問題があります。そのため、別の方法で映画ページURLを探す必要があります。

searchbytitleページ

一つの方法は、subscene.comのsearchbytitleページから映画タイトルを自動検索し、映画ページURLを取得することです。これを実行するためには、映画タイトルのリストを予め持っておく必要があります。幸い、IMDBというサイトがあるため、IMDBから映画タイトル一覧を取得することによって、searchbytitleへタイトルを渡すことによって映画ページURLを取得できます。

parser_v4の機能追加とリファクタリング

リファクタリングしたい部分

  • Google Translate APIを使うとカネがかかるため、辞書ルックアップによる翻訳を用いたい。
  • ドキュメントアラインメントコードを分離したい。
  • 文アラインメント部分のコードをもっと高速化したい。

コード

コードの全貌をこのブログに書くのはしんどいため、以下のgithubプロジェクト内でコードを更新していきます:

リファクタリングしているコード: GitHub - sugiyamath/JESC: JESC code release

このプロジェクトは、本家のJESCをフォークしています。

考察

JESCのクローラ以外の部分は、srtファイルに対して実行するようになっているため、汎用的に実行することが可能です。ただ、クローラ部分については、すでにJESCのコードの更新が止まって2年が経過しているため、動作が不安定です。

特定のWebサイトをスクレイピングする場合、そのWebサイトの構造に強く依存します。そのため、構造が更新された場合は同一のクローラが動作しなくなることはあり得ることです。

変わりにくい要素としては、映画ページURLのURLの構造、または検索ページのリクエストの構造です。この性質を利用する場合、BeautifulSoupのようなhtmlパーサに頼る代わりに、正規表現に頼ることによってスクレイピングが成功しやすくなります。

subscene.comの場合、

regex = re.compile(r'href="(/subtitles/.+?/japanese/[0-9]+)"')
results = re.findall(regex, r.content)

のような正規表現を利用すれば、映画ページURLを高速に取得できます。つまり、HTMLパーサに頼る必要がない場合は、正規表現や文字列操作に頼ることにより、長期的に利用できるスクレイパーとして利用できる可能性が高いといえます。

参考

  1. GitHub - rpryzant/JESC: JESC code release
  2. https://nlp.stanford.edu/projects/jesc/
  3. Subscene - Passionate about good subtitles