Trực quan hoá nội dung nhúng bằng t-SNE

Xem trên ai.google.dev Chạy trong Google Colab Xem nguồn trên GitHub

Tổng quan

Hướng dẫn này trình bày cách trực quan hoá và thực hiện thao tác phân cụm bằng các mục nhúng trong Gemini API. Bạn sẽ hình dung một tập hợp con của tập dữ liệu 20 Newsgroup bằng cách sử dụng t-SNE và phân cụm tập hợp con đó bằng thuật toán KMeans.

Để biết thêm thông tin về cách bắt đầu sử dụng các mục nhúng được tạo từ Gemini API, hãy xem tài liệu Bắt đầu nhanh về Python.

Điều kiện tiên quyết

Bạn có thể chạy quy trình bắt đầu nhanh này trong Google Colab.

Để hoàn thành quy trình bắt đầu nhanh này về môi trường phát triển của riêng bạn, hãy đảm bảo rằng môi trường của bạn đáp ứng các yêu cầu sau:

  • Python 3.9 trở lên
  • Cài đặt jupyter để chạy sổ tay.

Thiết lập

Trước tiên, hãy tải thư viện Gemini API Python xuống và cài đặt.

pip install -U -q google.generativeai
import re
import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import google.generativeai as genai

# Used to securely store your API key
from google.colab import userdata

from sklearn.datasets import fetch_20newsgroups
from sklearn.manifold import TSNE
from sklearn.cluster import KMeans
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

Lấy khoá API

Để có thể sử dụng Gemini API, trước tiên, bạn phải có khoá API. Nếu bạn chưa có khoá, hãy tạo khoá chỉ bằng một cú nhấp chuột trong Google AI Studio.

Tải khoá API

Trong Colab, hãy thêm khoá vào trình quản lý khoá bí mật bên dưới biểu tượng "🔑" trên bảng điều khiển bên trái. Đặt tên cho tệp đó là API_KEY.

Sau khi bạn có khoá API, hãy chuyển khoá đó vào SDK. Bạn có thể làm điều này theo hai cách:

  • Đặt khoá vào biến môi trường GOOGLE_API_KEY (SDK sẽ tự động nhận khoá từ đó).
  • Truyền khoá cho genai.configure(api_key=...)
# Or use `os.getenv('API_KEY')` to fetch an environment variable.
API_KEY=userdata.get('API_KEY')

genai.configure(api_key=API_KEY)
for m in genai.list_models():
  if 'embedContent' in m.supported_generation_methods:
    print(m.name)
models/embedding-001
models/embedding-001

Tập dữ liệu

Tập dữ liệu văn bản 20 Newsgroups chứa 18.000 bài đăng của nhóm tin tức về 20 chủ đề được chia thành các tập huấn luyện và kiểm thử. Việc phân chia giữa tập dữ liệu huấn luyện và tập dữ liệu kiểm thử là dựa trên các thông báo được đăng trước và sau một ngày cụ thể. Trong hướng dẫn này, bạn sẽ sử dụng tập hợp con huấn luyện.

newsgroups_train = fetch_20newsgroups(subset='train')

# View list of class names for dataset
newsgroups_train.target_names
['alt.atheism',
 'comp.graphics',
 'comp.os.ms-windows.misc',
 'comp.sys.ibm.pc.hardware',
 'comp.sys.mac.hardware',
 'comp.windows.x',
 'misc.forsale',
 'rec.autos',
 'rec.motorcycles',
 'rec.sport.baseball',
 'rec.sport.hockey',
 'sci.crypt',
 'sci.electronics',
 'sci.med',
 'sci.space',
 'soc.religion.christian',
 'talk.politics.guns',
 'talk.politics.mideast',
 'talk.politics.misc',
 'talk.religion.misc']

Đây là ví dụ đầu tiên trong tập huấn luyện.

idx = newsgroups_train.data[0].index('Lines')
print(newsgroups_train.data[0][idx:])
Lines: 15

 I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is 
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.

Thanks,

- IL
   ---- brought to you by your neighborhood Lerxst ----
# Apply functions to remove names, emails, and extraneous words from data points in newsgroups.data
newsgroups_train.data = [re.sub(r'[\w\.-]+@[\w\.-]+', '', d) for d in newsgroups_train.data] # Remove email
newsgroups_train.data = [re.sub(r"\([^()]*\)", "", d) for d in newsgroups_train.data] # Remove names
newsgroups_train.data = [d.replace("From: ", "") for d in newsgroups_train.data] # Remove "From: "
newsgroups_train.data = [d.replace("\nSubject: ", "") for d in newsgroups_train.data] # Remove "\nSubject: "
# Put training points into a dataframe
df_train = pd.DataFrame(newsgroups_train.data, columns=['Text'])
df_train['Label'] = newsgroups_train.target
# Match label to target name index
df_train['Class Name'] = df_train['Label'].map(newsgroups_train.target_names.__getitem__)
# Retain text samples that can be used in the gecko model.
df_train = df_train[df_train['Text'].str.len() < 10000]

