StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

無償配布の日本語・英語対訳コーパスのデータを綺麗にする

★めぼしいコーパスをまとめてダウンロードしてクリーニングして統合するPythonのコードを最後にのせてます。
★少しソースを増やして、最終的には後日のエントリに書いてあるようなコーパス構成にしました。

日英対訳の(なるべく)綺麗なデータを得たい

Transformerで機械翻訳をやろうと思ったのですが、てっとり早く試すだけの場合、綺麗な演習用データが配布されてる「英語-ドイツ語」や「英語-フランス語」のデータセットを使う人が多いんじゃないでしょうか。
でもそれだと、性能が実感できないので、なんとか「英語 ⇒ 日本語」の機械翻訳を実践したいと思いました。
そこでネックになるのが、

  • 品質がよくて
  • 分量が十分で
  • 無償配布されている

という条件をみたす対訳コーパスが、都合よくあるわけではないということです。


下記のページにいろいろまとまってるのですが、少なくとも私が確認した無償のものに関しては、どれも一長一短という感じで、「これさえ使っておけば」というものがない。
日本語対訳データ


たとえば、NTTの研究所がつくったJParaCrawlってのは2500万件ぐらいあって数としてはすごいのですが、後述のとおり、中身をみると品質にかなり問題があると思いました。
ソフトウェアのマニュアルの対訳なんかは、訳文は正確なんだろうけど、「それ学習してどうすんの」という部分が多い。
法律や公文書の対訳データは、内容が偏っててそれだけでは多様性が足りない。これは京都Wikiなんかにも言える。
その他、品質はいいけど一文が短かったり、内容的には申し分ないけど件数が10万件そこそこしかなかったりもする。


そこで、いくつかのめぼしいコーパスのデータをクリーニングして、使いやすいデータを抽出し、統合して一つの対訳コーパスとして使うことにしました。
本エントリの次のセクションでは、私が確認した各コーパスの内容と処理方針について簡単にメモしておきます。
そして最後のセクションで、ダウンロードから前処理までまとめて実行するコードを載せておきます。


ちなみに私は、使いにくいものは避けて、最終的に以下のものを採用し、合計約128万件の対訳コーパスを作成しました。

  • TED字幕(元データは15万8535件/私は全部使用)
  • 青空文庫等の小説(元データは11万8143件/data augmentationを含めた処理後11万8825件)
  • 京都Wiki(元データは44万3849件/処理後21万8038件)
  • 映画字幕(元は280万1388件/処理後31万3927件)
  • 田中コーパス(元データは14万7918件/私は全部使用)
  • 法令対訳コーパス(元データは26万2449件/処理後18万5607件)
  • JparaCrawl(元データは2574万0835件/私は20万件を使用)

(これを全部合わせると134万2843件で、そこから全体をみて英文400文字、日本語文200文字以内のものに限った結果、128万0621件になった。これは、学習時にミニバッチ内で最長の文に合わせて他の文をパディングする処理をしており、長文が1つでも含まれるとバッチがデカくなりすぎてメモリを圧迫し、そのせいで全体としてバッチサイズを大きくできなくなってしまうため。)


にわかTransformererなので肌感覚がないんですが、この程度の規模だとTransformerの強みが活かされず、LSTMとかのほうがいいのかも?
ただし上記件数は、文が長すぎたり短すぎたりするものを削ったり、一部のデータセットに偏らないように件数を絞って使うことにした結果なので、たとえば映画字幕とJParaCrawlからのサンプルを増やしたりすれば100万単位のコーパスも作れると言えば作れます。
各コーパスについて、詳細は後述しますが、それぞれの大まかな印象は以下のような感じです。

  • TED字幕:品質がとてもよく、口語調のサンプルが手に入るのもいい。
  • 青空文庫等の小説:データの加工が多少必要だが、文章の品質がいい。後述の処理によって比較的長文のサンプルが手に入ってよかった。
  • 京都Wiki:訳文はしっかりしてて件数も多いが、内容が偏り過ぎ。
  • 映画字幕:件数が膨大だが、1つ1つが短く、意訳も多いという癖がある。
  • 田中コーパス:品質がいいだけに、14万件しかないのが惜しい。
  • 法令対訳コーパス:内容が偏るが、品質はとてもいい。あと「長文」サンプルが稼げる。
  • JparaCrawl:件数が2500万と大きいが、品質はいまいち。


できあがった統合コーパスの品質についてですが、ランダムに20件抽出したのものが以下のとおりで、変なのも混じってますがまぁ良いんじゃないでしょうか。

ネルソン・マンデラは、アパルトヘイトの暗黒の日々の後、法の下の人種差別という灰の中から、南アフリカ共和国を引き上げました。彼は性的指向による差別を、憲法内で禁ずる世界初の国へと、南アフリカ共和国を導いたのです。
Nelson Mandela led South Africa after the dark and brutal days of Apartheid, and out of the ashes of that legalized racial discrimination, he led South Africa to become the first country in the world to ban discrimination based on sexual orientation within its constitution.
 
大海人皇子が挙兵のため吉野から東国に出立したときに、皇子に従った二十数人の男の一人であった。
He was one of about two dozen of Prince Oama's followers when the Prince left Yoshino for Togoku to take up army.
 
二つの情熱を組み合わせたんですね。
So, you know, merging the two passions.
 
この事故は、カメラが、一部始終、記録していました。
The entire accident is caught on camera.
 
そこへ行っていただけませんか。
Will you please go there?
 
それをやってみるなんて狂気に近い。
It is little short of lunacy to try it.
 
彼女からの手紙が郵便受けにはいっていた。
I found her letter in the mailbox.
 
明治維新後はこうした規定は廃止され、各宗派それぞれの規定に基づいて(大抵は得度時に)度牒を交付する事になっている。
After the Meiji Restoration, these provisions were abolished and each sect has issued Docho official certificates (mostly at the time of entrance into the priesthood) on the basis of their respective stipulations.
 
全列車JR西日本223系電車1000番台及びJR西日本223系電車2000番台電車使用で、8両または12両編成で運転。
All trains use the cars of the JR (West) Suburban Train Series 223, model No. 1000s or the JR (West) Suburban Train Series 223, model No. 2000s, and they're operated in units of eight or 12 cars.
 
