Dongjin commited on
Commit
fcd46ca
·
unverified ·
1 Parent(s): cc4b346

벡터 DB 대시보드 관련 코드 삭제

Browse files
Files changed (1) hide show
  1. src/visualization/streamlit_app.py +0 -404
src/visualization/streamlit_app.py DELETED
@@ -1,404 +0,0 @@
1
- """
2
- 벡터DB 시각화 Streamlit 앱
3
- ChromaDB 데이터를 2D/3D로 시각화
4
- """
5
- import io
6
- import os
7
- import streamlit as st
8
- import pandas as pd
9
- import numpy as np
10
- import plotly.express as px
11
- import sys
12
- from pathlib import Path
13
-
14
- # 프로젝트 루트를 Python 경로에 추가
15
- root_dir = Path(__file__).parent.parent.parent
16
- sys.path.insert(0, str(root_dir))
17
-
18
- from src.visualization.vector_db_loader import VectorDBLoader
19
- from src.visualization.dimensionality_reduction import DimensionalityReducer
20
- from src.utils.config import RAGConfig
21
-
22
- # ===== 자동 초기화 함수 =====
23
- @st.cache_resource
24
- def initialize_data():
25
- """ChromaDB가 없으면 자동으로 전처리 + 임베딩 실행"""
26
-
27
- config = RAGConfig()
28
-
29
- # ChromaDB가 이미 존재하는지 확인
30
- if os.path.exists(config.DB_DIRECTORY):
31
- try:
32
- # ChromaDB 연결 테스트
33
- loader = VectorDBLoader(config)
34
- info = loader.get_collection_info()
35
- if info['total_documents'] > 0:
36
- st.success(f"✅ 기존 ChromaDB 로드 완료 ({info['total_documents']}개 문서)")
37
- return True
38
- except:
39
- st.warning("⚠️ 기존 ChromaDB가 손상되었습니다. 재생성합니다.")
40
-
41
- # ChromaDB가 없으면 생성
42
- st.info("🔄 ChromaDB를 생성합니다. 최초 1회만 실행되며 약 2-3분 소요됩니다...")
43
-
44
- try:
45
- # 전처리 실행
46
- with st.spinner("1/2 전처리 실행 중..."):
47
- from src.loader.preprocess_pipeline import RAGPreprocessPipeline
48
- from src.utils.preprocess_config import PreprocessConfig
49
-
50
- preprocess_config = PreprocessConfig()
51
- pipeline = RAGPreprocessPipeline(preprocess_config)
52
- df_chunks = pipeline.run()
53
- st.success(f"✅ 전처리 완료: {len(df_chunks)}개 청크")
54
-
55
- # 임베딩 실행
56
- with st.spinner("2/2 임베딩 실행 중..."):
57
- from src.embedding.rag_data_processing import RAGVectorDBPipeline
58
-
59
- rag_pipeline = RAGVectorDBPipeline(config)
60
- rag_pipeline.build()
61
- st.success("✅ ChromaDB 생성 완료!")
62
-
63
- return True
64
-
65
- except Exception as e:
66
- st.error(f"❌ 초기화 실패: {e}")
67
- st.info("""
68
- ### 💡 수동 실행이 필요합니다
69
-
70
- 로컬 환경에서:
71
- ```bash
72
- python main.py --step all
73
- ```
74
- """)
75
- return False
76
-
77
- # ===== 페이지 설정 =====
78
- st.set_page_config(
79
- page_title="벡터DB 시각화",
80
- page_icon="🔍",
81
- layout="wide",
82
- initial_sidebar_state="expanded"
83
- )
84
-
85
-
86
- # ===== 스타일 =====
87
- st.markdown("""
88
- <style>
89
- .main-header {
90
- font-size: 2.5rem;
91
- font-weight: bold;
92
- margin-bottom: 1rem;
93
- }
94
- .sub-header {
95
- font-size: 1.2rem;
96
- color: #666;
97
- margin-bottom: 2rem;
98
- }
99
- .metric-container {
100
- background-color: #f0f2f6;
101
- padding: 1rem;
102
- border-radius: 0.5rem;
103
- margin-bottom: 1rem;
104
- }
105
- </style>
106
- """, unsafe_allow_html=True)
107
-
108
-
109
- # ===== 캐싱 함수 =====
110
- @st.cache_data
111
- def load_data():
112
- """ChromaDB 데이터 로드 (캐싱)"""
113
- config = RAGConfig()
114
- loader = VectorDBLoader(config)
115
- df = loader.to_dataframe()
116
-
117
- # 추출 실패 문서 필터링
118
- df = df[~df['document'].str.contains('\[추출 실패', na=False)]
119
- df = df[~df['document'].str.contains('\[PDF 추출 실패', na=False)]
120
- df = df[~df['document'].str.contains('\[HWP 추출 실패', na=False)]
121
-
122
- # 인덱스 리셋
123
- df = df.reset_index(drop=True)
124
-
125
- print(f"✅ 유효한 문서: {len(df)}개")
126
-
127
- # 임베딩 벡터 추출
128
- embeddings = np.array(df['embedding'].tolist())
129
-
130
- return df, embeddings
131
-
132
-
133
- @st.cache_data
134
- def reduce_dimensions(embeddings, method, n_components):
135
- """차원 축소 (캐싱)"""
136
- reducer = DimensionalityReducer(
137
- method=method,
138
- n_components=n_components
139
- )
140
- reduced = reducer.fit_transform(embeddings)
141
- return reduced
142
-
143
-
144
- # ===== 메인 앱 =====
145
- def main():
146
- st.set_page_config(
147
- page_title="벡터DB 시각화",
148
- page_icon="🔍",
149
- layout="wide"
150
- )
151
- # 헤더
152
- st.markdown('<div class="main-header">🔍 벡터DB 시각화</div>', unsafe_allow_html=True)
153
- st.markdown('<div class="sub-header">ChromaDB 임베딩 공간 탐색</div>', unsafe_allow_html=True)
154
-
155
- # 자동 초기화
156
- if not initialize_data():
157
- return
158
-
159
- # 데이터 로드
160
- with st.spinner("데이터 로드 중..."):
161
- try:
162
- df, embeddings = load_data()
163
- except Exception as e:
164
- st.error(f"❌ 데이터 로드 실패: {e}")
165
- st.info("먼저 임베딩 단계를 실행하세요: `python main.py --step embed`")
166
- return
167
-
168
- # 데이터가 없으면 종료
169
- if len(df) == 0:
170
- st.warning("⚠️ ChromaDB에 데이터가 없습니다!")
171
- st.info("먼저 임베딩 단계를 실행하세요: `python main.py --step embed`")
172
- return
173
-
174
- # ===== 사이드바 =====
175
- with st.sidebar:
176
- st.header("⚙️ 설정")
177
-
178
- # 통계 정보
179
- st.markdown("### 📊 데이터 정보")
180
- st.metric("총 문서 수", len(df))
181
- st.metric("임베딩 차원", embeddings.shape[1])
182
-
183
- st.markdown("---")
184
-
185
- # 차원 축소 설정
186
- st.markdown("### 🎯 차원 축소")
187
-
188
- method = st.selectbox(
189
- "방법",
190
- options=['pca', 'tsne'],
191
- format_func=lambda x: {
192
- 'pca': 'PCA (빠름)',
193
- 'tsne': 't-SNE (느림, 더 정확)'
194
- }[x]
195
- )
196
-
197
- n_components = st.radio(
198
- "차원",
199
- options=[2, 3],
200
- format_func=lambda x: f"{x}D"
201
- )
202
-
203
- st.markdown("---")
204
-
205
- # 필터링 옵션
206
- st.markdown("### 🎨 시각화 옵션")
207
-
208
- # 색상 기준
209
- color_options = ['없음'] + [col for col in df.columns
210
- if col not in ['id', 'document', 'embedding', 'x', 'y', 'z']]
211
-
212
- color_by = st.selectbox(
213
- "색상 기준",
214
- options=color_options
215
- )
216
-
217
- # 크기 옵션
218
- point_size = st.slider(
219
- "포인트 크기",
220
- min_value=3,
221
- max_value=15,
222
- value=8
223
- )
224
-
225
- # 투명도
226
- opacity = st.slider(
227
- "투명도",
228
- min_value=0.1,
229
- max_value=1.0,
230
- value=0.7,
231
- step=0.1
232
- )
233
-
234
- st.markdown("---")
235
-
236
- # 필터링
237
- st.markdown("### 🔍 필터")
238
-
239
- filter_col = st.selectbox(
240
- "필터링 기준",
241
- options=['없음'] + color_options[1:] # '없음' 제외한 나머지
242
- )
243
-
244
- filter_values = []
245
- if filter_col != '없음':
246
- unique_values = df[filter_col].unique()
247
- filter_values = st.multiselect(
248
- f"{filter_col} 선택",
249
- options=unique_values,
250
- default=list(unique_values)[:5] if len(unique_values) > 5 else list(unique_values)
251
- )
252
-
253
- # ===== 차원 축소 =====
254
- with st.spinner(f"{method.upper()}로 차원 축소 중..."):
255
- reduced = reduce_dimensions(embeddings, method, n_components)
256
-
257
- # DataFrame에 좌표 추가
258
- df_viz = df.copy()
259
- df_viz['x'] = reduced[:, 0]
260
- df_viz['y'] = reduced[:, 1]
261
- if n_components == 3:
262
- df_viz['z'] = reduced[:, 2]
263
-
264
- # 필터링 적용
265
- if filter_col != '없음' and filter_values:
266
- df_viz = df_viz[df_viz[filter_col].isin(filter_values)]
267
- st.info(f"필터링 결과: {len(df_viz)}개 문서")
268
-
269
- # ===== 시각화 =====
270
- st.markdown("---")
271
- st.markdown("### 📈 임베딩 공간 시각화")
272
-
273
- # hover 데이터 준비
274
- hover_data = {
275
- 'document': True,
276
- 'x': ':.2f',
277
- 'y': ':.2f'
278
- }
279
-
280
- if n_components == 3:
281
- hover_data['z'] = ':.2f'
282
-
283
- # 메타데이터 hover에 추가
284
- for col in ['파일명', '발주 기관', '사업명']:
285
- if col in df_viz.columns:
286
- hover_data[col] = True
287
-
288
- # 색상 설정
289
- color = None if color_by == '없음' else color_by
290
-
291
- # 2D 시각화
292
- if n_components == 2:
293
- fig = px.scatter(
294
- df_viz,
295
- x='x',
296
- y='y',
297
- color=color,
298
- hover_data=hover_data,
299
- title=f"벡터 임베딩 공간 ({method.upper()}, 2D)",
300
- labels={'x': 'PC1' if method == 'pca' else 'Dim 1',
301
- 'y': 'PC2' if method == 'pca' else 'Dim 2'},
302
- height=700,
303
- opacity=opacity
304
- )
305
-
306
- fig.update_traces(marker=dict(size=point_size))
307
-
308
- # 3D 시각화
309
- else:
310
- fig = px.scatter_3d(
311
- df_viz,
312
- x='x',
313
- y='y',
314
- z='z',
315
- color=color,
316
- hover_data=hover_data,
317
- title=f"벡터 임베딩 공간 ({method.upper()}, 3D)",
318
- labels={'x': 'PC1' if method == 'pca' else 'Dim 1',
319
- 'y': 'PC2' if method == 'pca' else 'Dim 2',
320
- 'z': 'PC3' if method == 'pca' else 'Dim 3'},
321
- height=700,
322
- opacity=opacity
323
- )
324
-
325
- fig.update_traces(marker=dict(size=point_size))
326
-
327
- # 레이아웃 업데이트
328
- fig.update_layout(
329
- showlegend=True,
330
- hovermode='closest',
331
- plot_bgcolor='white'
332
- )
333
-
334
- st.plotly_chart(fig, use_container_width=True)
335
-
336
- # ===== 통계 정보 =====
337
- st.markdown("---")
338
- st.markdown("### 📊 통계 정보")
339
-
340
- col1, col2, col3, col4 = st.columns(4)
341
-
342
- with col1:
343
- st.metric("표시된 문서", len(df_viz))
344
-
345
- with col2:
346
- st.metric("필터링된 문서", len(df) - len(df_viz))
347
-
348
- with col3:
349
- if method == 'pca':
350
- # PCA 설명된 분산 표시
351
- reducer = DimensionalityReducer(method='pca', n_components=n_components)
352
- reducer.fit_transform(embeddings)
353
- explained_var = reducer.reducer.explained_variance_ratio_.sum()
354
- st.metric("설명된 분산", f"{explained_var:.1%}")
355
- else:
356
- st.metric("차원 축소 방법", "t-SNE")
357
-
358
- with col4:
359
- st.metric("임베딩 차원", embeddings.shape[1])
360
-
361
- # ===== 데이터 테이블 =====
362
- if st.checkbox("📋 데이터 테이블 보기", value=False):
363
- st.markdown("---")
364
- st.markdown("### 📋 데이터 테이블")
365
-
366
- # 표시할 컬럼 선택
367
- display_cols = st.multiselect(
368
- "표시할 컬럼 선택",
369
- options=[col for col in df_viz.columns if col != 'embedding'],
370
- default=['파일명', '발주 기관', '사업명'][:min(3, len(df_viz.columns))]
371
- )
372
-
373
- if display_cols:
374
- st.dataframe(
375
- df_viz[display_cols],
376
- use_container_width=True,
377
- height=400
378
- )
379
-
380
- # ===== 다운로드 옵션 =====
381
- st.markdown("---")
382
- st.markdown("### 💾 데이터 다운로드")
383
-
384
- df_download = df_viz.drop(columns=['embedding'])
385
-
386
- # BytesIO 버퍼 생성
387
- buffer = io.BytesIO()
388
-
389
- # Excel 파일 생성
390
- with pd.ExcelWriter(buffer, engine='openpyxl') as writer:
391
- df_download.to_excel(writer, index=False, sheet_name='VectorDB')
392
-
393
- st.download_button(
394
- label="📥 Excel 다운로드",
395
- data=buffer.getvalue(),
396
- file_name="vectordb_visualization.xlsx",
397
- mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
398
- use_container_width=True
399
- )
400
-
401
- st.caption("💡 Excel에서 바로 열 수 있으며 한글이 정상 표시됩니다.")
402
-
403
- if __name__ == "__main__":
404
- main()