fasttextのテストコード

俺的メモ

Posted by めろん on 2025/07/08 20:55

import fasttext
import MeCab
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import re
import json # JSON出力のためにインポート

# 1. データの準備(仮のデータ - タイトルと説明文を紐付け)
# 実際にはコミックシーモア、ビッコミから取得した少年漫画のデータを格納してください。
# 各辞書には 'id', 'title', 'description'(作品の説明文やジャンル、キーワードなど)を含めることを想定します。

# コミックシーモアの少年漫画データ
comic_cmoa_shonen_manga = [
    {'id': 'CM001', 'title': '大冒険の始まり', 'description': '冒険ファンタジー、仲間との絆、成長、魔法、友情、バトル。'},
    {'id': 'CM002', 'title': '青春ラブコメ日和', 'description': '学園ラブコメ、青春、日常、ドタバタ、幼馴染、学園祭。'},
    {'id': 'CM003', 'title': '銀河の守護者', 'description': 'SFアクション、ロボット、宇宙、未来、異星人、戦闘。'},
    {'id': 'CM004', 'title': '不屈のストライカー', 'description': 'スポーツ漫画、野球、努力、チームワーク、ライバル、甲子園。'},
    {'id': 'CM005', 'title': '異世界転生記', 'description': '異世界転生、チート能力、ハーレム、魔王、スライム、成り上がり。'},
    {'id': 'CM006', 'title': '魔道士の弟子', 'description': '魔法使いの弟子が世界を救う、成長、ファンタジー。'},
]

# ビッコミの少年漫画データ
biccomi_shonen_manga = [
    {'id': 'BC001', 'title': '伝説の勇者', 'description': '王道冒険物語、仲間、強敵、修行、秘宝、世界を救う。'},
    {'id': 'BC002', 'title': 'きらめき学園デイズ', 'description': '青春学園ドラマ、恋愛、友情、部活動、文化祭、甘酸っぱい。'},
    {'id': 'BC003', 'title': 'アンドロイドの反乱', 'description': '近未来SF、サイボーグ、ディストピア、謎解き、陰謀、超能力。'},
    {'id': 'BC004', 'title': '炎のキッカー', 'description': 'サッカー漫画、天才、挫折、努力、全国大会、夢。'},
    {'id': 'BC005', 'title': '転生したらスライムだった件', 'description': '異世界ファンタジー、魔物、ダンジョン、冒険者、最強、スローライフ。'},
    {'id': 'BC006', 'title': '魔法使いの夜', 'description': '魔法使いの少女と少年の物語、ファンタジー、学園。'},
]

# ---
## 2. 前処理(形態素解析 - 分かち書き)
def tokenize_text(text):
    """
    MeCabを使ってテキストを分かち書きします。
    数字や記号は削除し、ひらがな、カタカナ、漢字、英数字のみを対象とします。
    """
    mecab = MeCab.Tagger("-Owakati")
    # 不要な文字(記号類)を削除し、半角スペースに置換。ひらがな、カタカナ、漢字、英数字のみを残す。
    cleaned_text = re.sub(r'[^ws]', ' ', text)
    wakati_text = mecab.parse(cleaned_text).strip()
    return wakati_text

# ---
## 3. FastTextモデルの学習
def train_fasttext_model(data_cmoa, data_biccomi, model_path="shonen_manga_embeddings.bin"):
    """
    コミックシーモアとビッコミの少年漫画データからFastTextモデルを学習します。
    """
    all_descriptions = []
    for p in data_cmoa + data_biccomi:
        # タイトルも説明に含めて学習させることで、タイトル間の類似度も考慮しやすくなる
        all_descriptions.append(tokenize_text(p['title'] + ' ' + p['description']))

    # FastTextの学習用ファイルを作成
    training_data_file = 'fasttext_manga_training_data.txt'
    with open(training_data_file, 'w', encoding='utf-8') as f:
        for desc in all_descriptions:
            f.write(desc + ' ')

    print("FastTextモデルを学習中...")
    # データが少ないため、minCountを1に設定し、dimとwsはデフォルト付近で調整
    # 実際のデータ量に応じて調整してください
    model = fasttext.train_unsupervised(training_data_file, model='skipgram', dim=100, ws=5, minCount=1)
    model.save_model(model_path)
    print(f"FastTextモデルの学習が完了しました: {model_path}")
    return model