殆どが状況証拠だった、物的証拠が、中々見つからない
at first, the evidence was mostly circumstantial, physical proof of the murders being hard to come by.
 
それ自体が実質的に社会の一員としてふるまう
how might we think differently about our relations with technologies
 
これは大変なことです。マウス細胞だけでなく、ヒトの皮膚細胞から、ヒト幹細胞を作れるからです。
That turns out to be a big deal because now you can take, not just mouse cells, but you can human skin cells and turn them into human stem cells.
 
見落としてたから、他にもいくつか、見落としがあったわ
ms. wick overlooked them, as she did much else in this case.
 
ライフ・ストリーム・ドラゴンは、シンクロ召喚に成功したとき
when i successfully synchro summon life stream dragon
 
礫の一部を打撃して造るチョッパー・チョッピングツールを主体とする。
Its main items were Choppers and Chopping tools which were produced by beating a part of a pebble.
 
(拍手)、そしてアップル愛好家がいるのもわかってますよ。
(Applause) And I know some of you are Apple aficionados.
 
私はバスの中で財布を取られた。
I had my wallet stolen on the bus.
 
私は暫く待つように言われた。
I was told to wait for a while.
 
胆のうの手術でしたら、この廊下をずっと行って右に曲がってください。
For gall bladder surgery, go down this hall and take a right.
 
彼女の住所を教えてくれませんか。
Will you give me her address?


少なくとも、どれか1つのコーパスを単独で使うよりは遥かにいいと感じられるので、満足してます。
長いものから短いものまで、書き言葉から話し言葉まで、多様性もあるし(といっても長い例文は不足してますが)。
無償コーパスの中身を自分で精査してみた人でないと実感が湧かないとは思うのですが、とにかく私は、JParaCrawlの品質に絶望したあとなので、これをみるとオアシスのように感じられますw
ちなみにここから5万件ぐらいだけ抽出してTransformerにかけてみたら、小規模でも思ったよりそれらしい出力が出ました。全データ使った学習は後でやります。
 
 

各コーパスの性質や処理方針についてのメモ

TEDの字幕

The Multitarget TED Talks Task (MTTT)

TEDトークの字幕のデータで、口語調なのだが、かなり整っていて品質は高いと思える。
あと、短いセリフはいくつかまとめて1行になっている点もよい。対訳コーパスは1文が短すぎるものが多く、文脈を考慮して意味を読み取れるTransformerの力が発揮できない気がするので。
日本語文をみると、以下のような感じで、句読点がなく半角スペース区切りになっている。

私たちのミッションの多くは 写真やビデオを撮影するものです 撮影が始まってしまえば たいていは コーヒーを取りに行き しばらくは のんびりと待つのです のんびりとできないで 無人機が戻らなかったらどうしようと パニックになる人もいますけどね\n

適当に読点や句点を入れる必要があるんですが、文末は句点だとしても、文中のすべてを読点にすればいいわけではないのが難しいところ。
そこで、

  • 文中で「ね 」「です 」「ます 」等の場合は句点を入れる。
  • 文中でそれ以外は読点を入れる。
  • 文末は句点を入れる。

という変換をやってみたところ、全体的にかなりいい感じになった。
 

青空文庫等の小説

https://www2.nict.go.jp/astrec-att/member/mutiyama/align/index.html

青空文庫、杉田玄白プロジェクト、グーテンベルク・プロジェクトなど、小説のオープンなデータのうち、日英の対訳が揃っているもののデータらしい。
日本語分はChasenで分かち書きされていて、単語間にスペースが入っているのだが、これはTransformerで解釈する上では不要なので後で消す。
文献情報や注釈など、翻訳の学習によくなさそうなデータも混じっているのだが、流石に小説として読めるテキストだけあって、文章は綺麗である。
ただし、小説なので当然「意訳」されているところもあってそれがネックな感じはした。
あとなぜか、1文1行になっていないところがけっこうあり、ひと続きの文なのに2行に分かれてたりする。
また、1文1行になっていても、それが理想的なわけではなく、前後の文脈がないと意味が取れない文もある。
そこで、以下のようにクリーニングした。

  • 日本語なのに英数字がたくさん入っている文を削除(注釈とかなので)。
  • 英文が大文字で始まっていないところは、前の文と続けるようにした。
  • 文脈の連続性を多少でも考慮できるように、全体的に2文ずつセットで1文にしてまとめた(なのでサンプルサイズが半分になる)。強引な処理だが、これによって「長文」のサンプルが稼げるメリットもあるように思った。さきほど言ったように、対訳コーパスは短すぎるものが多いので。
  • で、あとで思いついたのだが、ペアにする組み合わせをずらすことで別の文をつくることができる。情報としては重複しているが翻訳タスク上は別のサンプルだとも言えるので、これでdata augmentationになる。そこでずらしたバージョンを追加し、さらに2文連結する前の短い文も残すことして、結果的に11万件ぐらいのコーパスになった。

 

京都Wiki

京都フリー翻訳タスク

京都に関連するWikipediaの情報を集めて英訳したものらしい。
翻訳はしっかりしている印象なのだが、仏教や歴史の説明が多くて話題が偏っているので、このコーパスだけ学習してもあまり意味がない気がする。
あと、たとえば以下のようなリスト的な情報もあり、これ訳してどうすんのと思ってしまう。

『正法眼蔵』(しょうぼうげんぞう)
『正法眼蔵,正法眼蔵随聞記日本古典文学大系81』(西尾実ほか校注.岩波書店,1965年)
『現代語訳正法眼蔵(一)~(一二)』(西嶋和夫訳.金沢文庫,1970年)
『正法眼蔵(一)~(四)』(水野弥穂子校注.岩波文庫,1990年)
『正法眼蔵』(石井恭二訳、河出文庫、2004年)
『永平広録』(石井恭二訳、河出書房新社、2005年)
『永平清規』
『典座教訓』(てんぞきょうくん)
『典座教訓 赴粥飯法』(中村璋八ほか全訳注.講談社学術文庫,1991年)
『赴粥飯法』(ふしゅくはんほう)
『正法眼蔵随聞記』(しょうぼうげんぞうずいもんき)懐奘編-道元の講義録。
『正法眼蔵随聞記 新校註解』(大久保道舟校註.山喜房仏書林,1958年)
『正法眼蔵随聞記』(古田紹欽訳注.角川文庫,1960年)
『正法眼蔵随聞記』(和辻哲郎校訂.岩波文庫,1982年改版)
『正法眼蔵随聞記』(水野弥穂子訳.ちくま学芸文庫,1992年)
『正法眼蔵随聞記 現代語訳』(池田魯参訳.大蔵出版,1993年)
『正法眼蔵随聞記』(山崎正一全訳注.講談社学術文庫,2003年)

