Tatoeba という多言語例文サイトがあります。データは CC ライセンスで提供されています。
このページにあるファイルは、プログラムを組んで利用するためのものです。
今回は次の2つのファイルを使用します。
- sentences.tar.bz2
- links.tar.bz2
これらを SQLite に格納して、指定した言語の組み合わせで例文を抽出します。
eng: What is it?
fra: Qu'est-ce que c'est ?
jpn: これは何ですか。
eng: I will be back soon.
fra: Je serai bientôt de retour.
jpn: すぐに戻ります。
例文
まず本体の例文データを処理します。
sentences.tar.bz2 を展開します。入っているのは sentences.csv という1ファイルだけです。タブ区切りのテキストファイルです。
例文番号 [tab] 言語名 [tab] 例文
※ 2020年4月18日現在、展開後のファイルサイズは436MB、例文数は828万件です。毎日1,000件以上のペースで増えているようです。
取り込み
SQLite3 を起動します。指定したファイル名でデータベースが作成されます。
$ sqlite3 tatoeba.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
初期設定を行います。取り込み元データにダブルクォーテーションが含まれている関係上、通常のタブ区切りを指定してもうまくいかないため、ascii で区切り文字を指定します。
.headers on
.mode ascii
.separator "\t" "\n"
SQLite に取り込みます。
CREATE TABLE sentences(id INTEGER, lang CHAR(8), text TEXT);
.import sentences.csv sentences
確認
最初の5件を表示します。中国語です。
SELECT * FROM sentences LIMIT 5;
id lang text
1 cmn 我們試試看!
2 cmn 我该去睡觉了。
3 cmn 你在干什麼啊?
4 cmn 這是什麼啊?
5 cmn 今天是6月18号,也是Muiriel的生日!
英語の例文を5件表示します。
SELECT * FROM sentences WHERE lang = 'eng' LIMIT 5;
id lang text
1276 eng Let's try something.
1277 eng I have to go to sleep.
1280 eng Today is June 18th and it is Muiriel's birthday!
1282 eng Muiriel is 20 now.
1283 eng The password is "Muiriel".
上位5言語を表示します。
SELECT lang, count(*) AS c FROM sentences GROUP BY lang ORDER BY count DESC LIMIT 5;
lang c
eng 1314951
rus 760210
ita 748858
tur 689489
epo 617347
言語ごとの例文数はサイトにも掲載されており、言語の略称が何を指すのかも確認できます。
※ 略称は ISO 639-3 に準拠しています。
正規化
データベースとして扱いやすくするため正規化を試みます。
言語名
言語名を専用のテーブルで管理して番号を割り振ります。
CREATE TABLE langs(lid INTEGER PRIMARY KEY, name CHAR(8));
INSERT INTO langs(name)
SELECT DISTINCT lang FROM sentences ORDER BY lang;
CREATE INDEX langs_lang_idx ON langs(name);
内容を確認します。
SELECT * FROM langs LIMIT 5;
lid name
1 \N
2 abk
3 acm
4 ady
5 afb
※ \N
は良く分かりませんが、削除された項目のようです。
言語
先ほど割り当てた番号を使って、例文番号と言語のテーブルを作成します。
CREATE TABLE slang(id INTEGER PRIMARY KEY, lang INTEGER);
INSERT INTO slang
SELECT id, lid FROM sentences
INNER JOIN langs ON lang = name;
CREATE INDEX slang_lang_idx ON slang(lang);
内容を確認します。
SELECT * FROM slang LIMIT 5;
id lang
1 59
2 59
3 59
4 59
5 59
言語名を取得するには内部結合を使用します。
SELECT * FROM slang INNER JOIN langs ON lang = lid LIMIT 5;
id lang lid name
1 59 59 cmn
2 59 59 cmn
3 59 59 cmn
4 59 59 cmn
5 59 59 cmn
例文
例文番号と例文のテーブルを作成します。
CREATE TABLE stext(id INTEGER PRIMARY KEY, text TEXT);
INSERT INTO stext SELECT id, text FROM sentences;
内容を確認します。
SELECT * FROM stext LIMIT 5;
id text
1 我們試試看!
2 我该去睡觉了。
3 你在干什麼啊?
4 這是什麼啊?
5 今天是6月18号,也是Muiriel的生日!
元データを復元するには内部結合を組み合わせます。
SELECT stext.id, name as lang, text FROM stext
INNER JOIN slang ON slang.id = stext.id
INNER JOIN langs ON lang = lid
LIMIT 5;
id lang text
1 cmn 我們試試看!
2 cmn 我该去睡觉了。
3 cmn 你在干什麼啊?
4 cmn 這是什麼啊?
5 cmn 今天是6月18号,也是Muiriel的生日!
リンク
翻訳を対応付けるにはリンクのデータを使用します。
links.tar.bz2 を展開します。入っているのは links.csv という1ファイルだけです。タブ区切りのテキストファイルです。
例文番号 [tab] 訳の例文番号
※ 2020年4月18日現在、展開後のファイルサイズは256MBです。
取り込み
SQLite に取り込みます。
CREATE TABLE links(id INTEGER, link INTEGER);
.import links.csv links
CREATE INDEX links_id_idx ON links(id);
確認
内容を確認します。
SELECT * FROM links LIMIT 5;
id link
1 1276
1 2481
1 5350
1 5972
1 180624
id が指す例文に対しての翻訳が列挙されています。内部結合によって翻訳を確認します。
SELECT links.id, link, name as lang, text FROM links
INNER JOIN slang ON slang.id = link
INNER JOIN stext ON stext.id = link
INNER JOIN langs ON lang = lid
LIMIT 5;
id link lang text
1 1276 eng Let's try something.
1 2481 spa ¡Intentemos algo!
1 5350 kor 뭔가 해보자!
1 5972 nld Laten we iets proberen!
1 180624 por Vamos tentar alguma coisa!
言語を指定して抽出
指定した言語間で翻訳が存在する例文を抽出します。
1つの言語への翻訳
例として、英語の例文に対して日本語の翻訳が存在するものを抽出します。
WITH slname AS (SELECT id, name AS lang FROM slang INNER JOIN langs ON lid = lang),
llang AS (SELECT links.id, link, lang FROM links INNER JOIN slname ON slname.id = link)
SELECT t.id, t.lang, l1.link, l1.lang
FROM (SELECT id, lang FROM slname WHERE lang = 'eng') AS t
INNER JOIN llang AS l1 ON l1.id = t.id AND l1.lang = 'jpn'
LIMIT 5;
id lang link lang
1276 eng 4702 jpn
1277 eng 4703 jpn
1277 eng 344904 jpn
1280 eng 4705 jpn
1282 eng 4707 jpn
1277が2つあるのは、1つの英文に対して複数の和訳が存在することを表します。
2つの言語への翻訳
例として、英語の例文に対して日本語とチェコ語(ces)の翻訳が存在するものを抽出します。
WITH slname AS (SELECT id, name AS lang FROM slang INNER JOIN langs ON lid = lang),
llang AS (SELECT links.id, link, lang FROM links INNER JOIN slname ON slname.id = link)
SELECT t.id, t.lang, l1.link, l1.lang, l2.link, l2.lang
FROM (SELECT id, lang FROM slname WHERE lang = 'eng') AS t
INNER JOIN llang AS l1 ON l1.id = t.id AND l1.lang = 'jpn'
INNER JOIN llang AS l2 ON l2.id = t.id AND l2.lang = 'ces'
LIMIT 5;
id lang link lang link lang
1276 eng 4702 jpn 338207 ces
1277 eng 4703 jpn 338210 ces
1277 eng 344904 jpn 338210 ces
1280 eng 4705 jpn 338214 ces
1280 eng 4705 jpn 3564938 ces
このやり方だと、1つの例文に対して日本語とチェコ語の翻訳が複数あれば、結果が無駄に増えてしまいます。そのため個別の link を表示するのは諦めて、id だけを表示するようにします。
WITH slname AS (SELECT id, name AS lang FROM slang INNER JOIN langs ON lid = lang),
llang AS (SELECT links.id, link, lang FROM links INNER JOIN slname ON slname.id = link)
SELECT DISTINCT t.id
FROM (SELECT id, lang FROM slname WHERE lang = 'eng') AS t
INNER JOIN llang AS l1 ON l1.id = t.id AND l1.lang = 'jpn'
INNER JOIN llang AS l2 ON l2.id = t.id AND l2.lang = 'ces'
LIMIT 5;
id
1276
1277
1280
1282
1283
間接訳
先ほどの実行結果から一部を引用します。
id lang link lang link lang
1276 eng 4702 jpn 338207 ces
4702に対する翻訳を表示します。
SELECT t.id, link, name as lang, text
FROM (SELECT * FROM links WHERE id = 4702) as t
INNER JOIN slang ON slang.id = link
INNER JOIN stext ON stext.id = link
INNER JOIN langs ON lang = lid;
id link lang text
4702 77 deu Lass uns etwas versuchen!
4702 1276 eng Let's try something.
4702 2481 spa ¡Intentemos algo!
4702 3091 fra Essayons quelque chose !
4702 5350 kor 뭔가 해보자!
4702 5409 rus Давайте что-нибудь попробуем!
4702 5972 nld Laten we iets proberen!
4702 180624 por Vamos tentar alguma coisa!
4702 349370 pol Spróbujmy coś zrobić!
4702 2431762 cmn 做点儿什么尝试下吧。
4702 2740008 fin Yritetään jotain.
4702 3389511 fin Yritetäänpä tehdä jotain.
4702 3864964 ita Proviamo a fare qualcosa.
4702 4713467 ind Mari kita mencoba melakukan sesuatu.
チェコ語(ces)がありません。これが意味するのは、1276 (eng) に対する翻訳に 4702 (jpn) と 338207 (ces) が含まれますが、4702 (jpn) に対する翻訳に 338207 (ces) は含まれないということです。
Tatoeba のサイトで 4702 を表示すると、338207 "Zkusme něco!" は「間接訳」という扱いになります。
間接訳を実装するには、リンクされている例文から更にリンクをたどる必要があります。
言語別リンクテーブル
今回は実装の容易さを優先して、間接訳を調べるのではなく、次のような方針で処理します。
例えば日本語とチェコ語のペアを抽出する場合、日本語の例文にチェコ語の翻訳がリンクされているケースだけでなく、チェコ語の例文に日本語がリンクされているケースと、他の言語の例文に対して日本語とチェコ語の翻訳がリンクされているケースも抽出します。
処理を容易にするため、例文の言語とリンクされている言語を混ぜたテーブルを作ります。
CREATE TABLE llang(id INTEGER, lang INTEGER);
INSERT INTO llang
SELECT * FROM slang
UNION
SELECT DISTINCT links.id AS id, lang FROM links
INNER JOIN slang ON link = slang.id;
CREATE INDEX llang_id_idx ON llang(id);
CREATE INDEX llang_lang_idx ON llang(lang);
内容を確認します。
SELECT * FROM llang LIMIT 5;
id lang
1 16
1 33
1 34
1 43
1 59
組み合わせの抽出
指定した言語の組み合わせで例文を抽出するためには、ある id に対して指定した言語がすべて含まれているかを調べます。
例として英語・日本語・チェコ語の組み合わせを抽出します。
WITH lname AS (SELECT id, name AS lang FROM llang INNER JOIN langs ON lid = lang)
SELECT t.id FROM (SELECT id FROM lname WHERE lang = 'eng') AS t
INNER JOIN lname AS l1 ON l1.id = t.id AND l1.lang = 'jpn'
INNER JOIN lname AS l2 ON l2.id = t.id AND l2.lang = 'ces'
LIMIT 5;
id
3
35
39
42
43
抽出した組み合わせに対してテキストを表示します。
WITH lname AS (SELECT id, name AS lang FROM llang INNER JOIN langs ON lid = lang),
ids AS (
SELECT t.id AS id1 FROM (SELECT id FROM lname WHERE lang = 'eng') AS t
INNER JOIN lname AS l1 ON l1.id = t.id AND l1.lang = 'jpn'
INNER JOIN lname AS l2 ON l2.id = t.id AND l2.lang = 'ces' LIMIT 3),
sentences AS (
SELECT slang.id AS sid, name AS lang, text FROM slang
INNER JOIN langs ON lid = lang
INNER JOIN stext ON stext.id = slang.id)
SELECT * FROM (
SELECT id1 AS id, sid AS link, lang, text FROM ids
INNER JOIN sentences ON sid = id1
UNION ALL
SELECT id, link, lang, text FROM ids
INNER JOIN links ON links.id = id1
INNER JOIN sentences ON sid = link)
WHERE lang IN ('eng', 'jpn', 'ces') ORDER BY id, lang;
id link lang text
3 338213 ces Co děláš?
3 16492 eng What are you doing?
3 4704 jpn 何してるの?
3 187615 jpn 何をしているの。
3 344912 jpn 何をしていますか。
35 1058169 ces Takhle nemůžu žít.
35 1315 eng I can't live that kind of life.
35 4742 jpn 私はそんな風には生きられない。
39 1058175 ces Většina lidí si myslí, že jsem blázen.
39 1322 eng Most people think I'm crazy.
39 4747 jpn 大抵の人は僕を気違いだと思っている。
これが今回やりたかったことです。
スクリプト
処理を Python スクリプトにまとめました。
コマンドラインオプションで複数言語を指定して例文を抽出できます。
※ -n
は表示する例文の数、-m
は1つの例文に対して表示する翻訳の上限です。
$ python gets.py -n 3 -m 1 eng jpn ces
sentences: 8141
#3 cmn: 你在干什麼啊?
eng: What are you doing?
jpn: 何してるの?
ces: Co děláš?
#35 cmn: 我不能活那種命。
eng: I can't live that kind of life.
jpn: 私はそんな風には生きられない。
ces: Takhle nemůžu žít.
#39 cmn: 大部份的人覺得我瘋了。
eng: Most people think I'm crazy.
jpn: 大抵の人は僕を気違いだと思っている。
ces: Většina lidí si myslí, že jsem blázen.
今後は抽出した例文の活用方法を考えたいです。
関連記事
多言語辞書 Wiktionary のダンプからデータを抽出する記事です。