Spaces:
Runtime error
Runtime error
kootaeng2
commited on
Commit
ยท
e850536
0
Parent(s):
Initial commit with final, clean project files
Browse files- .gitattributes +4 -0
- .github/workflows/sync-to-hub.yml +31 -0
- .gitignore +19 -0
- .hfignore +0 -0
- Dockerfile +18 -0
- README.md +111 -0
- notebooks/explore_data.py +126 -0
- requirements.txt +0 -0
- scripts/save_complete_model.py +20 -0
- scripts/train_model.py +149 -0
- scripts/upload_model.py +30 -0
- src/app.py +68 -0
- src/emotion_engine.py +46 -0
- src/recommender.py +44 -0
- templates/emotion_homepage.html +229 -0
.gitattributes
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
korean-emotion-classifier-final/model.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*safetensores filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/sync-to-hub.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face hub # ์ด Action์ ์ด๋ฆ
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main] # GitHub์ 'main' ๋ธ๋์น์ push ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ์คํ๋ฉ๋๋ค.
|
| 6 |
+
|
| 7 |
+
jobs:
|
| 8 |
+
sync-to-hub:
|
| 9 |
+
runs-on: ubuntu-latest
|
| 10 |
+
steps:
|
| 11 |
+
- uses: actions/checkout@v3
|
| 12 |
+
with:
|
| 13 |
+
fetch-depth: 0
|
| 14 |
+
lfs: true
|
| 15 |
+
|
| 16 |
+
- name: Push to hub
|
| 17 |
+
env:
|
| 18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }} # 1๋จ๊ณ์์ GitHub์ ์ ์ฅํ HF_TOKEN ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
|
| 19 |
+
run: |
|
| 20 |
+
# ์ฌ์ฉ์ ์ด๋ฆ(koons)๊ณผ Space ์ด๋ฆ(emotion-chatbot)์ด ํฌํจ๋ ์ฃผ์๋ฅผ ์ค์ ํฉ๋๋ค.
|
| 21 |
+
# git-lfs๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค.
|
| 22 |
+
git config --global user.email "[email protected]"
|
| 23 |
+
git config --global user.name "Hugging Face"
|
| 24 |
+
|
| 25 |
+
# 'huggingface'๋ผ๋ ์ด๋ฆ์ผ๋ก ์๊ฒฉ ์ ์ฅ์ ์ฃผ์๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 26 |
+
# ์ฃผ์ ํ์: https://[์ฌ์ฉ์์ด๋ฆ]:[ํ ํฐ]@[Hugging Face ์ฃผ์]
|
| 27 |
+
git remote add huggingface https://koons:${HF_TOKEN}@huggingface.co/spaces/koons/emotion-chatbot
|
| 28 |
+
|
| 29 |
+
# ํ์ฌ ๋ก์ปฌ์ HEAD(์ต์ ์ปค๋ฐ)๋ฅผ huggingface ์๊ฒฉ ์ ์ฅ์์ main ๋ธ๋์น๋ก ๊ฐ์ ํธ์ํฉ๋๋ค.
|
| 30 |
+
# -f ์ต์
์ Space์ ๊ธฐ์กด ๊ธฐ๋ก์ ๋ฎ์ด์ฐ๋ฏ๋ก, GitHub๋ฅผ ๊ธฐ์ค์ผ๋ก ํญ์ ์ต์ ์ํ๋ฅผ ์ ์งํฉ๋๋ค.
|
| 31 |
+
git push huggingface HEAD:main -f
|
.gitignore
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๊ฐ์ํ๊ฒฝ ํด๋
|
| 2 |
+
venv/
|
| 3 |
+
.venv/
|
| 4 |
+
|
| 5 |
+
# ํ์ด์ฌ ์บ์ ํ์ผ
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.pyc
|
| 8 |
+
|
| 9 |
+
# VS Code ์ค์ ํด๋
|
| 10 |
+
.vscode/
|
| 11 |
+
|
| 12 |
+
# ํ๋ จ ๋ก๊ทธ ํด๋
|
| 13 |
+
logs/
|
| 14 |
+
|
| 15 |
+
# ํ๋ จ๋ ๋ชจ๋ธ์ด ์ ์ฅ๋ ํด๋
|
| 16 |
+
results/
|
| 17 |
+
|
| 18 |
+
# ๊ธฐํ ์ด์์ฒด์ ํ์ผ
|
| 19 |
+
.DS_Store
|
.hfignore
ADDED
|
File without changes
|
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. ๋ฒ ์ด์ค ์ด๋ฏธ์ง ์ ํ (ํ์ด์ฌ 3.10 ๋ฒ์ )
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# 2. ์์
ํด๋ ์ค์
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 3. ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
|
| 8 |
+
COPY requirements.txt requirements.txt
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# 4. ํ๋ก์ ํธ ์ ์ฒด ์ฝ๋ ๋ณต์ฌ
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# 5. Hugging Face Spaces๊ฐ ์ฌ์ฉํ ํฌํธ(7860) ์ด๊ธฐ
|
| 15 |
+
EXPOSE 7860
|
| 16 |
+
|
| 17 |
+
# 6. ์ต์ข
์คํ ๋ช
๋ น์ด (gunicorn์ผ๋ก src ํด๋ ์์ app.py๋ฅผ ์คํ)
|
| 18 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "src.app:app"]
|
README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ค ์ผ๊ธฐ ๊ธฐ๋ฐ ๊ฐ์ ๋ถ์ ๋ฐ ์ฝํ
์ธ ์ถ์ฒ ์น ์ ํ๋ฆฌ์ผ์ด์
|
| 2 |
+
|
| 3 |
+
> ์ฌ์ฉ์์ ์ผ๊ธฐ ํ
์คํธ๋ฅผ AI๋ก ๋ถ์ํ์ฌ ๊ฐ์ ์ ํ์
ํ๊ณ , '๊ฐ์ ์์ฉ' ๋๋ '๊ธฐ๋ถ ์ ํ'์ด๋ผ๋ ๋ ๊ฐ์ง ์ ํ์ง์ ๋ฐ๋ผ ๋ง์ถคํ ์ฝํ
์ธ (์ํ, ์์
, ์ฑ
)๋ฅผ ์ถ์ฒํด์ฃผ๋ ์น ๊ธฐ๋ฐ ์๋น์ค์
๋๋ค.
|
| 4 |
+
|
| 5 |
+
<br>
|
| 6 |
+
|
| 7 |
+

|
| 8 |
+

|
| 9 |
+

|
| 10 |
+

