import torch import torch.nn as nn from transformers import AutoTokenizer, AutoModel import streamlit as st import os import numpy as np st.markdown(""" """, unsafe_allow_html=True) KOTE_LABELS = [ '불평/불만', '환영/호의', '감동/감탄', '지긋지긋', '고마움', '슬픔', '화남/분노', '존경', '기대감', '우쭐댐/무시함', '안타까움/실망', '비장함', '의심/불신', '뿌듯함', '편안/쾌적', '신기함/관심', '아껴주는', '부끄러움', '공포/무서움', '절망', '한심함', '역겨움/징그러움', '짜증', '어이없음', '없음', '패배/자기혐오', '귀찮음', '힘듦/지침', '즐거움/신남', '깨달음', '죄책감', '증오/혐오', '흐뭇함(귀여움/예쁨)', '당황/난처', '경악', '부담/안_내킴', '서러움', '재미없음', '불쌍함/연민', '놀람', '행복', '불안/걱정', '기쁨', '안심/신뢰' ] class MLPClassifier(nn.Module): def __init__(self, input_dim=1024, num_labels=44): super(MLPClassifier, self).__init__() self.mlp = nn.Sequential( nn.Linear(input_dim, 512), nn.BatchNorm1d(512), nn.ReLU(), nn.Dropout(0.3), nn.Linear(512, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_labels) ) def forward(self, x): return self.mlp(x) @st.cache_resource def load_model(): device = torch.device("cpu") tokenizer = AutoTokenizer.from_pretrained("klue/roberta-large") base_model = AutoModel.from_pretrained("klue/roberta-large").eval() mlp_model = MLPClassifier().eval() ckpt_path = os.path.join("checkpoints", "mlp_model.pth") mlp_model.load_state_dict(torch.load(ckpt_path, map_location=device)) return tokenizer, base_model, mlp_model tokenizer, base_model, mlp_model = load_model() def predict_emotion(text, top_k=5): encoded = tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=512) with torch.no_grad(): outputs = base_model(**encoded) cls_emb = outputs.last_hidden_state[:, 0, :] logits = mlp_model(cls_emb) probs = torch.sigmoid(logits).squeeze(0).numpy() result = sorted(zip(KOTE_LABELS, probs), key=lambda x: x[1], reverse=True) return result[:top_k], probs tabs = st.tabs(["감정 분석 체험", "AI는 어떻게 감정을 이해할까?", "Few-shot Fine-tuning이란?", "활용과 의의", "기타 자료료"]) with tabs[0]: st.markdown('
🎭 한국어 감정 분석 AI 체험
', unsafe_allow_html=True) st.markdown('
2025년 1학기 디지털 인문학 입문 (SLA23501) · Team 샐러드볼
강수현 · 김동우 · 정예은 · 정현성 · 최종윤
', unsafe_allow_html=True) st.markdown("""
인공지능은 입력된 문장을 분석해 감정이 어떻게 표현되었는지를 예측합니다.
아래에 문장을 입력하거나 예시 문장을 불러온 후, 감정 예측 버튼을 눌러 체험해보세요.
""", unsafe_allow_html=True) if "text_input" not in st.session_state: st.session_state.text_input = "" col1, col2 = st.columns([1, 1]) with col1: if st.button("📌 예시 문장 불러오기"): st.session_state.text_input = "그걸 이제 말해줘? 친절하네 정말" with col2: predict_clicked = st.button("🔍 감정 예측하기") text = st.text_area( "✍️ 문장을 입력하세요:", value=st.session_state.text_input, height=120, placeholder="예: 오늘 하루 정말 행복했어요." ) st.session_state.text_input = text if predict_clicked: if text.strip(): with st.spinner("AI가 감정을 분석 중입니다..."): results, full_probs = predict_emotion(text) top_emotion, top_prob = results[0] st.markdown( f'
✅ 가장 강하게 표현된 감정: {top_emotion} ({top_prob:.2f})
', unsafe_allow_html=True ) st.subheader("📊 상위 감정 결과") for label, prob in results: st.markdown(f"- **{label}**: `{prob:.3f}`") st.subheader("📈 확률 분포 (Top 5)") st.bar_chart({label: prob for label, prob in results}) st.markdown("
", unsafe_allow_html=True) else: st.warning("문장을 먼저 입력해주세요.") with tabs[1]: st.markdown('
🤖 인공지능은 감정을 어떻게 이해할까요?
', unsafe_allow_html=True) st.markdown("""
인공지능은 문장을 '숫자의 벡터'로 바꾸어 이해합니다.

이 과정은 다음과 같은 단계로 이루어집니다:
  1. 사전학습 언어 모델(KLUE-RoBERTa)이 문장을 읽고 핵심 의미를 추출합니다.
  2. 이 결과를 바탕으로 감정 분류기(MLP)가 감정을 예측합니다.
  3. 각 감정에 대한 가능성을 확률로 보여줍니다.
""", unsafe_allow_html=True) with tabs[2]: st.markdown('
🧠 Few-shot Fine-tuning이란?
', unsafe_allow_html=True) st.markdown("""
우리가 사용하는 KLUE-RoBERTa 모델은 이미 수많은 문장을 학습한 거대한 언어 모델입니다.

하지만 감정 분석이라는 특정한 작업에 맞게 조금 더 학습시키는 과정이 필요합니다.

이때 전체 모델을 다시 학습하지 않고, 마지막 분류기(MLP)만 학습하는 방식이 바로 Few-shot Fine-tuning입니다.

이 방법을 통해 적은 양의 감정 데이터만으로도 높은 성능을 달성할 수 있습니다.
""", unsafe_allow_html=True) with tabs[3]: st.markdown('
📌 이 기술은 어디에 쓰일 수 있을까요?
', unsafe_allow_html=True) st.markdown("""
이 감정 분석 기술은 단순히 문장의 감정을 분류하는 데 그치지 않고, 디지털 사회에서의 감정 흐름공론장의 정서적 구조를 이해하는 데까지 확장될 수 있습니다.

특히 다음과 같은 분야에 활용될 수 있습니다: 나아가 이 기술은 디지털 인문학의 새로운 분석 도구로 활용되어, 텍스트 기반 여론의 정서적 구조를 보다 깊이 있게 이해하는 기반이 될 수 있습니다.
""", unsafe_allow_html=True) with tabs[4]: st.image("image/clustering-2.png", use_column_width=True) st.image("image/clustering-10.png", use_column_width=True) st.image("image/clustering-plutchiks.png", use_column_width=True) st.image("image/clustering-plutchiks-bert.png", use_column_width=True)