正規表現・自然言語処理

研究室の勉強会で正規表現自然言語処理について話したのでそれのまとめ.

正規表現

クロールしたWebページのテキストから時間に関する部分だけを抽出したい,
といった時に機械的にパターンに当てはまる部分を抽出するための表現.

メタ文字
  • .:任意の一文字
  • [~]:括弧内の任意の一文字
  • [^~]:括弧内の~以外の任意の一文字
  • ?:0or1回
  • *:0回以上
  • +:1回以上
  • |:or
  • (~):グループ化
  • {1, 3}:1回以上3回以下
    • ?を付けるとなるべく少なくなるように取得
文字クラス
  • \s:空白文字
  • \w:[a-zA-Z0-9_]
  • \d:[0-9]
  • \b:スペース

ちなみに漢字は文字コードの連続から以下のように表すことが可能.もちろん漢字はこれだけではない.

  • [一-龠] ... [一丁丂…龟龠](u’\u4e00’〜u’\u9fa0’)
メタ文字

. ^ $ * + ( ) { } [ ] \ |

これらの記号を使うときには \ を付ける
つまり,pythonにおいて \ を使うには \\ としなければならないことから,
正規表現で \ を使うには \\\\ としなければならない.
これは

r'\\'

というように文字列の前に r を付けることでその文字列が正規表現を表してますよ,という宣言になり,
\ を4つ使うようなことがなくなる.

正規表現における注意点

or を表す | は処理が遅い.

  1. (a|b|c|d|e|f)
  2. (?:a|b|c|d|e|f)
  3. [a-f]

という3つだと2.は1.の3倍,3.は1.の20倍の速度が出る,らしい.
2.は1.からキャプチャ機能を省いたもの.

re

pythonでreモジュールを用いる.

In [1]: import re
In [2]: text = (u'pythonとはlightweightな,'
           u'programming言語である').encode('utf-8')
In [3]: pattern = '\w+'
In [4]: regex = re.compile(pattern)
In [5]: match = regex.finditer(text)
In [6]: for m in match:
   ...:     print m.group()
   ...:    
python
lightweight
programming
In [7]: matches = regex.findall(text)
In [8]: print matches
['python', 'lightweight', 'programming']
In [9]: pattern = r"(\w+)\W*(\w+)\W*(\w+)"
In [10]: regex = re.compile(pattern)
In [11]: match = regex.search(text)
In [12]: print match.group(0)
pythonとはlightweightな,programming
In [13]: print match.group(1)
python
In [14]: print match.group(2)
lightweight
In [15]: print match.group(3)
programming
In [16]: p2 = "(?P<one>\w+)\W*(?P<two>\w+)”
In [17]: print match2.group('two')
lightweight

最後のp2のように?Pでグループにタグを付けることが出来る.
この程度知っておけば簡単な正規表現は使えるはず.

自然言語処理

まず,自然言語とはWikipediaによると,

自然言語(しぜんげんご、英: Natural language)とは、人間によって日常の意思疎通のために用いられる、文化的背景を持って自然に発展してきた記号体系である。大別すると音声による話し言葉と文字や記号として書かれる書き言葉がある。

となっている.
この自然言語を機械的に処理するためには

といったプロセスがある.

形態素解析

形態素解析とは語の品詞を特定することである.
例えば,

すもももももももものうち

という文章は

すもも:名詞, も:助詞, もも:名詞, も:助詞, もも:名詞, の:助詞, うち:名詞

と分析することが出来る.
形態素解析器にはMeCabや茶筅,JUMANなどがある.
MeCabpythonで使うには以下のようにする.

In [1]: import MeCab
In [2]: tagger = MeCab.Tagger()
In [3]: text = u'すもももももももものうち'.encode('utf-8')
In [4]: node = tagger.parseToNode(text)
In [5]: while node.next:
   ...:     node = node.next
   ...:     print node.feature, node.surface, node.posid
   ...:    
名詞,一般,*,*,*,*,すもも,スモモ,スモモ すもも 38
助詞,係助詞,*,*,*,*,も,モ,モ も 16
名詞,一般,*,*,*,*,もも,モモ,モモ もも 38
助詞,係助詞,*,*,*,*,も,モ,モ も 16
名詞,一般,*,*,*,*,もも,モモ,モモ もも 38
助詞,連体化,*,*,*,*,の,ノ,ノ の 24
名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ うち 66
BOS/EOS,*,*,*,*,*,*,*,*  0
  • node.surface:文字列
  • node.feature:文字列の品詞
  • node.posid:文字列の品詞ID

品詞IDについては品詞IDの定義を参照.

構文解析

文章の文法的な関係を解析すること.

美しき水車小屋の乙女

この文章における「美しき」の解釈は2通りある.
こういった状況を解消するために行う.

語の正規化

英単語の大文字・小文字の変換を行う.

In [1]: print 'Python'.lower()
python
In [2]: print 'PYTHON'.lower()
python
In [3]: print 'python'.capitalize()
Python
In [4]: print 'python'.upper()
PYTHON
ステミング処理

語の語幹を取り出す処理のこと.
複数形と単数形や派生語を同じものとして扱うために行う.

