ユーニックス総合研究所

  • home
  • archives
  • python-janome-kaibun

Janomeで回文判定アプリを作る【Python, 自然言語処理】

回文判定アプリを作る

人間の使う言語は「自然言語」と呼ばれています。
この自然言語を機械的に解析するのが「自然言語処理」です。

自然言語処理では「形態素解析」という工程があります。
形態素解析を行うことで日本語の文章を単語のリストに分割できます。
Pythonの形態素解析ライブラリには「Janome」というライブラリがあります。

今回はJanomeを使って入力された文章が回文かどうか判定するアプリを作りたいと思います。
具体的には↓を見ていきます。

  • 回文とは?
  • 形態素解析とは?
  • Janomeとは?
  • 回文判定アプリの設計
  • 回文判定アプリの実装
  • 回文判定アプリを使う

回文とは?

回文(かいぶん)とは「新聞紙」のように頭から呼んでもお尻から呼んでも同じ読み方になる文のことです。
「新聞紙」は発音では「シンブンシ」ですが、頭から読んでも「シンブンシ」だしお尻から読んでも「シンブンシ」です。
このような文章のことを回文と言います。

形態素解析とは?

自然言語処理は人間の話す言語を解析する処理のことです。
この解析処理には工程があって、形態素解析、構文解析、意味解析、文脈解析と続きます。
形態素解析はこの工程の中の最初の工程のことです。

形態素解析の目的は文章を単語のリストに分割することです。
日本語の文章は英語の文章のようにスペースで区切られていません。
英語の文章を単語ごとに分割するにはスペースでチョップすればOKですが、日本語の場合はそうもいきません。
そこで形態素解析では辞書を使った単語の分割を行います。
辞書と照らし合わせながら単語を参照していくことで、単語を分割できるという寸法です。

形態素解析は自然言語処理における基礎的な技術に位置づけられています。
この工程を行うことで、その後の工程の処理が可能になっていくので、もっとも基本ともいえる処理です。

Janomeとは?

形態素解析を行うライブラリとして有名なのがMeCab(メカブ)です。
これは昔からあるライブラリで、広く形態素解析の処理に使われています。
Pythonで最近人気が出てきてるのが形態素解析ライブラリである「Janome(ジャノメ)」です。

JanomeはMeCabの辞書を使っています。速度ではMeCabに劣りますが、精度的にはMeCabと同程度の精度を実現可能と言われています。

Janomeの特徴はそのインストールの簡易さです。MeCabを使う場合はバイナリファイルのダウンロードなどが必要になりますが、Janomeの場合はpipで簡単にインストールできます。
Janomeを使う場合は↓のようにpipでJanomeをインストールしておきます。

> pip install Janome  

回文判定アプリの設計

回文判定アプリの設計は、まず無限ループを作ってその中でユーザーからの入力を受けます。
そして入力をJanomeで形態素解析します。
Janomeは単語を分割すると、それをトークンとして保存します。
トークンにはreadingというその単語の読み方が保存されています。

このreadingを解析することで、その単語が回文かどうか判定することが可能になります。
具体的にはreadingの長さだけループを回し、readingの先頭と末尾の文字を比較していきます。
そうすることでそれぞれが一致していなかったら回文ではないという判定を取ります。

トークンのreadingが回文だったら「回文です。」と表示し、回文でなかったら「回文ではありません。」と表示します。
出力が終わったらループの先頭に戻ってユーザーの入力を受けるところから始めます。

回文判定アプリの実装

回文判定アプリのコード全文は↓になります。

from janome.tokenizer import Tokenizer  
import unittest   


def analyze(text):  
    """  
    引数textを形態素解析する  
    """  
    t = Tokenizer()  
    return t.tokenize(text)  


