ナード戦隊データマン

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

入力語と共起しやすい単語をレコメンド

記事を書く人が利用するボキャブラリーやアイデアに悩んでいるとき、入力語に関連する単語をレコメンドすることによってアイデアが生まれるかもしれません。ここでは、そのような単語レコメンドのためのスクリプトを書きます。

f:id:mathgeekjp:20190509100346j:plain

wikipediaダンプから共起グラフを作成

enwiki-latest-pages-articles.xml.bz2をダウンロードし、展開したxmlファイルをdumpという名前で保存します。

そして、dumpから、文内の共起アンカーを取得します。

# coding: utf-8
import re
from tqdm import tqdm
import pickle


def extract_mention(exp):
    tmp = exp[2:-2]
    tmp2 = tmp[0].upper() + tmp[1:]
    if "|" in tmp2:
        entity, mention = tmp2.split("|")
        mention = mention.strip()
    else:
        mention = tmp[:]
    return mention


if __name__ == "__main__":
    reg = re.compile(r"\[\[.+?\]\]")
    out = {}
    mention_dict = {}
    mention_num = 0
    with open("dump", errors='ignore') as f1:
        with open("mention_graph.txt", "w") as f2:
            for line in tqdm(f1):
                mentions = []
                for x in re.findall(reg, line):
                    try:
                        mention = extract_mention(x)
                        if mention not in mention_dict:
                            mention_dict[mention] = mention_num
                            mention_num += 1
                        mentions.append(mention)
                    except Exception:
                        continue

                for i in range(len(mentions) - 1):
                    for j in range(i + 1, len(mentions)):
                        pair = [
                            mention_dict[mentions[i]],
                            mention_dict[mentions[j]]
                        ]
                        f2.write(str(pair[0]) + "\t" + str(pair[1]) + "\n")

    with open("mention2idx.pkl", "wb") as f:
        pickle.dump(mention_dict, f)

グラフをsqlite3へ保存

from tqdm import tqdm
import sqlite3


def create_table(conn):
    c = conn.cursor()
    sql = """
create table if not exists graph (
    id integer primary key,
    from_id integer NOT NULL,
    to_id integer NOT NULL
);
"""
    c.execute(sql)


def insert_graph(conn, f, t):
    c = conn.cursor()
    sql = "insert into graph(from_id,to_id) values (?,?)"
    c.execute(sql, (f, t))


if __name__ == "__main__":
    debug = False
    conn = sqlite3.connect("db_mention.sqlite3")
    create_table(conn)
    with open("mention_graph.txt") as f:
        for line in tqdm(f):
            line = list(map(int, line.strip().split("\t")))
            insert_graph(conn, line[0], line[1])
            insert_graph(conn, line[1], line[0])
    conn.commit()
    c = conn.cursor()
    c.execute("create index graph_index_from on graph (from_id)")
    c.execute("create index graph_index_to on graph (to_id)")
    c.execute("create index graph_index_from_to on graph (from_id, to_id)")
    print("Done!")

recommendモジュールの作成

# coding: utf-8
import sqlite3
import pickle


def recommend(word, m2id, id2m, dbfile="./db_mention.sqlite3", k=10):
    conn = sqlite3.connect(dbfile)
    from_id = m2id[word]
    c = conn.cursor()
    c.execute(
        "select count(*),to_id from graph where from_id=? group by to_id",
        (from_id, ))
    result = c.fetchall()
    return [
        id2m[x[1]]
        for x in sorted(result, key=lambda x: x[0], reverse=True)[:k]
    ]


if __name__ == "__main__":
    print("prepareing data...")
    with open("./mention2idx.pkl", "rb") as f:
        m2id = pickle.load(f)
        id2m = {x[1]: x[0] for x in m2id.items()}

    word = None
    while (word != "0"):
        try:
            word = input("word >").strip()
            print(recommend(word, m2id, id2m))
        except KeyError:
            continue

モジュールの実行

$ python word_recommender.py
prepareing data...
word >GNU
['Linux', 'free software', 'Debian', 'Richard Stallman', 'coreutils', 'Free Software Foundation', 'GNU', 'Unix-like', 'operating system', 'Windows']
word >Abe Shinzo
['Japanese Self-Defense Force<nowiki/>s', 'Vanderbilt University', 'Prime Minister', 'Japan', 'United Nations Command', 'Yasukuni', 'Nautilus Institute', 'stuff', 'Koike Yuriko']
word >Emacs
['Vim', 'text editor', 'talk', 'vi', 'Richard Stallman', 'Emacs Lisp', 'Unix', 'GNU', 'C', 'Lisp']

考察

このモデルは、名詞的な共起関係を抽出するのには適しているかもしれませんが、述語は取得できません。 例えば、"sports day"に対してレコメンドされるのは以下です:

"football", "netball", "rugby", "trophy", "cricket", "houses", "Deepavali", "100 meters", "public holidays", "quiz"

X is played on sports day. (文脈的な集合) X is a kind of sports day. (下位集合) X is a class of sports day. (上位集合) X is in the same class of sports day.(同レベルの集合)

共起する語は、上記のような何らかの述語関係によって接続されている可能性があります。このような関係を分類することができるのであれば、分類ごとに単語を表示することが可能になるので、より便利なレコメンデーション機能になりそうな気はします。

しかし、名詞と名詞の関係を予測する、という問題そのものを解く必要が出てきます。OpenIEのようなタスクもありますが、より少数の本質的な分類を行うためにはどうすればよいのか、という点が課題です。

補足

これらのスクリプトの実行のためには、200G程度のストレージと16G程度のメモリが必要です。