v1.0.4; Add save_additional_output
This commit is contained in:
198
robogauge/utils/visualize/plot_latent_tsne.py
Normal file
198
robogauge/utils/visualize/plot_latent_tsne.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from sklearn.manifold import TSNE
|
||||
import os
|
||||
import glob
|
||||
import pandas as pd
|
||||
from sklearn.metrics import silhouette_score, silhouette_samples, calinski_harabasz_score
|
||||
|
||||
# ================= 配置区域 =================
|
||||
data_root = "/root/Coding/RoboGauge/logs_latent/moe"
|
||||
terrains = ['flat', 'wave', 'slope_fd', 'slope_bd', 'stairs_fd', 'stairs_bd', 'obstacle']
|
||||
|
||||
command_map = {
|
||||
0: "Pos X (Forward)",
|
||||
1: "Neg X (Backward)",
|
||||
2: "Pos Y (Left)",
|
||||
3: "Neg Y (Right)",
|
||||
4: "Pos Yaw (Turn Left)",
|
||||
5: "Neg Yaw (Turn Right)"
|
||||
}
|
||||
|
||||
cmd_colors = {
|
||||
0: '#d62728', 1: '#1f77b4', 2: '#2ca02c',
|
||||
3: '#ff7f0e', 4: '#9467bd', 5: '#8c564b'
|
||||
}
|
||||
|
||||
# t-SNE 计算较慢,且点太多会严重重叠
|
||||
# 建议每种指令采样 800 - 1000 个点即可,足以看清分布
|
||||
MAX_SAMPLES_PER_CMD = 2000
|
||||
|
||||
# t-SNE 参数
|
||||
# perplexity: 困惑度,通常在 5-50 之间。越大越关注全局结构,越小越关注局部邻居。
|
||||
TSNE_PERPLEXITY = 30
|
||||
TSNE_ITER = 1000
|
||||
|
||||
# ================= 功能函数 =================
|
||||
|
||||
# key = 'weights' # 'latent'
|
||||
key = 'latent'
|
||||
|
||||
def load_combined_command_data(root_path, terrain_list, cmd_id):
|
||||
"""读取指定指令在所有地形下的数据并合并"""
|
||||
cmd_latents = []
|
||||
filename = f"moe_info_{cmd_id}.npz"
|
||||
|
||||
for t_name in terrain_list:
|
||||
search_pattern = os.path.join(root_path, f"go2_moe_{t_name}_latent", "*", filename)
|
||||
files = glob.glob(search_pattern)
|
||||
for f in files:
|
||||
try:
|
||||
data = np.load(f)
|
||||
if key in data:
|
||||
cmd_latents.append(data[key])
|
||||
except: pass
|
||||
|
||||
if not cmd_latents: return None
|
||||
return np.concatenate(cmd_latents, axis=0)
|
||||
|
||||
# ================= 主程序 =================
|
||||
|
||||
print(f"Start processing t-SNE visualization...")
|
||||
|
||||
all_data = []
|
||||
all_labels = []
|
||||
|
||||
# 1. 数据收集
|
||||
for cmd_id, cmd_name in command_map.items():
|
||||
raw_data = load_combined_command_data(data_root, terrains, cmd_id)
|
||||
|
||||
if raw_data is not None:
|
||||
# 随机下采样
|
||||
n_total = len(raw_data)
|
||||
if n_total > MAX_SAMPLES_PER_CMD:
|
||||
indices = np.random.choice(n_total, MAX_SAMPLES_PER_CMD, replace=False)
|
||||
data_sample = raw_data[indices]
|
||||
else:
|
||||
data_sample = raw_data
|
||||
|
||||
all_data.append(data_sample)
|
||||
all_labels.extend([cmd_id] * len(data_sample))
|
||||
print(f" - Cmd {cmd_id}: {len(data_sample)} samples")
|
||||
|
||||
if not all_data:
|
||||
print("Error: No data loaded.")
|
||||
exit()
|
||||
|
||||
X = np.concatenate(all_data, axis=0)
|
||||
y = np.array(all_labels)
|
||||
|
||||
print(f"Running t-SNE on {X.shape} matrix...")
|
||||
print(f" (Perplexity={TSNE_PERPLEXITY}, Iterations={TSNE_ITER})")
|
||||
print(" This may take a moment...")
|
||||
|
||||
# 2. t-SNE 降维
|
||||
# init='pca' 通常比 'random' 更稳定,能更好地保留全局结构
|
||||
tsne = TSNE(
|
||||
n_components=2,
|
||||
perplexity=TSNE_PERPLEXITY,
|
||||
n_iter=TSNE_ITER,
|
||||
init='pca',
|
||||
learning_rate='auto',
|
||||
random_state=42,
|
||||
verbose=1,
|
||||
)
|
||||
X_embedded = tsne.fit_transform(X)
|
||||
|
||||
# 3. 可视化
|
||||
plt.figure(figsize=(12, 10), dpi=120)
|
||||
|
||||
for cmd_id in command_map.keys():
|
||||
indices = (y == cmd_id)
|
||||
points = X_embedded[indices]
|
||||
|
||||
if len(points) > 0:
|
||||
plt.scatter(
|
||||
points[:, 0],
|
||||
points[:, 1],
|
||||
c=cmd_colors[cmd_id],
|
||||
label=command_map[cmd_id],
|
||||
alpha=0.6,
|
||||
s=20 #稍微大一点的点
|
||||
)
|
||||
|
||||
plt.title(f"t-SNE Visualization of {key.capitalize()} Space by Command\n(Perplexity={TSNE_PERPLEXITY}, Mixed Terrains)")
|
||||
# t-SNE 的坐标轴没有物理意义,所以隐藏刻度通常更好看
|
||||
plt.xticks([])
|
||||
plt.yticks([])
|
||||
plt.legend(title="Control Commands", markerscale=2, bbox_to_anchor=(1.05, 1), loc='upper left')
|
||||
plt.tight_layout()
|
||||
|
||||
save_path = os.path.join(data_root, f'{key}_tsne_by_command.png')
|
||||
plt.savefig(save_path)
|
||||
print(f"Done! Visualization saved to {save_path}")
|
||||
plt.show()
|
||||
|
||||
def evaluate_latent_space(X, y, command_map):
|
||||
"""
|
||||
X: (N, D) 原始高维 Latent 向量 (例如 32维)
|
||||
y: (N,) 对应的指令标签 (0-5)
|
||||
command_map: label到名称的映射字典
|
||||
"""
|
||||
print(f"--- Evaluating Latent Space (Samples: {X.shape[0]}, Dim: {X.shape[1]}) ---")
|
||||
|
||||
# 1. 全局指标 (Global Metrics)
|
||||
# 轮廓系数 (Silhouette): 越接近 1 越好
|
||||
global_sil = silhouette_score(X, y)
|
||||
# Calinski-Harabasz Index (CHI): 数值越大越好 (表示类间离散度高,类内离散度低)
|
||||
global_chi = calinski_harabasz_score(X, y)
|
||||
|
||||
print(f"Global Silhouette Score: {global_sil:.4f} (范围 -1 到 1, 越大越好)")
|
||||
print(f"Global Calinski-Harabasz: {global_chi:.1f} (数值越大越好)")
|
||||
print("-" * 30)
|
||||
|
||||
# 2. 逐类指标 (Per-Class Metrics)
|
||||
# 计算每个样本的轮廓系数
|
||||
sample_silhouette_values = silhouette_samples(X, y)
|
||||
|
||||
class_scores = []
|
||||
|
||||
for label in sorted(command_map.keys()):
|
||||
# 提取当前类的所有样本的轮廓系数
|
||||
ith_class_silhouette_values = sample_silhouette_values[y == label]
|
||||
|
||||
# 计算该类的平均分
|
||||
avg_score = np.mean(ith_class_silhouette_values)
|
||||
|
||||
# 计算该类的紧密度 (Intra-class distance) - 可选
|
||||
# 这里直接用轮廓系数代表聚集程度
|
||||
|
||||
class_scores.append({
|
||||
"Command ID": label,
|
||||
"Command Name": command_map[label],
|
||||
"Silhouette Score": avg_score,
|
||||
"Sample Count": len(ith_class_silhouette_values)
|
||||
})
|
||||
|
||||
# 转为 DataFrame 展示
|
||||
df = pd.DataFrame(class_scores)
|
||||
print(df.to_string(index=False))
|
||||
print("-" * 30 + "\n")
|
||||
|
||||
return df, global_sil
|
||||
|
||||
# 1. 评估原始高维空间 (Intrinsic Quality)
|
||||
print(">>> Evaluating Original High-Dim Latent Space Quality...")
|
||||
df_original, score_original = evaluate_latent_space(X, y, command_map)
|
||||
print(f"Original Space Score: {score_original:.4f}")
|
||||
|
||||
# 2. 评估 t-SNE 降维后的 2D 空间 (Visual Cluster Quality)
|
||||
print("\n>>> Evaluating t-SNE Embedded Space Quality (2D)...")
|
||||
# 直接传入 X_embedded 即可,evaluate_latent_space 函数对维度不敏感
|
||||
df_tsne, score_tsne = evaluate_latent_space(X_embedded, y, command_map)
|
||||
|
||||
# 保存 t-SNE 的评估结果
|
||||
save_path_tsne = os.path.join(data_root, f'{key}_tsne_2d_evaluation.csv')
|
||||
df_tsne.to_csv(save_path_tsne, index=False)
|
||||
print(f"t-SNE 2D Evaluation results saved to {save_path_tsne}")
|
||||
print(f"t-SNE Space Score: {score_tsne:.4f}")
|
||||
Reference in New Issue
Block a user