
해당 distribution 그래프에서 각 선의 교차점을 계산해보려고 한다.
import numpy as np
import pandas as pd
from scipy.stats import gaussian_kde
def one_intersection_kde(x_succ, x_fail, n_grid=2000):
x_succ = np.asarray(x_succ, dtype=float)
x_fail = np.asarray(x_fail, dtype=float)
# x 범위
lo = min(x_succ.min(), x_fail.min())
hi = max(x_succ.max(), x_fail.max())
grid = np.linspace(lo, hi, n_grid)
# KDE
kde_s = gaussian_kde(x_succ)
kde_f = gaussian_kde(x_fail)
diff = kde_s(grid) - kde_f(grid)
# 부호가 바뀌는 구간 찾기
idx = np.where(diff[:-1] * diff[1:] < 0)[0]
i = idx[0]
# 선형 보간으로 x 근사
x0, x1 = grid[i], grid[i+1]
y0, y1 = diff[i], diff[i+1]
x_star = x0 - y0 * (x1 - x0) / (y1 - y0)
return float(x_star)
1. x_succ, x_fail을 numpy 배열로 변환
x_succ = np.asarray(x_succ, dtype=float)
x_fail = np.asarray(x_fail, dtype=float)
df = pd.read_csv("experiment_result_scores.csv")로 csv 파일을 읽어 함수를 호출하면 pandas Series 형태로 들어오므로2. 교점을 찾을 x축 구간을 정의 및 grid 샘플링
lo = min(x_succ.min(), x_fail.min())
hi = max(x_succ.max(), x_fail.max())
grid = np.linspace(lo, hi, n_grid)
grid: 연속적인 x축을 컴퓨터가 다룰 수 있도록 이산 점들로 쪼갠 것3. 성공/실패 각각에 대해 KDE(커널 밀도 추정) 모델을 만들기
kde_s = gaussian_kde(x_succ)
kde_f = gaussian_kde(x_fail)
gaussian_kde: 샘플 score들로부터 확률 밀도 함수(PDF)를 추정하는 기법kde_s, kde_f는 함수처럼 동작함kde_s: x에서의 성공 분포 밀도값kde_f: x에서의 실패 분초 밀도값4. 두 곡선의 차이(diff) 계산
diff = kde_s(grid) - kde_f(grid)
5. diff의 부호가 바뀌는 구간 찾기 (= 교점이 있는 구간)
idx = np.where(diff[:-1] * diff[1:] < 0)[0]
diff는 grid 점들로 계산한 값이므로 정확히 0이 나오지 않을 수 있음diff[:-1] * diff[1:] < 0 이면 두 값의 부호가 다르다는 뜻6. 선형 보간으로 교점 x 계산
i = idx[0]
x0, x1 = grid[i], grid[i+1]
y0, y1 = diff[i], diff[i+1]
x_star = x0 - y0 * (x1 - x0) / (y1 - y0)

7. 최종 결과 반환
return float(x_star)
코드 실행 결과
Visual red-yellow: 0.5399406519498681
Visual yellow-green = 0.7175686585568468
Semantic red-green = 0.06631270799912138

=> 그래프가 정확히 교차하는 지점이 아님!
이유
해결 방법
import numpy as np
def intersection_x_from_lines(line1, line2, n_grid=20000):
"""
line1, line2: matplotlib Line2D (ax.lines에서 가져온 KDE 선)
"""
x1, y1 = line1.get_data()
x2, y2 = line2.get_data()
# 두 곡선이 실제로 겹치는 x 구간만 사용
lo = max(np.min(x1), np.min(x2))
hi = min(np.max(x1), np.max(x2))
grid = np.linspace(lo, hi, n_grid)
# 각 곡선을 같은 grid로 보간
y1i = np.interp(grid, x1, y1)
y2i = np.interp(grid, x2, y2)
diff = y1i - y2i
idx = np.where(diff[:-1] * diff[1:] < 0)[0]
i = idx[0]
x0, x1_ = grid[i], grid[i+1]
d0, d1 = diff[i], diff[i+1]
x_star = x0 - d0 * (x1_ - x0) / (d1 - d0)
return float(x_star)
그래프 생성 함수는 다음과 같이
sns.histplot(data=df, x="vis_success_score", kde=True, ax=ax_vis,
color="green", label="Success (Original-GMP)", element="step", alpha=0.3)
line_green = ax_vis.lines[-1]
sns.histplot(data=df, x="vis_fail_clothes_score", kde=True, ax=ax_vis,
color="orange", label="Fail (Diff Clothes)", element="step", alpha=0.3)
line_orange = ax_vis.lines[-1]
sns.histplot(data=df, x="vis_fail_thing_score", kde=True, ax=ax_vis,
color="red", label="Fail (Diff Things)", element="step", alpha=0.3)
line_red = ax_vis.lines[-1]
# 그래프에 그려진 KDE 선 기준 교점 계산
vis_thing_cloth_x = intersection_x_from_lines(line_red, line_orange)
vis_success_cloth_x = intersection_x_from_lines(line_green, line_orange)
결과
Visual red-yellow: 0.5325409337303397
Visual yellow-green: 0.7290050105111927
Semantic red-green: 0.06745537268189729

