Yoon-gu Hwang Claude commited on
Commit
3e3dc68
ยท
0 Parent(s):

Initial commit: Concept Drift Simulator

Browse files

- 4๊ฐ€์ง€ ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜• ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (Sudden, Gradual, Incremental, Recurring)
- Plotly ๊ธฐ๋ฐ˜ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๊ฐํ™”
- Gradio UI ๊ตฌ์„ฑ
- ๋“œ๋ฆฌํ”„ํŠธ ๋ถ„์„ ๊ธฐ๋Šฅ

๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

Files changed (7) hide show
  1. .gitignore +17 -0
  2. README.md +56 -0
  3. analyzer.py +81 -0
  4. app.py +92 -0
  5. drift_simulator.py +97 -0
  6. requirements.txt +3 -0
  7. visualizer.py +110 -0
.gitignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ env/
7
+ venv/
8
+ ENV/
9
+ .venv
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+ .DS_Store
14
+ .idea/
15
+ .vscode/
16
+ *.log
17
+ flagged/
README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Concept Drift Simulator
2
+
3
+ ์ปจ์…‰ ๋“œ๋ฆฌํ”„ํŠธ(Concept Drift)์˜ 4๊ฐ€์ง€ ์ฃผ์š” ์œ ํ˜•์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•˜๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค.
4
+
5
+ ## ์ฃผ์š” ๊ธฐ๋Šฅ
6
+
7
+ 4๊ฐ€์ง€ ์ปจ์…‰ ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜• ์‹œ๋ฎฌ๋ ˆ์ด์…˜:
8
+
9
+ 1. **Sudden (๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ)**: ํŠน์ • ์‹œ์ ์—์„œ ๋ฐ์ดํ„ฐ ๋ถ„ํฌ๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ
10
+ 2. **Gradual (์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ)**: ์ด์ „ ๋ถ„ํฌ์™€ ์ƒˆ ๋ถ„ํฌ๊ฐ€ ์„ž์ด๋ฉฐ ์ฒœ์ฒœํžˆ ์ „ํ™˜
11
+ 3. **Incremental (์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ)**: ์ž‘์€ ๋‹จ๊ณ„๋กœ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ๊ณ„๋‹จ์‹ ํŒจํ„ด ํ˜•์„ฑ
12
+ 4. **Recurring (๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ)**: ์ด์ „ ๋ถ„ํฌ๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ๋‚˜ํƒ€๋‚จ
13
+
14
+ ## ๊ธฐ์ˆ  ์Šคํƒ
15
+
16
+ - **Gradio**: ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ UI ๊ตฌ์„ฑ
17
+ - **Plotly**: ๋™์  ์‹œ๊ฐํ™”
18
+ - **NumPy**: ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ฐ ๋ถ„์„
19
+
20
+ ## ์„ค์น˜ ๋ฐ ์‹คํ–‰
21
+
22
+ ```bash
23
+ # ์˜์กด์„ฑ ์„ค์น˜
24
+ pip install -r requirements.txt
25
+
26
+ # ์•ฑ ์‹คํ–‰
27
+ python app.py
28
+ ```
29
+
30
+ ๋ธŒ๋ผ์šฐ์ €์—์„œ `http://localhost:7860` ์ ‘์†
31
+
32
+ ## ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
33
+
34
+ 1. ์™ผ์ชฝ ํŒจ๋„์—์„œ ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜• ์„ ํƒ
35
+ 2. "์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰" ๋ฒ„ํŠผ ํด๋ฆญ
36
+ 3. ์˜ค๋ฅธ์ชฝ์— ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ Plotly ์ฐจํŠธ์™€ ๋ถ„์„ ๊ฒฐ๊ณผ ํ™•์ธ
37
+
38
+ ## ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
39
+
40
+ ```
41
+ concept-drift/
42
+ โ”œโ”€โ”€ app.py # Gradio ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
43
+ โ”œโ”€โ”€ drift_simulator.py # ๋“œ๋ฆฌํ”„ํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
44
+ โ”œโ”€โ”€ visualizer.py # Plotly ์‹œ๊ฐํ™”
45
+ โ”œโ”€โ”€ analyzer.py # ๋“œ๋ฆฌํ”„ํŠธ ๋ถ„์„
46
+ โ”œโ”€โ”€ requirements.txt # ํŒจํ‚ค์ง€ ์˜์กด์„ฑ
47
+ โ””โ”€โ”€ README.md
48
+ ```
49
+
50
+ ## Hugging Face Spaces
51
+
52
+ ๋ฐฐํฌ๋œ ์•ฑ: (๋ฐฐํฌ ํ›„ ๋งํฌ ์ถ”๊ฐ€)
53
+
54
+ ## ๋ผ์ด์„ ์Šค
55
+
56
+ MIT License
analyzer.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from typing import Dict, Tuple
3
+
4
+ def analyze_drift(X: np.ndarray, y: np.ndarray, drift_points: np.ndarray, drift_type: str) -> Dict[str, any]:
5
+ """๋“œ๋ฆฌํ”„ํŠธ ๋ฐ์ดํ„ฐ ๋ถ„์„"""
6
+
7
+ analysis = {
8
+ "drift_type": drift_type,
9
+ "total_samples": len(X),
10
+ "num_drift_points": len(drift_points),
11
+ "drift_locations": drift_points.tolist() if len(drift_points) > 0 else [],
12
+ }
13
+
14
+ # ์ „์ฒด ํ†ต๊ณ„
15
+ analysis["mean_y"] = float(np.mean(y))
16
+ analysis["std_y"] = float(np.std(y))
17
+ analysis["min_y"] = float(np.min(y))
18
+ analysis["max_y"] = float(np.max(y))
19
+
20
+ # ์„ธ๊ทธ๋จผํŠธ๋ณ„ ๋ถ„์„
21
+ segments = []
22
+ segment_boundaries = [0] + drift_points.tolist() + [len(X)]
23
+
24
+ for i in range(len(segment_boundaries) - 1):
25
+ start = segment_boundaries[i]
26
+ end = segment_boundaries[i + 1]
27
+
28
+ segment_y = y[start:end]
29
+ segment_X = X[start:end]
30
+
31
+ # ์„ ํ˜• ํšŒ๊ท€ ๊ณ„์ˆ˜ ๊ณ„์‚ฐ (๊ธฐ์šธ๊ธฐ)
32
+ if len(segment_X) > 1:
33
+ coeffs = np.polyfit(segment_X, segment_y, 1)
34
+ slope = float(coeffs[0])
35
+ intercept = float(coeffs[1])
36
+ else:
37
+ slope = 0.0
38
+ intercept = float(segment_y[0]) if len(segment_y) > 0 else 0.0
39
+
40
+ segments.append({
41
+ "segment_id": i,
42
+ "start_idx": int(start),
43
+ "end_idx": int(end),
44
+ "mean": float(np.mean(segment_y)),
45
+ "std": float(np.std(segment_y)),
46
+ "slope": slope,
47
+ "intercept": intercept
48
+ })
49
+
50
+ analysis["segments"] = segments
51
+
52
+ return analysis
53
+
54
+
55
+ def format_analysis_summary(analysis: Dict) -> str:
56
+ """๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์šด ํ˜•์‹์œผ๋กœ ํฌ๋งท"""
57
+
58
+ summary = f"""
59
+ ## ๋“œ๋ฆฌํ”„ํŠธ ๋ถ„์„ ๊ฒฐ๊ณผ
60
+
61
+ **๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜•:** {analysis['drift_type'].upper()}
62
+
63
+ **์ „์ฒด ๋ฐ์ดํ„ฐ:**
64
+ - ์ด ์ƒ˜ํ”Œ ์ˆ˜: {analysis['total_samples']}
65
+ - ๋“œ๋ฆฌํ”„ํŠธ ๋ฐœ์ƒ ํšŸ์ˆ˜: {analysis['num_drift_points']}
66
+ - ํ‰๊ท : {analysis['mean_y']:.2f}
67
+ - ํ‘œ์ค€ํŽธ์ฐจ: {analysis['std_y']:.2f}
68
+ - ๋ฒ”์œ„: [{analysis['min_y']:.2f}, {analysis['max_y']:.2f}]
69
+
70
+ **์„ธ๊ทธ๋จผํŠธ๋ณ„ ๋ถ„์„:**
71
+ """
72
+
73
+ for seg in analysis['segments']:
74
+ summary += f"""
75
+ **์„ธ๊ทธ๋จผํŠธ {seg['segment_id'] + 1}** (์ƒ˜ํ”Œ {seg['start_idx']}-{seg['end_idx']})
76
+ - ํ‰๊ท : {seg['mean']:.2f}
77
+ - ํ‘œ์ค€ํŽธ์ฐจ: {seg['std']:.2f}
78
+ - ๊ด€๊ณ„์‹: y = {seg['slope']:.2f}x + {seg['intercept']:.2f}
79
+ """
80
+
81
+ return summary
app.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from drift_simulator import (
3
+ generate_sudden_drift,
4
+ generate_gradual_drift,
5
+ generate_incremental_drift,
6
+ generate_recurring_drift,
7
+ get_drift_description
8
+ )
9
+ from visualizer import create_drift_visualization
10
+ from analyzer import analyze_drift, format_analysis_summary
11
+
12
+ def simulate_and_visualize(drift_type: str):
13
+ """๋“œ๋ฆฌํ”„ํŠธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ + ์‹œ๊ฐํ™” + ๋ถ„์„"""
14
+
15
+ # ๋“œ๋ฆฌํ”„ํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
16
+ if drift_type == "Sudden (๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ)":
17
+ X, y, drift_points = generate_sudden_drift(n_samples=1000, drift_point=500)
18
+ drift_key = "sudden"
19
+ elif drift_type == "Gradual (์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ)":
20
+ X, y, drift_points = generate_gradual_drift(n_samples=1000, drift_start=300, drift_end=700)
21
+ drift_key = "gradual"
22
+ elif drift_type == "Incremental (์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ)":
23
+ X, y, drift_points = generate_incremental_drift(n_samples=1000, n_steps=5)
24
+ drift_key = "incremental"
25
+ elif drift_type == "Recurring (๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ)":
26
+ X, y, drift_points = generate_recurring_drift(n_samples=1000, cycle_length=250)
27
+ drift_key = "recurring"
28
+ else:
29
+ return None, "์˜ฌ๋ฐ”๋ฅธ ๋“œ๋ฆฌํ”„ํŠธ ํƒ€์ž…์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."
30
+
31
+ # ์‹œ๊ฐํ™”
32
+ fig = create_drift_visualization(X, y, drift_points, drift_key)
33
+
34
+ # ๋ถ„์„
35
+ analysis = analyze_drift(X, y, drift_points, drift_key)
36
+ summary = format_analysis_summary(analysis)
37
+
38
+ # ์„ค๋ช… ์ถ”๊ฐ€
39
+ description = get_drift_description(drift_key)
40
+ full_summary = f"### {description}\n\n{summary}"
41
+
42
+ return fig, full_summary
43
+
44
+
45
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค
46
+ with gr.Blocks(title="Concept Drift Simulator", theme=gr.themes.Soft()) as demo:
47
+ gr.Markdown(
48
+ """
49
+ # Concept Drift Simulator
50
+
51
+ ์ปจ์…‰ ๋“œ๋ฆฌํ”„ํŠธ(Concept Drift)์˜ 4๊ฐ€์ง€ ์œ ํ˜•์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
52
+
53
+ ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜•์„ ์„ ํƒํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ฐจํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
54
+ """
55
+ )
56
+
57
+ with gr.Row():
58
+ with gr.Column(scale=1):
59
+ drift_type_radio = gr.Radio(
60
+ choices=[
61
+ "Sudden (๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ)",
62
+ "Gradual (์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ)",
63
+ "Incremental (์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ)",
64
+ "Recurring (๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ)"
65
+ ],
66
+ label="๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜• ์„ ํƒ",
67
+ value="Sudden (๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ)"
68
+ )
69
+
70
+ simulate_btn = gr.Button("์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
71
+
72
+ with gr.Column(scale=2):
73
+ plot_output = gr.Plot(label="๋“œ๋ฆฌํ”„ํŠธ ์‹œ๊ฐํ™”")
74
+
75
+ analysis_output = gr.Markdown(label="๋ถ„์„ ๊ฒฐ๊ณผ")
76
+
77
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
78
+ simulate_btn.click(
79
+ fn=simulate_and_visualize,
80
+ inputs=drift_type_radio,
81
+ outputs=[plot_output, analysis_output]
82
+ )
83
+
84
+ # ์ดˆ๊ธฐ ๋กœ๋“œ
85
+ demo.load(
86
+ fn=simulate_and_visualize,
87
+ inputs=drift_type_radio,
88
+ outputs=[plot_output, analysis_output]
89
+ )
90
+
91
+ if __name__ == "__main__":
92
+ demo.launch()
drift_simulator.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from typing import Tuple
3
+
4
+ def generate_sudden_drift(n_samples: int = 1000, drift_point: int = 500) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
5
+ """๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ: t ์‹œ์ ์—์„œ ๊ฐ‘์ž๊ธฐ ๋ฐ์ดํ„ฐ ๋ถ„ํฌ ๋ณ€๊ฒฝ"""
6
+ X = np.linspace(0, 10, n_samples)
7
+ y = np.zeros(n_samples)
8
+
9
+ # Before drift: y = 2*X + noise
10
+ y[:drift_point] = 2 * X[:drift_point] + np.random.normal(0, 1, drift_point)
11
+
12
+ # After drift: y = -X + 5 + noise (์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ด€๊ณ„)
13
+ y[drift_point:] = -X[drift_point:] + 5 + np.random.normal(0, 1, n_samples - drift_point)
14
+
15
+ drift_points = np.array([drift_point])
16
+ return X, y, drift_points
17
+
18
+
19
+ def generate_gradual_drift(n_samples: int = 1000, drift_start: int = 300, drift_end: int = 700) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
20
+ """์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ: ๋‘ ๋ถ„ํฌ๊ฐ€ ์„ž์ด๋ฉฐ ์ฒœ์ฒœํžˆ ์ „ํ™˜"""
21
+ X = np.linspace(0, 10, n_samples)
22
+ y = np.zeros(n_samples)
23
+
24
+ # Before drift: y = 2*X
25
+ y[:drift_start] = 2 * X[:drift_start] + np.random.normal(0, 1, drift_start)
26
+
27
+ # Gradual transition: mixture of old and new concepts
28
+ transition_length = drift_end - drift_start
29
+ for i in range(drift_start, drift_end):
30
+ weight = (i - drift_start) / transition_length
31
+ old_concept = 2 * X[i] + np.random.normal(0, 1)
32
+ new_concept = -X[i] + 5 + np.random.normal(0, 1)
33
+ y[i] = (1 - weight) * old_concept + weight * new_concept
34
+
35
+ # After drift: y = -X + 5
36
+ y[drift_end:] = -X[drift_end:] + 5 + np.random.normal(0, 1, n_samples - drift_end)
37
+
38
+ drift_points = np.array([drift_start, drift_end])
39
+ return X, y, drift_points
40
+
41
+
42
+ def generate_incremental_drift(n_samples: int = 1000, n_steps: int = 5) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
43
+ """์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ: ๊ณ„๋‹จ์‹์œผ๋กœ ์ž‘์€ ๋ณ€ํ™”๊ฐ€ ๋ˆ„์ """
44
+ X = np.linspace(0, 10, n_samples)
45
+ y = np.zeros(n_samples)
46
+
47
+ step_size = n_samples // (n_steps + 1)
48
+ drift_points = []
49
+
50
+ for step in range(n_steps + 1):
51
+ start_idx = step * step_size
52
+ end_idx = (step + 1) * step_size if step < n_steps else n_samples
53
+
54
+ # ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ๊ธฐ์šธ๊ธฐ๊ฐ€ ์กฐ๊ธˆ์”ฉ ๋ณ€ํ™”
55
+ slope = 2 - (step / n_steps) * 3 # 2์—์„œ -1๋กœ ์ ์ง„์  ๋ณ€ํ™”
56
+ intercept = (step / n_steps) * 5 # 0์—์„œ 5๋กœ ์ ์ง„์  ๋ณ€ํ™”
57
+
58
+ y[start_idx:end_idx] = slope * X[start_idx:end_idx] + intercept + np.random.normal(0, 1, end_idx - start_idx)
59
+
60
+ if step > 0:
61
+ drift_points.append(start_idx)
62
+
63
+ return X, y, np.array(drift_points)
64
+
65
+
66
+ def generate_recurring_drift(n_samples: int = 1000, cycle_length: int = 250) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
67
+ """๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ: ์ด์ „ ๋ถ„ํฌ๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์žฌ๋“ฑ์žฅ"""
68
+ X = np.linspace(0, 10, n_samples)
69
+ y = np.zeros(n_samples)
70
+
71
+ drift_points = []
72
+
73
+ for i in range(n_samples):
74
+ cycle_pos = i % cycle_length
75
+
76
+ if cycle_pos < cycle_length // 2:
77
+ # Concept A: y = 2*X
78
+ y[i] = 2 * X[i] + np.random.normal(0, 1)
79
+ else:
80
+ # Concept B: y = -X + 5
81
+ y[i] = -X[i] + 5 + np.random.normal(0, 1)
82
+
83
+ if cycle_pos == cycle_length // 2:
84
+ drift_points.append(i)
85
+
86
+ return X, y, np.array(drift_points)
87
+
88
+
89
+ def get_drift_description(drift_type: str) -> str:
90
+ """๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜•๋ณ„ ์„ค๋ช… ๋ฐ˜ํ™˜"""
91
+ descriptions = {
92
+ "sudden": "๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ: ํŠน์ • ์‹œ์ ์—์„œ ๋ฐ์ดํ„ฐ ๋ถ„ํฌ๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: ํŒฌ๋ฐ๋ฏน, ์ •์ฑ… ๋ณ€๊ฒฝ ๋“ฑ",
93
+ "gradual": "์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ: ์ด์ „ ๋ถ„ํฌ์™€ ์ƒˆ ๋ถ„ํฌ๊ฐ€ ์„ž์ด๋ฉฐ ์ฒœ์ฒœํžˆ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. ์ „ํ™˜ ๊ธฐ๊ฐ„ ๋™์•ˆ ๋‘ ์ปจ์…‰์ด ๊ณต์กดํ•ฉ๋‹ˆ๋‹ค.",
94
+ "incremental": "์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ: ์ž‘์€ ๋‹จ๊ณ„๋กœ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ๊ณ„๋‹จ์‹ ํŒจํ„ด์„ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค.",
95
+ "recurring": "๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ: ์ด์ „ ๋ถ„ํฌ๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๊ณ„์ ˆ์„ฑ์ด๋‚˜ ์ฃผ๊ธฐ์  ํŒจํ„ด์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค."
96
+ }
97
+ return descriptions.get(drift_type, "์•Œ ์ˆ˜ ์—†๋Š” ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜•")
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ plotly
3
+ numpy
visualizer.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.graph_objects as go
2
+ import numpy as np
3
+ from typing import Tuple
4
+
5
+ def create_drift_visualization(X: np.ndarray, y: np.ndarray, drift_points: np.ndarray, drift_type: str) -> go.Figure:
6
+ """๋“œ๋ฆฌํ”„ํŠธ ๋ฐ์ดํ„ฐ๋ฅผ Plotly๋กœ ์‹œ๊ฐํ™”"""
7
+
8
+ fig = go.Figure()
9
+
10
+ # ๋ฉ”์ธ ๋ฐ์ดํ„ฐ scatter plot
11
+ fig.add_trace(go.Scatter(
12
+ x=X,
13
+ y=y,
14
+ mode='markers',
15
+ name='Data Points',
16
+ marker=dict(
17
+ size=4,
18
+ color=np.arange(len(X)),
19
+ colorscale='Viridis',
20
+ showscale=True,
21
+ colorbar=dict(title="Time")
22
+ ),
23
+ hovertemplate='X: %{x:.2f}<br>y: %{y:.2f}<extra></extra>'
24
+ ))
25
+
26
+ # ๋“œ๋ฆฌํ”„ํŠธ ๋ฐœ์ƒ ์ง€์  ํ‘œ์‹œ
27
+ y_min, y_max = y.min(), y.max()
28
+ y_range = y_max - y_min
29
+
30
+ for i, drift_point in enumerate(drift_points):
31
+ fig.add_vline(
32
+ x=X[drift_point],
33
+ line_dash="dash",
34
+ line_color="red",
35
+ annotation_text=f"Drift {i+1}",
36
+ annotation_position="top"
37
+ )
38
+
39
+ # ๋ ˆ์ด์•„์›ƒ ์„ค์ •
40
+ title_map = {
41
+ "sudden": "Sudden (Abrupt) Drift - ๊ธ‰๊ฒฉํ•œ ๋“œ๋ฆฌํ”„ํŠธ",
42
+ "gradual": "Gradual Drift - ์ ์ง„์  ๋“œ๋ฆฌํ”„ํŠธ",
43
+ "incremental": "Incremental Drift - ์ฆ๋ถ„์  ๋“œ๋ฆฌํ”„ํŠธ",
44
+ "recurring": "Recurring Drift - ๋ฐ˜๋ณต์  ๋“œ๋ฆฌํ”„ํŠธ"
45
+ }
46
+
47
+ fig.update_layout(
48
+ title=dict(
49
+ text=title_map.get(drift_type, "Concept Drift"),
50
+ x=0.5,
51
+ xanchor='center'
52
+ ),
53
+ xaxis_title="Feature (X)",
54
+ yaxis_title="Target (y)",
55
+ hovermode='closest',
56
+ template='plotly_white',
57
+ height=600,
58
+ showlegend=True,
59
+ legend=dict(
60
+ yanchor="top",
61
+ y=0.99,
62
+ xanchor="left",
63
+ x=0.01
64
+ )
65
+ )
66
+
67
+ return fig
68
+
69
+
70
+ def create_comparison_visualization(drift_data_dict: dict) -> go.Figure:
71
+ """์—ฌ๋Ÿฌ ๋“œ๋ฆฌํ”„ํŠธ ์œ ํ˜•์„ ํ•œ ๋ฒˆ์— ๋น„๊ต"""
72
+ from plotly.subplots import make_subplots
73
+
74
+ fig = make_subplots(
75
+ rows=2, cols=2,
76
+ subplot_titles=("Sudden Drift", "Gradual Drift", "Incremental Drift", "Recurring Drift")
77
+ )
78
+
79
+ positions = [(1, 1), (1, 2), (2, 1), (2, 2)]
80
+ drift_types = ["sudden", "gradual", "incremental", "recurring"]
81
+
82
+ for (row, col), drift_type in zip(positions, drift_types):
83
+ if drift_type in drift_data_dict:
84
+ X, y, drift_points = drift_data_dict[drift_type]
85
+
86
+ fig.add_trace(
87
+ go.Scatter(
88
+ x=X,
89
+ y=y,
90
+ mode='markers',
91
+ marker=dict(size=3, color=np.arange(len(X)), colorscale='Viridis'),
92
+ showlegend=False
93
+ ),
94
+ row=row, col=col
95
+ )
96
+
97
+ # ๋“œ๋ฆฌํ”„ํŠธ ์ง€์  ํ‘œ์‹œ
98
+ for drift_point in drift_points:
99
+ fig.add_vline(
100
+ x=X[drift_point],
101
+ line_dash="dash",
102
+ line_color="red",
103
+ row=row, col=col
104
+ )
105
+
106
+ fig.update_xaxes(title_text="X")
107
+ fig.update_yaxes(title_text="y")
108
+ fig.update_layout(height=800, title_text="Concept Drift Types Comparison", showlegend=False)
109
+
110
+ return fig