def is_kaibun(s):  
    """  
    引数sが回文だったらTrueを返し、回文でなかったらFalseを返す  
    """  
    i = 0  
    fln = len(s)  
    hln = int(len(s) // 2)  

    if fln <= 1:  
        return True  

    while i < hln:  
        if s[i] != s[fln - i - 1]:  
            return False  
        i += 1  

    return True  


def merge_reading(toks):  
    """  
    トークン列のreadingをマージして返す  
    """  
    r = ''  
    for tok in toks:  
        r += tok.reading  
    return r  


def main():  
    while True:  
        # 入力を受け取る  
        try:  
            text = input('in > ')  
        except KeyboardInterrupt:  
            break  

        # 入力されたテキストを形態素解析する  
        toks = list(analyze(text))  
        if not len(toks):  
            continue  

        # トークン列のreadingをマージして回文かどうか判定する  
        reading = merge_reading(toks)  
        if is_kaibun(reading):  
            print('回文です。')  
        else:  
            print('回文ではありません。')  


main()  


class Test(unittest.TestCase):  
    def test_is_kaibun(self):  
        def eq(a, b):  
            toks = list(analyze(a))  
            reading = merge_reading(toks)  
            self.assertEqual(is_kaibun(reading), b)  

        eq('蚊', True)  
        eq('新聞紙', True)  
        eq('歌歌う', True)  
        eq('柿の木か', True)  
        eq('遠い音', True)  

        eq('こけし', False)  
        eq('ふともも', False)  

アプリの解説は↓になります。

必要モジュールのインポート

スクリプトの先頭で必要モジュールをインポートしておきます。

from janome.tokenizer import Tokenizer  
import unittest   

TokenizerはJanomeの形態素解析器です。
それから回文判定関数のテストのためunittestをインポートしてます。

main関数

アプリはmain関数から始まります。

def main():  
    while True:  
        # 入力を受け取る  
        try:  
            text = input('in > ')  
        except KeyboardInterrupt:  
            break  

        # 入力されたテキストを形態素解析する  
        toks = list(analyze(text))  
        if not len(toks):  
            continue  

        # トークン列のreadingをマージして回文かどうか判定する  
        reading = merge_reading(toks)  
        if is_kaibun(reading):  
            print('回文です。')  
        else:  
            print('回文ではありません。')  


main()  

main関数ではまずinput()関数でユーザーから入力を受け取ります。
そしてその入力(text)をanalyze()関数で解析してトークン列にします。
トークン列のreadingmerge_reading()関数でマージします。
そのマージしたreadingis_kaibun()関数で判定し、回文なら「回文です。」と表示して回文でなければ「回文ではありません。」と表示します。

analyze()関数

analyze()関数は引数textを形態素解析してトークン列に変換します。

def analyze(text):  
    """  
    引数textを形態素解析する  
    """  
    t = Tokenizer()  
    return t.tokenize(text)  

JanomeのTokenizerクラスをオブジェクト(t)にしてます。
tokenize()メソッドに引数textを渡してパースし、結果をトークン列にします。

merge_reading関数

merge_reading()関数はトークン列のreadingを1つの文字列にマージして返します。

def merge_reading(toks):  
    """  
    トークン列のreadingをマージして返す  
    """  
    r = ''  
    for tok in toks:  
        r += tok.reading  
    return r  

is_kaibun関数

is_kaibun()関数は引数sが回文かどうか判定します。

def is_kaibun(s):  
    """  
    引数sが回文だったらTrueを返し、回文出なかったらFalseを返す  
    """  
    i = 0  
    fln = len(s)  # 文字列の全長  
    hln = int(len(s) // 2)  # 文字列の全長の半分  

    if fln <= 1:  # 全長が1以下だったらTrue  
        return True  

    while i < hln:  
        # 文字列の先頭と末尾を比較していく  
        if s[i] != s[fln - i - 1]:  
            return False  
        i += 1  

    return True  

アルゴリズムとしては文字列の先頭と末尾を順に比較していって、一致していなかったらFalseを返し、すべて一致していたらTrueを返します。
変数flnが文字列の全長(full length)で、変数hlnが全長の半分(half length)です。

テスト

is_kaibun()関数の動作はこころもとないものです。
もし私がis_kaibun()のテストを書かなかったらis_kaibun()に亀裂が入ってそこから世界は崩壊していくでしょう。
そういうわけなのでテストを書きました。

class Test(unittest.TestCase):  
    def test_is_kaibun(self):  
        def eq(a, b):  
            toks = list(analyze(a))  
            reading = merge_reading(toks)  
            self.assertEqual(is_kaibun(reading), b)  

        eq('蚊', True)  
        eq('新聞紙', True)  
        eq('歌歌う', True)  
        eq('柿の木か', True)  
        eq('遠い音', True)  

        eq('こけし', False)  
        eq('ふともも', False)  

テストの内容はeq()関数が主です。この関数内で引数aをパースしてis_kaibun()の結果がbになっているかチェックしています。
↓のテストは回文かどうかのテストで、

        eq('蚊', True)  
        eq('新聞紙', True)  
        eq('歌歌う', True)  
        eq('柿の木か', True)  
        eq('遠い音', True)  

↓のテストは回文ではないことを期待するテストです。

        eq('こけし', False)  
        eq('ふともも', False)  

回文判定アプリを使う

回文判定アプリは起動すると↓のように動作します。

in > 新聞紙  
回文です。  

in > 遠い音  
回文です。  

in > 犬  
回文ではありません。  

in > 猫  
回文ではありません。  

in > 子猫  
回文です。  

in >  

回文かどうか判定できてますね。
漢字の文章も問題なく解析できてます。

おわりに

今回はJanomeで回文判定器を作りました。
形態素解析を行うとこのように比較的に簡単に日本語の文章を解析することが出来ます。

🦝 < 形態素解析で日本語を丸裸

🐭 < チョップしてあげる