訳文はたぶん綺麗なほうで件数も多いのだが、内容が偏っているという感じなので、全件は使わず、短すぎる文と長すぎる文を除いて絞ることとした。(長いほうは除きたい理由が具体的にあるわけではないのだが、どのコーパスにも言えることとして、めちゃめちゃ長い行はなんかよくわからん情報が入っていることが多い。Wikipediaだし。)

 

映画の字幕

JESC

映画の字幕の対訳データで、件数がすごく多い(280万件)のと、データのフォーマットが綺麗で使いやすいところがメリットだと思う。
ただし、日本語で句読点が付いてない文が多く、文と文の切れ目が半角スペースで区切られている。これをどう処理するか、真面目に考えると難しいのだが、面倒なのでとりあえず全部読点で置き換えることにした。
また、想像すれば分かると思うが、映画の字幕なのでかなり意訳が混じっており、日本語と英語が素直に対応していない部分も多い。
あと、これも字幕だから当然なのだが、全体的に文が短い。
元の件数が非常に多いので、文の長さが一定以上の長さのものだけ抽出。
 

田中コーパス

Tanaka Corpus - EDRDG Wiki

どういう由来のものかよく調べてないのだが、自分が見た中では、かなり品質のいい対訳データだと感じた。
文章が整っていて、翻訳も正確。ただ残念ながら1文1文が短くて、語学の教材の典型的な例文という感じ。
フォーマットの整理だけして、全て使うことにした。
 

JParaCrawl

JParaCrawl

これが最もくせものだと思う。
ウェブからクロールしてきたデータで、件数が2500万件もあるので、内容の多様さと規模が魅力だから、そこだけ聞くと使いたくなる。
コーパス作成手順としては、日本語と英語が半々に近い分量含まれているサイトをまず集めて、そこから内容的に対応してそうな日本文・英文のペアを自動で抽出しているらしい。


ところが中身をみてみると、割と品質が低いと言わざるを得ない感じ。そもそも「できの悪い機械翻訳」みたいな訳文がたくさん含まれているし、意味的に全然対応していないペアも含まれている。
で、明確な説明はみつからなかったのだが、どうやら対訳としての品質(意味の類似度?)のようなスコアがすべてのペアについている。じゃあそのスコアが高い文だけ使えば良いのでは?と思ったものの、スコアが高い文をみてみると、

ゆんフリー写真素材集 : No. 9877 草津温泉 湯畑 [日本 / 群馬]

という商品名みたいなやつが大量に並んでいる部分もあって、なかなか難しい。
ただ、スコア0.5から0.8までを0.5刻みで階級化し、各階級から30の例文をランダムに取って、ChatGPTに与えて「適切な訳ならgood、不適切ならbad」と評価するよう頼んでみたところ(横着な作業だが…)、やっぱり高スコアのほうがgoodは多かった。そこで、0.7以上のデータを使うことにして、長すぎるものと短すぎるものを削ったり、日本文に英字が含まれているものを削ったり、英語が大文字で始まってないもの、日本語が。でおわっていないもの等を削除してある程度クリーニングし、そこから20万件だけを選んで使うことにした。

 

ダウンロード、整理統合のPythonコード

以下に、ファイルのダウンロード、クリーニング、件数絞り、ファイル統合などの前処理をまとめたコードを置いておきます。
スマートな書き方は追求してなくて、記述が冗長になってるし、部分的に変数名が重複してたりもするかも知れません。
でも上から順に実行する分にはとりあえず問題ないです。さっき動作確認のために、Google Colab上でまとめて動かしてみたら、TEDとJParaCrawlのダウンロードに時間がかかるものの、他の処理は一瞬でした。
冒頭で作業ディレクトリのパスを指定するだけで、あとはまとめて実行でOKです。


最終的に、日本語と英語のそれぞれのファイルができますが、行ごとに訳文として対応しています。
コーパス順とかに並んでるのではなく、シャッフルしてます(学習で使うときにどうせシャッフルするため)。


京都、映画字幕は一定の字数範囲に収まるサンプルに限る処理でけっこう件数が減っているし、JParaCrawlはクリーニング後には367万件ほど残っていたのを20万件だけサンプルしているので、増やしたい場合はこのあたりで調整できると思います。


全体の量も増やしたいところではありますが、最も改善が必要なのは「長文」のサンプルが足りない点。これは、法令の対訳データが長文なので使うと良いかもしれない(以下のリンクは憲法の例)。まとめて抜く方法が分からなかったので今回は採用しなかったけど。
The Constitution of Japan - Japanese/English - Japanese Law Translation


……と思ったら法令の対訳をまとめたコーパスがあったので使うことにしました。
全件で26万2449件あり、短い文を除いて使うことにしました。
日英法令対訳コーパス

 

'''
全体の設定
'''

# Google Driveにマウント
from google.colab import drive
drive.mount('/content/drive')

# 作業ディレクトリ変更
import os
import sys
import re
import random
os.chdir('作業ディレクトリのパスを入れる') 
# Google ColabにGoogle Driveをマウントして使ってる時は、パスは以下のようになる。
# /content/drive/MyDrive/xxxxxxxxxxxx


'''
TED talkのデータセット
https://www.cs.jhu.edu/~kevinduh/a/multitarget-tedtalks/
'''

### ダウンロードと解凍(強制上書き)
!mkdir -p ted
!wget -O ted/multitarget-ted.tgz https://www.cs.jhu.edu/~kevinduh/a/multitarget-tedtalks/multitarget-ted.tgz
!tar -xzf ted/multitarget-ted.tgz -C ted/

