Spaces:
Running
Running
File size: 3,599 Bytes
21bdc64 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 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 # ํด๋ฌ์คํฐ ํ๊ท ์๋ ๊ด๋ จ์ฑ ํํ (๋ฐฑํฉยท์๋ค ๋ฑ ํจ์
์กํ ๊ตฐ์ง ์ ๊ฑฐ)
@lru_cache(maxsize=1)
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
|