我们使用一个简单的 k-means 算法来演示如何进行聚类。聚类可以帮助发现数据中有价值的、隐藏的群体。数据集在 Get_embeddings_from_dataset Notebook 中创建。

# 导入
import numpy as np
import pandas as pd
from ast import literal_eval

# 加载数据
datafile_path = "./data/fine_food_reviews_with_embeddings_1k.csv"

df = pd.read_csv(datafile_path)
df["embedding"] = df.embedding.apply(literal_eval).apply(np.array)  # 将字符串转换为 numpy 数组
matrix = np.vstack(df.embedding.values)
matrix.shape

(1000, 1536)

1. 使用 K-means 查找聚类

我们展示了 K-means 最简单的用法。您可以选择最适合您用例的聚类数量。

from sklearn.cluster import KMeans

n_clusters = 4

kmeans = KMeans(n_clusters=n_clusters, init="k-means++", random_state=42)
kmeans.fit(matrix)
labels = kmeans.labels_
df["Cluster"] = labels

df.groupby("Cluster").Score.mean().sort_values()

Cluster 0 4.105691 1 4.191176 2 4.215613 3 4.306590 Name: Score, dtype: float64

from sklearn.manifold import TSNE
import matplotlib
import matplotlib.pyplot as plt

tsne = TSNE(n_components=2, perplexity=15, random_state=42, init="random", learning_rate=200)
vis_dims2 = tsne.fit_transform(matrix)

x = [x for x, y in vis_dims2]
y = [y for x, y in vis_dims2]

for category, color in enumerate(["purple", "green", "red", "blue"]):
    xs = np.array(x)[df.Cluster == category]
    ys = np.array(y)[df.Cluster == category]
    plt.scatter(xs, ys, color=color, alpha=0.3)

    avg_x = xs.mean()
    avg_y = ys.mean()

    plt.scatter(avg_x, avg_y, marker="x", color=color, s=100)
plt.title("使用 t-SNE 在语言 2d 中可视化的聚类")

Text(0.5, 1.0, '使用 t-SNE 在语言 2d 中可视化的聚类')

png

聚类在 2d 投影中的可视化。在此次运行中,绿色聚类(#1)似乎与其他聚类有很大不同。让我们看一下每个聚类的几个样本。

2. 聚类中的文本样本和命名聚类

让我们展示每个聚类的随机样本。我们将使用 gpt-4 根据该聚类中 5 个随机评论的样本来命名聚类。

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))

# 读取属于每个组的评论。
rev_per_cluster = 5

for i in range(n_clusters):
    print(f"Cluster {i} Theme:", end=" ")

    reviews = "\n".join(
        df[df.Cluster == i]
        .combined.str.replace("Title: ", "")
        .str.replace("\n\nContent: ", ":  ")
        .sample(rev_per_cluster, random_state=42)
        .values
    )

    messages = [
        {"role": "user", "content": f'What do the following customer reviews have in common?\n\nCustomer reviews:\n"""\n{reviews}\n"""\n\nTheme:'}
    ]

    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages,
        temperature=0,
        max_tokens=64,
        top_p=1,
        frequency_penalty=0,
        presence_penalty=0)
    print(response.choices[0].message.content.replace("\n", ""))

    sample_cluster_rows = df[df.Cluster == i].sample(rev_per_cluster, random_state=42)
    for j in range(rev_per_cluster):
        print(sample_cluster_rows.Score.values[j], end=", ")
        print(sample_cluster_rows.Summary.values[j], end=":   ")
        print(sample_cluster_rows.Text.str[:70].values[j])

    print("-" * 100)

Cluster 0 Theme: 这些客户评论的主题是亚马逊上购买的食品。 5, 喜欢这些无麸质健康能量棒,在亚马逊上订购省了很多钱: 这些 Kind 能量棒又好又健康又无麸质。我的女儿可以 1, 应该更突出地宣传椰子作为一种成分: 首先,这些应该被称为 Mac - 椰子能量棒,因为椰子是第二位的 5, 非常好!!: 就像糖果一样
很棒的味道,绝对值得购买
我甚至 o 5, 很棒的产品: 在搜遍了镇上的每一家店都没有找到橙皮之后 5, 美味: 橡皮蛙是我吃过的最喜欢的糖果。当然


Cluster 1 Theme: 宠物食品评论 2, 杂乱无章,显然不好吃: 我的猫不是非常喜欢。当然,她会舔掉肉汁,但会留下 4, 猫喜欢它: 我的 7 只猫喜欢这种食物,但对人类来说有点难吃。块 5, 停不下来!!!: 我们的迷你狮子狗幼犬怎么也吃不够。每次她看到 1, 食物导致生病: 我把我的猫从 Blue Buffalo Wildnerness 食物换成了这种 5, 我的毛茸茸宝贝们喜欢这些!: 摇晃容器,它们就会跑过来。就连我的公猫,他也不是


Cluster 2 Theme: 所有评论都关于不同种类的咖啡。 5, Fog Chaser Coffee: 这种咖啡口感醇厚,味道浓郁。价格远低于 5, 口味极佳: 对我来说,这是一款很棒的咖啡,一旦您尝试了,就会喜欢它,这 4, 好,但不是 Wolfgang Puck 那么好: 老实说,我不得不承认我期望能好一点。那不是 5, 正是我喜欢的咖啡: Coffee Masters 的榛子咖啡曾经在一个当地的咖啡/茶点店出售/ 5, Rodeo Drive 是疯狂好喝的咖啡!: Rodeo Drive 是我的最爱,我准备订购更多!那


Cluster 3 Theme: 这些客户评论的主题是食品和饮料产品。 5, 苏打水的绝佳替代品: 这是苏打水的绝佳替代品。它为那些 5, 太方便了,而且价格这么低!: 我需要两个香草豆来制作我丈夫的 Love Goddess 蛋糕 2, 不太奶酪: 大约一个月前买的。首先,它闻起来很糟糕……它的味道 5, 美味!: 我不是一个非常喜欢啤酒的人。我确实偶尔喜欢 Blue Moon(所有 3, 一般般: 我买这个牌子是因为它是我家附近的 Ranch 99 唯一有的。我


需要注意的是,聚类不一定会与您打算使用它们的方式相匹配。更多的聚类将侧重于更具体的模式,而少量的聚类通常会侧重于数据中最大的差异。