ナード戦隊データマン

機械学習と自然言語処理についてのブログ

相互翻訳されたhtml内の文をalignmentする

Sentence Alignment: 翻訳文は常に1対1で対応しているわけではありませんが、Sentence Alignmentでは、そのようなコーパスから1対1に対応したパラレルテキストを生成するタスクです。

github.com

やりたいこと

何らかの方法で「構造が極めて類似している」と判定された、相互翻訳されたWebページのペアがあるとします。そのhtmlのペアから、さらに1対1対応した文のペアのリストを抽出するのが目標です。

かんたんな方法

HTML構造が極めて類似しているのであれば、対応したxpathの文は一致している可能性が高いはずです。例えば、こちらにあるen.htmlとja.htmlは構造的に似た翻訳ページと言えます。

そこで、以下のようなコードを実行します。

import sys
import math
from lxml import etree
from difflib import SequenceMatcher
import MeCab
from functools import reduce
import operator
import re

tagger = MeCab.Tagger("-Owakati")


def tokenizer(text):
    return tagger.parse(text).split()


def p_nodetexts(text1, text2, tokenizer1, tokenizer2):
    s1 = len(tokenizer1(text1))
    s2 = len(tokenizer2(text2))
    diff_word = abs(s1/(s1+s2+1) - s2/(s1+s2+1))
    return 1/(diff_word+1)


def parse_html(html):
    root = etree.fromstring(html.encode("utf-8"), parser=etree.HTMLParser())
    return etree.ElementTree(root)


def getFromDict(dataDict, mapList):
    return reduce(operator.getitem, mapList, dataDict)


def setInDict(dataDict, mapList, value):
    getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value


def format_text(text):
    text = text.replace("\n", " ").replace("\r", "")
    text = re.sub(r"[ ]+", " ", text)
    return text


def get_same_xpath(tree1, tree2):
    xpaths = {}
    texts = []
    stack = [tree1.getroot()]
    while stack:
        elm1 = stack.pop()
        tags = ["html", "title", "head", "body", "meta", "link", "script", "!--"]
        stack += elm1.getchildren()
        if elm1.tag in tags:
            continue
        path1 = tree1.getpath(elm1)
        try:
            elm2 = tree2.xpath(path1)[0]
        except IndexError:
            continue
        if elm2 is not None:
            path1 = path1.split("/")[1:]
            for i, tag in enumerate(path1):
                if tag not in getFromDict(xpaths, path1[:i]):
                    setInDict(xpaths, path1[:i + 1], {})
                else:
                    if elm1.text and elm2.text:
                        elm1.text = format_text(elm1.text)
                        elm2.text = format_text(elm2.text)
                        getFromDict(xpaths, path1[:i])[tag]["_src"] = elm1.text
                        getFromDict(xpaths, path1[:i])[tag]["_tgt"] = elm2.text
                        texts.append(elm1.text + "\t" + elm2.text)
    return xpaths, list(set(texts))


def align_texts(texts, threshold=0.65, tokenize=tokenizer):
    out = []
    for text in texts:
        try:
            text1, text2 = text.split("\t")
        except:
            continue
        p = p_nodetexts(text1, text2, tokenize, tokenize)
        print(p)
        if p < threshold:
            continue
        else:
            out.append(text)
    return out


if __name__ == "__main__":
    import json
    import pprint
    with open(sys.argv[1]) as f:
        tree1 = parse_html(f.read())

    with open(sys.argv[2]) as f:
        tree2 = parse_html(f.read())

    xpaths, texts = get_same_xpath(tree1, tree2)
    pprint.pprint(texts)

すると、対応するxpathの文が抽出されます。抽出されたものの例は以下で公開しています。

html_alignment_tools/test_txt.txt at master · sugiyamath/html_alignment_tools · GitHub

length-based method

抽出された文の中には、翻訳として適さないペアが存在しています。そこで、文の単語数を使って、ある値を出力し、その値が閾値以下であれば除外することを検討してみます。

def p_nodetexts(text1, text2, tokenizer1, tokenizer2):
    s1 = len(tokenizer1(text1))
    s2 = len(tokenizer2(text2))
    diff_word = abs(s1/(s1+s2+1) - s2/(s1+s2+1))
    return 1/(diff_word+1)

def align_texts(texts, threshold=0.65, tokenize=tokenizer):
    out = []
    for text in texts:
        try:
            text1, text2 = text.split("\t")
        except:
            continue
        p = p_nodetexts(text1, text2, tokenize, tokenize)
        if p < threshold:
            continue
        else:
            out.append(text)
    return out