df_train

Tiếp theo, bạn sẽ lấy mẫu một số dữ liệu bằng cách lấy 100 điểm dữ liệu trong tập dữ liệu huấn luyện và bỏ một vài danh mục để xem hướng dẫn này. Chọn danh mục khoa học để so sánh.

# Take a sample of each label category from df_train
SAMPLE_SIZE = 150
df_train = (df_train.groupby('Label', as_index = False)
                    .apply(lambda x: x.sample(SAMPLE_SIZE))
                    .reset_index(drop=True))

# Choose categories about science
df_train = df_train[df_train['Class Name'].str.contains('sci')]

# Reset the index
df_train = df_train.reset_index()
df_train
df_train['Class Name'].value_counts()
sci.crypt          150
sci.electronics    150
sci.med            150
sci.space          150
Name: Class Name, dtype: int64

Tạo các mục nhúng

Trong phần này, bạn sẽ tìm hiểu cách tạo các mục nhúng cho các văn bản khác nhau trong khung dữ liệu bằng cách sử dụng các mục nhúng từ Gemini API.

Thay đổi về API đối với mục Nhúng với mô hình nhúng-001

Đối với mô hình nhúng mới, Nhúng-001, có một tham số loại tác vụ mới và tiêu đề không bắt buộc (chỉ hợp lệ với task_type=RETRIEVAL_DOCUMENT).

Các tham số mới này chỉ áp dụng cho các mô hình nhúng mới nhất.Có các loại tác vụ sau:

Loại việc cần làm Nội dung mô tả
RETRIEVAL_QUERY Chỉ định văn bản đã cho là một truy vấn trong chế độ cài đặt tìm kiếm/truy xuất.
RETRIEVAL_DOCUMENT Chỉ định văn bản đã cho là một tài liệu trong chế độ cài đặt tìm kiếm/truy xuất.
SEMANTIC_SIMILARITY Cho biết văn bản đã cho sẽ được dùng để xác định tính tương đồng về mặt văn bản theo ngữ nghĩa (STS).
PHÂN LOẠI Cho biết các mục nhúng sẽ được dùng để phân loại.
PHÂN TÍCH Chỉ định xem các mục nhúng có được dùng để phân cụm hay không.
from tqdm.auto import tqdm
tqdm.pandas()

from google.api_core import retry

def make_embed_text_fn(model):

  @retry.Retry(timeout=300.0)
  def embed_fn(text: str) -> list[float]:
    # Set the task_type to CLUSTERING.
    embedding = genai.embed_content(model=model,
                                    content=text,
                                    task_type="clustering")
    return embedding["embedding"]

  return embed_fn

def create_embeddings(df):
  model = 'models/embedding-001'
  df['Embeddings'] = df['Text'].progress_apply(make_embed_text_fn(model))
  return df

df_train = create_embeddings(df_train)
0%|          | 0/600 [00:00<?, ?it/s]

Giảm kích thước

Độ dài của vectơ nhúng tài liệu là 768. Để hình dung cách các tài liệu được nhúng được nhóm lại với nhau, bạn sẽ cần áp dụng tính năng giảm kích thước vì bạn chỉ có thể trực quan hoá các mục nhúng trong không gian 2D hoặc 3D. Các tài liệu tương tự về ngữ cảnh nên được đặt gần nhau hơn trong không gian thay vì các tài liệu không giống nhau.

len(df_train['Embeddings'][0])
768
# Convert df_train['Embeddings'] Pandas series to a np.array of float32
X = np.array(df_train['Embeddings'].to_list(), dtype=np.float32)
X.shape
(600, 768)

Bạn sẽ áp dụng phương pháp Nhúng lân cận Stochastic phân phối (t-SNE) để thực hiện việc giảm kích thước. Kỹ thuật này làm giảm số lượng phương diện, trong khi vẫn duy trì các cụm (các điểm gần nhau thì nằm gần nhau). Đối với dữ liệu gốc, mô hình này cố gắng xây dựng phân phối mà theo đó các điểm dữ liệu khác là "lân cận" (ví dụ: chúng có cùng ý nghĩa). Sau đó, công cụ này tối ưu hoá một hàm mục tiêu để duy trì mức phân phối tương tự trong hình ảnh trực quan.

tsne = TSNE(random_state=0, n_iter=1000)
tsne_results = tsne.fit_transform(X)
df_tsne = pd.DataFrame(tsne_results, columns=['TSNE1', 'TSNE2'])
df_tsne['Class Name'] = df_train['Class Name'] # Add labels column from df_train to df_tsne
df_tsne
fig, ax = plt.subplots(figsize=(8,6)) # Set figsize
sns.set_style('darkgrid', {"grid.color": ".6", "grid.linestyle": ":"})
sns.scatterplot(data=df_tsne, x='TSNE1', y='TSNE2', hue='Class Name', palette='hls')
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
plt.title('Scatter plot of news using t-SNE');
plt.xlabel('TSNE1');
plt.ylabel('TSNE2');
plt.axis('equal')
(-46.191162300109866,
 53.521015357971194,
 -39.96646995544434,
 37.282975387573245)