=> 정확하게 그래프의 교점을 찾았다!
전체 코드
import numpy as np
import pandas as pd
from scipy.stats import gaussian_kde
import os
import sys
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
from tqdm import tqdm
current_dir = os.path.dirname(os.path.abspath(__file__))
src_dir = os.path.dirname(current_dir)
if src_dir not in sys.path:
sys.path.append(src_dir)
DATA_ROOT = Path("/home/hana/GMP/gmp_loop/data/visual_semantic_test")
# Input/Output Paths
INPUT_PATH_CSV = DATA_ROOT / "experiment_path.csv"
OUTPUT_SCORE_CSV = DATA_ROOT / "experiment_result_scores.csv"
OUTPUT_PLOT_PATH = DATA_ROOT / "score_distribution.png"
OUTPUT_PANEL_ROOT = DATA_ROOT / "panels"
# Panel Generation Settings
SCALE = 2
IMG_BOX = 384 * SCALE
TEXT_HEIGHT = 60 * SCALE
PADDING = 30 * SCALE
PID_WIDTH = 160 * SCALE
BG_COLOR = (255, 255, 255)
TEXT_COLOR = (0, 0, 0)
SCORE_COLOR_SUCCESS = (0, 100, 0)
SCORE_COLOR_FAIL = (150, 0, 0)
def one_intersection_kde(x_succ, x_fail, n_grid=10000):
x_succ = np.asarray(x_succ, dtype=float)
x_fail = np.asarray(x_fail, dtype=float)
# x 범위
lo = min(x_succ.min(), x_fail.min())
hi = max(x_succ.max(), x_fail.max())
grid = np.linspace(lo, hi, n_grid)
# KDE
kde_s = gaussian_kde(x_succ)
kde_f = gaussian_kde(x_fail)
diff = kde_s(grid) - kde_f(grid)
# 부호가 바뀌는 "유일한" 구간 찾기
idx = np.where(diff[:-1] * diff[1:] < 0)[0]
i = idx[0]
# 선형 보간으로 x 근사
x0, x1 = grid[i], grid[i+1]
y0, y1 = diff[i], diff[i+1]
x_star = x0 - y0 * (x1 - x0) / (y1 - y0)
return float(x_star)
import numpy as np
def intersection_x_from_lines(line1, line2, n_grid=20000):
"""
line1, line2: matplotlib Line2D (ax.lines에서 가져온 KDE 선)
return: 교점 x (1개라고 가정)
"""
x1, y1 = line1.get_data()
x2, y2 = line2.get_data()
# 두 곡선이 실제로 겹치는 x 구간만 사용
lo = max(np.min(x1), np.min(x2))
hi = min(np.max(x1), np.max(x2))
grid = np.linspace(lo, hi, n_grid)
# 각 곡선을 같은 grid로 보간(interpolation)
y1i = np.interp(grid, x1, y1)
y2i = np.interp(grid, x2, y2)
diff = y1i - y2i
idx = np.where(diff[:-1] * diff[1:] < 0)[0]
if len(idx) == 0:
raise ValueError("교점이 없습니다.")
if len(idx) > 1:
raise ValueError(f"교점이 1개가 아닙니다. 교점 구간 수: {len(idx)}")
i = idx[0]
x0, x1_ = grid[i], grid[i+1]
d0, d1 = diff[i], diff[i+1]
x_star = x0 - d0 * (x1_ - x0) / (d1 - d0)
return float(x_star)
def step2_analyze_distribution():
print("\n[Step 2] Analyzing Score Distribution...")
if not OUTPUT_SCORE_CSV.exists():
print("Score file not found. Run Step 1 first.")
return False
df = pd.read_csv(OUTPUT_SCORE_CSV).dropna(subset=[
"vis_success_score", "vis_fail_clothes_score", "vis_fail_thing_score",
"sem_success_score", "sem_fail_score"
])
fig, axes = plt.subplots(2, 1, figsize=(12, 12))
plt.subplots_adjust(hspace=0.3)
sns.set_theme(style="whitegrid")
# =========================
# A) Visual
# =========================
ax_vis = axes[0]
ax_vis.set_title("Visual Consistency Score Distribution", fontsize=15, fontweight='bold')
sns.histplot(data=df, x="vis_success_score", kde=True, ax=ax_vis,
color="green", label="Success (Original-GMP)", element="step", alpha=0.3)
line_green = ax_vis.lines[-1] # 방금 그려진 KDE 선
sns.histplot(data=df, x="vis_fail_clothes_score", kde=True, ax=ax_vis,
color="orange", label="Fail (Diff Clothes)", element="step", alpha=0.3)
line_orange = ax_vis.lines[-1]
sns.histplot(data=df, x="vis_fail_thing_score", kde=True, ax=ax_vis,
color="red", label="Fail (Diff Things)", element="step", alpha=0.3)
line_red = ax_vis.lines[-1]
# “그래프에 그려진 KDE 선” 기준 교점 계산
vis_thing_cloth_x = intersection_x_from_lines(line_red, line_orange)
vis_success_cloth_x = intersection_x_from_lines(line_green, line_orange)
print(f"Visual red-yellow: {vis_thing_cloth_x}")
print(f"Visual yellow-green: {vis_success_cloth_x}")
# 교점 세로선 표시
ax_vis.axvline(vis_thing_cloth_x, linestyle="--", linewidth=2, color="black",
label=f"X (thing-cloth) = {vis_thing_cloth_x:.4f}")
ax_vis.axvline(vis_success_cloth_x, linestyle="--", linewidth=2, color="black",
label=f"X (succ-cloth) = {vis_success_cloth_x:.4f}")
y_top = ax_vis.get_ylim()[1]
ax_vis.text(vis_thing_cloth_x, y_top*0.95, f"{vis_thing_cloth_x:.4f}",
rotation=90, va="top", ha="right", fontsize=10)
ax_vis.text(vis_success_cloth_x, y_top*0.95, f"{vis_success_cloth_x:.4f}",
rotation=90, va="top", ha="right", fontsize=10)
ax_vis.legend()
# =========================
# B) Semantic
# =========================
ax_sem = axes[1]
ax_sem.set_title("Semantic Consistency Score Distribution", fontsize=15, fontweight='bold')
sns.histplot(data=df, x="sem_success_score", kde=True, ax=ax_sem,
color="green", label="Success (Correct Category)", element="step", alpha=0.3)
line_sem_green = ax_sem.lines[-1]
sns.histplot(data=df, x="sem_fail_score", kde=True, ax=ax_sem,
color="red", label="Fail (Wrong Category)", element="step", alpha=0.3)
line_sem_red = ax_sem.lines[-1]
# “그래프에 그려진 KDE 선” 기준 교점 계산
sem_x = intersection_x_from_lines(line_sem_green, line_sem_red)
print(f"Semantic red-green: {sem_x}")
ax_sem.axvline(sem_x, linestyle="--", linewidth=2, color="black",
label=f"X (succ-fail) = {sem_x:.4f}")
y_top2 = ax_sem.get_ylim()[1]
ax_sem.text(sem_x, y_top2*0.95, f"{sem_x:.4f}",
rotation=90, va="top", ha="right", fontsize=10)
ax_sem.legend()
plt.savefig(OUTPUT_PLOT_PATH, dpi=300)
plt.close()
print(f"Distribution plot saved to: {OUTPUT_PLOT_PATH}")
return True