このスコアは、「異なる言語であっても、単語数はだいたい近くなる」というような仮定を持っています。

    xpaths, texts = get_same_xpath(tree1, tree2)
    texts1 = align_texts(texts)
    texts2 = align_texts(texts, threshold=0.0)
    pprint.pprint(set(texts2).difference(set(texts1)))

以下は、除外されたペアです。

 'Event\t在学生からのメッセージ',

 'Event\t研究分野のご案内',

 'Low Temperature Quantum Physics Group performs the experimental researches '
 'in condensed matter physics at very low temperatures, in high magnetic '
 'fields and under high pressures. Besides the developments of new techniques '
 'and detection systems under such extreme conditions, synthesis of novel '
 'materials and growth of high quality single crystals are other major '
 'activities of the group.\t物質は一辺が1 cmの箱当たりにして10',

 'Material science is one of the key word to consider the modern society, and '
 'the structural and crystal physics gives a fundamental base for this '
 'field. \t'
 '結晶は原子が規則的に並んだものですが、相転移(例えば黒鉛がダイヤモンドになる等)が起こると原子や電子の分布は何らかの事情で変化します。それがたとえ僅か0.1Å以下の変化であっても結晶の性質(誘電性、伝導性、磁性など)が大きく変わることがしばしば起こります。このような原子や電子の変位をX '
 '線や中性子線などを用いた回折実験で「観る」ことにより、結晶の世界の法則を明らかにしていきます。',

 'Our research group is aiming at the ultrafast optical/THz manipulations of '
 'corrective electron and spin motions in solids by using advance light source '
 'such as mono-few optical cycle IR pulss and THz pulse.\t'
 '現代の光科学は、物質の性質を調べるという従来の枠組みを超えて、光で新しい物質相を '
 '創り、制御する、よりダイナミックな領域へと広がりつつあります。これは超高速、大容量の光通信、光コンピューティングを目指す社会的要請(Society '
 '5.0)に沿ったものです。 '
 '本研究グループでは、従来の半導体などを対象とした光機能の研究の枠を超え、高温超伝導体、量子スピン液体/マヨラナフェルミオン物質や、強相関ディラック半金属など、量子多体効果が生み出す「強相関電子」の世界を対象とします。極限的な赤外短パルス光(パルス幅~ '
 '5 フェムト秒、フェムト秒=千兆分の一 (10',

 'Our research targets are understanding of fundamental crystal growth '
 'mechanisms and development of crystal growth technology for obtaining '
 'high-quality materials. \t'
 '結晶成長物理グループでは、液相または気相から固相が形成される過程で生じる様々な現象を研究対象としています。半導体、金属合金、酸化物などの実用バルク材料の多くは液相からの結晶成長により作製されています。結晶成長過程において固液界面でどのようなメカニズムで結晶が成長し、結晶材料の組織がどのようなメカニズムで形成されていくのか、といった融液成長の本質はほとんど理解されていません。当グループでは結晶成長メカニズムを基礎的に解明し、これをベースに新規な結晶成長技術を開発し、高品質結晶材料を実現することを目指しています。',

 '\u3000We are currently studying nano size materials comprised of IV\t'
 '近年の物性物理の進展は、物質の極微細構造に踏み込んで物性と構造との関係を理解し、それを基礎として新しい素材を開拓する時代に突入している。この様な物性分野の進展は、これまで微細加工技術を基礎として進められてきた。現在では、電子線加工技術などにより数百Å程度のサイズの加工が可能である。さらに、トンネルスペクトロスコピーを利用すれば原子を1個ずつ制御する事が可能な時代でもある。'

例えば、'Event\t在学生からのメッセージ' というペアは翻訳として適していません。Eventを日本語で訳しても、「在学生からのメッセージ」にはならないからです。

このように、length-based手法を適用すると、適していないペアが除外されたことがわかります。

課題

このタスクの課題は、「より多く(recall)の翻訳文を、より正確に(precision)」抽出することです。length-based手法は、データも訓練も使わずに実行ができました。しかし、精度をあげようと思ったら、辞書や特徴量設計+訓練が必要になるかもしれません。

ただ、辞書や訓練する手間がかかるような手法は、多数の言語に対応させる際に面倒になります。各々の言語ごとにデータを用意しなければならないためです。bitextor1のようなツールがヨーロッパ言語にしか対応していないのは、少なくとも、言語ごとのトーカナイザと辞書の問題があります。

精度の高いコーパスを作成するためには、例えば抽出された文を人の目で確かめる作業が必要になるかもしれません。しかし、数億のペアを作成するためには、何年もかかる作業になってしまうでしょう。

また、xpathを指定する方法の場合、「より多くの翻訳文を抽出する」という部分が難点になります。翻訳されたページにおいて、xpathが正確に一致している要素は、dom構造が単に類似しているだけの要素よりもずっと少ないです。

参考