|
| 11 |
+
|
| 12 |
+
<br>
|
| 13 |
+
|
| 14 |
+
## โจ ์ฃผ์ ๊ธฐ๋ฅ (Key Features)
|
| 15 |
+
|
| 16 |
+
* **AI ๊ธฐ๋ฐ ๊ฐ์ ๋ถ์:** Hugging Face์ `klue/roberta-base` ๋ชจ๋ธ์ AI Hub์ '๊ฐ์ฑ๋ํ ๋ง๋ญ์น' ๋ฐ์ดํฐ์
์ผ๋ก ๋ฏธ์ธ ์กฐ์ (Fine-tuning)ํ์ฌ, ํ๊ตญ์ด ํ
์คํธ์ ๋ํ ๋์ ์ ํ๋์ ๊ฐ์ ์์ธก์ ์ํํฉ๋๋ค.
|
| 17 |
+
* **์ํฉ๋ณ ๋ง์ถค ์ถ์ฒ:** ๋ถ์๋ ๊ฐ์ ์ ๋ํด '๊ฐ์ ์ ๊น์ด ๋๋ผ๊ณ ์ถ์ ๋(์์ฉ)'์ '๊ฐ์ ์์ ๋ฒ์ด๋๊ณ ์ถ์ ๋(์ ํ)'๋ผ๋ ๋ ๊ฐ์ง ์ ํ์ง๋ฅผ ์ ๊ณตํ์ฌ, ์ฌ์ฉ์์ ํ์ฌ ๋์ฆ์ ๋ง๋ ์ฐจ๋ณํ๋ ์ฝํ
์ธ ๋ฅผ ์ถ์ฒํฉ๋๋ค.
|
| 18 |
+
* **๋ค์ด์ด๋ฆฌ ํ์คํ ๋ฆฌ:** ์ฌ์ฉ์๊ฐ ์์ฑํ ์ผ๊ธฐ์ AI์ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์น ๋ธ๋ผ์ฐ์ ์ `localStorage`์ ์ ์ฅํ์ฌ, ์ธ์ ๋ ๊ณผ๊ฑฐ์ ๊ธฐ๋ก์ ๋ค์ ํ์ธํ๊ณ ๊ฐ์ ์ ํ๋ฆ์ ํ์
ํ ์ ์์ต๋๋ค.
|
| 19 |
+
* **์ฌ์ฉ์ ์นํ์ ์น ์ธํฐํ์ด์ค:** Flask ๊ธฐ๋ฐ์ ์น ์๋ฒ์ ๋์ ์ธ JavaScript๋ฅผ ํตํด, ๋๊ตฌ๋ ์ฝ๊ฒ ์์ ์ ๊ฐ์ ์ ๊ธฐ๋กํ๊ณ ์ถ์ฒ์ ๋ฐ์ ์ ์๋ ์ง๊ด์ ์ธ UI/UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
|
| 20 |
+
|
| 21 |
+
<br>
|
| 22 |
+
|
| 23 |
+
## ๐ฅ๏ธ ํ๋ก์ ํธ ๋ฐ๋ชจ (Demo)
|
| 24 |
+
|
| 25 |
+
์์ ํํ์ด์ง(https://kootaeng2.github.io/Emotion_Chatbot_project/templates/emotion_homepage.html)
|
| 26 |
+
|
| 27 |
+
<br>
|
| 28 |
+
|
| 29 |
+
## โ๏ธ ๊ธฐ์ ์คํ (Tech Stack)
|
| 30 |
+
|
| 31 |
+
| ๊ตฌ๋ถ | ๊ธฐ์ |
|
| 32 |
+
| :--- | :--- |
|
| 33 |
+
| **Backend** | Flask |
|
| 34 |
+
| **Frontend**| HTML, CSS, JavaScript |
|
| 35 |
+
| **AI / Data**| Python 3.10, PyTorch, Hugging Face Transformers, Scikit-learn, Pandas |
|
| 36 |
+
| **AI Model**| `klue/roberta-base` (Fine-tuned) |
|
| 37 |
+
|
| 38 |
+
<br>
|
| 39 |
+
|
| 40 |
+
## ๐ ํด๋ ๊ตฌ์กฐ (Folder Structure)
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
sentiment_analysis_project/
|
| 44 |
+
โโโ src/
|
| 45 |
+
โ โโโ app.py # ์น ์๋ฒ ์คํ ํ์ผ
|
| 46 |
+
โ โโโ chatbot.py # ํฐ๋ฏธ๋ ์ฑ๋ด ์คํ ํ์ผ
|
| 47 |
+
โ โโโ emotion_engine.py # ๊ฐ์ ๋ถ์ ์์ง ๋ชจ๋
|
| 48 |
+
โ โโโ recommender.py # ์ถ์ฒ ๋ก์ง ๋ชจ๋
|
| 49 |
+
โโโ scripts/
|
| 50 |
+
โ โโโ train_model.py # AI ๋ชจ๋ธ ํ๋ จ ์คํฌ๋ฆฝํธ
|
| 51 |
+
โโโ notebooks/
|
| 52 |
+
โ โโโ 1_explore_data.py # ๋ฐ์ดํฐ ํ์ ๋ฐ ์๊ฐํ์ฉ ๋
ธํธ๋ถ
|
| 53 |
+
โโโ data/ # ์๋ณธ ๋ฐ์ดํฐ์
|
| 54 |
+
โโโ results/ # ํ๋ จ๋ ๋ชจ๋ธ ํ์ผ (Git ๋ฏธํฌํจ)
|
| 55 |
+
โโโ templates/ # HTML ํ์ผ
|
| 56 |
+
โโโ static/ # CSS, ํด๋ผ์ด์ธํธ JS ํ์ผ
|
| 57 |
+
โโโ .gitignore # Git ๋ฌด์ ํ์ผ ๋ชฉ๋ก
|
| 58 |
+
โโโ README.md # ํ๋ก์ ํธ ์ค๋ช
์
|
| 59 |
+
โโโ requirements.txt # ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชฉ๋ก
|
| 60 |
+
```
|
| 61 |
+
<br>
|
| 62 |
+
|
| 63 |
+
## ๐ ์ค์น ๋ฐ ์คํ ๋ฐฉ๋ฒ (Installation & Run)
|
| 64 |
+
|
| 65 |
+
**1. ํ๋ก์ ํธ ๋ณต์ (Clone)**
|
| 66 |
+
```bash
|
| 67 |
+
git clone [https://github.com/kootaeng2/Emotion_Chatbot_project.git](https://github.com/kootaeng2/Emotion_Chatbot_project.git)
|
| 68 |
+
cd Emotion_Chatbot_project
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**2. ๊ฐ์ํ๊ฒฝ ์์ฑ ๋ฐ ํ์ฑํ (Python 3.10 ๊ธฐ์ค)**
|
| 72 |
+
```bash
|
| 73 |
+
# Python 3.10 ๋ฒ์ ์ ์ง์ ํ์ฌ ๊ฐ์ํ๊ฒฝ ์์ฑ
|
| 74 |
+
py -3.10 -m venv venv
|
| 75 |
+
# ๊ฐ์ํ๊ฒฝ ํ์ฑํ
|
| 76 |
+
.\venv\Scripts\Activate
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
**3. ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น**
|
| 80 |
+
```bash
|
| 81 |
+
# PyTorch (CUDA 11.8 ๋ฒ์ )๋ฅผ ๋จผ์ ์ค์นํฉ๋๋ค.
|
| 82 |
+
pip install torch torchvision torchaudio --index-url [https://download.pytorch.org/whl/cu118](https://download.pytorch.org/whl/cu118)
|
| 83 |
+
# ๋๋จธ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํฉ๋๋ค.
|
| 84 |
+
pip install -r requirements.txt
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
**4. AI ๋ชจ๋ธ ํ๋ จ (์ต์ด 1ํ ํ์)**
|
| 88 |
+
> **์ฃผ์:** ์ด ๊ณผ์ ์ AI Hub์์ '๊ฐ์ฑ๋ํ ๋ง๋ญ์น' ์๋ณธ ๋ฐ์ดํฐ์
์ ๋ค์ด๋ก๋ํ์ฌ `data` ํด๋์ ์์น์ํจ ํ ์งํํด์ผ ํฉ๋๋ค. ํ๋ จ์๋ RTX 4060 GPU ๊ธฐ์ค ์ฝ 30-40๋ถ์ด ์์๋ฉ๋๋ค.
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
python train_model.py
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
**5. ์น ์ ํ๋ฆฌ์ผ์ด์
์คํ**
|
| 95 |
+
```bash
|
| 96 |
+
python app.py
|
| 97 |
+
```
|
| 98 |
+
* ์๋ฒ๊ฐ ์คํ๋๋ฉด, ์น ๋ธ๋ผ์ฐ์ ๋ฅผ ์ด๊ณ ์ฃผ์์ฐฝ์ `http://127.0.0.1:5000` ์ ์
๋ ฅํ์ธ์.
|
| 99 |
+
|
| 100 |
+
<br>
|
| 101 |
+
|
| 102 |
+
## ๐ ๋ชจ๋ธ ์ฑ๋ฅ (Model Performance)
|
| 103 |
+
|
| 104 |
+
'๊ฐ์ฑ๋ํ ๋ง๋ญ์น' ๊ฒ์ฆ ๋ฐ์ดํฐ์
(Validation Set)์ผ๋ก ํ๊ฐํ ์ต์ข
๋ชจ๋ธ์ ์ฑ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
|
| 105 |
+
|
| 106 |
+
| ํ๊ฐ์งํ (Metric) | ์ ์ (Score) |
|
| 107 |
+
| :--- | :---: |
|
| 108 |
+
| **Accuracy** (์ ํ๋) | **85.3%** |
|
| 109 |
+
| **F1-Score** (Weighted)| **0.852** |
|
| 110 |
+
|
| 111 |
+
|
notebooks/explore_data.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๋ฐ์ดํฐ์
์ ์ฌ์ฉ ์กฐ์ฌ ๋ณด๊ณ ๊ทธ๋ํ
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import json
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
# ํ์ผ ๊ฒฝ๋ก ์ค์
|
| 8 |
+
file_path = './data/'
|
| 9 |
+
|
| 10 |
+
# ํ๋ จ ๋ผ๋ฒจ JSON ํ์ผ ๋ถ๋ฌ์ค๊ธฐ
|
| 11 |
+
with open(file_path + 'training-label.json', 'r', encoding='utf-8') as file:
|
| 12 |
+
training_data_raw = json.load(file)
|
| 13 |
+
|
| 14 |
+
# ํ์ํ ๋ฐ์ดํฐ๋ง ์ถ์ถํ์ฌ ๋ฆฌ์คํธ์ ์ ์ฅ
|
| 15 |
+
extracted_data = []
|
| 16 |
+
|
| 17 |
+
# ๋ฐ์ดํฐ๋ ๋ฆฌ์คํธ์ด๋ฏ๋ก ๋ฐ๋ก ์ํํฉ๋๋ค.
|
| 18 |
+
for dialogue in training_data_raw:
|
| 19 |
+
try:
|
| 20 |
+
# 1. ๊ฐ์ ๋ผ๋ฒจ ์ถ์ถ (emotion ํค๋ profile ์์ ์์ต๋๋ค)
|
| 21 |
+
emotion_type = dialogue['profile']['emotion']['type']
|
| 22 |
+
|
| 23 |
+
# 2. ๋ํ ํ
์คํธ ์ถ์ถ (talk ํค ์์ content๊ฐ ์์ต๋๋ค)
|
| 24 |
+
dialogue_content = dialogue['talk']['content']
|
| 25 |
+
|
| 26 |
+
# 3. ๋์
๋๋ฆฌ์ value๋ค(ํ
์คํธ)๋ง ์ถ์ถํฉ๋๋ค.
|
| 27 |
+
texts = list(dialogue_content.values())
|
| 28 |
+
|
| 29 |
+
# 4. ๋ชจ๋ ํ
์คํธ๋ฅผ ํ๋์ ๋ฌธ์์ด๋ก ํฉ์นฉ๋๋ค.
|
| 30 |
+
# ๋น ๋ฌธ์์ด์ ์ ๊ฑฐํ๊ณ ํฉ์น๋ ๊ฒ์ด ์ข์ต๋๋ค.
|
| 31 |
+
full_text = " ".join([text for text in texts if text.strip()])
|
| 32 |
+
|
| 33 |
+
# 5. ํฉ์ณ์ง ํ
์คํธ์ ๊ฐ์ ๋ผ๋ฒจ์ด ๋ชจ๋ ์ ํจํ ๊ฒฝ์ฐ์๋ง ์ถ๊ฐํฉ๋๋ค.
|
| 34 |
+
if full_text and emotion_type:
|
| 35 |
+
extracted_data.append({'text': full_text, 'emotion': emotion_type})
|
| 36 |
+
|
| 37 |
+
except KeyError:
|
| 38 |
+
# 'profile', 'emotion', 'talk', 'content' ๋ฑ์ ํค๊ฐ ์๋ ํญ๋ชฉ์ ๊ฑด๋๋๋๋ค.
|
| 39 |
+
continue
|
| 40 |
+
|
| 41 |
+
# ์๋ก์ด ๋ฐ์ดํฐํ๋ ์ ์์ฑ
|
| 42 |
+
df_train = pd.DataFrame(extracted_data)
|
| 43 |
+
|
| 44 |
+
# 6. ํฉ์ณ์ง ๋ฐ์ดํฐ ํ์ธ
|
| 45 |
+
print("--- ์ถ์ถ๋ ํ๋ จ ๋ฐ์ดํฐํ๋ ์์ ์ฒซ 5์ค ---")
|
| 46 |
+
print(df_train.head())
|
| 47 |
+
|
| 48 |
+
print("\n--- ๋ฐ์ดํฐํ๋ ์ ํฌ๊ธฐ ---")
|
| 49 |
+
print(f"ํ๋ จ ๋ฐ์ดํฐ: {df_train.shape}")
|
| 50 |
+
|
| 51 |
+
# ๊ธฐ์กด ํ๋ จ ๋ฐ์ดํฐ ๋ก๋ ์ฝ๋ ์๋์ ์ด์ด์ ์์ฑํด ์ฃผ์ธ์.
|
| 52 |
+
# ------------------------------------------------------------------
|
| 53 |
+
|
| 54 |
+
# 1. ๊ฒ์ฆ ๋ผ๋ฒจ JSON ํ์ผ ๋ถ๋ฌ์ค๊ธฐ
|
| 55 |
+
with open(file_path + 'validation-label.json', 'r', encoding='utf-8') as file:
|
| 56 |
+
validation_data_raw = json.load(file)
|
| 57 |
+
|
| 58 |
+
# 2. ๊ฒ์ฆ ๋ฐ์ดํฐ ์ถ์ถ
|
| 59 |
+
extracted_val_data = []
|
| 60 |
+
|
| 61 |
+
for dialogue in validation_data_raw:
|
| 62 |
+
try:
|
| 63 |
+
emotion_type = dialogue['profile']['emotion']['type']
|
| 64 |
+
dialogue_content = dialogue['talk']['content']
|
| 65 |
+
texts = list(dialogue_content.values())
|
| 66 |
+
full_text = " ".join([text for text in texts if text.strip()])
|
| 67 |
+
|
| 68 |
+
if full_text and emotion_type:
|
| 69 |
+
extracted_val_data.append({'text': full_text, 'emotion': emotion_type})
|
| 70 |
+
|
| 71 |
+
except KeyError:
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
+
# 3. ์๋ก์ด ๋ฐ์ดํฐํ๋ ์ ์์ฑ
|
| 75 |
+
df_val = pd.DataFrame(extracted_val_data)
|
| 76 |
+
|
| 77 |
+
# 4. ๊ฒ์ฆ ๋ฐ์ดํฐ ํ์ธ
|
| 78 |
+
print("\n--- ์ถ์ถ๋ ๊ฒ์ฆ ๋ฐ์ดํฐํ๋ ์์ ์ฒซ 5์ค ---")
|
| 79 |
+
print(df_val.head())
|
| 80 |
+
|
| 81 |
+
print("\n--- ๊ฒ์ฆ ๋ฐ์ดํฐํ๋ ์ ํฌ๊ธฐ ---")
|
| 82 |
+
print(f"๊ฒ์ฆ ๋ฐ์ดํฐ: {df_val.shape}")
|
| 83 |
+
|
| 84 |
+
# main.py์ ๊ธฐ์กด ์ฝ๋ ๋งจ ์๋์ ์ด์ด์ ์์ฑํฉ๋๋ค.
|
| 85 |
+
# -----------------------------------------------------------
|
| 86 |
+
# --- [Phase 1] ๋ฐ์ดํฐ ํ์ ๋ฐ ์ ์ฒ๋ฆฌ ---
|
| 87 |
+
# -----------------------------------------------------------
|
| 88 |
+
import matplotlib.pyplot as plt
|
| 89 |
+
import seaborn as sns
|
| 90 |
+
|
| 91 |
+
# 1. ๋ฐ์ดํฐ ํ์ ๋ฐ ์๊ฐํ
|
| 92 |
+
print("\n--- [Phase 1-1] ๋ฐ์ดํฐ ํ์ ๋ฐ ์๊ฐํ ์์ ---")
|
| 93 |
+
|
| 94 |
+
# ํ๊ธ ํฐํธ ์ค์ (Windows: Malgun Gothic, Mac: AppleGothic)
|
| 95 |
+
plt.rcParams['font.family'] = 'Malgun Gothic'
|
| 96 |
+
plt.rcParams['axes.unicode_minus'] = False # ๋ง์ด๋์ค ๊ธฐํธ ๊นจ์ง ๋ฐฉ์ง
|
| 97 |
+
|
| 98 |
+
# ํ๋ จ ๋ฐ์ดํฐ์ ๊ฐ์ ๋ถํฌ ํ์ธ
|
| 99 |
+
print("\n--- ํ๋ จ ๋ฐ์ดํฐ ๊ฐ์ ๋ถํฌ ---")
|
| 100 |
+
print(df_train['emotion'].value_counts())
|
| 101 |
+
|
| 102 |
+
# ๊ฐ์ ๋ถํฌ ์๊ฐํ
|
| 103 |
+
plt.figure(figsize=(10, 6))
|
| 104 |
+
sns.countplot(data=df_train, y='emotion', order=df_train['emotion'].value_counts().index)
|
| 105 |
+
plt.title('ํ๋ จ ๋ฐ์ดํฐ ๊ฐ์ ๋ถํฌ ์๊ฐํ', fontsize=15)
|
| 106 |
+
plt.xlabel('๊ฐ์', fontsize=12)
|
| 107 |
+
plt.ylabel('๊ฐ์ ', fontsize=12)
|
| 108 |
+
plt.grid(axis='x', linestyle='--', alpha=0.7)
|
| 109 |
+
plt.show() # ๊ทธ๋ํ ์ฐฝ ๋ณด์ฌ์ฃผ๊ธฐ
|
| 110 |
+
|
| 111 |
+
print("\n์๊ฐํ ์๋ฃ. ๊ทธ๋ํ ์ฐฝ์ ๋ซ์ผ๋ฉด ๋ค์ ๋จ๊ณ๊ฐ ์งํ๋ฉ๋๋ค.")
|
| 112 |
+
|
| 113 |
+
# 2. ํ
์คํธ ์ ์
|
| 114 |
+
print("\n--- [Phase 1-2] ํ
์คํธ ์ ์ ์์ ---")
|
| 115 |
+
# ์ด๋ฏธ re ๋ชจ๋์ ์์์ import ํ์ต๋๋ค.
|
| 116 |
+
|
| 117 |
+
def clean_text(text):
|
| 118 |
+
# ์ ๊ทํํ์์ ์ฌ์ฉํ์ฌ ํ๊ธ, ์์ด, ์ซ์, ๊ณต๋ฐฑ์ ์ ์ธํ ๋ชจ๋ ๋ฌธ์ ์ ๊ฑฐ
|
| 119 |
+
return re.sub(r'[^๊ฐ-ํฃa-zA-Z0-9 ]', '', text)
|
| 120 |
+
|
| 121 |
+
# ํ๋ จ/๊ฒ์ฆ ๋ฐ์ดํฐ์ ์ ์ ํจ์ ์ ์ฉ
|
| 122 |
+
df_train['cleaned_text'] = df_train['text'].apply(clean_text)
|
| 123 |
+
df_val['cleaned_text'] = df_val['text'].apply(clean_text)
|
| 124 |
+
|
| 125 |
+
print("ํ
์คํธ ์ ์ ์๋ฃ.")
|
| 126 |
+
print(df_train[['text', 'cleaned_text']].head())
|
requirements.txt
ADDED
|
Binary file (1.61 kB). View file
|
|
|
scripts/save_complete_model.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# save_complete_model.py
|
| 2 |
+
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
| 3 |
+
|
| 4 |
+
# ๊ธฐ์กด ํ๋ จ ๊ฒฐ๊ณผ๋ฌผ์ด ์ ์ฅ๋ ๊ฒฝ๋ก
|
| 5 |
+
checkpoint_path = "./results/checkpoint-9681"
|
| 6 |
+
|
| 7 |
+
# '์์ ํ ๋ชจ๋ธ'์ ์ ์ฅํ ์ ํด๋ ์ด๋ฆ
|
| 8 |
+
output_dir = "./korean-emotion-classifier-final"
|
| 9 |
+
|
| 10 |
+
print(f"'{checkpoint_path}'์์ ๋ชจ๋ธ๊ณผ ํ ํฌ๋์ด์ ๋ฅผ ๋ถ๋ฌ์ต๋๋ค...")
|
| 11 |
+
model = AutoModelForSequenceClassification.from_pretrained(checkpoint_path)
|
| 12 |
+
tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
|
| 13 |
+
print("๋ถ๋ฌ์ค๊ธฐ ์๋ฃ.")
|
| 14 |
+
|
| 15 |
+
print(f"'{output_dir}' ํด๋์ ์์ ํ ๋ชจ๋ธ๊ณผ ํ ํฌ๋์ด์ ๋ฅผ ์ ์ฅํฉ๋๋ค...")
|
| 16 |
+
model.save_pretrained(output_dir)
|
| 17 |
+
tokenizer.save_pretrained(output_dir)
|
| 18 |
+
|
| 19 |
+
print("์ ์ฅ ์๋ฃ! 'korean-emotion-classifier-final' ํด๋๋ฅผ ํ์ธํ์ธ์.")
|
| 20 |
+
print("์ด ํด๋ ์์ ํ์ผ๋ค์ 'my-local-model' ํด๋๋ก ์ฎ๊ฒจ์ฃผ์๋ฉด ๋ฉ๋๋ค.")
|
scripts/train_model.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# train_model.py
|
| 2 |
+
# AI ๋ชจ๋ธ์ ํ๋ จํ๋ ์คํฌ๋ฆฝํธ, ๋ค์ ์ฌ์ฉ๊ฐ๋ฅํ ์ญ์ x
|
| 3 |
+
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import json
|
| 6 |
+
import re
|
| 7 |
+
import sys
|
| 8 |
+
import transformers
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
from transformers import AutoTokenizer
|
| 12 |
+
|
| 13 |
+
# --- 1. ๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์ ์ฒ๋ฆฌ ---
|
| 14 |
+
|
| 15 |
+
print("--- [Phase 1] ๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์ ์ฒ๋ฆฌ ์์ ---")
|
| 16 |
+
# ํ์ผ ๊ฒฝ๋ก ์ค์
|
| 17 |
+
file_path = './data/'
|
| 18 |
+
|
| 19 |
+
# ํ๋ จ/๊ฒ์ฆ ๋ฐ์ดํฐ ๋ก๋ฉ (์ด์ ๊ณผ ๋์ผ)
|
| 20 |
+
with open(file_path + 'training-label.json', 'r', encoding='utf-8') as file:
|
| 21 |
+
training_data_raw = json.load(file)
|
| 22 |
+
with open(file_path + 'validation-label.json', 'r', encoding='utf-8') as file:
|
| 23 |
+
validation_data_raw = json.load(file)
|
| 24 |
+
|
| 25 |
+
# DataFrame ์์ฑ ํจ์ (์ฝ๋๋ฅผ ๊น๋ํ๊ฒ ํ๊ธฐ ์ํด ํจ์๋ก ๋ฌถ์)
|
| 26 |
+
def create_dataframe(data_raw):
|
| 27 |
+
extracted_data = []
|
| 28 |
+
for dialogue in data_raw:
|
| 29 |
+
try:
|
| 30 |
+
emotion_type = dialogue['profile']['emotion']['type']
|
| 31 |
+
dialogue_content = dialogue['talk']['content']
|
| 32 |
+
full_text = " ".join(list(dialogue_content.values()))
|
| 33 |
+
if full_text and emotion_type:
|
| 34 |
+
extracted_data.append({'text': full_text, 'emotion': emotion_type})
|
| 35 |
+
except KeyError:
|
| 36 |
+
continue
|
| 37 |
+
return pd.DataFrame(extracted_data)
|
| 38 |
+
|
| 39 |
+
df_train = create_dataframe(training_data_raw)
|
| 40 |
+
df_val = create_dataframe(validation_data_raw)
|
| 41 |
+
|
| 42 |
+
# ํ
์คํธ ์ ์
|
| 43 |
+
def clean_text(text):
|
| 44 |
+
return re.sub(r'[^๊ฐ-ํฃa-zA-Z0-9 ]', '', text)
|
| 45 |
+
|
| 46 |
+
df_train['cleaned_text'] = df_train['text'].apply(clean_text)
|
| 47 |
+
df_val['cleaned_text'] = df_val['text'].apply(clean_text)
|
| 48 |
+
print("โ
๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์ ์ฒ๋ฆฌ ์๋ฃ!")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# --- 2. AI ๋ชจ๋ธ๋ง ์ค๋น ---
|
| 52 |
+
print("\n--- [Phase 2] AI ๋ชจ๋ธ๋ง ์ค๋น ์์ ---")
|
| 53 |
+
# ๋ชจ๋ธ ๋ฐ ํ ํฌ๋์ด์ ๋ถ๋ฌ์ค๊ธฐ
|
| 54 |
+
MODEL_NAME = "klue/roberta-base"
|
| 55 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
| 56 |
+
|
| 57 |
+
# ํ
์คํธ ํ ํฐํ
|
| 58 |
+
train_tokenized = tokenizer(list(df_train['cleaned_text']), return_tensors="pt", max_length=128, padding=True, truncation=True)
|
| 59 |
+
val_tokenized = tokenizer(list(df_val['cleaned_text']), return_tensors="pt", max_length=128, padding=True, truncation=True)
|
| 60 |
+
|
| 61 |
+
# ๋ผ๋ฒจ ์ธ์ฝ๋ฉ
|
| 62 |
+
unique_labels = sorted(df_train['emotion'].unique())
|
| 63 |
+
label_to_id = {label: id for id, label in enumerate(unique_labels)}
|
| 64 |
+
id_to_label = {id: label for label, id in label_to_id.items()}
|
| 65 |
+
df_train['label'] = df_train['emotion'].map(label_to_id)
|
| 66 |
+
df_val['label'] = df_val['emotion'].map(label_to_id)
|
| 67 |
+
print("โ
ํ ํฐํ ๋ฐ ๋ผ๋ฒจ ์ธ์ฝ๋ฉ ์๋ฃ!")
|
| 68 |
+
print("์ด์ ๋ชจ๋ธ ํ๋ จ์ ์ํ ๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ต๋๋ค.")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# [Phase 3]์ ๊ธฐ์กด ์ฝ๋๋ฅผ ์๋ ๋ด์ฉ์ผ๋ก ๊ต์ฒดํด์ฃผ์ธ์.
|
| 72 |
+
# -----------------------------------------------------------
|
| 73 |
+
# --- [Phase 3] ๋ชจ๋ธ ํ์ต ๋ฐ ํ๊ฐ (์ต์ ๊ธฐ๋ฅ ๋ฒ์ ) ---
|
| 74 |
+
# -----------------------------------------------------------
|
| 75 |
+
import torch
|
| 76 |
+
from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer
|
| 77 |
+
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
|
| 78 |
+
|
| 79 |
+
print("\n--- [Phase 3] ๋ชจ๋ธ ํ์ต ๋ฐ ํ๊ฐ ์์ ---")
|
| 80 |
+
|
| 81 |
+
# 1. PyTorch Dataset ํด๋์ค ์ ์ (์ด์ ๊ณผ ๋์ผ)
|
| 82 |
+
class EmotionDataset(torch.utils.data.Dataset):
|
| 83 |
+
def __init__(self, encodings, labels):
|
| 84 |
+
self.encodings = encodings
|
| 85 |
+
self.labels = labels
|
| 86 |
+
def __getitem__(self, idx):
|
| 87 |
+
item = {key: val[idx].clone().detach() for key, val in self.encodings.items()}
|
| 88 |
+
item['labels'] = torch.tensor(self.labels[idx])
|
| 89 |
+
return item
|
| 90 |
+
def __len__(self):
|
| 91 |
+
return len(self.labels)
|
| 92 |
+
|
| 93 |
+
train_dataset = EmotionDataset(train_tokenized, df_train['label'].tolist())
|
| 94 |
+
val_dataset = EmotionDataset(val_tokenized, df_val['label'].tolist())
|
| 95 |
+
print("โ
PyTorch ๋ฐ์ดํฐ์
์์ฑ์ด ์๋ฃ๋์์ต๋๋ค.")
|
| 96 |
+
|
| 97 |
+
# 2. AI ๋ชจ๋ธ ๋ถ๋ฌ์ค๊ธฐ (์ด์ ๊ณผ ๋์ผ)
|
| 98 |
+
model = AutoModelForSequenceClassification.from_pretrained(
|
| 99 |
+
MODEL_NAME,
|
| 100 |
+
num_labels=len(unique_labels),
|
| 101 |
+
id2label=id_to_label,
|
| 102 |
+
label2id=label_to_id
|
| 103 |
+
)
|
| 104 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 105 |
+
model.to(device)
|
| 106 |
+
print(f"โ
๋ชจ๋ธ ๋ก๋ฉ ์๋ฃ! ๋ชจ๋ธ์ {device}์์ ์คํ๋ฉ๋๋ค.")
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# 3. ๋ชจ๋ธ ์ฑ๋ฅ ํ๊ฐ๋ฅผ ์ํ ํจ์ ์ ์ (์์ ์๋ฃ)
|
| 110 |
+
def compute_metrics(pred):
|
| 111 |
+
labels = pred.label_ids
|
| 112 |
+
# ๋ฐ๋ก ์ด ๋ถ๋ถ์ด ์์ ๋์์ต๋๋ค.
|
| 113 |
+
preds = pred.predictions.argmax(-1)
|
| 114 |
+
|
| 115 |
+
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='weighted', zero_division=0)
|
| 116 |
+
acc = accuracy_score(labels, preds)
|
| 117 |
+
return {'accuracy': acc, 'f1': f1, 'precision': precision, 'recall': recall}
|
| 118 |
+
|
| 119 |
+
# 4. ํ๋ จ์ ์ํ ์์ธ ์ค์ (Arguments) ์ ์ (๋ชจ๋ ๋ถ๊ฐ ์ต์
์ ๊ฑฐ)
|
| 120 |
+
training_args = TrainingArguments(
|
| 121 |
+
output_dir='./results', # ๋ชจ๋ธ์ด ์ ์ฅ๋ ์์น (ํ์)
|
| 122 |
+
num_train_epochs=3, # ํ๋ จ ํ์
|
| 123 |
+
per_device_train_batch_size=16, # ํ๋ จ ๋ฐฐ์น ์ฌ์ด์ฆ
|
| 124 |
+
# ๋๋จธ์ง ๋ชจ๋ ํ๊ฐ/์ ์ฅ ๊ด๋ จ ์ต์
์ ๋ชจ๋ ์ ๊ฑฐํฉ๋๋ค.
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# ---!!! ํต์ฌ ์๏ฟฝ๏ฟฝ๏ฟฝ ์ฌํญ 2 !!!---
|
| 128 |
+
# 5. Trainer ์ ์ (ํ๊ฐ ๊ด๋ จ ๊ธฐ๋ฅ ๋นํ์ฑํ)
|
| 129 |
+
trainer = Trainer(
|
| 130 |
+
model=model,
|
| 131 |
+
args=training_args,
|
| 132 |
+
train_dataset=train_dataset,
|
| 133 |
+
# ํ๋ จ ์ค ํ๊ฐ๋ฅผ ํ์ง ์์ผ๋ฏ๋ก ์๋ ์ต์
๋ค์ ์ ์ธํฉ๋๋ค.
|
| 134 |
+
# eval_dataset=val_dataset,
|
| 135 |
+
# compute_metrics=compute_metrics
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# 6. ๋ชจ๋ธ ํ๋ จ ์์!
|
| 139 |
+
print("\n๐ฅ AI ๋ชจ๋ธ ํ๋ จ์ ์์ํฉ๋๋ค...")
|
| 140 |
+
trainer.train()
|
| 141 |
+
print("\n๐ ๋ชจ๋ธ ํ๋ จ ์๋ฃ!")
|
| 142 |
+
|
| 143 |
+
# 7. ์ต์ข
๋ชจ๋ธ ํ๊ฐ๋ ํ๋ จ์ด ๋๋ ํ '๋ณ๋๋ก' ์คํ
|
| 144 |
+
print("\n--- ์ต์ข
๋ชจ๋ธ ์ฑ๋ฅ ํ๊ฐ ---")
|
| 145 |
+
# ๋นํ์ฑํํ๋ ํ๊ฐ ๋ฐ์ดํฐ์
์ evaluate ํจ์์ ์ง์ ์ ๋ฌํด์ค๋๋ค.
|
| 146 |
+
final_evaluation = trainer.evaluate(eval_dataset=val_dataset)
|
| 147 |
+
print(final_evaluation)
|
| 148 |
+
|
| 149 |
+
print("\n๋ชจ๋ ๊ณผ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ๋๋ฌ์ต๋๋ค! results ํด๋์์ ํ๋ จ๋ ๋ชจ๋ธ์ ํ์ธํ์ธ์.")
|
scripts/upload_model.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# upload_model.py
|
| 2 |
+
|
| 3 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 4 |
+
|
| 5 |
+
# ---!!! 1. ์ด ๋ถ๋ถ์ ์ต์ข
์ ์ผ๋ก ๊ฒฐ์ ํ ์ ๋ณด๋ก ์์ ํด์ฃผ์ธ์ !!!---
|
| 6 |
+
YOUR_HF_ID = "taehoon222" # ์ฌ์ฉ์๋์ Hugging Face ID
|
| 7 |
+
YOUR_MODEL_NAME = "korean-emotion-classifier" # ์ถ์ฒ ๋ชจ๋ธ ์ด๋ฆ (์ํ๋ ์ด๋ฆ์ผ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ)
|
| 8 |
+
# ----------------------------------------------------
|
| 9 |
+
|
| 10 |
+
# 2. ๋ด ์ปดํจํฐ์ ์ ์ฅ๋, ํ๋ จ์ด ์๋ฃ๋ ๋ชจ๋ธ์ ๊ฒฝ๋ก
|
| 11 |
+
LOCAL_MODEL_PATH = 'E:/sentiment_analysis_project/results/checkpoint-9681'
|
| 12 |
+
|
| 13 |
+
print(f"'{LOCAL_MODEL_PATH}'์์ ๋ชจ๋ธ์ ๋ถ๋ฌ์ต๋๋ค...")
|
| 14 |
+
try:
|
| 15 |
+
tokenizer = AutoTokenizer.from_pretrained(LOCAL_MODEL_PATH)
|
| 16 |
+
model = AutoModelForSequenceClassification.from_pretrained(LOCAL_MODEL_PATH)
|
| 17 |
+
print("โ
๋ก์ปฌ ๋ชจ๋ธ ๋ก๋ฉ ์ฑ๊ณต!")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"โ ๋ก์ปฌ ๋ชจ๋ธ์ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค: {e}")
|
| 20 |
+
exit()
|
| 21 |
+
|
| 22 |
+
# 3. Hugging Face Hub์ ์
๋ก๋ํฉ๋๋ค.
|
| 23 |
+
NEW_REPO_ID = f"{YOUR_HF_ID}/{YOUR_MODEL_NAME}"
|
| 24 |
+
print(f"'{NEW_REPO_ID}' ์ด๋ฆ์ผ๋ก Hub์ ์
๋ก๋๋ฅผ ์์ํฉ๋๋ค...")
|
| 25 |
+
try:
|
| 26 |
+
tokenizer.push_to_hub(NEW_REPO_ID)
|
| 27 |
+
model.push_to_hub(NEW_REPO_ID)
|
| 28 |
+
print("\n๐๐๐ ๋ชจ๋ธ ์
๋ก๋์ ์ฑ๊ณตํ์ต๋๋ค! ๐๐๐")
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"\nโ ์
๋ก๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
|
src/app.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
|
| 3 |
+
from flask import Flask, render_template, request, jsonify
|
| 4 |
+
# emotion_engine.py์์ ๋ ํจ์๋ฅผ ๋ชจ๋ ์ฌ๋ฐ๋ฅด๊ฒ import ํฉ๋๋ค.
|
| 5 |
+
from emotion_engine import load_emotion_classifier, predict_emotion
|
| 6 |
+
# recommender.py์์ ๋๋ฌธ์ Recommender 'ํด๋์ค'๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ import ํฉ๋๋ค.
|
| 7 |
+
from recommender import Recommender
|
| 8 |
+
import random
|
| 9 |
+
|
| 10 |
+
app = Flask(__name__)
|
| 11 |
+
|
| 12 |
+
print("AI ์ฑ๋ด ์๋ฒ๋ฅผ ์ค๋น ์ค์
๋๋ค...")
|
| 13 |
+
# ์๋ฒ๊ฐ ์์๋ ๋ AI ์์ง๊ณผ ์ถ์ฒ๊ธฐ๋ฅผ ๊ฐ๊ฐ ํ ๋ฒ์ฉ๋ง ๋ก๋ํฉ๋๋ค.
|
| 14 |
+
emotion_classifier = load_emotion_classifier()
|
| 15 |
+
recommender = Recommender()
|
| 16 |
+
# ์นํ์ด์ง์ ๊ฐ์ ๋ณ ์ด๋ชจ์ง๋ฅผ ๋ณด๋ด์ฃผ๊ธฐ ์ํ ๋์
๋๋ฆฌ์
๋๋ค.
|
| 17 |
+
emotion_emoji_map = {
|
| 18 |
+
'๊ธฐ์จ': '๐', 'ํ๋ณต': '๐', '์ฌ๋': 'โค๏ธ',
|
| 19 |
+
'๋ถ์': '๐', '์ฌํ': '๐ข', '์์ฒ': '๐',
|
| 20 |
+
'๋ถ๋
ธ': '๐ ', 'ํ์ค': '๐คข', '์ง์ฆ': '๐ค',
|
| 21 |
+
'๋๋': '๐ฎ',
|
| 22 |
+
'์ค๋ฆฝ': '๐',
|
| 23 |
+
}
|
| 24 |
+
print("โ
AI ์ฑ๋ด ์๋ฒ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ค๋น๋์์ต๋๋ค.")
|
| 25 |
+
|
| 26 |
+
@app.route("/")
|
| 27 |
+
def home():
|
| 28 |
+
"""์น ๋ธ๋ผ์ฐ์ ๊ฐ ์ฒ์ ์ ์ํ์ ๋ ๋ณด์ฌ์ค ๋ฉ์ธ ํ์ด์ง๋ฅผ ์ค์ ํฉ๋๋ค."""
|
| 29 |
+
# templates ํด๋ ์์ ์๋ emotion_homepage.html ํ์ผ์ ํ๋ฉด์ ๋ณด์ฌ์ค๋๋ค.
|
| 30 |
+
return render_template("emotion_homepage.html")
|
| 31 |
+
|
| 32 |
+
@app.route("/api/recommend", methods=["POST"])
|
| 33 |
+
def api_recommend():
|
| 34 |
+
"""์นํ์ด์ง์ '์ถ์ฒ ๋ฐ๊ธฐ' ๋ฒํผ ํด๋ฆญ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์
๋๋ค."""
|
| 35 |
+
# 1. ์นํ์ด์ง๋ก๋ถํฐ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ผ๊ธฐ ๋ด์ฉ์ ๋ฐ์ต๋๋ค.
|
| 36 |
+
user_diary = request.json.get("diary")
|
| 37 |
+
if not user_diary:
|
| 38 |
+
return jsonify({"error": "์ผ๊ธฐ ๋ด์ฉ์ด ์์ต๋๋ค."}), 400
|
| 39 |
+
|
| 40 |
+
# 2. emotion_engine์ ์ฌ์ฉํด ๊ฐ์ ์ ์์ธกํฉ๋๋ค.
|
| 41 |
+
predicted_emotion = predict_emotion(emotion_classifier, user_diary)
|
| 42 |
+
|
| 43 |
+
# 3. recommender๋ฅผ ์ฌ์ฉํด '์์ฉ'๊ณผ '์ ํ' ์ถ์ฒ์ ๋ชจ๋ ๋ฐ์ต๋๋ค.
|
| 44 |
+
accept_recs = recommender.recommend(predicted_emotion, "์์ฉ")
|
| 45 |
+
change_recs = recommender.recommend(predicted_emotion, "์ ํ")
|
| 46 |
+
|
| 47 |
+
# 4. ๊ฐ ์ถ์ฒ ๋ชฉ๋ก์์ ๋๋ค์ผ๋ก ํ๋์ฉ ์ ํํฉ๋๋ค. (๊ฒฐ๊ณผ๊ฐ ์์ ๊ฒฝ์ฐ๋ฅผ ๋๋น)
|
| 48 |
+
accept_choice = random.choice(accept_recs) if accept_recs else "์ถ์ฒ ์์"
|
| 49 |
+
change_choice = random.choice(change_recs) if change_recs else "์ถ์ฒ ์์"
|
| 50 |
+
|
| 51 |
+
# 5. ์นํ์ด์ง์ ๋ณด์ฌ์ค ์ต์ข
ํ
์คํธ๋ฅผ ์กฐํฉํฉ๋๋ค.
|
| 52 |
+
recommendation_text = (
|
| 53 |
+
f"<b>[ ์ด ๊ฐ์ ์ ๋ ๊น์ด ๋๋ผ๊ณ ์ถ๋ค๋ฉด... (์์ฉ) ]</b><br>"
|
| 54 |
+
f"โข {accept_choice}<br><br>"
|
| 55 |
+
f"<b>[ ์ด ๊ฐ์ ์์ ๋ฒ์ด๋๊ณ ์ถ๋ค๋ฉด... (์ ํ) ]</b><br>"
|
| 56 |
+
f"โข {change_choice}"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
# 6. ์ต์ข
๊ฒฐ๊ณผ๋ฅผ JSON ํํ๋ก ์นํ์ด์ง์ ๋๋ ค์ค๋๋ค.
|
| 60 |
+
response_data = {
|
| 61 |
+
"emotion": predicted_emotion,
|
| 62 |
+
"emoji": emotion_emoji_map.get(predicted_emotion, '๐ค'),
|
| 63 |
+
"recommendation": recommendation_text
|
| 64 |
+
}
|
| 65 |
+
return jsonify(response_data)
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
app.run(debug=True)
|
src/emotion_engine.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# emotion_engine.py
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
def load_emotion_classifier():
|
| 8 |
+
# ํ์ฌ ์คํฌ๋ฆฝํธ ํ์ผ์ ๋๋ ํฐ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
|
| 9 |
+
base_path = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
+
|
| 11 |
+
# ๋ชจ๋ธ ํด๋์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ๋ง๋ญ๋๋ค.
|
| 12 |
+
MODEL_PATH = os.path.join(base_path, "korean-emotion-classifier-final")
|
| 13 |
+
|
| 14 |
+
# ๊ฒฝ๋ก๊ฐ ๋ก์ปฌ ๋๋ ํฐ๋ฆฌ์ธ์ง ํ์ธ
|
| 15 |
+
if not os.path.isdir(MODEL_PATH):
|
| 16 |
+
print(f"โ ์ค๋ฅ: ์ง์ ๋ ๊ฒฝ๋ก '{MODEL_PATH}'์ ๋ชจ๋ธ ํด๋๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค.")
|
| 17 |
+
return None
|
| 18 |
+
|
| 19 |
+
print(f"--- ์ต์ข
๋ชจ๋ธ ๊ฒฝ๋ก ํ์ธ: [{MODEL_PATH}] ---")
|
| 20 |
+
print(f"๋ก์ปฌ ์ ๋ ๊ฒฝ๋ก '{MODEL_PATH}'์์ ๋ชจ๋ธ์ ์ง์ ๋ถ๋ฌ์ต๋๋ค...")
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
# 1. from_pretrained()์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ์ ๋ฌํฉ๋๋ค.
|
| 24 |
+
# 2. `local_files_only=True`๋ ์ ๊ฑฐํฉ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋์ผ๋ก ์ธ์ํฉ๋๋ค.
|
| 25 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
|
| 26 |
+
model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH)
|
| 27 |
+
|
| 28 |
+
print("โ
๋ก์ปฌ ๋ชจ๋ธ ํ์ผ ์ง์ ๋ก๋ฉ ์ฑ๊ณต!")
|
| 29 |
+
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"โ ๋ชจ๋ธ ๋ก๋ฉ ์ค ์ค๋ฅ: {e}")
|
| 32 |
+
# ์ค๋ฅ๊ฐ ๋ฐ์ํ ์์ธ์ ์ ํํ ์ถ๋ ฅํฉ๋๋ค.
|
| 33 |
+
print(f"์์ธ ์ค๋ฅ ๋ฉ์์ง: {e}")
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
+
device = 0 if torch.cuda.is_available() else -1
|
| 37 |
+
emotion_classifier = pipeline("text-classification", model=model, tokenizer=tokenizer, device=device)
|
| 38 |
+
|
| 39 |
+
return emotion_classifier
|
| 40 |
+
|
| 41 |
+
# predict_emotion ํจ์๋ ๊ทธ๋๋ก ๋ก๋๋ค.
|
| 42 |
+
def predict_emotion(classifier, text):
|
| 43 |
+
if not text or not text.strip(): return "๋ด์ฉ ์์"
|
| 44 |
+
if classifier is None: return "์ค๋ฅ: ๊ฐ์ ๋ถ์ ์์ง ์ค๋น ์๋จ."
|
| 45 |
+
result = classifier(text)
|
| 46 |
+
return result[0]['label']
|
src/recommender.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# recommender.py
|
| 2 |
+
|
| 3 |
+
class Recommender:
|
| 4 |
+
"""
|
| 5 |
+
๊ฐ์ ๊ณผ ์ฌ์ฉ์ ์ ํ์ ๋ฐ๋ผ ์ฝํ
์ธ ๋ฅผ ์ถ์ฒํ๋ ํด๋์ค์
๋๋ค.
|
| 6 |
+
"""
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.recommendation_db = {
|
| 9 |
+
'๊ธฐ์จ': {
|
| 10 |
+
'์์ฉ': ["์ํ: ์ํฐ์ ์์์ ํ์ค์ด ๋๋ค", "์์
: Pharrell Williams - Happy", "์ฑ
: ์ฐฝ๋ฌธ ๋์ด ๋๋ง์น 100์ธ ๋
ธ์ธ"],
|
| 11 |
+
'์ ํ': ["์ํ: ์ผ์ํฌ ํ์ถ", "์์
: ์ด๋ฃจ๋ง - River Flows In You"]
|
| 12 |
+
},
|
| 13 |
+
'ํ๋ณต': {
|
| 14 |
+
'์์ฉ': ["์ํ: ๋น๊ธด ์ด๊ฒ์ธ", "์์
: ์ฟจ - All for You", "์ฑ
: ๊พธ๋ปฌ์จ์ ํ๋ณต์ฌํ"],
|
| 15 |
+
'์ ํ': ["์ํ: ํฌ๋ ์คํธ ๊ฒํ", "์์
: ํ ์ด - ์ข์ ์ฌ๋"]
|
| 16 |
+
},
|
| 17 |
+
'๋ถ์': {
|
| 18 |
+
'์์ฉ': ["์ํ: ์ธ์ฌ์ด๋ ์์", "์์
: ์๋ก๊ฐ ๋๋ ์ฐ์ฃผ๊ณก ํ๋ ์ด๋ฆฌ์คํธ", "์ฑ
: ๋ฏธ์๋ฐ์ ์ฉ๊ธฐ"],
|
| 19 |
+
'์ ํ': ["์ํ: ๊ทนํ์ง์
", "์์
: Maroon 5 - Moves Like Jagger"]
|
| 20 |
+
},
|
| 21 |
+
'๋ถ๋
ธ': {
|
| 22 |
+
'์์ฉ': ["์ํ: ์กด ์
", "์์
: ๋์ํ์ธ - Du Hast"],
|
| 23 |
+
'์ ํ': ["์ํ: ๋ฆฌํ ํฌ๋ ์คํธ", "์์
: ๋
ธ๋ผ ์กด์ค - Don't Know Why"]
|
| 24 |
+
},
|
| 25 |
+
'์ฌํ': {
|
| 26 |
+
'์์ฉ': ["์ํ: ์ดํฐ๋ ์ ์ค์ธ", "์์
: ๋ฐํจ์ - ๋์ ๊ฝ", "์ฑ
: 1๋ฆฌํฐ์ ๋๋ฌผ"],
|
| 27 |
+
'์ ํ': ["์ํ: ์-E", "์์
: ๊ฑฐ๋ถ์ด - ๋นํ๊ธฐ"]
|
| 28 |
+
},
|
| 29 |
+
'์์ฒ': {
|
| 30 |
+
'์์ฉ': ["์ํ: ์บ์คํธ ์ด์จ์ด", "์์
: ๊น๊ด์ - ์๋ฅธ ์ฆ์์", "์ฑ
: ์ฃฝ๊ณ ์ถ์ง๋ง ๋ก๋ณถ์ด๋ ๋จน๊ณ ์ถ์ด"],
|
| 31 |
+
'์ ํ': ["์ํ: ๊ธ๋ฌ๋ธ (์นํจ๋ฅผ ๋ ๋ ์ผ๊ตฌ์ ์์ํ ์ด์ ๊ณผ ๊ฐ๋์ ๋๊ปด๋ณด์ธ์)", "์์
: ์ฅ์๋ฌ๋น - ์๊ณ ํ์ด, ์ค๋๋"]
|
| 32 |
+
},
|
| 33 |
+
'๋๋': {
|
| 34 |
+
'์์ฉ': ["์ํ: ์์ค ์ผ์ค", "์์
: ๋ฐ์ง์ - ์ด๋จธ๋์ด ๋๊ตฌ๋"],
|
| 35 |
+
'์ ํ': ["์์
: Bach - Air on G String", "์ฑ
: ๊ณ ์ํ ์๋ก ๋ฐ์์ง๋ ๊ฒ๋ค"]
|
| 36 |
+
},
|
| 37 |
+
'์ค๋ฆฝ': {
|
| 38 |
+
'์์ฉ': ["์ํ: ํจํฐ์จ", "์์
: ์์ํ Lo-fi ํ๋ ์ด๋ฆฌ์คํธ", "์ฑ
: ๋ณดํต์ ์กด์ฌ"],
|
| 39 |
+
'์ ํ': ["์ํ: ์คํ์ด๋๋งจ: ๋ด ์ ๋๋ฒ์ค", "์์
: Queen - Don't Stop Me Now"]
|
| 40 |
+
},
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
def recommend(self, emotion: str, choice: str) -> list:
|
| 44 |
+
return self.recommendation_db.get(emotion, {}).get(choice, ["๐ฅ ์์ฝ์ง๋ง, ์์ง ์ค๋น๋ ์ถ์ฒ์ด ์์ด์."])
|
templates/emotion_homepage.html
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- ํํ์ด์ง -->
|
| 2 |
+
|
| 3 |
+
<!DOCTYPE html>
|
| 4 |
+
<html lang="ko">
|
| 5 |
+
<head>
|
| 6 |
+
<meta charset="UTF-8">
|
| 7 |
+
<title> ์ผ๊ธฐ ๊ธฐ๋ฐ ์ถ์ฒ ์นํ์ด์ง</title>
|
| 8 |
+
<style>
|
| 9 |
+
body {
|
| 10 |
+
font-family: "Spoqa Han Sans Neo", Arial, sans-serif;
|
| 11 |
+
padding: 50px;
|
| 12 |
+
background: linear-gradient(135deg, #ece9f7, #cacae0); /* ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ */
|
| 13 |
+
transition: background 0.5s ease;
|
| 14 |
+
}
|
| 15 |
+
.container {
|
| 16 |
+
background: white;
|
| 17 |
+
border-radius: 20px;
|
| 18 |
+
padding: 30px;
|
| 19 |
+
box-shadow: 0 8px 32px #0001;
|
| 20 |
+
max-width: 600px;
|
| 21 |
+
margin: 40px auto;
|
| 22 |
+
}
|
| 23 |
+
h1 { color: #65e5c9; margin-bottom: 18px; }
|
| 24 |
+
textarea {
|
| 25 |
+
width: 100%;
|
| 26 |
+
border-radius: 8px;
|
| 27 |
+
border: 1px solid #d0d0e0;
|
| 28 |
+
padding: 16px;
|
| 29 |
+
font-size: 18px;
|
| 30 |
+
margin-bottom: 12px;
|
| 31 |
+
background: #f7f7fb;
|
| 32 |
+
resize: vertical;
|
| 33 |
+
}
|
| 34 |
+
button {
|
| 35 |
+
background: #6598e5;
|
| 36 |
+
color: white;
|
| 37 |
+
padding: 12px 24px;
|
| 38 |
+
border-radius: 8px;
|
| 39 |
+
border: none;
|
| 40 |
+
font-size: 18px;
|
| 41 |
+
cursor: pointer;
|
| 42 |
+
transition: 0.2s;
|
| 43 |
+
margin-right: 8px;
|
| 44 |
+
}
|
| 45 |
+
button:hover { background: #5540a3; }
|
| 46 |
+
#result {
|
| 47 |
+
margin-top: 24px;
|
| 48 |
+
font-size: 17px;
|
| 49 |
+
color: #444a70;
|
| 50 |
+
background: #f2f2f7;
|
| 51 |
+
border-radius: 8px;
|
| 52 |
+
padding: 18px;
|
| 53 |
+
box-shadow: 0 2px 8px #cfcfe0;
|
| 54 |
+
min-height: 40px;
|
| 55 |
+
}
|
| 56 |
+
.history-title {
|
| 57 |
+
margin: 32px 0 12px 0;
|
| 58 |
+
color: #8855b0;
|
| 59 |
+
font-size: 20px;
|
| 60 |
+
}
|
| 61 |
+
#history {
|
| 62 |
+
background: #fff9;
|
| 63 |
+
border-radius: 10px;
|
| 64 |
+
padding: 14px 12px;
|
| 65 |
+
font-size: 15px;
|
| 66 |
+
min-height: 40px;
|
| 67 |
+
box-shadow: 0 2px 8px #edebf5;
|
| 68 |
+
margin-bottom: 15px;
|
| 69 |
+
}
|
| 70 |
+
.history-item {
|
| 71 |
+
margin-bottom: 8px;
|
| 72 |
+
border-bottom: 1px solid #eee;
|
| 73 |
+
padding-bottom: 6px;
|
| 74 |
+
}
|
| 75 |
+
.history-date {
|
| 76 |
+
color: #b2a4d4;
|
| 77 |
+
font-size: 13px;
|
| 78 |
+
margin-bottom: 3px;
|
| 79 |
+
display: block;
|
| 80 |
+
}
|
| 81 |
+
.clear-btn {
|
| 82 |
+
background: #e25b66;
|
| 83 |
+
color: white;
|
| 84 |
+
font-size: 14px;
|
| 85 |
+
padding: 7px 13px;
|
| 86 |
+
border-radius: 6px;
|
| 87 |
+
border: none;
|
| 88 |
+
cursor: pointer;
|
| 89 |
+
float: right;
|
| 90 |
+
transition: 0.2s;
|
| 91 |
+
}
|
| 92 |
+
.clear-btn:hover { background: #c73442; }
|
| 93 |
+
/* ํ
๋ง ๋ณ๊ฒฝ ๋ฒํผ ์คํ์ผ */
|
| 94 |
+
.theme-buttons {
|
| 95 |
+
text-align: right;
|
| 96 |
+
margin-bottom: 15px;
|
| 97 |
+
}
|
| 98 |
+
.theme-buttons button {
|
| 99 |
+
font-size: 14px;
|
| 100 |
+
padding: 5px 10px;
|
| 101 |
+
margin-left: 5px;
|
| 102 |
+
}
|
| 103 |
+
</style>
|
| 104 |
+
</head>
|
| 105 |
+
<body>
|
| 106 |
+
<div class="container">
|
| 107 |
+
|
| 108 |
+
<!-- ํ
๋ง ๋ณ๊ฒฝ ๋ฒํผ -->
|
| 109 |
+
<div class="theme-buttons">
|
| 110 |
+
<button onclick="changeTheme('light')">๐ ๋ฐ์ ๋ชจ๋</button>
|
| 111 |
+
<button onclick="changeTheme('dark')">๐ ์ด๋์ด ๋ชจ๋</button>
|
| 112 |
+
<button onclick="changeTheme('pastel')">๐จ ํ์คํ
๋ชจ๋</button>
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
<h1>์ค๋์ ์ผ๊ธฐ ์
๋ ฅ</h1>
|
| 116 |
+
<textarea id="diary" placeholder="์ค๋ ์์๋ ์ผ์ ์์ ๋กญ๊ฒ ์
๋ ฅํด ๋ณด์ธ์."></textarea><br>
|
| 117 |
+
<button onclick="recommend()">์ถ์ฒ ๋ฐ๊ธฐ</button>
|
| 118 |
+
<div id="result"></div>
|
| 119 |
+
|
| 120 |
+
<div class="history-title">
|
| 121 |
+
๋์ ์ผ๊ธฐ ํ์คํ ๋ฆฌ
|
| 122 |
+
<button class="clear-btn" onclick="clearHistory()">๋ชจ๋ ์ง์ฐ๊ธฐ</button>
|
| 123 |
+
</div>
|
| 124 |
+
<div id="history"></div>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
<script>
|
| 128 |
+
// ํ
๋ง ๋ณ๊ฒฝ ํจ์ + ๋ก์ปฌ์คํ ๋ฆฌ์ง ์ ์ฅ
|
| 129 |
+
function changeTheme(mode) {
|
| 130 |
+
if (mode === 'light') {
|
| 131 |
+
document.body.style.background = 'linear-gradient(135deg, #ffffff, #f0f0f0)';
|
| 132 |
+
}
|
| 133 |
+
else if (mode === 'dark') {
|
| 134 |
+
document.body.style.background = 'linear-gradient(135deg, #2c2c3e, #1a1a28)';
|
| 135 |
+
}
|
| 136 |
+
else if (mode === 'pastel') {
|
| 137 |
+
document.body.style.background = 'linear-gradient(135deg, #ffefd5, #ffe4e1)';
|
| 138 |
+
}
|
| 139 |
+
localStorage.setItem('selectedTheme', mode); // ์ ํํ ํ
๋ง ์ ์ฅ
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// ํ์ด์ง ๋ก๋์ ์ ์ฅ๋ ํ
๋ง ๋ถ๋ฌ์ค๊ธฐ
|
| 143 |
+
window.onload = function() {
|
| 144 |
+
const savedTheme = localStorage.getItem('selectedTheme');
|
| 145 |
+
if (savedTheme) {
|
| 146 |
+
changeTheme(savedTheme);
|
| 147 |
+
}
|
| 148 |
+
renderHistory();
|
| 149 |
+
};
|
| 150 |
+
|
| 151 |
+
async function recommend() {
|
| 152 |
+
const diary = document.getElementById('diary').value.trim();
|
| 153 |
+
const resultDiv = document.getElementById('result');
|
| 154 |
+
|
| 155 |
+
if (!diary) {
|
| 156 |
+
resultDiv.innerHTML = "์ผ๊ธฐ ๋ด์ฉ๏ฟฝ๏ฟฝ ์
๋ ฅํด ์ฃผ์ธ์.";
|
| 157 |
+
return;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
resultDiv.innerHTML = "์ถ์ฒ ์ฝํ
์ธ ๋ฅผ ์์ฑ ์ค์
๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค์ฃผ์ธ์...";
|
| 161 |
+
|
| 162 |
+
try {
|
| 163 |
+
const response = await fetch('/api/recommend', {
|
| 164 |
+
method: 'POST',
|
| 165 |
+
headers: { 'Content-Type': 'application/json' },
|
| 166 |
+
body: JSON.stringify({ diary })
|
| 167 |
+
});
|
| 168 |
+
const data = await response.json();
|
| 169 |
+
|
| 170 |
+
if (data.error) {
|
| 171 |
+
resultDiv.innerHTML = `์ค๋ฅ: ${data.error}`;
|
| 172 |
+
} else {
|
| 173 |
+
resultDiv.innerHTML = `
|
| 174 |
+
<strong>๊ฐ์ ๋ถ์:</strong> ${data.emotion} ${data.emoji}<br>
|
| 175 |
+
<strong>์ถ์ฒ:</strong> ${data.recommendation}
|
| 176 |
+
`;
|
| 177 |
+
saveDiary({
|
| 178 |
+
text: diary,
|
| 179 |
+
emotion: data.emotion,
|
| 180 |
+
emoji: data.emoji,
|
| 181 |
+
date: new Date().toISOString()
|
| 182 |
+
});
|
| 183 |
+
renderHistory();
|
| 184 |
+
}
|
| 185 |
+
} catch (error) {
|
| 186 |
+
console.error('Fetch error:', error);
|
| 187 |
+
resultDiv.innerHTML = '์๋ฒ ํต์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.';
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
function saveDiary(entry) {
|
| 192 |
+
let diaryHistory = JSON.parse(localStorage.getItem('diaryHistory') || '[]');
|
| 193 |
+
diaryHistory.unshift(entry);
|
| 194 |
+
if (diaryHistory.length > 20) diaryHistory = diaryHistory.slice(0, 20);
|
| 195 |
+
localStorage.setItem('diaryHistory', JSON.stringify(diaryHistory));
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
function renderHistory() {
|
| 199 |
+
const historyDiv = document.getElementById('history');
|
| 200 |
+
const diaryHistory = JSON.parse(localStorage.getItem('diaryHistory') || '[]');
|
| 201 |
+
|
| 202 |
+
if (!diaryHistory.length) {
|
| 203 |
+
historyDiv.innerHTML = "์ ์ฅ๋ ์ผ๊ธฐ๊ฐ ์์ต๋๋ค.";
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
historyDiv.innerHTML = diaryHistory.map(item => `
|
| 208 |
+
<div class="history-item">
|
| 209 |
+
<span class="history-date">
|
| 210 |
+
${new Date(item.date).toLocaleString('ko-KR', {
|
| 211 |
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
| 212 |
+
hour: '2-digit', minute: '2-digit'
|
| 213 |
+
})}
|
| 214 |
+
</span>
|
| 215 |
+
<span>${item.emoji} <strong>[${item.emotion}]</strong> : ${item.text}</span>
|
| 216 |
+
</div>
|
| 217 |
+
`).join('');
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
function clearHistory() {
|
| 221 |
+
if (confirm('์ ๋ง ๋ชจ๋ ์ญ์ ํ์๊ฒ ์ด์?')) {
|
| 222 |
+
localStorage.removeItem('diaryHistory');
|
| 223 |
+
renderHistory();
|
| 224 |
+
document.getElementById('result').innerHTML = '';
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
</script>
|
| 228 |
+
</body>
|
| 229 |
+
</html>
|