Pythonの日本語形態素解析ライブラリ　

## はじめに
なぞなぞLINE botを作る過程で、回答の正誤判定を行う際に、必要になったので、調べたことを備忘録として残しておこうと思います。
例えば、「アイスクリーム」が答えだとしたら、「アイス」と回答したら、正解だと判定させたいという風に、同音異義語や、ひらがな、漢字、カタカナ表記の違いを許容する実装方法を調べた結果です。

## 形態素解析とは
> 形態素解析（morphological analysis）とは、検索エンジンなどにも用いられている自然言語処理（NLP）の手法の一つです。
ある文章・フレーズを「意味を持つ最小限の単位」に分解して、文章やフレーズの内容を判断するために用いられます。
※形態素とは、言語上で意味をもつ最小の単位を表す用語です。
Pythonでは、MeCabやJanomeを活用することで形態素解析を行うことが出来ます。

引用サイト：https://aiacademy.jp/media/?p=3009

python環境で利用できる日本語形態素解析ライブラリを比較されている記事もありました。
メカブ？ジャノメ？なんと、スダチもある🟢

引用サイト：https://qiita.com/e10persona/items/fddc795e70a05f3bc907

## 結論： MeCabを使うのがいいみたい
Cloud RunでDockerfileをもとにデプロイしているので、Dockerfileの修正が必要でした。
このサイトがわかりやすかったので掲載させていただきます。
https://www.tech-teacher.jp/blog/mecab-python/

### 1. Dockerfileに MeCab とその依存関係を追加
```Dockerfile
# Pythonのベースイメージを指定
FROM python:3.10

# 作業ディレクトリを設定
WORKDIR /app

# MeCabに必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    mecab \
    libmecab-dev \
    mecab-ipadic-utf8 \
    && apt-get clean

# PYTHONPATH 環境変数を設定
ENV PYTHONPATH=/app

# Mecabの辞書パスを環境変数で設定（コンテナ内の正しい辞書パスを指定）
ENV MECABRC=/etc/mecabrc

# 依存関係をインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリケーションのソースコードをコピー
COPY . .

# ポートを指定
EXPOSE 8000

# アプリケーションを起動
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

```
### 2. Mecabを使うPythonコード内で辞書パスを指定
Cloud Run 環境のように軽量なコンテナ環境では、辞書パスを明示的に指定する必要があります。
```python
import MeCab

# MeCabの辞書パスを指定して初期化
mecab = MeCab.Tagger("-Ochasen -d /usr/lib/mecab/dic/ipadic")
```
この例では、/usr/lib/mecab/dic/ipadic に辞書がインストールされている前提です。辞書が他の場所にある場合は、そのパスを指定してください。

## 実装例
ユーザーの入力を形態素解析や正規表現を用いて柔軟に判定するには、まず形態素解析でユーザーの入力を単語ごとに分解し、次に同じ意味の言葉（同音異義語や表記揺れ、類義語など）を考慮して正解と照合します。

ここでは、`MeCab` を用いた形態素解析と正規表現を使った実装例を紹介します。

### 1. 形態素解析を利用してユーザーの入力を単語ごとに分解
`MeCab` を利用してユーザーの入力を分解し、得られた形態素をもとに正解と照合します。