### ファイル読み込み
# 本データセットは\n付いたまま処理する。
with open('ted/multitarget-ted/en-ja/raw/ted_train_en-ja.raw.ja', errors='ignore') as f:
    ted_lines_ja_train = f.readlines()
with open('ted/multitarget-ted/en-ja/raw/ted_train_en-ja.raw.en', errors='ignore') as f:
    ted_lines_en_train = f.readlines()
with open('ted/multitarget-ted/en-ja/raw/ted_test1_en-ja.raw.ja', errors='ignore') as f:
    ted_lines_ja_test = f.readlines()
with open('ted/multitarget-ted/en-ja/raw/ted_test1_en-ja.raw.en', errors='ignore') as f:
    ted_lines_en_test = f.readlines()
with open('ted/multitarget-ted/en-ja/raw/ted_dev_en-ja.raw.ja', errors='ignore') as f:
    ted_lines_ja_dev = f.readlines()
with open('ted/multitarget-ted/en-ja/raw/ted_dev_en-ja.raw.en', errors='ignore') as f:
    ted_lines_en_dev = f.readlines()

### リスト連結
ted_lines_ja = ted_lines_ja_train + ted_lines_ja_test + ted_lines_ja_dev
ted_lines_en = ted_lines_en_train + ted_lines_en_test + ted_lines_en_dev

len(ted_lines_ja)

# サンプルを確認
for i in random.sample(list(range(len(ted_lines_ja))), 5):
  print(ted_lines_ja[i])
  print(ted_lines_en[i])

### 句読点の挿入

'''
日本語が以下のような感じで、句読点がなく半角スペース区切りになっている。

私たちのミッションの多くは 写真やビデオを撮影するものです 撮影が始まってしまえば たいていは コーヒーを取りに行き しばらくは のんびりと待つのです のんびりとできないで 無人機が戻らなかったらどうしようと パニックになる人もいますけどね\n

文中でも句点を入れたほうがよさそうだが、難しいので、
①文中で「ね 」「です 」「ます 」等は句点を入れる。
②文中でそれ以外は読点を入れる。
③文末は句点を入れる。
これでだいぶきれいになる。
'''

