HAL_DATA_techBlog

HALDATAの技術ブログです。

レビューデータの意味的クラスタリング:OpenAI Embedding + UMAP + HDBSCAN

こんにちはHALDATAのTNです。
今回はレビューデータを使ったクラスタリングの記事を書きます。

はじめに
商品やサービスに対するレビューは、ユーザーの本音が詰まった貴重なデータです。しかし、その量が多くなると、一つひとつ目を通すのはなかなか大変です。

そこで今回は、OpenAIのEmbeddingを使ってレビューを数値化し、UMAPで次元を圧縮、さらにHDBSCANで自動的にグループ分けすることで、レビュー内容を意味的に整理・分析する方法を紹介します。

データ準備

まず、必要なライブラリをインストールします:


pip install datasets pandas openai numpy umap-learn hdbscan python-dotenv

データの取得とEmbedding生成

1.get_data.pyでは、Hugging FaceのAmazonレビューデータセットを使用し、OpenAI Embeddingを生成します:


import pandas as pd
import numpy as np
from datasets import load_dataset
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()

# Hugging FaceからAmazonレビューのデータセットを読み込み
dataset = load_dataset("SetFit/amazon_reviews_multi_ja")
df = pd.DataFrame(dataset['train'])
review_texts = df['text'].tolist()[:100]  # テスト用に100件に制限

# OpenAI Embeddingの取得
client = OpenAI()
client.api_key = os.getenv("OPENAI_API_KEY")
embeddings = []

for text in review_texts:
    response = client.embeddings.create(
        input=text,
        model="text-embedding-3-small"
    )
    embeddings.append(response.data[0].embedding)

embeddings = np.array(embeddings)

次元削減とクラスタリング

UMAPによる次元削減

2.compute_umap.pyでは、高次元のEmbeddingを2次元に削減します:


import umap
import numpy as np

# UMAP設定
reducer = umap.UMAP(
    n_neighbors=15,      # 近傍点数(局所構造の詳細度)
    min_dist=0.1,        # 低次元での最小距離(密集度)
    n_components=2,      # 2次元に削減
    metric='cosine',     # Embeddingには余弦距離が適している
    random_state=42
)

# 次元削減実行
reduced_embeddings = reducer.fit_transform(embeddings)

 

HDBSCANクラスタリング

3.clustering.pyでは、次元削減されたデータに対してクラスタリングを実行します:


import hdbscan
import numpy as np

# クラスタリング実行
clusterer = hdbscan.HDBSCAN(
    min_cluster_size=5,  # 最小クラスターサイズ
    min_samples=3,       # コアポイントになる最小近傍数
    metric='euclidean',  # 削減後はユークリッド距離
    cluster_selection_method='eom'  # 'eom'か'leaf'
)

cluster_labels = clusterer.fit_predict(reduced_embeddings)

UMAPの有無で可視化

4.display_result.pyでは、クラスタリング結果を分析し、各クラスターの特徴を抽出します:


import numpy as np
import matplotlib.pyplot as plt
import hdbscan
import japanize_matplotlib

# データの読み込み
embeddings = np.load('embeddings.npy')
reduced_embeddings = np.load('reduced_embeddings.npy')

# 元の高次元データでのクラスタリング
clusterer_original = hdbscan.HDBSCAN(
    min_cluster_size=5,
    min_samples=3,
    metric='euclidean',
    cluster_selection_method='eom'
)
labels_original = clusterer_original.fit_predict(embeddings)

# UMAPで削減したデータでのクラスタリング
clusterer_reduced = hdbscan.HDBSCAN(
    min_cluster_size=5,
    min_samples=3,
    metric='euclidean',
    cluster_selection_method='eom'
)
labels_reduced = clusterer_reduced.fit_predict(reduced_embeddings)

# 結果の集計
n_clusters_original = len(set(labels_original)) - (1 if -1 in labels_original else 0)
n_noise_original = list(labels_original).count(-1)
n_clusters_reduced = len(set(labels_reduced)) - (1 if -1 in labels_reduced else 0)
n_noise_reduced = list(labels_reduced).count(-1)

# 可視化
plt.figure(figsize=(15, 6))

# 元のデータでのクラスタリング結果
plt.subplot(1, 2, 1)
scatter1 = plt.scatter(embeddings[:, 0], embeddings[:, 1], 
                      c=labels_original, cmap='tab20', alpha=0.6)
plt.title(f'元のデータでのクラスタリング\nクラスター数: {n_clusters_original}, ノイズ: {n_noise_original}')
plt.xlabel('次元1')
plt.ylabel('次元2')

# UMAP削減後のクラスタリング結果
plt.subplot(1, 2, 2)
scatter2 = plt.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1], 
                      c=labels_reduced, cmap='tab20', alpha=0.6)
plt.title(f'UMAP削減後のクラスタリング\nクラスター数: {n_clusters_reduced}, ノイズ: {n_noise_reduced}')
plt.xlabel('UMAP1')
plt.ylabel('UMAP2')

plt.tight_layout()
plt.savefig('clustering_comparison.png')
plt.close()

print(f"元のデータでのクラスタリング結果:")
print(f"- クラスター数: {n_clusters_original}")
print(f"- ノイズ点の数: {n_noise_original}")
print(f"\nUMAP削減後のクラスタリング結果:")
print(f"- クラスター数: {n_clusters_reduced}")
print(f"- ノイズ点の数: {n_noise_reduced}")

可視化の結果

元々はサンプルに入れたデータが少ないのも影響してノイズばかり(左)なのが、
UMAP後ではノイズが減少してクラスターと判定されるようになりました。

まとめ

今回紹介した手法では、OpenAIのEmbeddingを使ってレビューを意味ベースで数値化し、UMAPで次元を圧縮、さらにHDBSCANで自然なクラスタに分類することができます。
これにより、意味は似ていても表現の異なるレビューを自動でグループ化でき、全体の傾向をつかみやすくなります。

また、クラスタ数をあらかじめ指定する必要がないため、実データに柔軟に対応できるのも利点です。
さらに、明らかに他と異なるレビュー(ノイズ)も自動で検出されるため、内容の分析やフィルタリングにも活用しやすくなります。

 

参考記事

t-SNEより強いUMAPを(工学的に)理解したい #Python - Qiita