#### 形態素解析を用いた `check_user_answer` の修正例
```python
import MeCab
import re

# MeCabの初期化（辞書パスが必要な場合は適宜設定）
mecab = MeCab.Tagger("-Ochasen")

# 同義語や表記揺れを含む辞書（必要に応じて追加する）
SYNONYM_DICT = {
    "かがみ": ["鏡", "カガミ", "kagami", "ミラー", "mirror"],
    # 他の単語も追加可能
}

# 形態素解析を行う関数
def parse_text(text):
    node = mecab.parseToNode(text)
    words = []
    while node:
        word = node.surface
        if word:  # 空文字は無視
            words.append(word)
        node = node.next
    return words

# 正解判定関数
def check_user_answer(user_id, user_answer):
    logger.debug(f"check_user_answer関数が呼び出されました:{user_answer}と回答が送信されました")
    try:
        # ユーザーIDに紐づいた問題と答えを取得
        riddle = riddle_store.get(user_id)
        logger.debug(f"riddle:{riddle}を取得しました")
        if not riddle:
            return "まだ問題が出題されていません。"

        # 正解をチェック
        correct_answer = riddle["answer"]
        logger.debug(f"correct_answer:{correct_answer}を取得しました")

        # カギカッコやスペース、改行などを除去し、すべて小文字に変換
        clean_correct_answer = re.sub(r"[「」\s]", "", correct_answer.lower())

        # ユーザーの入力も同様にクリーニング
        clean_user_answer = re.sub(r"[「」\s]", "", user_answer.lower())

        # ユーザーの入力を形態素解析で分解
        parsed_user_answer = parse_text(clean_user_answer)
        logger.debug(f"parsed_user_answer:{parsed_user_answer}を解析しました")

        # 正解を形態素解析で分解
        parsed_correct_answer = parse_text(clean_correct_answer)
        logger.debug(f"parsed_correct_answer:{parsed_correct_answer}を解析しました")

        # 同義語判定
        def is_synonym(user_word, correct_word):
            # SYNONYM_DICTを使って同義語判定
            if correct_word in SYNONYM_DICT:
                return user_word in SYNONYM_DICT[correct_word]
            return user_word == correct_word

        # 分解された単語リストの中に同義語が含まれているかチェック
        for user_word in parsed_user_answer:
            for correct_word in parsed_correct_answer:
                if is_synonym(user_word, correct_word):
                    logger.debug(f"同義語判定成功: {user_word} == {correct_word}")
                    return "おめでとう！正解です！"

        return "残念！もう一度考えてみてください。"
    except Exception as e:
        logger.error(f"Failed to check answer: {e}")
        return "回答の確認中にエラーが発生しました。"
```

### 2. 正規表現を使った柔軟な判定
ユーザーの入力が例えば「カガミ」「mirror」といったバリエーションであっても正解と判定するため、正規表現で簡単なバリエーションチェックを行うこともできます。

#### 正規表現を用いた `check_user_answer` の修正例
```python
# 正規表現を使った正解判定関数
def check_user_answer_with_regex(user_id, user_answer):
    logger.debug(f"check_user_answer_with_regex関数が呼び出されました:{user_answer}と回答が送信されました")
    try:
        # ユーザーIDに紐づいた問題と答えを取得
        riddle = riddle_store.get(user_id)
        logger.debug(f"riddle:{riddle}を取得しました")
        if not riddle:
            return "まだ問題が出題されていません。"

        # 正解をチェック
        correct_answer = riddle["answer"]
        logger.debug(f"correct_answer:{correct_answer}を取得しました")

        # 同義語のリストを使用して正規表現を作成
        synonyms = SYNONYM_DICT.get(correct_answer, [correct_answer])
        pattern = "|".join(synonyms)  # 同義語リストを "|" で繋ぐ

        # ユーザーの入力をクリーニング
        clean_user_answer = re.sub(r"[「」\s]", "", user_answer.lower())

        # 正規表現で同義語のどれかに一致するかをチェック
        if re.search(pattern, clean_user_answer):
            return "おめでとう！正解です！"
        elif clean_user_answer == "降参":
            return f"正解は『{correct_answer}』でした！"
        else:
            return "残念！もう一度考えてみてください。"
    except Exception as e:
        logger.error(f"Failed to check answer: {e}")
        return "回答の確認中にエラーが発生しました。"
```

### 実装の流れ

1. **形態素解析** を利用してユーザーの回答を単語に分解。
2. **同義語辞書** を使って入力された単語が正解の意味と一致するかを判定。
3. **正規表現** を用いた柔軟な判定も可能。

このようにして、ユーザーが入力した言葉のバリエーション（「かがみ」「鏡」「カガミ」「ミラー」など）を考慮した回答判定が行えます。


## jaconvライブラリーでひらがなとカタカナ変換ができる
Pythonで使える日本語の形態素解析ができるライブラリーの一つです。

具体的には以下のことができます。
- 「ひらがな」から「カタカナ」の変換
- 半角・全角の相互変換

詳しい使い方は、こちらの方の記事がわかりやすかったので、掲載させていただきます。
https://qiita.com/shakechi/items/d12641d6cad01479785f

最後までお読みいただき、ありがとうございました！誤り等ありましたら、ご指摘いただけるとありがたいです。