ted_lines_ja = [l.replace("です ", "です。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ます ", "ます。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("でした ", "でした。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ました ", "ました。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ません ", "ません。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("でしょう ", "でしょう。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("でしょうか ", "でしょうか。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ますか ", "ますか。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ませんか ", "ませんか。") for l in ted_lines_ja]
ted_lines_ja = [l.replace("ね ", "ね。") for l in ted_lines_ja]
ted_lines_ja = [l.replace(" ", "、") for l in ted_lines_ja]
ted_lines_ja = [l.replace("\n", "。\n") for l in ted_lines_ja]

print(len(ted_lines_ja))
print(len(ted_lines_en))

# 長さ確認(異常なサンプルがないか)
print(max([len(l) for l in ted_lines_ja]))
print(min([len(l) for l in ted_lines_ja]))
print(max([len(l) for l in ted_lines_en]))
print(min([len(l) for l in ted_lines_en]))

# 対訳の確認(念のため)
for i in random.sample(list(range(len(ted_lines_ja))), 10):
  print(ted_lines_ja[i])
  print(ted_lines_en[i])

### 保存
with open("ted/ted_clean_ja.txt", "w") as file:
    for l in ted_lines_ja:
        _ = file.write(l)
with open("ted/ted_clean_en.txt", "w") as file:
    for l in ted_lines_en:
        _ = file.write(l)


'''
青空文庫等の学習データ(NICT)の取得とクリーニング
https://www2.nict.go.jp/astrec-att/member/mutiyama/align/index.html
Project Gutenberg や青空文庫やプロジェクト杉田玄白などの作品について,日本語文と英語文との対訳文対応を付けたもの。
'''

### ダウンロードと解凍
!mkdir -p aozora
!wget -O aozora/para.zip https://www2.nict.go.jp/astrec-att/member/mutiyama/align/download/para.zip
!unzip -o aozora/para.zip -d aozora/

### まず日本語のほうの文字コードをutf-8にして保存
# 一部なぜか読めない文字列があるみたいなのでignore(スキップ)
with open('aozora/para/ja.txt', 'r',encoding='shift_jis', errors='ignore') as f:
    text_jp = f.read()
with open('aozora/para/ja_utf8.txt', 'w', encoding="utf-8") as f:
    f.write(text_jp)

### 1行ずつのリスト形式で読み込み
with open('aozora/para/ja_utf8.txt', errors='ignore') as f:
    aozora_lines_ja = f.readlines()
with open('aozora/para/en.txt', errors='ignore') as f:
    aozora_lines_en = f.readlines()

len(aozora_lines_ja)

# サンプルを確認
for i in random.sample(list(range(len(aozora_lines_ja))), 5):
  print(aozora_lines_ja[i])
  print(aozora_lines_en[i])

### 基本的なクリーニング

# 日本語はChasenで分かち書きされてるのでくっつける。
aozora_lines_ja = [l.replace(' ','').strip() for l in aozora_lines_ja]

# 英語は末尾の\nだけ削る
aozora_lines_en = [l.strip() for l in aozora_lines_en]

# 英語と日本語のどちらもアイテムがあるものだけ抽出する
valid_ix = [i for i in range(len(aozora_lines_ja)) if (len(aozora_lines_ja[i]) > 0 and len(aozora_lines_en[i]) > 0)]
aozora_lines_ja = [aozora_lines_ja[i] for i in valid_ix]
aozora_lines_en = [aozora_lines_en[i] for i in valid_ix]

### 分断された文の結合
'''
1文が2行に分けてある部分もあり、それは行頭が大文字かどうかで判断できるので、
英語で行頭が大文字じゃない(カッコとかも含む)ところはくっつける
ただし作品名とかが
################/tempest.alml/################みたいに
入ってる部分は除く
'''
for i in range(len(aozora_lines_en)):
  if (aozora_lines_en[i][0].isupper() == False) and (aozora_lines_en[i][0] != '#'):
    aozora_lines_en[i] = aozora_lines_en[i-1] + ' ' + aozora_lines_en[i]
    aozora_lines_en[i-1] = ''
    aozora_lines_ja[i] = aozora_lines_ja[i-1] + aozora_lines_ja[i]
    aozora_lines_ja[i-1] = ''

# 結合されたセンテンスがおかしくないか確認
merged_en = [aozora_lines_en[i] for i in range(len(aozora_lines_en)) if len(aozora_lines_en[i])>0 and len(aozora_lines_en[i-1])==0]
merged_ja = [aozora_lines_ja[i] for i in range(len(aozora_lines_ja)) if len(aozora_lines_ja[i])>0 and len(aozora_lines_ja[i-1])==0]
for i in random.sample(list(range(len(merged_en))), 3):
  print(merged_en[i])
  print(merged_ja[i])

# 空の要素を削除
valid_ix = [i for i in range(len(aozora_lines_ja)) if (len(aozora_lines_ja[i]) > 0 and len(aozora_lines_en[i]) > 0)]
aozora_lines_ja = [aozora_lines_ja[i] for i in valid_ix]
aozora_lines_en = [aozora_lines_en[i] for i in valid_ix]

print(len(aozora_lines_ja))
print(len(aozora_lines_en))

### URLが入ってるところはだいたいおかしいので削る
target_ix = [i for i in range(len(aozora_lines_ja)) if ('http://' in aozora_lines_ja[i]) or ('https://' in aozora_lines_ja[i])]

# 降順にソートしてからpopで削除
ix_sorted = sorted(target_ix, reverse=True)
for ix in ix_sorted:
    aozora_lines_ja.pop(ix)
    aozora_lines_en.pop(ix)

### 英字まじりの文をまとめて削除

'''
いろいろ見直した結果、日本文に英字が10文字以上連続で入ってるやつは
全部削除したほうがよさそうなので消す。
この操作、
################/tortoise.alml/################
みたいな作品タイトル行も除去できる。
'''

target_ix = [i for i in range(len(aozora_lines_ja)) if bool(re.search(r'[a-zA-Z,\.!?\"\':; ]{10,}', aozora_lines_ja[i]))]
ix_sorted = sorted(target_ix, reverse=True)
for ix in ix_sorted:
    aozora_lines_ja.pop(ix)
    aozora_lines_en.pop(ix)

### 適当に文を連結

'''
元が小説のデータなので、文脈上連続している文が多く、その連続を踏まえないと、
「he」を具体的な名前で和訳している箇所の意味が分からなかったりする。
そこで、適当だが、2文ずつ連結することで、全体的にやや長い対訳サンプルにする。
ただしそれをやるとサンプルが半分になって3万件程度になる。
そこで、連結するペアをずらすことで2パターン作成し、さらに連結前の文も残すことで、
一種のdata augmantationを行う(全く同じ文はないことになるのでOK。)
このコーパスは質が高いので、多めに取っておきたいというのもある。
'''

aozora_lines_en_Concat1 = [aozora_lines_en[i] + ' ' + aozora_lines_en[i + 1] for i in range(0, len(aozora_lines_en) - 1, 2)]
aozora_lines_en_Concat2 = [aozora_lines_en[i] + ' ' + aozora_lines_en[i + 1] for i in range(1, len(aozora_lines_en) - 1, 2)]
aozora_lines_ja_Concat1 = [aozora_lines_ja[i] + aozora_lines_ja[i + 1] for i in range(0, len(aozora_lines_ja) - 1, 2)]
aozora_lines_ja_Concat2 = [aozora_lines_ja[i] + aozora_lines_ja[i + 1] for i in range(1, len(aozora_lines_ja) - 1, 2)]

aozora_lines_en = aozora_lines_en + aozora_lines_en_Concat1 + aozora_lines_en_Concat2
aozora_lines_ja = aozora_lines_ja + aozora_lines_ja_Concat1 + aozora_lines_ja_Concat2

# 対訳の確認(念のため)
for i in random.sample(list(range(len(aozora_lines_ja))), 10):
  print(aozora_lines_ja[i])
  print(aozora_lines_en[i])

print(len(aozora_lines_ja))
print(len(aozora_lines_en))

# クリーニング済みのテキストを保存
with open("aozora/aozora_clean_ja.txt", "w") as file:
    for l in aozora_lines_ja:
        _ = file.write(l+'\n')
with open("aozora/aozora_clean_en.txt", "w") as file:
    for l in aozora_lines_en:
        _ = file.write(l+'\n')


"""
京都フリー翻訳タスク
http://www.phontron.com/kftt/index-ja.html
"""

### ダウンロードと解凍(強制上書き)
!mkdir -p kyoto
!wget -O kyoto/kftt-data-1.0.tar.gz http://www.phontron.com/kftt/download/kftt-data-1.0.tar.gz
!tar -xzf kyoto/kftt-data-1.0.tar.gz -C kyoto/

### ファイル読み込み
# 本データセットは\n付いたまま処理する。
with open('kyoto/kftt-data-1.0/data/orig/kyoto-train.ja', errors='ignore') as f:
    kyoto_lines_ja_train = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-train.en', errors='ignore') as f:
    kyoto_lines_en_train = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-dev.ja', errors='ignore') as f:
    kyoto_lines_ja_dev = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-dev.en', errors='ignore') as f:
    kyoto_lines_en_dev = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-test.ja', errors='ignore') as f:
    kyoto_lines_ja_test = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-test.en', errors='ignore') as f:
    kyoto_lines_en_test = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-tune.ja', errors='ignore') as f:
    kyoto_lines_ja_tune = f.readlines()
with open('kyoto/kftt-data-1.0/data/orig/kyoto-tune.en', errors='ignore') as f:
    kyoto_lines_en_tune = f.readlines()

### リスト連結
kyoto_lines_ja = kyoto_lines_ja_train + kyoto_lines_ja_dev + kyoto_lines_ja_test + kyoto_lines_ja_tune
kyoto_lines_en = kyoto_lines_en_train + kyoto_lines_en_dev + kyoto_lines_en_test + kyoto_lines_en_tune

len(kyoto_lines_ja)

# サンプルを確認
for i in random.sample(list(range(len(kyoto_lines_ja))), 5):
  print(kyoto_lines_ja[i])
  print(kyoto_lines_en[i])

### 日本語で35文字以上200文字未満を取る。
target_ix = [i for i in range(len(kyoto_lines_ja)) if (len(kyoto_lines_ja[i]) >= 35) and (len(kyoto_lines_ja[i]) <= 200)]
kyoto_lines_ja = [kyoto_lines_ja[i] for i in target_ix]
kyoto_lines_en = [kyoto_lines_en[i] for i in target_ix]

# 対訳の確認(念のため)
for i in random.sample(list(range(len(kyoto_lines_ja))), 5):
  print(kyoto_lines_ja[i])
  print(kyoto_lines_en[i])

print(len(kyoto_lines_ja))
print(len(kyoto_lines_en))

### 保存
# クリーニング済みのテキストを保存
with open("kyoto/kyoto_clean_ja.txt", "w") as file:
    for l in kyoto_lines_ja:
        _ = file.write(l)
with open("kyoto/kyoto_clean_en.txt", "w") as file:
    for l in kyoto_lines_en:
        _ = file.write(l)


"""
映画の字幕
https://nlp.stanford.edu/projects/jesc/index.html
"""

### ダウンロードと解凍(強制上書き)
!mkdir -p subtitle
!wget -O subtitle/raw.tar.gz https://nlp.stanford.edu/projects/jesc/data/raw.tar.gz
!tar -xzf subtitle/raw.tar.gz -C subtitle/

### 読み込み
with open('subtitle/raw/raw') as f:
    subtitle_lines = f.readlines()

### 日本語と英語に分ける
subtitle_lines_en = [l.strip().split('\t')[0] for l in subtitle_lines]
subtitle_lines_ja = [l.strip().split('\t')[1] for l in subtitle_lines]

len(subtitle_lines_ja)

# サンプルを確認
for i in random.sample(list(range(len(subtitle_lines_ja))), 5):
  print(subtitle_lines_ja[i])
  print(subtitle_lines_en[i])

### 読点を入れる
subtitle_lines_ja = [l.replace(" ", "、") for l in subtitle_lines_ja]

# 対訳の確認(念のため)
for i in random.sample(list(range(len(subtitle_lines_ja))), 5):
  print(subtitle_lines_ja[i])
  print(subtitle_lines_en[i])

### 日本語で25文字以上のものに限定する
target_ix = [i for i in range(len(subtitle_lines_ja)) if (len(subtitle_lines_ja[i]) >= 25)]
subtitle_lines_ja = [subtitle_lines_ja[i] for i in target_ix]
subtitle_lines_en = [subtitle_lines_en[i] for i in target_ix]

print(len(subtitle_lines_ja))
print(len(subtitle_lines_en))

### 保存
# クリーニング済みのテキストを保存
with open("subtitle/subtitle_clean_ja.txt", "w") as file:
    for l in subtitle_lines_ja:
        _ = file.write(l+'\n')
with open("subtitle/subtitle_clean_en.txt", "w") as file:
    for l in subtitle_lines_en:
        _ = file.write(l+'\n')


"""
田中コーパス
http://www.edrdg.org/wiki/index.php/Tanaka_Corpus
"""

### ダウンロードと解凍(強制上書き)
!mkdir -p tanaka
!wget -O tanaka/examples.utf.gz ftp://ftp.edrdg.org/pub/Nihongo/examples.utf.gz
!gunzip -f tanaka/examples.utf.gz

### 読み込み
with open('tanaka/examples.utf') as f:
    tanaka_lines = f.readlines()

# B: で始まる行を削除
tanaka_lines = [l for l in tanaka_lines if l.startswith('A: ')]

# 先頭の'A: 'の部分を削除
tanaka_lines = [l.replace('A: ', '', 1) for l in tanaka_lines]

# '#ID' 以降の部分を削除
tanaka_lines = [l.split('#ID')[0].strip() for l in tanaka_lines]

### 日本語と英語に分ける
tanaka_ja = [l.split('\t')[0] for l in tanaka_lines]
tanaka_en = [l.split('\t')[1] for l in tanaka_lines]

len(tanaka_ja)

# 対訳の確認(念のため)
for i in random.sample(list(range(len(tanaka_ja))), 5):
  print(tanaka_ja[i])
  print(tanaka_en[i])

### 保存
# クリーニング済みのテキストを保存
with open("tanaka/tanaka_clean_ja.txt", "w") as file:
    for l in tanaka_ja:
        _ = file.write(l+'\n')
with open("tanaka/tanaka_clean_en.txt", "w") as file:
    for l in tanaka_en:
        _ = file.write(l+'\n')


"""
日英法令対訳
http://www.phontron.com/jaen-law/index-ja.html
"""

### ダウンロードと解凍(強制上書き)
!mkdir -p law
!wget -O law/jaen-law.tar.gz http://www.phontron.com/jaen-law/jaen-law.tar.gz
!tar -xzf law/jaen-law.tar.gz -C law/

# ファイル読み込み
with open('law/jaen-law/txt/law-corpus.en') as f:
    law_en_lines = f.readlines()
with open('law/jaen-law/txt/law-corpus.ja') as f:
    law_ja_lines = f.readlines()

print(len(law_en_lines))
print(len(law_ja_lines))

### 日本語で20文字以上の行に限定する
target_ix = [i for i in range(len(law_ja_lines)) if (len(law_ja_lines[i]) >= 20)]
law_ja_lines = [law_ja_lines[i] for i in target_ix]
law_en_lines = [law_en_lines[i] for i in target_ix]

print(len(law_en_lines))
print(len(law_ja_lines))

# 対訳の確認(念のため)
for i in random.sample(list(range(len(law_en_lines))), 5):
  print(law_en_lines[i])
  print(law_ja_lines[i])

### 保存
# クリーニング済みのテキストを保存
with open("law/law_clean_ja.txt", "w") as file:
    for l in law_ja_lines:
        _ = file.write(l)
with open("law/law_clean_en.txt", "w") as file:
    for l in law_en_lines:
        _ = file.write(l)


"""
JParaCrawl(NTT)の取得とクリーニング
http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl/
"""

### ダウンロードと解凍(強制上書き)
!mkdir -p jparacrawl
!wget -O jparacrawl/en-ja.tar.gz http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl/release/3.0/bitext/en-ja.tar.gz
!tar -xzf jparacrawl/en-ja.tar.gz -C jparacrawl/

# 全行を読み込みする場合(読み込み自体は数十秒ぐらいでできる)
with open('jparacrawl/en-ja/en-ja.bicleaner05.txt') as f:
    crawl_lines = f.readlines()

# サンプルを確認
for l in random.sample(crawl_lines, 5):
  print(l.strip().split('\t')[-1])
  print(l.strip().split('\t')[-2])

'''
# 数値のところの分布を確認すると以下のようになったが、
# 0.79以上みたいなスコアが高いものに限定してしまうと、
# ゆんフリー写真素材集 : No. 9877 草津温泉 湯畑 [日本 / 群馬]
# みたいなのばっかりになるのでよくないっぽい。
# サンプル総数:25,740,835
# 0.4以上0.5未満:0.0
# 0.5以上0.6未満:0.1152207377888091
# 0.6以上0.7未満:0.16798157480128365
# 0.7以上0.8未満:0.7167976874099072
# 0.75以上0.8未満:0.3224694925397719
# 0.75以上0.775未満:0.2583718049550452(8,300,634件)
# 0.76以上:0.20323936655512534(5,231,551件)
# 0.77以上:0.09364913764452475(2,410,607件)
# 0.775以上:0.06409768758472675(1,649,928件)
# 0.78以上:0.031290127146225054(805,434件)
# 0.785以上:0.009059340926586104(233,195件)
# 0.79以上:0.0011227297016588624(28,900件)
# 0.8以上:0.0

scores = [float(l.strip().split('\t')[2]) for l in crawl_lines]
'''

'''
# どのスコア帯のサンプルが比較的健全であるかを目視確認

min_score = 0.75
max_score = 0.80
ranged_lines = [l for l in crawl_lines if float(l.strip().split('\t')[2]) >= min_score and float(l.strip().split('\t')[2]) <= max_score]
for l in random.sample(ranged_lines, 5):
  print(l.strip().split('\t')[-1])
  print(l.strip().split('\t')[-2])
  print('\n')
'''

'''
スコア0.6から0.8まで、0.05刻みで階級をつくり、各階級から30の対訳ペアをランダムに
抽出して、ChtGPTに「適切な訳かどうか」をgood/badで評価させたところ、以下のとおり。

0.50-0.55 14 GOODs
0.55-0.60 18 GOODs
0.60-0.65 18 GOODs
0.65-0.70 21 GOODs
0.70-0.75 21 GOODs
0.75-0.80 26 GOODs

本当はもっと精査したほうがいいと思うが、まず0.65以上に絞った上で、
・日本語文が短いものと長いものを削除
・日本語文の中に3文字以上の連続する英字が混じっているものを削除
・日本語文が句点でおわっていないものを削除
・英文が大文字で始まっていないものを削除
・日本語文と英語文の長さの比が極端なものを削除
することにする。
'''

### クリーニング用に範囲内のサンプルを抽出
min_score = 0.70
max_score = 0.80
crawl_lines = [l for l in crawl_lines if float(l.strip().split('\t')[2]) >= min_score and float(l.strip().split('\t')[2]) <= max_score]

### 日本語と英語に分ける
crawl_ja = [l.strip().split('\t')[-1] for l in crawl_lines]  # 一番うしろが日本語
crawl_en = [l.strip().split('\t')[-2] for l in crawl_lines]  # うしろから2番目が英語

'''
ヒストグラムで日本語文と英語文の長さの比の分布を確認
length_ratio = [(len(crawl_en[i]) / len(crawl_ja[i])) for i in range(len(crawl_en))]
import matplotlib.pyplot as plt
import numpy as np
bins = np.arange(0, 4.2, 0.2).tolist()

plt.hist(length_ratio, bins=bins, rwidth=0.8)
plt.show()
'''

### 長さの比が極端でないものだけ残す
target_ix = [i for i in range(len(crawl_en)) if (len(crawl_en[i]) / len(crawl_ja[i])) > 1.8 and (len(crawl_en[i]) / len(crawl_ja[i]))  < 2.6]

crawl_en = [crawl_en[i] for i in target_ix]
crawl_ja = [crawl_ja[i] for i in target_ix]

print(len(crawl_en))
print(len(crawl_ja))

### 日本文が長いものと短いものを削除
medium_length_ix = [i for i in range(len(crawl_ja)) if (len(crawl_ja[i]) > 30) and (len(crawl_ja[i]) < 300)]
crawl_ja = [crawl_ja[i] for i in medium_length_ix]
crawl_en = [crawl_en[i] for i in medium_length_ix]

print(len(crawl_en))
print(len(crawl_ja))

### 日本文が句点で終わるものだけ抽出
target_ix = [i for i in range(len(crawl_ja)) if crawl_ja[i][-1] == '。']

crawl_en = [crawl_en[i] for i in target_ix]
crawl_ja = [crawl_ja[i] for i in target_ix]

### 英文が大文字で始まるものだけ抽出
target_ix = [i for i in range(len(crawl_en)) if crawl_en[i][0].isupper()]

crawl_en = [crawl_en[i] for i in target_ix]
crawl_ja = [crawl_ja[i] for i in target_ix]

### 日本文に英字を含まないものだけ抽出
target_ix = [i for i in range(len(crawl_ja)) if not re.search(r'[a-zA-Z!@#$%^&*()-=_+[\]{};:\'",.<>?/|\\]', crawl_ja[i])]
crawl_en = [crawl_en[i] for i in target_ix]
crawl_ja = [crawl_ja[i] for i in target_ix]

'''
### 日本文に連続する3文字以上の英字部分を含むものを削除
target_ix = [i for i in range(len(crawl_ja)) if bool(re.search(r'[a-zA-Z,\.!?\"\':; ]{3,}', crawl_ja[i]))]
ix_sorted = sorted(target_ix, reverse=True)
for ix in ix_sorted:
    crawl_ja.pop(ix)
    crawl_en.pop(ix)
'''

print(len(crawl_en))
print(len(crawl_ja))

# 対訳の確認(念のため)
for i in random.sample(list(range(len(crawl_ja))), 20):
  print(crawl_ja[i])
  print(crawl_en[i])

### 保存
# クリーニング済みのテキストを保存
with open("jparacrawl/crawl_clean_ja.txt", "w") as file:
    for l in crawl_ja:
        _ = file.write(l+'\n')
with open("jparacrawl/crawl_clean_en.txt", "w") as file:
    for l in crawl_en:
        _ = file.write(l+'\n')


"""
すべてのコーパスの合体
"""

### 全部読み込み

# TED
with open('ted/ted_clean_ja.txt') as f:
    ted_clean_ja = f.readlines()
with open('ted/ted_clean_en.txt') as f:
    ted_clean_en = f.readlines()
print('TED:'+str(len(ted_clean_ja)))

# 青空文庫等
with open('aozora/aozora_clean_ja.txt') as f:
    aozora_clean_ja = f.readlines()
with open('aozora/aozora_clean_en.txt') as f:
    aozora_clean_en = f.readlines()
print('青空:'+str(len(aozora_clean_ja)))

# 京都
with open('kyoto/kyoto_clean_ja.txt') as f:
    kyoto_clean_ja = f.readlines()
with open('kyoto/kyoto_clean_en.txt') as f:
    kyoto_clean_en = f.readlines()
print('京都:'+str(len(kyoto_clean_ja)))

# 映画字幕
with open('subtitle/subtitle_clean_ja.txt') as f:
    subtitle_clean_ja = f.readlines()
with open('subtitle/subtitle_clean_en.txt') as f:
    subtitle_clean_en = f.readlines()
print('映画:'+str(len(subtitle_clean_ja)))

# 田中コーパス
with open('tanaka/tanaka_clean_ja.txt') as f:
    tanaka_clean_ja = f.readlines()
with open('tanaka/tanaka_clean_en.txt') as f:
    tanaka_clean_en = f.readlines()
print('田中:'+str(len(tanaka_clean_ja)))

# 法令
with open('law/law_clean_ja.txt') as f:
    law_clean_ja = f.readlines()
with open('law/law_clean_en.txt') as f:
    law_clean_en = f.readlines()
print('法令:'+str(len(law_clean_en)))

# JParaCrawl
with open('jparacrawl/crawl_clean_ja.txt') as f:
    crawl_clean_ja = f.readlines()
with open('jparacrawl/crawl_clean_en.txt') as f:
    crawl_clean_en = f.readlines()
print('JPara:'+str(len(crawl_clean_ja)))

print('計:' + str(len(ted_clean_ja)+len(aozora_clean_ja)+len(kyoto_clean_ja)+len(subtitle_clean_ja)+len(tanaka_clean_ja)+len(crawl_clean_ja)+len(law_clean_en)))

### 適当な分量に制限

# 京都
target_ix = random.sample(list(range(len(kyoto_clean_ja))), len(kyoto_clean_ja))  # 全部
kyoto_clean_ja = [kyoto_clean_ja[i] for i in target_ix]
kyoto_clean_en = [kyoto_clean_en[i] for i in target_ix]

# 映画
target_ix = random.sample(list(range(len(subtitle_clean_ja))), len(subtitle_clean_ja))  # 結全部
subtitle_clean_ja = [subtitle_clean_ja[i] for i in target_ix]
subtitle_clean_en = [subtitle_clean_en[i] for i in target_ix]

# JParaCrawl
target_ix = random.sample(list(range(len(crawl_clean_ja))), 200000)
crawl_clean_ja = [crawl_clean_ja[i] for i in target_ix]
crawl_clean_en = [crawl_clean_en[i] for i in target_ix]

### コーパスの合体とシャッフル
mixed_ja = ted_clean_ja + aozora_clean_ja + kyoto_clean_ja + subtitle_clean_ja + tanaka_clean_ja + crawl_clean_ja + law_clean_ja
mixed_en = ted_clean_en + aozora_clean_en + kyoto_clean_en + subtitle_clean_en + tanaka_clean_en + crawl_clean_en + law_clean_en

lines_ix = list(range(len(mixed_ja)))
random.shuffle(lines_ix)  # 代入しなくていい

mixed_ja = [mixed_ja[i] for i in lines_ix]
mixed_en = [mixed_en[i] for i in lines_ix]

print(len(mixed_ja))
print(len(mixed_en))

# 空の要素や要素数1の要素があるかどうかのチェック
empty_ix = [i for i in range(len(kyoto_clean_ja)) if len(kyoto_clean_ja[i]) == 1]
len(empty_ix)

# 対訳の確認(念のため)
for i in random.sample(list(range(len(mixed_ja))), 10):
  print(mixed_ja[i])
  print(mixed_en[i])

# 文の長さの分布確認
length_ja = [len(l) for l in mixed_ja]
length_en = [len(l) for l in mixed_en]

import matplotlib.pyplot as plt
import numpy as np

bins = np.arange(0, 425, 25).tolist()
plt.hist(length_ja, bins=bins, rwidth=0.8)
plt.show()

bins = np.arange(0, 850, 50).tolist()
plt.hist(length_en, bins=bins, rwidth=0.8)
plt.show()

# 分布を確認し、日本語は200文字、英文は400文字までに限定することにした
# (学習時のout of memory対策)

target_ix = [i for i in range(len(mixed_ja)) if len(mixed_ja[i]) <= 200 and len(mixed_en[i]) <= 400]
mixed_ja = [mixed_ja[i] for i in target_ix]
mixed_en = [mixed_en[i] for i in target_ix]

with open("mixed_1.3M_ja.txt", "w") as file:
    for l in mixed_ja:
        _ = file.write(l)
with open("mixed_1.3M_en.txt", "w") as file:
    for l in mixed_en:
        _ = file.write(l)

# 対訳の確認(念のため)
for i in random.sample(list(range(len(mixed_ja))), 20):
  print(mixed_ja[i])
  print(mixed_en[i])

### 保存
# クリーニング済みのテキストを保存
with open("mixed_1.3M_ja.txt", "w") as file:
    for l in mixed_ja:
        _ = file.write(l)
with open("mixed_1.3M_en.txt", "w") as file:
    for l in mixed_en:
        _ = file.write(l)