Spaces:
Running
Running
| # S-BERT ์๋ฒ ๋ฉ โ UMAP ์ฐจ์์ถ์ โ HDBSCAN ๋ฐ๋ ๊ตฐ์งํ๋ก ํค์๋๋ฅผ ์๋ฏธ ํด๋ฌ์คํฐ๋ก ๋ฌถ๋ ๋ชจ๋ | |
| import warnings | |
| from functools import lru_cache | |
| import hdbscan | |
| import pandas as pd | |
| import umap | |
| from sentence_transformers import SentenceTransformer | |
| warnings.filterwarnings("ignore") # UMAP/HDBSCAN์ ๋น์น๋ช ๊ฒฝ๊ณ ์ต์ | |
| # ๋ค๊ตญ์ด ๊ฒฝ๋ ๋ชจ๋ธ(ํ๊ตญ์ด ํฌํจ, ~120MB). ํ์ง ์ฐ์ ์ jhgan/ko-sroberta-multitask๋ก ๊ต์ฒด ๊ฐ๋ฅ | |
| MODEL_NAME = "paraphrase-multilingual-MiniLM-L12-v2" | |
| MIN_KEYWORDS = 20 # ์ด๋ณด๋ค ์ ์ผ๋ฉด ๊ตฐ์ง์ด ๋ฌด์๋ฏธ | |
| RELEVANCE_THRESHOLD = 0.35 # ํค์๋๋ณ ์๋ ์ ์ฌ๋ ํํ (๊ณ์ฐ๊ธฐยทํค๋ณด๋ ๋ฑ ๋ฌด๊ด์ด ์ ๊ฑฐ) | |
| CLUSTER_RELEVANCE_THRESHOLD = 0.45 # ํด๋ฌ์คํฐ ํ๊ท ์๋ ๊ด๋ จ์ฑ ํํ (๋ฐฑํฉยท์๋ค ๋ฑ ํจ์ ์กํ ๊ตฐ์ง ์ ๊ฑฐ) | |
| def _model() -> SentenceTransformer: | |
| # ๋ชจ๋ธ์ ํ ๋ฒ๋ง ๋ก๋ํด ์ฌ์ฌ์ฉ (์ฝ๋ ์คํํธ ๋น์ฉ ์ ๊ฐ) | |
| return SentenceTransformer(MODEL_NAME) | |
| def filter_by_relevance(df: pd.DataFrame, seed: str, threshold: float = RELEVANCE_THRESHOLD): | |
| # ์๋์ ์ ์ฌ๋๊ฐ ๋ฎ์ ๋ฌด๊ด ํค์๋ ์ ๊ฑฐ. (ํํฐ๋ df, ์ ๊ทํ ์๋ฒ ๋ฉ) ๋ฐํ | |
| model = _model() | |
| seed_emb = model.encode([seed], normalize_embeddings=True)[0] | |
| kw_emb = model.encode(df["keyword"].tolist(), normalize_embeddings=True) | |
| sims = kw_emb @ seed_emb # ์ ๊ทํ๋ผ ์์ด ๋ด์ = ์ฝ์ฌ์ธ ์ ์ฌ๋ | |
| df = df.copy() | |
| df["relevance"] = sims | |
| mask = sims >= threshold | |
| return df[mask].reset_index(drop=True), kw_emb[mask] | |
| def cluster_keywords(df: pd.DataFrame, embeddings=None, min_cluster_size: int = 10) -> list[dict]: | |
| # ํค์๋ df๋ฅผ ์๋ฏธ ๊ตฐ์ง์ผ๋ก ๋ฌถ์ด ๊ฒ์๋์ ํด๋ฌ์คํฐ ์์ฝ ๋ฐํ (๋ ธ์ด์ฆ -1 ์ ์ธ) | |
| # embeddings: filter_by_relevance๊ฐ ๋ง๋ ์๋ฒ ๋ฉ์ ๋๊ธฐ๋ฉด ์ฌ๊ณ์ฐ์ ์๋ตํ๋ค | |
| if len(df) < MIN_KEYWORDS: | |
| return [] | |
| df = df.copy() | |
| df["total_volume"] = df["search_volume_pc"].fillna(0) + df["search_volume_mobile"].fillna(0) | |
| # 1) ์ ๊ทํ ์๋ฒ ๋ฉ (์์ผ๋ฉด ์๋ก ๊ณ์ฐ) | |
| emb = embeddings if embeddings is not None else _model().encode( | |
| df["keyword"].tolist(), normalize_embeddings=True, show_progress_bar=False | |
| ) | |
| # 2) UMAP์ผ๋ก 5์ฐจ์ ์ถ์ (๊ณ ์ฐจ์ ๋ฉ๊ฐ ํด๋ฌ์คํฐ ๋ฐฉ์ง, ์ฝ์ฌ์ธ ๊ธฐ๋ฐ) | |
| n_neighbors = min(10, len(df) - 1) | |
| reduced = umap.UMAP( | |
| n_neighbors=n_neighbors, n_components=5, min_dist=0.0, | |
| metric="cosine", random_state=42, | |
| ).fit_transform(emb) | |
| # 3) HDBSCAN leaf ๋ฐฉ์์ผ๋ก ์ธ๋ถํ๋ ๋์ง ๊ตฐ์ง ์ถ์ถ | |
| df["cluster_id"] = hdbscan.HDBSCAN( | |
| min_cluster_size=min_cluster_size, cluster_selection_method="leaf" | |
| ).fit_predict(reduced) | |
| clusters = [] | |
| for cid, g in df[df["cluster_id"] != -1].groupby("cluster_id"): | |
| # ํด๋ฌ์คํฐ ์ ์ฒด๊ฐ ์๋์ ๋์จํ ํจ์ ์กํ๋ฉด ๊ตฐ์ง์งธ ์ ์ธ | |
| if "relevance" in g.columns and g["relevance"].mean() < CLUSTER_RELEVANCE_THRESHOLD: | |
| continue | |
| g = g.sort_values("total_volume", ascending=False) | |
| clusters.append({ | |
| "cluster_id": int(cid), | |
| "cluster_label": g.iloc[0]["keyword"], # ๋ํ ํค์๋(์ต๊ณ ๊ฒ์๋) | |
| "keyword_count": int(len(g)), | |
| "total_search_volume": int(g["total_volume"].sum()), | |
| "top_keywords": g["keyword"].head(4).tolist(), # ์ฃผ์ ํฌํจ ํค์๋ | |
| }) | |
| clusters.sort(key=lambda c: c["total_search_volume"], reverse=True) | |
| return clusters | |