人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

テキストからWikipedia見出し語を抽出

WindowsでMeCab Pythonを使う(2010/11/21)のつづきです。形態素解析を使ってると単語が変なところで切れていたり、未知語が多かったりと不満点が出てきます。また、応用によっては、形態素ではなく、複合語単位で抽出したいということもしばしばあります。たとえば、

人工知能は、コンピュータに人間と同様の知能を実現させようという試み、あるいはそのための一連の基礎技術をさす。
人工知能という名前は1956年にダートマス会議でジョン・マッカーシーにより命名された。
現在では、機械学習、自然言語処理、パターン認識などの研究分野がある。(Wikipedia『人工知能』を改変)

という文章をMeCabで形態素解析して名詞のみ取り出すと、

人工 知能 コンピュータ 人間 同様 知能 実現 試み ため 一連 基礎 技術 人工 知能 名前 1956 年
ダート マス 会議 ジョン マッカーシー 命名 現在 機械 学習 自然 言語 処理 パターン 認識 研究 分野

となります。一方、Yahoo!形態素解析(2009/4/15)を使うと

人工 知能 コンピュータ 人間 同様 知能 実現 試み ため 一連 基礎 技術 人工 知能 名前 1956
ダート マス 会議 ジョン マッカーシー 命名 現在 機械 学習 自然 言語 処理 パターン 認識 研究 分野

となります。「年」がないだけでMeCabの結果とほとんど同じです。形態素解析は、人工知能、機械学習、自然言語処理、ダートマス会議、ジョン・マッカーシー大先生などの複合語や人名がばらばらにされてしまいます*1ここでの目的は、テキストから複合語のままで名詞を抽出することです。この問題を解決する手段としてWikipediaのデータを使ってみます*2。次の2つのやり方を試してみました。

  1. MeCabのユーザ辞書にWikipedia見出し語を登録する
  2. Ternary Search Tree (TST) を使ってテキストからWikipedia見出し語を最長一致で切り出す

Wikipedia見出し語データ

WikipediaのデータはWikipedia:データベースダウンロードからダウンロードすることができます。見出し語のリストや本文も含めた全データもダウンロードできます。ここでは、見出し語ファイルのみ使います。現時点で最新の2010/11/02の見出し語リストファイルall-titles-in-ns0.gz (6.8MB) をダウンロードしました。中を見ると

人工現実感
人工甘味料
人工生命
人工生命体
人工皮膚
人工皮革
人工着色料
人工知能
人工知能学会
人工磁場シールド
人工神経
人工神経_(再生医療)
人工筋肉
人工粘性

こんな感じで見出し語がずらずら並んでます。ファイルサイズは24MBで1146584語が登録されています(2010/11/2時点)。いろんな語が登録されていて面白いです。人名、地名、学術用語も豊富です。この単語を全部MeCabのユーザ辞書にぶちこんでやれば「人工知能」などの複合語の単位で形態素が抽出できそうです。

Wikipedia見出し語をMeCabユーザ辞書に登録

MeCabは、ユーザがシステム辞書やユーザ辞書に単語を追加できるようになっています。単語の追加方法のページを見るとユーザ辞書への追加は、

  • user.csvというファイルに単語を追加
  • mecab-dict-indexというツールでcsv形式の辞書をコンパイル
  • 解析時に-u {ユーザ辞書ファイル}オプションを追加

だそうです。まず、Wikipedia見出し語の単語リスト(jawiki-20101102-all-titles-in-ns0)をuser.csvに変換するPythonスクリプトです。実行するとuser.csvができます。単語の品詞は、すべて名詞・一般にしています。読みは単語リストからは取れないので無視しました。また、単語のコストは、MeCab の辞書構造と汎用テキスト変換ツールとしての利用を参考にしました。また、すべての見出し語を登録せず、1文字の単語や数字のみの単語は除外しました。

#coding:utf-8
import codecs
import re
import unicodedata
import sys

# wikipedia2csv.py

alias = re.compile(ur"_\(.*?\)")
alldigit = re.compile(ur"^[0-9]+$")

