latexのソースからMeCabを用いて名詞だけ抜き出す

■記事の概要:latexのソースから形態素解析器を用いて名詞だけ抜き出したリストを作る.
■環境:MinGW または MSYS2 (Cygwin でも多分大丈夫だろうな~と思うが試していない)
■主な道具:MeCab, python3, make

■動機と背景:latexで作成しているPDFファイルのページ数が増えてきたので,索引を付けようと考えた.(索引はlatex の\indexという命令で作れるが,この記事ではこれ以上触れない).最初は索引の項目を手で付けていたが,思いついたものを十個ぐらい挙げたら飽きてしまった.そこで,《latexソースファイルの中から名詞だけ抜き出す》ことを考えた.

■環境整備
1.MinGW または MSYS2 については環境整備が済んでいるものとする.
2.MeCabのインストール:Windows用に配布されているバイナリを使う.公式からリンクされているダウンロードページ(http://taku910.github.io/mecab/#download)からBinary package for MS-Windowsという項目の直下の mecab-0.996.exe をダウンロードし,インストールした.インストール時にエンコーディングを指定するように言われる.今回の目的はlatexのソース処理なので utf-8 を選ぶ.(デフォルトだとShift-JPだったように記憶している).
3.Pythonのインストール:Windows用に配布されているバイナリを使う.公式ページ(https://www.python.org/)からDownload/latestに飛び,そこから「Windows x86-64 executable installer」と題されたファイルをダウンロードし,インストールした.この記事が書かれた時点での最新版は3.6.4であった.
4.パスを通す.MecabとPythonがMinGWシェルから見えるようにパスを通す必要がある.(MinGW なら
C:\MinGW\msys\1.0\home\YourUserName, MSYS2 なら C:\msys64\home\YourUserName などに置いてある).bashrc の下の方に次のような行を加える:

export MECABPATH=/c/MeCab
export PATH=$PATH:$MECABPATH/bin

export PYTHONPATH=/c/Python3.6.4
export PATH=$PATH:$PYTHONPATH
export PYTHONIOENCODING=UTF-8

※《/c/MeCab》や《/c/Python3.6.4》は各自のインストールの実態に合わせて適宜変更してほしい.
※最後の《export PYTHONIOENCODING=UTF-8》という行はパスを通しているのではなく,ユニコード文字(utf-8)をpython3で扱うときに必須らしい設定である.

■MeCab用のユーザー辞書作成
MeCab には「微分」や「積分」のような一般的な用語は(Windowsバイナリに同梱の)辞書ファイルに登録されている.しかし,それほど一般的でない用語は登録されておらず,バラバラの言葉や文字として認識されてしまう.そこでユーザ辞書を作成する.ユーザ辞書の元になるデータは次のような形式のcsvファイルである:

無限小,,,10,名詞,一般,*,*,*,*,個別名,アアアア,REGISTERED
無限大,,,10,名詞,一般,*,*,*,*,個別名,アアアア,REGISTERED
無限小量,,,10,名詞,一般,*,*,*,*,個別名,アアアア,REGISTERED
無限大量,,,10,名詞,一般,*,*,*,*,個別名,アアアア,REGISTERED

「アアアア」などとなっている箇所は本来きちんとした読みを登録するのだが面倒であり,今回の業務と無関係なのでこのようにしてある.(今回は説明を省略するが,一行に一つ単語を書いたファイルからこのようなファイルを自動生成している.)「REGISTERED」は後で解析結果を加工するためのヒントとするための追加タグである.

■Makefileの作成
次のようなMakefileを用意する.

help:
	@grep -E '^[0-9a-zA-Z_-]+[[:blank:]]*:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[1;32m%-30s\033[0m %s\n", $$1, $$2}'

dict : mecab.csv ## add user dictionary for mecab by mecab.csv
	mecab-dict-index -d$(MECABPATH)/dic/ipadic -u ./mecab.dic -f utf8 -t utf8 mecab.csv

mecab : hoge.tex ## analyze hoge.tex by mecab
	$(eval MECAB_OUT := $(strip $(basename .tex $ $(UNIQ)
	python extractNoun.py  $(UNIQ) > $(RESULT)
	rm  $(MECAB_OUT) $(UNIQ)

make dict としてやると mecab.csv が処理され mecab.dic というMeCab用ユーザー辞書が作成される.(helpターゲットのルールについては以前の記事を参考にしてほしい).

make mecab としてやるとまずターゲットの hoge.tex が MeCab で処理され,hoge-out.txt が生成される.このファイルはhoge.texのすべての内容を品詞分解して一行に一つずつ並べたものになっているので重複が多い.そこでhoge-out.txt sort し uniq して重複を除去して hoge-out-u.txtを生成する.(前段の sed については以前の記事を参考にされたい.)今回は「名詞」とタグ付けされたものだけ欲しいので,hoge-out-u.txt を自作のpythonスクリプト extractNoun.py で処理して最終結果の hoge-nouns.txt を得る.

★Makeルールの中で変数を定義する方法については『makefile 動的に変数に代入 共通して使う』を参考にさせて頂いた.

■pythonスクリプト extractNoun.py

# -*- coding: utf-8 -*-
import sys
import re #正規表現
import codecs
import copy

reNegIgnoreFlag = re.compile('REGISTERED')
reNeg = re.compile('接頭詞')
rePos = re.compile('(.+?)名詞')
reNum = re.compile('(\d+)')
reSymbols = re.compile('[!\$%\#&\(\){}\+,\-@\.\\\\=\"\'\*:;\?–\[\]|_`\^,]+')

def main():
    f = open(sys.argv[1], 'r', encoding="utf-8_sig")
    input = f.readlines()

    for line in input:
        myFilter(line)

def myFilter(line):
    pos = rePos.search(line)
    neg1 = reNeg.search(line)
    neg2 = reNegIgnoreFlag.search(line)

    if pos and (not neg1) and (not neg2):
        result = pos.group(1).strip()
        isnumber = reNum.search(result)
        containsSymbols = reSymbols.search(result)
        if (not isnumber) and (not containsSymbols):
            sys.stdout.write(result)
            sys.stdout.write('\n')

if __name__ == '__main__' : main()

解説:関数 main() では,一番目の引数 sys.argv[1] として与えられたファイル名を持つファイルを開き,readlines で行の集まりに分解し,各行を関数 myFilter() で処理している.
関数 myFilter() では,事前にコンパイルされた5つの正規表現 reNegIgnoreFlag,reNeg,rePos,reNum,reSymbols を使って「欲しそうなものを抜き出し,要らないものを弾く」ことをやっている.それぞれの正規表現の目的は次の通り:

・reNegIgnoreFlag:MeCabのユーザー辞書に登録済みの単語を検出するため.(MeCabのユーザー辞書のcsvファイルは索引づくりのヒントファイルを兼ねており,そこにまだ登録されていない単語を探すのが今回の目的なので).
・reNeg:「名詞」タグが付いているもののうち,さらに「接頭詞」というタグが付いたものを排除するため.このタグがつくものは「不」「無」「第」「最」などの一文字単語(?)であり,今回の目的にはノイズなので弾く.
・rePos:「名詞」タグを検出するため.
・reNum:年号などの数字列を弾くため.
・reSymbols:latexソースを自然言語解析器にかけた結果として大量の無意味記号列が(MeCabによって,仕方なく)「名詞」に分類されてしまうためそれらを弾くため.

広告
コメントする

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

w

%s と連携中

%d人のブロガーが「いいね」をつけました。