アイソモカ

知の遊牧民の開発記録

開発記録 200113 Mon (Stemming, 100本ノック #052)

前回 #051 は空白を単語の区切りとみなしていたが、hand-written rules などの - でつながった単語がある場合、-も単語の区切りを表しているよな? と今回解きながら思った。

あと、( ) , . " などの記号も単語に含めるべきではないのではないだろうか。 しかし、'についてはちょっと厄介で、'conceptual ontologies''は除きたいけど、Moore's Law' はこのまま置いておきたい。

…という部分も今回実装した。

 

 

言語処理100本ノック #052

52. ステミング

51の出力を入力として受け取り,Porterのステミングアルゴリズムを適用し,単語と語幹をタブ区切り形式で出力せよ.

Pythonでは,Porterのステミングアルゴリズムの実装として stemming モジュールを利用するとよい.

stemming の準備

参考 👉 Installing Packages — Python Packaging User Guide

pip に setuptools と wheel projects を入れる。

$ python -m pip install --upgrade pip setuptools wheel

stemming をインストールする。

$ pip install stemming

確認。

$ pip list
Package           Version
----------------- -------
...               ...
stemming          1.0.1  
...               ...

stemming の使い方

from stemming.porter2 import stem でインポートし、stem(単語)で語幹が得られる。

参考 👉 Porterのステミングアルゴリズムをpythonで使う - kzk0829’s blog

完成

記号を取り除く関数内で、 Moore's Law の ' は残したいので、 's というパターンの場合は ' を取り除かないようにした。

でもこれやと、rock 'n' roll の ' は取り除かれてまうんやなあ(今回解析する文章に入ってないし、まあええか)。

# knock_052.py

from stemming.porter2 import stem

nlp_file = 'nlp.txt'

def get_sentence(line):
    sentence = ''
    for i, char in enumerate(line):
        if char in ['.', ';', ':', '?', '!']:
            sentence += char
            if i+3 > len(line):
                # 文末文字 -> 改行
                yield sentence 
                sentence = ''
            elif line[i+1] == ' ':
                if str.isupper(line[i+2]):
                    # 文末文字 -> 空白 -> 英大文字
                    yield sentence
                    sentence = ''
        elif char == ' ' and len(sentence) <1:
            # 文の間の空白は読み飛ばす
            pass
        else:
            sentence += char
    if len(sentence) > 1:
        # タイトル行の処理はここで
        pass


def file_to_sentence(readfile):
    with open(readfile, encoding='utf-8') as f:
        for line in f:
            for sentence in get_sentence(line):
                yield sentence


# 記号を除くやつ
def remove_marks(word): 
    removed = ''
    for i, c in enumerate(word):
        if c in ['.', ';', ':', '?', '!', \
            ',', '(', ')', '"']:
            pass
        elif c in ["'"]:
            if i+1 < len(word):
                if word[i+1] in ['s']:
                    removed += c
                else:
                    pass
            else:
                pass
        else:
            removed += c
    return removed


if __name__ == '__main__':
    for sentence in file_to_sentence(nlp_file):
        for word in re.split(' |-', sentence): # 空白または - で文を区切る
            rword = remove_marks(word)
            if len(rword) > 1:
                print(rword, '\t', stem(rword))
        print('\n', end='')

結果

# python knock_052.py >> knock_052.txt (30行目まで)
Natural      Natur
language     languag
processing   process
NLP      NLP
is   is
field    field
of   of
computer     comput
science      scienc
artificial   artifici
intelligence     intellig
and      and
linguistics      linguist
concerned    concern
with     with
the      the
interactions     interact
between      between
computers    comput
and      and
human    human
natural      natur
languages    languag

As   As
such     such
NLP      NLP
is   is
related      relat
to   to

   

ステミングについてちょっと調べてみた

参考 👉 NLP: A quick guide to Stemming - Tushar Srivastava - Medium

概要

ステミングは、inflectional forms (活用形) から suffix (接尾辞) を除いて root word (語根) や stem word (語幹) にすること。

英語では、文法的なカテゴリの違いを表現するために語を活用させる。文法カテゴリというのは、 テンス(時制)やケース(格)、ウォイス(態)、アスペクト(相)、人称、数、性、ムード(話し手の気持ちや主観的態度)、有生性、定性などのこと。

活用した語を元に戻してやること(=ステミング)により、自然言語処理や言語研究の際に同義語や類義語が探しやすくなるわけ。

たとえば、動詞の活用形を stemming によりステミングすると次のようになる。

# test_stemming.py 
word        stem
-------     ----
wait        wait
waits       wait
waited      wait
waiting     wait

 

綴りが変わることもある。

  1. beauty, duty + -ful → beautiful, dutiful (-y changes to i)
  2. heavy, ready + -ness → heaviness, readiness (-y changes to i)
  3. able, possible + -ity → ability, possibility (-le changes to il)
  4. permit, omit + -ion → permission, omission (-t changes to ss)

stemming では、1, 2 はうまくいっているが、3, 4 はうまくいっていないっぽい。

# test_stemming.py 
word        stem
----------  ----
beauty      beauti
beautiful   beauti
----------  ----
heavy       heavi
heaviness   heavi
----------  ----
able        abl
ability     abil
----------  ----
permit      permit
permission  permiss

ちなみに、語根と語幹の違いは、語根のほうが小さい要素。 複合語の語幹は、複数の語根からなる(たとえば、wheelchairs -> wheelchair (stem) = wheel (root) + chair (root))。 stemming の出力は語幹のほう。

# test_stemming.py 
word        stem
----------  ----
wheelchairs wheelchair

Over Stemming

異なる語幹をもつ語が、同じ語幹にステミングされてしまうこと。

# test_stemming.py 
word        stem
----------  ----
universal   univers
university  univers
universe    univers

これらの語は、語源は関連しているが、現代での意味が大きく異なるので、 自然言語処理において類義語として扱わないほうが良いかも。

Under Stemming

同じ語幹にステミングされるべき語が、異なる語幹にステミングされてしまうこと。

# test_stemming.py 
word        stem
----------  ----
alumnus     alumnus
alumni      alumni
alumnae     alumna
----------  ----
have        have
had         had
has         has
having      have

難しいですね。

ステミングツールは、今回使用した stemming 以外にも色々ありそうなので、精度を上げたい場合には他のツールを使ってみるのも良いかもしれない。 上に書いた参考リンクの後半に、ステミングツールの紹介がある。

調べるのに使ったコード

# test_stemming.py
from stemming.porter2 import stem

wordlist = ['wait', 'waits', 'waited', 'waiting', \
    'beauty', 'beautiful', \
    'heavy', 'heaviness', \
    'able', 'ability', \
    'permit', 'permission', \
    'have', 'had', 'has', 'having', \
    'universal', 'university', 'universe', \
    'alumnus', 'alumni', 'alumnae', \
    'wheelchairs']

print('word', '\t', 'stem')
print('----------', '\t', '-------')

for w in wordlist:
    print(w, '\t', stem(w))