def isValid(word):
    """wordが登録対象の単語のときTrueを返す"""
    # 1文字の単語は登録しない
    if len(word) == 1:
        return False
    # コジコジ_(小惑星)のように別名は登録しない
    if alias.search(word) != None:
        return False
    # 数字だけの単語は登録しない
    if alldigit.search(word) != None:
        return False
    # 仮名2文字の単語は登録しない
    if len(word) == 2 and unicodedata.name(word[0])[0:8] == "HIRAGANA" and unicodedata.name(word[1])[0:8] == "HIRAGANA":
        return False
    # 仮名、漢字、数字、英字以外の文字を含む単語は登録しない
    for c in word:
        if not (unicodedata.name(c)[0:8] == "HIRAGANA" or
                unicodedata.name(c)[0:8] == "KATAKANA" or
                unicodedata.name(c)[0:3] == "CJK" or
                unicodedata.name(c)[0:5] == "DIGIT" or
                unicodedata.name(c)[0:5] == "LATIN"):
            return False
    return True

if __name__ == "__main__":
    fout = codecs.open("user.csv", "w", "utf-8")
    fin = codecs.open("jawiki-20101102-all-titles-in-ns0", "r", "utf-8")
    for line in fin:
        word = line.rstrip()
        # Wikipedia見出し語を整形
        if isValid(word):
            cost = int(max(-36000, -400 * len(word)**1.5))
            fout.write("%s,-1,-1,%d,名詞,一般,*,*,*,*,*,*,*,wikipedia\n" % (word, cost))
    fin.close()
    fout.close()

次に、user.csvをコンパイルします。MeCabのパスはデフォルトのままなので必要に応じて変えてください。

"C:\Program Files\MeCab\bin\mecab-dict-index.exe" -d "C:\Program Files\MeCab\dic\ipadic" -u user.dic -f utf-8 -t utf-8 user.csv

さっそくユーザ辞書を使って解析してみます。MeCab.Tagger()の引数にユーザ辞書のオプションを指定しました。

#coding:utf-8
import MeCab

def extractKeyword(text):
    """textを形態素解析して、名詞のみのリストを返す"""
    tagger = MeCab.Tagger('-u user.dic')
    node = tagger.parseToNode(text.encode('utf-8'))
    keywords = []
    while node:
        if node.feature.split(",")[0] == u"名詞":
            keywords.append(node.surface)
        node = node.next
    return keywords

if __name__ == "__main__":
    text = u"""人工知能は、コンピュータに人間と同様の知能を実現させようという試み、
        あるいはそのための一連の基礎技術をさす。
        人工知能という名前は1956年にダートマス会議でジョン・マッカーシーにより命名された。
        現在では、機械学習、自然言語処理、パターン認識などの研究分野がある。"""
    keywords = extractKeyword(str(text))
    for w in keywords:
        print w,

結果は、

人工知能 コンピュータ 人間 同様 知能 実現 試み ため 一連 基礎 技術 人工知能 名前 1956年 ダートマス会議
ジョン・マッカーシー 命名 現在 機械学習 自然言語処理 パターン認識 研究 分野

大成功!ちゃんと複合語で取り出せました。人工知能、ダートマス会議、ジョン・マッカーシー、機械学習、自然言語処理、パターン認識などはWikipediaの見出し語に登録されているのでちゃんと切り出せてます。ちなみにWikipedia見出し語は辞書登録時にwikipediaという目印を追加しています。

fout.write("%s,-1,-1,%d,名詞,一般,*,*,*,*,*,*,*,wikipedia\n" % (word, cost))

そのため、Taggerの素の出力は、

人工知能     名詞,一般,*,*,*,*,*,*,*,wikipedia
は     助詞,係助詞,*,*,*,*,は,ハ,ワ
、     記号,読点,*,*,*,*,、,、,、
コンピュータ     名詞,一般,*,*,*,*,*,*,*,wikipedia
に     助詞,格助詞,一般,*,*,*,に,ニ,ニ
人間     名詞,一般,*,*,*,*,*,*,*,wikipedia

のようになっておりWikipediaの見出し語に登録されている語のみを取り出すことも可能です。

TSTでWikipedia見出し語を抽出