database, databases ⇒ databas
initial, initialize, initialization ⇒ initi

pythonで行うには以下のように.

In [1]: import nltk
In [2]: stemmer = nltk.PorterStemmer()
In [3]: print stemmer.stem(’database')
databas
In [4]: print stemmer.stem(’databases')
databas
In [5]: print stemmer.stem('initial')
initi
In [6]: print stemmer.stem('initialization')
initi
In [7]: print stemmer.stem('Pythonic')
Python

nltk.PorterStemmer()の他にはnltk.LancasterStemmer()などもある.

語の見出し語化

与えられた語の、辞書における見出し語を求める.
レンマ化,とも言われる.

women ⇒ woman
databases ⇒ database

pythonで使うには以下のように.

In [1]: import nltk
In [2]: lemmatizer = nltk.WordNetLemmatizer()
In [3]: lemmatizer.lemmatize('women')
Out[3]: 'woman'
In [4]: lemmatizer.lemmatize('corpora')
Out[4]: 'corpus'

見出し語化は処理が重い...

ストップワードの除去

日本語では「は,が,の,を」など.
英語では「a,the,I,you」など.
文章中においてその語自体にあまり価値が無い語,と言える.
SlothLibやnltkにストップワードリストが存在する.
pythonでnltkのストップワードリストを用いるなら,

In [1]: from nltk.corpus import stopwords
In [2]: print stopwords.words('english')
...

日本語の場合はSlothLibから取ってくるか,自分で用意することも考えられる.

WordNet

英語の概念辞書(意味辞書)である.
英単語をSynsetと呼ばれる同義語のグループに分類し,簡単な定義や他のグループとの関係等が記述してある.
ざっくり言えば,語のDB.圧縮後で12M程度.
人力で用意されているので精度が良い.
名詞について以下のようなものが用意されている.

  • 上位語(hypernym): すべてのXがYの種類の一であるならYはXの上位語である。
  • 下位語(hyponym): すべてのYがXの種類の一であるならYはXの下位語である。
  • 同族語(coordinate term): XとYの上位語が同じなら、YはXの同族語である。
  • holonym: XがYの一部であるなら、YはXのholonymである。
  • meronym: YがXの一部であるなら、YはXのmeronymである。

動詞や形容詞にも同様に同義語が設定されている.
pythonでは以下のように.

In [1]: from nltk.corpus import wordnet
In [2]: print wordnet.synsets('dog')
[Synset('dog.n.01'), Synset('frump.n.01'), Synset('dog.n.03'), Synset('cad.n.01'), Synset('frank.n.02'), Synset('pawl.n.01'), Synset('andiron.n.01'), Synset('chase.v.01')]
In [3]: dog = wordnet.synset('dog.n.01')
In [4]: dog.definition
Out[4]: 'a member of the genus Canis (probably descended from the common wolf) that has been domesticated by man since prehistoric times; occurs in many breeds’
In [5]: dog.examples
Out[5]: ['the dog barked all night']
In [6]: dog.lemma_names
Out[6]: ['dog', 'domestic_dog', 'Canis_familiaris']
In [7]: dog.pos
Out[7]: 'n’
In [8]: print wordnet.lemmas('dog')
[Lemma('dog.n.01.dog'), Lemma('frump.n.01.dog'), Lemma('dog.n.03.dog'), Lemma('cad.n.01.dog'), Lemma('frank.n.02.dog'), Lemma('pawl.n.01.dog'), Lemma('andiron.n.01.dog'), Lemma('chase.v.01.dog')]

少し説明すると,

wordnet.synsets(word):wordの同義語集合を返す
wordnet.synset(~):上で得たうちのものを引数に渡して1つのsynsetについての情報を得る
.definition:~の定義
.examples:~の例文
.pos:~の品詞
.lemma_names:~の同義語集合に属している見出し語
wordnet.lemmas(word):wordの同義語集合全てを返す.

これらは基本的なもので,上位語・下位語などは以下のように.

In [1]: from nltk.corpus import wordnet as wn
In [2]: pc = wn.synset('personal_computer.n.01')
In [3]: pc.definition
Out[3]: 'a small digital computer based on a microprocessor and designed to be used by one person at a time'
In [4]: pc.hypernyms()
Out[4]: [Synset('digital_computer.n.01')]
In [5]: pc.hyponyms()
Out[5]: [Synset('desktop_computer.n.01'), Synset('portable_computer.n.01')]
In [6]: desktop = wn.synset('desktop_computer.n.01')
In [7]: desktop.hyponyms()
Out[7]: []
In [8]: desktop.hypernyms()
Out[8]: [Synset('personal_computer.n.01')]
In [9]: desktop.path_similarity(pc)
Out[9]: 0.5
  • hypernyms():上位語
  • hyponyms():下位語
  • path_similarity():共通の上位階層経由のパスの長さに基づく同義語集合間の類似度

となっている.

注意点としては,WordNetのデータは人力で設定されているため,あらゆる語を網羅しているわけではないため
抜けがある,ということである.

補足

nltk(natural language tool kit)のcorpusなどを使うには

nltk.download()

を実行しましょう.