png

So sánh kết quả với KMeans

Phân cụm KMeans là một thuật toán phân cụm phổ biến và thường được dùng cho phương pháp học không giám sát. Hàm này xác định lặp lại các điểm trung tâm k tốt nhất và chỉ định từng ví dụ cho tâm gần nhất. Nhập trực tiếp các mục nhúng vào thuật toán KMeans để so sánh hình ảnh trực quan của các mục nhúng với hiệu suất của thuật toán học máy.

# Apply KMeans
kmeans_model = KMeans(n_clusters=4, random_state=1, n_init='auto').fit(X)
labels = kmeans_model.fit_predict(X)
df_tsne['Cluster'] = labels
df_tsne
fig, ax = plt.subplots(figsize=(8,6)) # Set figsize
sns.set_style('darkgrid', {"grid.color": ".6", "grid.linestyle": ":"})
sns.scatterplot(data=df_tsne, x='TSNE1', y='TSNE2', hue='Cluster', palette='magma')
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
plt.title('Scatter plot of news using KMeans Clustering');
plt.xlabel('TSNE1');
plt.ylabel('TSNE2');
plt.axis('equal')
(-46.191162300109866,
 53.521015357971194,
 -39.96646995544434,
 37.282975387573245)

png

def get_majority_cluster_per_group(df_tsne_cluster, class_names):
  class_clusters = dict()
  for c in class_names:
    # Get rows of dataframe that are equal to c
    rows = df_tsne_cluster.loc[df_tsne_cluster['Class Name'] == c]
    # Get majority value in Cluster column of the rows selected
    cluster = rows.Cluster.mode().values[0]
    # Populate mapping dictionary
    class_clusters[c] = cluster
  return class_clusters
classes = df_tsne['Class Name'].unique()
class_clusters = get_majority_cluster_per_group(df_tsne, classes)
class_clusters
{'sci.crypt': 1, 'sci.electronics': 3, 'sci.med': 2, 'sci.space': 0}

Lấy phần lớn các cụm trên mỗi nhóm và xem có bao nhiêu thành viên thực tế của nhóm có trong cụm đó.

# Convert the Cluster column to use the class name
class_by_id = {v: k for k, v in class_clusters.items()}
df_tsne['Predicted'] = df_tsne['Cluster'].map(class_by_id.__getitem__)

# Filter to the correctly matched rows
correct = df_tsne[df_tsne['Class Name'] == df_tsne['Predicted']]

# Summarise, as a percentage
acc = correct['Class Name'].value_counts() / SAMPLE_SIZE
acc
sci.space          0.966667
sci.med            0.960000
sci.electronics    0.953333
sci.crypt          0.926667
Name: Class Name, dtype: float64
# Get predicted values by name
df_tsne['Predicted'] = ''
for idx, rows in df_tsne.iterrows():
  cluster = rows['Cluster']
  # Get key from mapping based on cluster value
  key = list(class_clusters.keys())[list(class_clusters.values()).index(cluster)]
  df_tsne.at[idx, 'Predicted'] = key

df_tsne

Để trình bày rõ hơn hiệu suất của KMeans được áp dụng cho dữ liệu của bạn, bạn có thể sử dụng ma trận nhầm lẫn. Ma trận nhầm lẫn cho phép bạn đánh giá hiệu suất của mô hình phân loại vượt ngoài độ chính xác. Bạn có thể xem những điểm bị phân loại sai. Bạn sẽ cần các giá trị thực tế và giá trị dự đoán mà bạn đã thu thập trong khung dữ liệu ở trên.

cm = confusion_matrix(df_tsne['Class Name'].to_list(), df_tsne['Predicted'].to_list())
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
                              display_labels=classes)
disp.plot(xticks_rotation='vertical')
plt.title('Confusion Matrix for Actual and Clustered Newsgroups');
plt.grid(False)

png

Các bước tiếp theo

Giờ đây, bạn đã tạo được hình ảnh trực quan của riêng mình về các mục nhúng bằng tính năng phân cụm! Hãy thử sử dụng dữ liệu văn bản của riêng bạn để trực quan hoá chúng dưới dạng tệp nhúng. Bạn có thể giảm kích thước để hoàn tất bước tạo hình ảnh trực quan. Lưu ý rằng TSNE rất hiệu quả trong việc phân cụm đầu vào, nhưng có thể mất nhiều thời gian hơn để hội tụ hoặc có thể bị kẹt ở mức tối thiểu cục bộ. Nếu gặp vấn đề này, bạn có thể cân nhắc sử dụng một kỹ thuật khác là phân tích các thành phần chính (PCA).

Ngoài ra, còn có các thuật toán phân cụm khác bên ngoài KMeans, chẳng hạn như phân cụm không gian dựa trên mật độ (DBSCAN).

Để tìm hiểu thêm về cách sử dụng tính năng nhúng, hãy xem các hướng dẫn khác sau đây: