728x90
반응형
개요
이번 포스팅에서는 Hugging Face의 Transformers 라이브러리와 Streamlit을 활용하여 감정 분석 웹 애플리케이션을 구현하는 방법을 다룹니다. 프로덕션 레벨의 코드 구조와 실제 구현 방법에 중점을 두어 설명하겠습니다.
기술 스택
- Python 3.8+
- Hugging Face Transformers
- Streamlit
- PyTorch
- VS Code

프로젝트 구조
sentiment_analysis/
├── .vscode/ # VSCode 설정
│ └── settings.json
├── src/ # 소스 코드
│ ├── __init__.py
│ ├── analyzer.py # 감정 분석 핵심 로직
│ ├── utils.py # 유틸리티 함수
│ └── app.py # Streamlit 애플리케이션
├── venv/ # 가상환경 (git ignore)
├── .gitignore # Git 설정
└── requirements.txt # 프로젝트 의존성
환경 설정
1. .vscode/settings.json
{
"python.defaultInterpreterPath": "${workspaceFolder}/venv/Scripts/python.exe",
"python.terminal.activateEnvironment": true,
"terminal.integrated.env.windows": {
"PYTHONPATH": "${workspaceFolder}"
},
"editor.formatOnSave": true,
"python.formatting.provider": "autopep8",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"[python]": {
"editor.defaultFormatter": "ms-python.python",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"files.exclude": {
"**/__pycache__": true,
"**/.pytest_cache": true
}
}
2. .gitignore
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/
# VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Project specific
.env
*.log
data/
models/
3. requirements.txt
streamlit==1.24.0
transformers==4.30.2
torch==2.0.1
pandas==2.0.3
plotly==5.15.0
python-dotenv==1.0.0
autopep8==2.0.2
pylint==2.17.4
코드 구현
analyzer.py - 감정 분석 핵심 로직
from typing import Dict, List, Union, Any
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
class SentimentAnalyzer:
"""감정 분석을 수행하는 클래스"""
def __init__(self) -> None:
"""
SentimentAnalyzer 초기화
다국어 BERT 모델을 로드하고 GPU 사용 가능 여부를 확인
"""
self.model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(
self.model_name
)
# GPU 사용 가능 여부 확인
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = self.model.to(self.device)
# 감정 레이블 정의
self.sentiment_labels = {
'1': '매우 부정',
'2': '부정',
'3': '중립',
'4': '긍정',
'5': '매우 긍정'
}
def analyze(self, text: str) -> Dict[str, Any]:
"""
단일 텍스트에 대한 감정 분석 수행
Args:
text (str): 분석할 텍스트
Returns:
Dict[str, Any]: 분석 결과 딕셔너리
"""
try:
# 텍스트 전처리
inputs = self.tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=512
)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
# 예측 수행
with torch.no_grad():
outputs = self.model(**inputs)
scores = torch.softmax(outputs.logits, dim=1)
# 결과 처리
prediction = torch.argmax(scores).item() + 1
confidence = torch.max(scores).item()
return {
'success': True,
'sentiment': self.sentiment_labels.get(str(prediction), '알 수 없음'),
'confidence': f'{confidence:.2%}',
'raw_score': confidence
}
except Exception as e:
print(f"Error in analyze: {str(e)}")
return {
'success': False,
'error': str(e)
}
def batch_analyze(
self,
texts: List[str],
batch_size: int = 16
) -> List[Dict[str, Any]]:
"""
여러 텍스트에 대한 배치 감정 분석 수행
Args:
texts (List[str]): 분석할 텍스트 리스트
batch_size (int, optional): 배치 크기. Defaults to 16.
Returns:
List[Dict[str, Any]]: 분석 결과 리스트
"""
results = []
try:
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
# 배치 전처리
inputs = self.tokenizer(
batch,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
# 배치 예측
with torch.no_grad():
outputs = self.model(**inputs)
scores = torch.softmax(outputs.logits, dim=1)
predictions = torch.argmax(scores, dim=1)
# 결과 처리
for j, pred in enumerate(predictions):
label = str(pred.item() + 1)
confidence = scores[j][pred].item()
results.append({
'label': self.sentiment_labels.get(label, '알 수 없음'),
'score': confidence
})
except Exception as e:
print(f"Error in batch_analyze: {str(e)}")
return results
utils.py - 유틸리티 함수
from typing import List, Dict, Any
import pandas as pd
import plotly.graph_objects as go
from plotly.graph_objs import Figure
def prepare_dataframe(
texts: List[str],
results: List[Dict[str, Any]]
) -> pd.DataFrame:
"""
분석 결과를 DataFrame으로 변환
Args:
texts (List[str]): 원본 텍스트 리스트
results (List[Dict[str, Any]]): 분석 결과 리스트
Returns:
pd.DataFrame: 결과 DataFrame
"""
return pd.DataFrame({
'텍스트': texts,
'감정': [r['label'] for r in results],
'확신도': [f"{r['score']:.2%}" for r in results]
})
def create_sentiment_chart(score: float) -> Figure:
"""
감정 분석 결과를 시각화하는 차트 생성
Args:
score (float): 감정 점수
Returns:
Figure: Plotly 차트 객체
"""
fig = go.Figure()
# 바 차트 추가
fig.add_trace(
go.Bar(
x=['부정', '중립', '긍정'],
y=[0.3, 0.3, score],
marker_color=['#FF6B6B', '#4ECDC4', '#45B7D1'],
text=[f'{v:.1%}' for v in [0.3, 0.3, score]],
textposition='auto',
)
)
# 차트 레이아웃 설정
fig.update_layout(
title={
'text': "감정 분석 결과",
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'
},
yaxis_title="확신도",
yaxis_range=[0, 1],
showlegend=False,
template='plotly_white'
)
return fig
def save_results(df: pd.DataFrame, filename: str) -> None:
"""
분석 결과를 CSV 파일로 저장
Args:
df (pd.DataFrame): 저장할 DataFrame
filename (str): 저장할 파일명
"""
df.to_csv(filename, index=False, encoding='utf-8-sig')
app.py - Streamlit 웹 애플리케이션
import streamlit as st
import pandas as pd
from typing import List
from analyzer import SentimentAnalyzer
from utils import prepare_dataframe, create_sentiment_chart
def init_session_state() -> None:
"""세션 상태 초기화"""
if 'analyzer' not in st.session_state:
st.session_state.analyzer = SentimentAnalyzer()
def process_multiple_texts(texts: List[str]) -> None:
"""
여러 텍스트 일괄 처리
Args:
texts (List[str]): 처리할 텍스트 리스트
"""
with st.spinner("분석 중..."):
results = st.session_state.analyzer.batch_analyze(texts)
df = prepare_dataframe(texts, results)
st.dataframe(df)
# 결과 다운로드 버튼
csv = df.to_csv(index=False).encode('utf-8-sig')
st.download_button(
label="결과 다운로드",
data=csv,
file_name="sentiment_analysis_results.csv",
mime="text/csv"
)
def main() -> None:
"""메인 애플리케이션"""
# 페이지 설정
st.set_page_config(
page_title="감정 분석기",
page_icon="🤗",
layout="wide"
)
# 세션 상태 초기화
init_session_state()
# 메인 타이틀
st.title("🤗 감정 분석기")
# 사이드바 설정
st.sidebar.title("설정")
analysis_type = st.sidebar.radio(
"분석 유형 선택",
["단일 텍스트", "다중 텍스트", "파일 업로드"]
)
if analysis_type == "단일 텍스트":
# 단일 텍스트 입력 및 분석
text = st.text_area(
"분석할 텍스트를 입력하세요:",
height=150,
help="분석할 텍스트를 입력하고 '분석하기' 버튼을 클릭하세요."
)
if st.button("분석하기", key="single_analyze"):
if text:
with st.spinner("분석 중..."):
result = st.session_state.analyzer.analyze(text)
if result['success']:
col1, col2 = st.columns(2)
with col1:
st.info(f"감정: {result['sentiment']}")
with col2:
st.info(f"확신도: {result['confidence']}")
# 시각화
st.plotly_chart(
create_sentiment_chart(result['raw_score']),
use_container_width=True
)
else:
st.warning("텍스트를 입력해주세요!")
elif analysis_type == "다중 텍스트":
# 다중 텍스트 입력 및 분석
texts = st.text_area(
"여러 줄의 텍스트를 입력하세요 (줄바꿈으로 구분):",
height=150,
help="각 줄에 하나의 텍스트를 입력하세요."
)
if st.button("일괄 분석", key="multi_analyze"):
text_list = [t.strip() for t in texts.split('\\n') if t.strip()]
if text_list:
process_multiple_texts(text_list)
else:
st.warning("텍스트를 입력해주세요!")
else:
# 파일 업로드 및 분석
uploaded_file = st.file_uploader(
"CSV 파일을 업로드하세요",
type=['csv'],
help="'text' 열이 포함된 CSV 파일을 업로드하세요."
)
if uploaded_file:
df = pd.read_csv(uploaded_file)
if 'text' in df.columns:
if st.button("파일 분석", key="file_analyze"):
texts = df['text'].tolist()
process_multiple_texts(texts)
else:
st.error("CSV 파일에 'text' 열이 없습니다!")
if __name__ == "__main__":
main()
실행 방법
1. 환경 설정
# 프로젝트 폴더 생성
mkdir sentiment_analysis
cd sentiment_analysis
# 가상환경 생성 및 활성화
python -m venv venv
source venv/bin/activate# Windows: .\\venv\\Scripts\\activate# 필요한 패키지 설치
pip install -r requirements.txt
2. 애플리케이션 실행
streamlit run src/app.py
주요 기능 설명
1. 감정 분석 기능
- BERT 기반의 다국어 감정 분석 모델 사용
- 5단계 감정 점수 제공 (매우 부정 ~ 매우 긍정)
- 배치 처리 지원으로 대량 텍스트 분석 가능
2. 웹 인터페이스
- 직관적인 사용자 인터페이스
- 실시간 분석 결과 시각화
- 다양한 입력 방식 지원 (단일/다중/파일)
- 분석 결과 CSV 다운로드
3. 성능 최적화
- GPU 가속 자동 감지 및 활용
- 배치 처리를 통한 대량 분석 최적화
- 메모리 효율적인 데이터 처리
성능 모니터링 및 개선 방안
1. 성능 모니터링
import time
from functools import wraps
def measure_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.2f} seconds")
return result
return wrapper
2. 캐시 구현
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_analyze(text: str) -> dict:
return analyzer.analyze(text)
향후 개선 사항
- 모델 개선
- 한국어 특화 모델 적용
- 모델 최적화 및 경량화
- 기능 추가
- 실시간 분석
- 다국어 지원 확장
- 분석 이력 관리
- 성능 개선
- 분산 처리 지원
- 캐싱 시스템 구현
- API 서버 구축
참고 자료
라이센스
이 프로젝트는 MIT 라이센스를 따릅니다.
이상으로 Hugging Face와 Streamlit을 활용한 감정 분석 웹 애플리케이션 구현에 대한 설명을 마치겠습니다.
반응형
'LLM(Open AI)' 카테고리의 다른 글
LLM의 기초 뼈대 세우기 (2) | 2025.04.01 |
---|---|
[Python/AI] Hugging Face 감정 분석 웹 애플리케이션 코드 분석 - 2편 (4) | 2024.11.17 |
Hugging Face 회원가입, 토큰 발급, APIKEY 발급 방법, 개발 환경 설정 (22) | 2024.11.15 |
NLP와 Hugging Face 란 어원 등 사용법 (17) | 2024.11.14 |
Asterisk : AI 음성처리 시스템 흐름 및 설정 (0) | 2024.11.08 |