テキストからWikipediaに登録されている見出し語を抽出したいという目的は同じですが、今度は、MeCabを使わずに別のアプローチを試みます。入力した任意のテキストから辞書に登録されている単語を切り出したい場合は、Trieというデータ構造が使えます(参考:Aho Corasick法)が、今回は、Trieを改良したTernary Search Treeというデータ構造を使ってみます。Pythonには、TSTを実装したPyTSTというライブラリがすでにあるので使ってみました。tst.TST()オブジェクトの辞書に単語を登録して、scan(text)を呼び出すとtextから辞書に登録した単語をすべて抽出できます。

#coding:utf-8
import codecs
import re
import sys
import tst  # pytst https://github.com/nlehuen/pytst/downloads
import unicodedata

alias = re.compile(ur"_\(.*?\)")
alldigit = re.compile(ur"^[0-9]+$")

def isValid(word):
    """wordが登録対象の単語のときTrueを返す"""
    # 1文字の単語は登録しない
    if len(word) == 1:
        return False
    # コジコジ_(小惑星)のように別名は登録しない
    if alias.search(word) != None:
        return False
    # 数字だけの単語は登録しない
    if alldigit.search(word) != None:
        return False
    # 仮名2文字の単語は登録しない
    if len(word) == 2 and unicodedata.name(word[0])[0:8] == "HIRAGANA" and unicodedata.name(word[1])[0:8] == "HIRAGANA":
        return False
    # 仮名、漢字、数字、英字以外の文字を含む単語は登録しない
    for c in word:
        if not (unicodedata.name(c)[0:8] == "HIRAGANA" or
                unicodedata.name(c)[0:8] == "KATAKANA" or
                unicodedata.name(c)[0:3] == "CJK" or
                unicodedata.name(c)[0:5] == "DIGIT" or
                unicodedata.name(c)[0:5] == "LATIN"):
            return False
    return True

def createKeywordTree(filename):
    """ファイルに登録されている見出し語からキーワードツリーを作成して返す"""
    try:
        fp = codecs.open(filename, "r", "utf-8")
    except IOError, e:
        print e
        return None

    tree = tst.TST()
    for line in fp:
        word = line.rstrip()
        if isValid(word):
            tree[word] = True  # wordをTSTに登録
    fp.close()
    return tree

def analysis(text, tree):
    """textからWikipedia見出し語を抽出する"""
    keywords = []
    result = tree.scan(text, tst.TupleListAction())
    for t in result:
        if t[2] == True:
            try:
                word = unicode(t[0], "utf-8")
                keywords.append(word)
            except UnicodeDecodeError:
                print e
                continue
    return keywords

if __name__ == "__main__":
    text = u"""人工知能は、コンピュータに人間と同様の知能を実現させようという試み、
        あるいはそのための一連の基礎技術をさす。
        人工知能という名前は1956年にダートマス会議でジョン・マッカーシーにより命名された。
        現在では、機械学習、自然言語処理、パターン認識などの研究分野がある。"""

    tree = createKeywordTree("jawiki-20101102-all-titles-in-ns0")
    keywords = analysis(text, tree)
    for w in keywords:
        print w

実行すると、

人工知能 コンピュータ 人間 知能 基礎 技術 人工知能 名前 1956年 ダートマス会議
ジョン・マッカーシー 命名 現在 機械学習 自然言語処理 パターン認識 研究

となります。MeCabは単語コストの調整が面倒で長い単語が切り出せないこともあるようなので、最長一致で抽出できるTSTの方が楽かもしれません。TSTの構築にちょっと時間かかるけど。

まとめ

今回は、テキストからWikipedia見出し語を抽出してみました。形態素よりもっと大きな単位で語を抽出できるのでお手軽固有表現抽出みたいな感じです。Wikipedia見出し語以外に、はてなキーワードも使えそうです。また、Wikipediaの本文を含めた巨大なダンプデータを使うと見出し語のカテゴリや読みも収集できます。Wikipediaのデータは、シソーラス辞書の作成オントロジーの自動構築など面白い応用がたくさん研究されてます。こういうデータを活用して賢い?無能を作るのは面白そうなテーマだなー。いずれ取り上げたいと思います。

*1:ばらばらにするとよくないみたいに書いてますが、実際は、bag-of-wordsモデルで文書分類するときはばらばらの方が精度が高い場合もあります。応用によりけりです。

*2:人名や地名の抽出は固有表現抽出と呼ばれる立派な研究分野がありますが、難しいこと考えずにもっとお手軽にやろうという感じです