# ---
## 4. 作品説明文のベクトル化
def get_sentence_vector(text, model):
    """
    分かち書きされたテキストの単語ベクトルを平均して文ベクトルを生成します。
    """
    tokens = text.split()
    if not tokens:
        return np.zeros(model.get_dimension()) # 空のテキストはゼロベクトル
   
    word_vectors = [model.get_word_vector(word) for word in tokens]
   
    return np.mean(word_vectors, axis=0)

def vectorize_manga_data(manga_list, model):
    """
    漫画リストの各作品の説明文をベクトル化します。
    """
    vectors_data = []
    for p in manga_list:
        # タイトルと説明文を結合してベクトル化
        wakati_desc = tokenize_text(p['title'] + ' ' + p['description'])
        vectors_data.append({
            'id': p['id'],
            'title': p['title'],
            'vector': get_sentence_vector(wakati_desc, model)
        })
    return vectors_data

# ---
## 5. 類似度計算と結果のJSON出力
def find_similar_manga(cmoa_data, biccomi_data, model, top_n_per_cmoa_item=3):
    """
    コミックシーモアの各漫画に近似するビッコミの漫画を見つけ、JSON形式で返します。
    """
    # ベクトル化
    cmoa_vectors_data = vectorize_manga_data(cmoa_data, model)
    biccomi_vectors_data = vectorize_manga_data(biccomi_data, model)

    # ベクトルのみを抽出してNumpy配列に変換
    cmoa_vectors = np.array([item['vector'] for item in cmoa_vectors_data])
    biccomi_vectors = np.array([item['vector'] for item in biccomi_vectors_data])

    similarities_results = []

    # コミックシーモアの各作品とビッコミの全作品との類似度を計算
    for i, cmoa_item in enumerate(cmoa_vectors_data):
        # コサイン類似度を計算
        similarities = cosine_similarity([cmoa_vectors[i]], biccomi_vectors)[0]
       
        # 類似度が高い順にソート
        sorted_indices = np.argsort(similarities)[::-1] # 降順ソート
       
        # 上位N件の近似作品を結果に追加
        for j in sorted_indices[:top_n_per_cmoa_item]:
            biccomi_item = biccomi_vectors_data[j]
            similarity_score = similarities[j]
           
            similarities_results.append({
                'comic_cmoa': {
                    'id': cmoa_item['id'],
                    'title': cmoa_item['title']
                },
                'biccomi': {
                    'id': biccomi_item['id'],
                    'title': biccomi_item['title']
                },
                'similarity_score': round(float(similarity_score), 4) # JSON用にfloatに変換し丸める
            })

    # 類似度スコアで降順ソート
    similarities_results_sorted = sorted(similarities_results, key=lambda x: x['similarity_score'], reverse=True)
   
    return json.dumps({"similar_manga_pairs": similarities_results_sorted}, ensure_ascii=False, indent=2)

# ---
## 実行部分
if __name__ == "__main__":
    # FastTextモデルの学習
    # モデルがすでに存在する場合はロードすることも可能
    try:
        model = fasttext.load_model("shonen_manga_embeddings.bin")
        print("既存のFastTextモデルをロードしました。")
    except ValueError: # ファイルが見つからない場合など
        model = train_fasttext_model(comic_cmoa_shonen_manga, biccomi_shonen_manga)

    # 類似度計算と結果の取得
    json_output = find_similar_manga(comic_cmoa_shonen_manga, biccomi_shonen_manga, model, top_n_per_cmoa_item=3)

    # JSON形式の出力を表示
    print(" --- 近似度が高い少年漫画の組み合わせ (JSON形式) ---")
    print(json_output)

    # 必要であればファイルに保存
    # with open('similar_manga_pairs.json', 'w', encoding='utf-8') as f:
    #     f.write(json_output)
    # print(" 結果を 'similar_manga_pairs.json' に保存しました。")