台語朗讀自動評分系統 - 詳細計劃案


📋 專案目的

核心目標

本專案旨在開發一套智能化台語朗讀自動評分系統,透過結合先進的語音辨識技術與台語語言學專業知識,為台語學習者提供即時、客觀、精準的發音評估工具,降低台語學習門檻,促進本土語言傳承。

解決的問題

  1. 缺乏即時回饋:傳統台語學習依賴教師或家人指導,無法隨時練習
  2. 評分主觀性:人工評分容易受個人標準影響,缺乏一致性
  3. 學習資源不足:台語教學資源相對匱乏,尤其是互動式學習工具
  4. 發音標準化困難:台語方言眾多,需要統一的評量標準
  5. 學習動機維持:缺乏量化成效追蹤,難以持續學習

目標使用者


⭐ 特色與優點

技術特色

1. 專業台語語音模型

2. 台羅拼音標準化

3. 雙語對照系統

顯示層:台語漢字(使用者友善)
評分層:台羅拼音(精確比對)

4. 多維度評分機制

評分維度 權重 說明
整體準確度 70% 完整語句的相似度
字元準確度 20% 逐字元比對正確率
聲調準確度 10% 聲調符號匹配度

5. 智能錯誤分析

功能優點

即時評分回饋

客觀公正評量

低門檻使用

豐富學習資源

個人化學習

開源與可擴展

創新亮點

🔬 AI + 語言學結合

整合深度學習語音辨識技術與台語音韻學專業知識,確保評分的技術性與專業性。

🎯 教育科技應用

將前沿 AI 技術落地於本土語言教育,縮小城鄉教育資源差距。

🌐 數位保存文化

透過數位化方式記錄與推廣台語,為語言保存貢獻科技力量。

📊 大數據應用潛力

收集(匿名化)學習數據,分析台語學習痛點,優化教學策略。

社會價值

🏫 教育普及化

👨‍👩‍👧‍👦 世代傳承

🌍 語言保存


一、專案概述

1.1 專案背景

台語(閩南語)作為台灣重要的本土語言,亟需有效的學習與評量工具。隨著108課綱將本土語言列為必修,台語教學需求大增,但師資與教材仍顯不足。本專案結合 AI 語音辨識技術,開發自動化評分系統,解決台語學習的痛點。

1.2 專案目標

1.3 專案範圍

MVP 階段(核心功能)

✅ 文稿生成(台羅+漢字雙語顯示)
✅ 錄音功能(支援瀏覽器與桌面)
✅ 語音辨識(Whisper 台語模型)
✅ 基礎評分機制(編輯距離演算法)
✅ Web 介面(Gradio/Streamlit)

不包含(未來版本)

❌ 詳細發音口型指導
❌ 多人競賽/排行榜模式
❌ 原生行動裝置 APP
❌ 即時語音對話練習
❌ 商業化付費功能

1.4 成功標準

指標 目標值
語音辨識準確率 ≥ 85%
評分與專家相關係數 ≥ 0.80
系統回應時間 ≤ 5 秒
使用者滿意度 (SUS) ≥ 70 分
文稿庫規模 ≥ 100 篇

二、系統架構設計

2.1 整體架構圖

┌──────────────────────────────────────────────────────┐
│              前端介面層 (Presentation Layer)           │
├──────────────────────────────────────────────────────┤
│  文稿顯示區  │  錄音控制面板  │  評分結果展示         │
│  ┌────────┐  │  ┌──────────┐  │  ┌──────────────┐  │
│  │漢字文本│  │  │🎤 錄音鈕  │  │  │分數: 87 分   │  │
│  │台羅註解│  │  │⏸ 停止    │  │  │等級: 良好     │  │
│  └────────┘  │  │🔊 播放    │  │  │建議事項...   │  │
│              │  └──────────┘  │  └──────────────┘  │
└──────────────┬───────────────────────────────────────┘
               │ HTTP/WebSocket
┌──────────────▼───────────────────────────────────────┐
│              應用服務層 (Application Layer)            │
├──────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │文稿管理服務 │  │錄音處理服務 │  │評分計算服務 │ │
│  │- 文稿選取   │  │- 音訊驗證   │  │- 語音辨識   │ │
│  │- 難度篩選   │  │- 格式轉換   │  │- 文字比對   │ │
│  │- 隨機生成   │  │- 降噪處理   │  │- 分數計算   │ │
│  └─────────────┘  └─────────────┘  └─────────────┘ │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│              核心引擎層 (Core Engine Layer)            │
├──────────────────────────────────────────────────────┤
│  ┌──────────────────┐  ┌──────────────────────────┐ │
│  │ Whisper 台語模型  │  │   文字處理引擎            │ │
│  │ (NUTN-KWS v0.5)  │  │   - 正規化               │ │
│  │ - 語音轉文字      │  │   - Levenshtein 距離     │ │
│  │ - 音訊特徵提取    │  │   - 台羅/漢字轉換(備用)  │ │
│  └──────────────────┘  └──────────────────────────┘ │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│              資料持久層 (Data Persistence Layer)       │
├──────────────────────────────────────────────────────┤
│  ┌────────────┐  ┌────────────┐  ┌────────────────┐│
│  │文稿資料庫  │  │音檔儲存    │  │學習記錄 DB     ││
│  │(JSON/SQLite│  │(/tmp/*.wav)│  │(使用者歷程)    ││
│  └────────────┘  └────────────┘  └────────────────┘│
└──────────────────────────────────────────────────────┘

2.2 資料流程圖

[開始] 使用者進入系統
   ↓
(1) 選擇難度 (easy/medium/hard)
   ↓
(2) 系統隨機選取文稿
   ↓
(3) 顯示漢字文本給使用者
   │
   │ (內部保存台羅標準答案)
   ↓
(4) 使用者閱讀文本
   ↓
(5) 點擊「開始錄音」按鈕
   ↓
(6) 麥克風擷取音訊 (16kHz, Mono)
   ↓
(7) 使用者點擊「停止」或達到時間上限
   ↓
(8) 音訊儲存為 WAV 檔案
   ↓
(9) [驗證] 檢查音訊品質
   │   ├─ 音量太小 → 提示重錄
   │   └─ 品質良好 → 繼續
   ↓
(10) 送入 Whisper 台語模型
   ↓
(11) 模型輸出辨識文字
   │   ├─ 情境A: 台羅拼音 → 直接進入評分
   │   └─ 情境B: 漢字 → 轉換為台羅 → 進入評分
   ↓
(12) 文字正規化處理
    (移除多餘空白、統一大小寫)
   ↓
(13) 計算編輯距離 (Levenshtein Distance)
   ↓
(14) 計算各項評分指標
    ├─ 整體準確度 (70%)
    ├─ 字元準確度 (20%)
    └─ 聲調準確度 (10%)
   ↓
(15) 加權計算最終分數
   ↓
(16) 生成評分報告
    ├─ 分數與等級
    ├─ 標準答案 vs 辨識結果
    ├─ 錯誤位置標示
    └─ 改進建議
   ↓
(17) 顯示結果給使用者
   ↓
(18) [選項] 重新練習 / 換下一題 / 查看歷史
   ↓
[結束]

2.3 模組依賴關係

main.py
  ├─ imports ─→ text_manager.py
  │               └─ 依賴 ─→ data/texts.json
  │
  ├─ imports ─→ recorder.py
  │               ├─ 依賴 ─→ sounddevice
  │               └─ 依賴 ─→ scipy
  │
  ├─ imports ─→ whisper_model.py
  │               ├─ 依賴 ─→ transformers
  │               ├─ 依賴 ─→ torch
  │               └─ 載入 ─→ NUTN-KWS/Whisper-Taiwanese-model-v0.5
  │
  └─ imports ─→ scorer.py
                  ├─ 依賴 ─→ Levenshtein
                  └─ 呼叫 ─→ whisper_model.py

三、技術選型

3.1 核心技術棧

語言與框架

技術 版本 用途 選擇理由
Python 3.9+ 主要開發語言 ML/AI 生態系完整,函式庫豐富
Gradio 4.x Web 介面框架 快速建立 ML 應用介面,內建錄音元件
Streamlit 1.x 替代方案 更靈活的客製化,適合複雜介面

AI/ML 模型與工具

技術 版本 用途 選擇理由
Whisper (台語版) v0.5 語音辨識 NUTN-KWS 專為台語微調
Transformers 4.30+ 模型載入 Hugging Face 標準介面
PyTorch 2.0+ 深度學習框架 Whisper 依賴的後端

音訊處理

技術 版本 用途 選擇理由
sounddevice 0.4+ 錄音功能 跨平台音訊 I/O
scipy 1.10+ 音訊檔案處理 WAV 格式讀寫
librosa 0.10+ (可選) 音訊分析 特徵提取、降噪

文字處理

技術 版本 用途 選擇理由
python-Levenshtein 0.21+ 編輯距離計算 高效能 C 實作
台灣言語工具 latest (備用) 漢字轉台羅 本土開發的台語工具

資料儲存

技術 版本 用途 選擇理由
JSON - 文稿資料庫 輕量、易編輯、版本控制友善
SQLite 3.x 學習記錄 本地資料庫,無需額外服務

3.2 開發工具

# 套件管理
pip / conda

# 版本控制
Git + GitHub

# 開發環境
VS Code / PyCharm

# 虛擬環境
venv / conda env

# 程式碼品質
pylint / black / mypy

# 測試框架
pytest

# 文件生成
Sphinx / MkDocs

3.3 Whisper 台語模型詳細規格

模型名稱: NUTN-KWS/Whisper-Taiwanese-model-v0.5
基底架構: OpenAI Whisper (Transformer Encoder-Decoder)
微調來源: 台語語音資料集
參數量: ~244M (medium 版本)

輸入規格:
  - 格式: WAV, MP3, FLAC
  - 採樣率: 16000 Hz (建議)
  - 聲道: 單聲道 (Mono)
  - 長度: 最長 30 秒 / 段

輸出格式: 
  - 待確認: 台羅拼音 或 漢字
  - 編碼: UTF-8
  - 結構: 純文字字串

推理效能:
  - CPU: ~5-10 秒 / 30秒音檔
  - GPU (CUDA): ~2-3 秒 / 30秒音檔
  - 記憶體: ~2GB (模型載入)

準確率 (WER):
  - 乾淨語音: ~10-15%
  - 一般環境: ~20-30%
  - 嘈雜環境: ~40-50%

3.4 系統需求

開發環境

作業系統:
  - Windows 10/11
  - macOS 11+ (Big Sur)
  - Ubuntu 20.04+ / Debian 11+

硬體需求:
  - CPU: Intel i5 / AMD Ryzen 5 以上
  - RAM: 16GB 建議 (最低 8GB)
  - 硬碟: 20GB 可用空間
  - GPU: NVIDIA GTX 1060 以上 (可選,用於加速)
  - 麥克風: 內建或外接皆可

網路:
  - 初次下載模型需要網路 (~1GB)
  - 執行時可離線運作

部署環境

伺服器規格 (若部署至雲端):
  - vCPU: 4 核心
  - RAM: 16GB
  - 儲存: 50GB SSD
  - 頻寬: 10Mbps+

推薦平台:
  - Heroku
  - Google Cloud Run
  - AWS EC2
  - Render

四、功能模組設計

4.1 文稿管理模組 (Text Manager)

4.1.1 資料結構定義

from dataclasses import dataclass
from datetime import datetime
from typing import Optional

@dataclass
class TaiwaneseText:
    """台語文稿資料結構"""
    id: str                      # UUID 唯一識別碼
    hanzi: str                   # 漢字文本(顯示用)
    tailo: str                   # 台羅拼音(標準答案)
    difficulty: str              # 難度: "easy" / "medium" / "hard"
    category: str                # 分類: "daily" / "greeting" / "literature" 等
    word_count: int              # 字數統計
    audio_reference: Optional[str] = None  # 參考音檔路徑(可選)
    tags: list[str] = None       # 標籤 ["購物", "日常"]
    created_at: datetime = None  # 建立時間
    author: str = "system"       # 來源作者

    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.now()
        if self.tags is None:
            self.tags = []

# 範例資料
example_text = TaiwaneseText(
    id="550e8400-e29b-41d4-a716-446655440000",
    hanzi="你好,食飽未?",
    tailo="Lí hó, tsia̍h-pá buē?",
    difficulty="easy",
    category="greeting",
    word_count=7,
    tags=["問候", "日常"]
)

4.1.2 文稿庫資料範例 (texts.json)

{
  "texts": [
    {
      "id": "text_001",
      "hanzi": "你好,食飽未?",
      "tailo": "Lí hó, tsia̍h-pá buē?",
      "difficulty": "easy",
      "category": "greeting",
      "word_count": 7,
      "tags": ["問候", "日常"],
      "created_at": "2025-01-01T00:00:00"
    },
    {
      "id": "text_002",
      "hanzi": "我的名叫阿明,今年二十歲,佇台北讀冊。",
      "tailo": "Guá ê miâ kiò A-bîng, kin-nî jī-tsa̍p huè, tī Tâi-pak tha̍k-chheh.",
      "difficulty": "medium",
      "category": "self_introduction",
      "word_count": 18,
      "tags": ["自我介紹", "學習"],
      "created_at": "2025-01-01T00:00:00"
    },
    {
      "id": "text_003",
      "hanzi": "一枝草,一點露,天無絕人之路。",
      "tailo": "Tsi̍t ki tsháu, tsi̍t tiám lōo, thinn bô tsa̍t jîn tsi lo̍o.",
      "difficulty": "hard",
      "category": "proverb",
      "word_count": 14,
      "tags": ["俗諺", "哲理"],
      "created_at": "2025-01-01T00:00:00"
    }
  ],
  "metadata": {
    "total_count": 100,
    "last_updated": "2025-01-15T12:00:00",
    "version": "1.0"
  }
}

4.1.3 文稿管理程式實作

# text_manager.py
import json
import random
from pathlib import Path
from typing import List, Optional
import logging

class TextManager:
    """台語文稿管理系統"""

    def __init__(self, data_dir: str = "data/texts"):
        self.data_dir = Path(data_dir)
        self.data_dir.mkdir(parents=True, exist_ok=True)
        self.texts_file = self.data_dir / "texts.json"
        self.texts = self._load_texts()

        logging.info(f"✅ 載入 {len(self.texts)} 篇文稿")

    def _load_texts(self) -> List[dict]:
        """載入文稿資料"""
        if not self.texts_file.exists():
            logging.warning("⚠️ 文稿檔案不存在,建立空資料庫")
            self._create_default_texts()

        try:
            with open(self.texts_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
                return data.get('texts', [])
        except Exception as e:
            logging.error(f"❌ 載入失敗: {e}")
            return []

    def _create_default_texts(self):
        """建立預設文稿"""
        default_data = {
            "texts": [
                {
                    "id": "text_001",
                    "hanzi": "你好,食飽未?",
                    "tailo": "Lí hó, tsia̍h-pá buē?",
                    "difficulty": "easy",
                    "category": "greeting",
                    "word_count": 7
                },
                {
                    "id": "text_002",
                    "hanzi": "我欲去學校讀冊。",
                    "tailo": "Guá beh khì ha̍k-hāu tha̍k-chheh.",
                    "difficulty": "easy",
                    "category": "daily",
                    "word_count": 9
                },
                {
                    "id": "text_003",
                    "hanzi": "今仔日天氣真好。",
                    "tailo": "Kin-á-ji̍t thinn-khì tsin hó.",
                    "difficulty": "easy",
                    "category": "daily",
                    "word_count": 8
                }
            ],
            "metadata": {
                "total_count": 3,
                "last_updated": "2025-01-01T00:00:00",
                "version": "1.0"
            }
        }

        with open(self.texts_file, 'w', encoding='utf-8') as f:
            json.dump(default_data, f, ensure_ascii=False, indent=2)

    def get_random_text(self, 
                       difficulty: Optional[str] = None,
                       category: Optional[str] = None) -> Optional[dict]:
        """隨機取得文稿"""
        filtered = self.texts

        # 依難度篩選
        if difficulty:
            filtered = [t for t in filtered if t['difficulty'] == difficulty]

        # 依分類篩選
        if category:
            filtered = [t for t in filtered if t['category'] == category]

        if not filtered:
            logging.warning("⚠️ 沒有符合條件的文稿")
            return None

        return random.choice(filtered)

    def get_text_by_id(self, text_id: str) -> Optional[dict]:
        """依 ID 取得文稿"""
        for text in self.texts:
            if text['id'] == text_id:
                return text
        return None

    def add_text(self, 
                hanzi: str, 
                tailo: str,
                difficulty: str,
                category: str,
                tags: List[str] = None) -> str:
        """新增文稿"""
        import uuid

        new_text = {
            "id": f"text_{str(uuid.uuid4())[:8]}",
            "hanzi": hanzi,
            "tailo": tailo,
            "difficulty": difficulty,
            "category": category,
            "word_count": len(hanzi),
            "tags": tags or [],
            "created_at": datetime.now().isoformat()
        }

        self.texts.append(new_text)
        self._save_texts()

        logging.info(f"✅ 新增文稿: {new_text['id']}")
        return new_text['id']

    def _save_texts(self):
        """儲存文稿資料"""
        data = {
            "texts": self.texts,
            "metadata": {
                "total_count": len(self.texts),
                "last_updated": datetime.now().isoformat(),
                "version": "1.0"
            }
        }

        with open(self.texts_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

    def get_statistics(self) -> dict:
        """取得統計資訊"""
        stats = {
            "total": len(self.texts),
            "by_difficulty": {},
            "by_category": {}
        }

        for text in self.texts:
            # 難度統計
            diff = text['difficulty']
            stats['by_difficulty'][diff] = stats['by_difficulty'].get(diff, 0) + 1

            # 分類統計
            cat = text['category']
            stats['by_category'][cat] = stats['by_category'].get(cat, 0) + 1

        return stats

4.1.4 文稿分類與難度規劃

難度 字數範圍 特徵 目標數量
Easy 5-15 字 日常詞彙、簡單句型、無罕見音 40 篇
Medium 15-30 字 含變調、複合句、常用成語 40 篇
Hard 30+ 字 文學用語、複雜句型、方言詞 20 篇

分類架構:

├── greeting (問候語) - 15 篇
├── self_introduction (自我介紹) - 15 篇
├── daily (日常對話) - 30 篇
├── shopping (購物) - 10 篇
├── family (家庭) - 10 篇
├── proverb (俗諺語) - 15 篇
└── literature (文學) - 5 篇

4.2 錄音處理模組 (Audio Recorder)

4.2.1 技術規格

AUDIO_SETTINGS = {
    'sample_rate': 16000,      # Hz (Whisper 建議值)
    'channels': 1,              # 單聲道
    'dtype': 'int16',           # 16-bit PCM
    'format': 'wav',            # 檔案格式
    'max_duration': 60,         # 秒
    'min_duration': 1,          # 秒
    'buffer_size': 1024         # 樣本
}

# 音訊品質檢查閾值
QUALITY_THRESHOLDS = {
    'min_rms': 100,            # 最小 RMS 音量
    'max_rms': 30000,          # 最大 RMS 音量
    'min_snr_db': 10,          # 最小信噪比 (dB)
    'max_clipping_ratio': 0.01 # 最大削波比例
}

4.2.2 錄音模組實作

# recorder.py
import sounddevice as sd
import scipy.io.wavfile as wav
import numpy as np
from pathlib import Path
import logging
from typing import Tuple, Optional

class AudioRecorder:
    """音訊錄製與處理"""

    def __init__(self, 
                 sample_rate: int = 16000,
                 channels: int = 1,
                 output_dir: str = "recordings"):
        self.sample_rate = sample_rate
        self.channels = channels
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)

        self.recording = None
        self.is_recording = False

        # 檢查音訊裝置
        self._check_audio_device()

    def _check_audio_device(self):
        """檢查可用的音訊裝置"""
        try:
            devices = sd.query_devices()
            logging.info(f"✅ 偵測到 {len(devices)} 個音訊裝置")

            # 找到預設輸入裝置
            default_input = sd.query_devices(kind='input')
            logging.info(f"📍 預設麥克風: {default_input['name']}")

        except Exception as e:
            logging.error(f"❌ 音訊裝置檢查失敗: {e}")

    def record(self, duration: int = 30) -> np.ndarray:
        """
        開始錄音

        Args:
            duration: 錄音時長(秒)

        Returns:
            音訊資料 (numpy array)
        """
        try:
            logging.info(f"🎙️ 開始錄音 ({duration} 秒)...")

            # 錄製音訊
            self.recording = sd.rec(
                int(duration * self.sample_rate),
                samplerate=self.sample_rate,
                channels=self.channels,
                dtype='int16'
            )

            # 等待錄音完成
            sd.wait()

            logging.info("✅ 錄音完成")
            return self.recording

        except Exception as e:
            logging.error(f"❌ 錄音失敗: {e}")
            return None

    def record_realtime(self, callback=None):
        """
        即時錄音(可中斷)

        Args:
            callback: 回調函數,接收音訊片段
        """
        # 進階功能:實作可中斷的即時錄音
        # 用於未來版本的即時回饋功能
        pass

    def save(self, 
             audio_data: np.ndarray,
             filename: str = "recording.wav") -> str:
        """
        儲存音檔

        Args:
            audio_data: 音訊資料
            filename: 檔案名稱

        Returns:
            完整檔案路徑
        """
        if audio_data is None:
            logging.error("❌ 無音訊資料可儲存")
            return None

        filepath = self.output_dir / filename

        try:
            wav.write(str(filepath), self.sample_rate, audio_data)
            logging.info(f"💾 音檔已儲存: {filepath}")
            return str(filepath)
        except Exception as e:
            logging.error(f"❌ 儲存失敗: {e}")
            return None

    def validate_quality(self, audio_data: np.ndarray) -> Tuple[bool, str]:
        """
        驗證音訊品質

        Returns:
            (是否通過, 訊息)
        """
        if audio_data is None or len(audio_data) == 0:
            return False, "音訊資料為空"

        # 1. 檢查音量
        rms = np.sqrt(np.mean(audio_data.astype(float)**2))

        if rms < 100:
            return False, "音量太小,請靠近麥克風"

        if rms > 30000:
            return False, "音量過大,請降低音量或遠離麥克風"

        # 2. 檢查削波(Clipping)
        max_amplitude = np.max(np.abs(audio_data))
        if max_amplitude >= 32767:  # int16 最大值
            clipping_ratio = np.sum(np.abs(audio_data) >= 32760) / len(audio_data)
            if clipping_ratio > 0.01:  # 超過 1% 削波
                return False, "訊號削波嚴重,請降低音量"

        # 3. 檢查靜音
        silent_ratio = np.sum(np.abs(audio_data) < 50) / len(audio_data)
        if silent_ratio > 0.9:  # 超過 90% 靜音
            return False, "未偵測到語音,請確認麥克風"

        # 4. 檢查時長
        duration = len(audio_data) / self.sample_rate
        if duration < 1:
            return False, "錄音時間太短"

        return True, f"音訊品質良好 (RMS: {rms:.0f}, 時長: {duration:.1f}秒)"

    def analyze_audio(self, audio_data: np.ndarray) -> dict:
        """
        分析音訊特徵(用於除錯)

        Returns:
            音訊特徵字典
        """
        if audio_data is None:
            return {}

        duration = len(audio_data) / self.sample_rate
        rms = np.sqrt(np.mean(audio_data.astype(float)**2))
        peak = np.max(np.abs(audio_data))

        return {
            'duration': duration,
            'sample_rate': self.sample_rate,
            'rms': float(rms),
            'peak': int(peak),
            'samples': len(audio_data),
            'dtype': str(audio_data.dtype)
        }

    def apply_noise_reduction(self, audio_data: np.ndarray) -> np.ndarray:
        """
        簡易降噪處理(可選)

        Note: 基礎版本,進階可使用 noisereduce 套件
        """
        # 簡單的高通濾波器,移除低頻噪音
        # 進階功能待實作
        return audio_data

# 使用範例
if __name__ == "__main__":
    recorder = AudioRecorder()

    # 錄音 10 秒
    audio = recorder.record(duration=10)

    # 驗證品質
    is_valid, message = recorder.validate_quality(audio)
    print(f"品質驗證: {message}")

    if is_valid:
        # 儲存檔案
        filepath = recorder.save(audio, "test_recording.wav")

        # 分析資訊
        info = recorder.analyze_audio(audio)
        print(f"音訊資訊: {info}")

4.2.3 Gradio 錄音整合

# Gradio 提供內建的 gr.Audio 元件,簡化錄音實作
import gradio as gr

def create_audio_interface():
    """建立錄音介面"""

    with gr.Blocks() as demo:
        gr.Markdown("## 🎤 台語錄音")

        # Gradio 會自動處理錄音
        audio_input = gr.Audio(
            source="microphone",      # 使用麥克風
            type="filepath",           # 回傳檔案路徑
            label="開始錄音",
            format="wav"               # WAV 格式
        )

        status = gr.Textbox(label="狀態")

        def process_audio(audio_path):
            if audio_path is None:
                return "請先錄音"

            # 驗證音訊品質
            recorder = AudioRecorder()
            rate, data = wav.read(audio_path)
            is_valid, msg = recorder.validate_quality(data)

            return msg

        audio_input.change(
            fn=process_audio,
            inputs=[audio_input],
            outputs=[status]
        )

    return demo

4.3 語音辨識模組 (Whisper Integration)

4.3.1 模型載入器

# whisper_model.py
from transformers import pipeline
import torch
import logging
from functools import lru_cache
from typing import Optional

class WhisperTaiwanese:
    """Whisper 台語模型封裝"""

    MODEL_NAME = "NUTN-KWS/Whisper-Taiwanese-model-v0.5"

    def __init__(self, use_gpu: bool = True):
        self.device = self._get_device(use_gpu)
        self.pipe = None
        self._is_loaded = False

        logging.info(f"🖥️ 使用裝置: {self.device}")

    def _get_device(self, use_gpu: bool) -> int:
        """偵測可用裝置"""
        if use_gpu and torch.cuda.is_available():
            logging.info("✅ 偵測到 GPU (CUDA)")
            return 0
        else:
            logging.info("📌 使用 CPU")
            return -1

    @lru_cache(maxsize=1)
    def load_model(self):
        """
        載入模型(使用快取避免重複載入)
        """
        if self._is_loaded:
            return self.pipe

        try:
            logging.info(f"⏳ 載入模型: {self.MODEL_NAME}")

            self.pipe = pipeline(
                "automatic-speech-recognition",
                model=self.MODEL_NAME,
                device=self.device
            )

            self._is_loaded = True
            logging.info("✅ 模型載入完成")

            return self.pipe

        except Exception as e:
            logging.error(f"❌ 模型載入失敗: {e}")
            raise

    def transcribe(self, 
                  audio_path: str,
                  return_timestamps: bool = False) -> dict:
        """
        語音轉文字

        Args:
            audio_path: 音檔路徑
            return_timestamps: 是否回傳時間戳

        Returns:
            辨識結果字典
        """
        if not self._is_loaded:
            self.load_model()

        try:
            logging.info(f"🎯 開始辨識: {audio_path}")

            result = self.pipe(
                audio_path,
                return_timestamps=return_timestamps
            )

            logging.info(f"✅ 辨識完成: {result['text'][:50]}...")

            return result

        except Exception as e:
            logging.error(f"❌ 辨識失敗: {e}")
            return {"text": "", "error": str(e)}

    def batch_transcribe(self, audio_paths: list) -> list:
        """批次辨識(用於測試)"""
        results = []
        for path in audio_paths:
            result = self.transcribe(path)
            results.append(result)
        return results

    def get_model_info(self) -> dict:
        """取得模型資訊"""
        return {
            "model_name": self.MODEL_NAME,
            "device": "GPU" if self.device == 0 else "CPU",
            "is_loaded": self._is_loaded,
            "cuda_available": torch.cuda.is_available()
        }

# 全域單例模式
_whisper_instance = None

def get_whisper_model(use_gpu: bool = True) -> WhisperTaiwanese:
    """取得 Whisper 模型實例(單例)"""
    global _whisper_instance

    if _whisper_instance is None:
        _whisper_instance = WhisperTaiwanese(use_gpu=use_gpu)
        _whisper_instance.load_model()

    return _whisper_instance

4.3.2 輸出格式處理策略

# text_processor.py
import re
import logging

class TextProcessor:
    """文字處理工具"""

    @staticmethod
    def is_tailo(text: str) -> bool:
        """
        判斷文字是否為台羅拼音

        檢查項目:
        - 包含台羅特殊符號 (ⁿ, -, ́, ̀ 等)
        - 拉丁字母為主
        - 符合台羅音節結構
        """
        # 台羅特徵
        tailo_markers = [
            'ⁿ',           # 鼻音符號
            '\u0301',      # 聲調符號 ́
            '\u0300',      # 聲調符號 ̀
            '\u0302',      # 聲調符號 ̂
            '\u030d',      # 聲調符號 ̍
            'ō', 'ū', 'ī', 'ā', 'ē',  # 長音
        ]

        # 檢查是否包含台羅符號
        has_tailo_marks = any(marker in text for marker in tailo_markers)

        # 檢查拉丁字母比例
        latin_ratio = sum(c.isalpha() for c in text) / len(text) if text else 0

        is_likely_tailo = has_tailo_marks or latin_ratio > 0.5

        logging.debug(f"判斷文字格式: '{text[:20]}...' → {'台羅' if is_likely_tailo else '漢字'}")

        return is_likely_tailo

    @staticmethod
    def normalize_tailo(text: str) -> str:
        """
        正規化台羅文字

        - 統一小寫
        - 移除多餘空白
        - 保留聲調符號
        """
        # 轉小寫(但保留聲調符號)
        text = text.lower()

        # 移除多餘空白
        text = re.sub(r'\s+', ' ', text).strip()

        # 移除標點符號(可選)
        # text = re.sub(r'[,!?.;:,!?。;:]', '', text)

        return text

    @staticmethod
    def normalize_hanzi(text: str) -> str:
        """正規化漢字文字"""
        # 移除空白
        text = re.sub(r'\s+', '', text)

        # 移除標點
        text = re.sub(r'[,!?.;:,!?。;:]', '', text)

        return text

    @staticmethod
    def hanzi_to_tailo(hanzi_text: str) -> str:
        """
        漢字轉台羅(備用方案)

        Note: 需要整合外部工具
        - 台灣言語工具
        - 自建字典
        """
        try:
            # TODO: 整合轉換工具
            # from tai5_uan5_gian5_gi2_kang1_ku7 import ...

            logging.warning("⚠️ 漢字轉台羅功能尚未實作")
            return hanzi_text

        except Exception as e:
            logging.error(f"❌ 轉換失敗: {e}")
            return hanzi_text

# 使用範例
processor = TextProcessor()

# 測試格式判斷
text1 = "Lí hó, tsia̍h-pá buē?"
print(processor.is_tailo(text1))  # True

text2 = "你好,食飽未?"
print(processor.is_tailo(text2))  # False

# 正規化
normalized = processor.normalize_tailo("  Lí  Hó  ")
print(normalized)  # "lí hó"

4.3.3 辨識流程整合

# recognition.py
from whisper_model import get_whisper_model
from text_processor import TextProcessor

class RecognitionPipeline:
    """完整辨識流程"""

    def __init__(self):
        self.whisper = get_whisper_model()
        self.processor = TextProcessor()

    def recognize(self, audio_path: str) -> dict:
        """
        執行語音辨識並處理結果

        Returns:
            {
                'text': 辨識文字,
                'format': 'tailo' or 'hanzi',
                'normalized': 正規化後的文字,
                'raw': 原始辨識結果
            }
        """
        # Step 1: 語音辨識
        result = self.whisper.transcribe(audio_path)
        raw_text = result.get('text', '')

        if not raw_text:
            return {
                'text': '',
                'format': 'unknown',
                'normalized': '',
                'raw': result
            }

        # Step 2: 判斷格式
        is_tailo = self.processor.is_tailo(raw_text)
        text_format = 'tailo' if is_tailo else 'hanzi'

        # Step 3: 正規化
        if is_tailo:
            normalized = self.processor.normalize_tailo(raw_text)
        else:
            # 如果是漢字,嘗試轉換為台羅
            logging.info("⚠️ 偵測到漢字輸出,需要轉換...")
            normalized = self.processor.hanzi_to_tailo(raw_text)

        return {
            'text': raw_text,
            'format': text_format,
            'normalized': normalized,
            'raw': result
        }

# 使用範例
pipeline = RecognitionPipeline()
result = pipeline.recognize("recording.wav")

print(f"原始: {result['text']}")
print(f"格式: {result['format']}")
print(f"正規化: {result['normalized']}")

4.4 評分引擎模組 (Scoring Engine)

4.4.1 核心評分演算法

# scorer.py
from Levenshtein import distance as levenshtein_distance
from typing import Dict, List, Tuple
import re
import logging

class TaiwaneseScorer:
    """台語發音評分引擎"""

    # 評分權重
    WEIGHTS = {
        'overall_accuracy': 0.70,     # 整體準確度
        'character_accuracy': 0.20,   # 字元準確度
        'tone_accuracy': 0.10         # 聲調準確度
    }

    def __init__(self):
        self.processor = TextProcessor()

    def score(self, 
             reference: str,
             recognized: str) -> Dict:
        """
        計算評分

        Args:
            reference: 標準答案(台羅)
            recognized: 辨識結果(台羅)

        Returns:
            評分結果字典
        """
        # 正規化
        ref_normalized = self.processor.normalize_tailo(reference)
        rec_normalized = self.processor.normalize_tailo(recognized)

        # 計算各項指標
        overall = self._overall_accuracy(ref_normalized, rec_normalized)
        character = self._character_accuracy(ref_normalized, rec_normalized)
        tone = self._tone_accuracy(ref_normalized, rec_normalized)

        # 加權計算最終分數
        final_score = (
            overall * self.WEIGHTS['overall_accuracy'] +
            character * self.WEIGHTS['character_accuracy'] +
            tone * self.WEIGHTS['tone_accuracy']
        )

        # 生成評分報告
        return {
            'final_score': round(final_score, 2),
            'level': self._get_level(final_score),
            'metrics': {
                'overall_accuracy': round(overall, 2),
                'character_accuracy': round(character, 2),
                'tone_accuracy': round(tone, 2)
            },
            'reference': ref_normalized,
            'recognized': rec_normalized,
            'edit_distance': levenshtein_distance(ref_normalized, rec_normalized),
            'errors': self._find_errors(ref_normalized, rec_normalized)
        }

    def _overall_accuracy(self, reference: str, recognized: str) -> float:
        """
        整體準確度(基於編輯距離)

        公式: (1 - 編輯距離 / 最大長度) × 100
        """
        if not reference or not recognized:
            return 0.0

        edit_dist = levenshtein_distance(reference, recognized)
        max_len = max(len(reference), len(recognized))

        accuracy = 100 * (1 - edit_dist / max_len)

        return max(0, accuracy)

    def _character_accuracy(self, reference: str, recognized: str) -> float:
        """
        字元準確度(逐字元比對)
        """
        ref_chars = reference.split()
        rec_chars = recognized.split()

        # 計算正確字元數
        correct = 0
        for i in range(min(len(ref_chars), len(rec_chars))):
            if ref_chars[i] == rec_chars[i]:
                correct += 1

        total = len(ref_chars)

        if total == 0:
            return 0.0

        return 100 * correct / total

    def _tone_accuracy(self, reference: str, recognized: str) -> float:
        """
        聲調準確度(比對聲調符號)
        """
        # 提取聲調符號
        ref_tones = self._extract_tones(reference)
        rec_tones = self._extract_tones(recognized)

        if not ref_tones:
            return 100.0  # 無聲調標記,給予滿分

        # 計算正確聲調數
        correct_tones = sum(
            1 for r, p in zip(ref_tones, rec_tones) if r == p
        )

        return 100 * correct_tones / len(ref_tones)

    def _extract_tones(self, text: str) -> List[str]:
        """提取聲調符號"""
        tone_marks = []

        # Unicode 組合字元的聲調符號
        tone_diacritics = [
            '\u0301',  # ́  第2調
            '\u0300',  # ̀  第3調
            '\u0302',  # ̂  第5調
            '\u0304',  # ̄  第7調
            '\u030d',  # ̍  第8調
        ]

        for char in text:
            for tone in tone_diacritics:
                if tone in char:
                    tone_marks.append(tone)
                    break

        return tone_marks

    def _find_errors(self, reference: str, recognized: str) -> List[Dict]:
        """
        找出錯誤位置與類型

        Returns:
            錯誤列表
        """
        errors = []

        ref_tokens = reference.split()
        rec_tokens = recognized.split()

        # 逐詞比對
        max_len = max(len(ref_tokens), len(rec_tokens))

        for i in range(max_len):
            ref = ref_tokens[i] if i < len(ref_tokens) else ""
            rec = rec_tokens[i] if i < len(rec_tokens) else ""

            if ref != rec:
                error_type = self._classify_error(ref, rec)

                errors.append({
                    'position': i,
                    'expected': ref,
                    'actual': rec,
                    'type': error_type
                })

        return errors

    def _classify_error(self, expected: str, actual: str) -> str:
        """
        分類錯誤類型

        - substitution: 替換錯誤
        - insertion: 插入錯誤
        - deletion: 刪除錯誤
        - tone: 聲調錯誤
        """
        if not expected:
            return "insertion"
        if not actual:
            return "deletion"

        # 移除聲調比較基本音節
        expected_base = re.sub(r'[\u0300-\u036f]', '', expected)
        actual_base = re.sub(r'[\u0300-\u036f]', '', actual)

        if expected_base == actual_base:
            return "tone"  # 僅聲調不同
        else:
            return "substitution"  # 音節錯誤

    def _get_level(self, score: float) -> str:
        """評分等級"""
        if score >= 90:
            return "優秀"
        elif score >= 80:
            return "良好"
        elif score >= 70:
            return "尚可"
        elif score >= 60:
            return "待改進"
        else:
            return "需重學"

    def generate_feedback(self, result: Dict) -> str:
        """
        生成個人化回饋建議
        """
        score = result['final_score']
        errors = result['errors']

        feedback = []

        # 整體評價
        if score >= 90:
            feedback.append("👏 發音非常標準,繼續保持!")
        elif score >= 80:
            feedback.append("😊 發音清晰,略有小瑕疵。")
        elif score >= 70:
            feedback.append("🤔 基本正確,但需要多加練習。")
        else:
            feedback.append("💪 還有進步空間,建議重複練習。")

        # 錯誤分析
        if errors:
            tone_errors = [e for e in errors if e['type'] == 'tone']
            other_errors = [e for e in errors if e['type'] != 'tone']

            if tone_errors:
                feedback.append(f"\n⚠️ 聲調錯誤 {len(tone_errors)} 處,請注意聲調變化。")

            if other_errors:
                feedback.append(f"\n⚠️ 發音錯誤 {len(other_errors)} 處,建議逐字練習。")

            # 列出前 3 個錯誤
            feedback.append("\n**錯誤詳情:**")
            for i, error in enumerate(errors[:3], 1):
                feedback.append(
                    f"{i}. 位置 {error['position']}: "
                    f"應為「{error['expected']}」,實為「{error['actual']}」"
                )

        return "\n".join(feedback)

# 使用範例
scorer = TaiwaneseScorer()

reference = "Lí hó, tsia̍h-pá buē?"
recognized = "Lí hó, tsiah-pa bue?"

result = scorer.score(reference, recognized)

print(f"分數: {result['final_score']}")
print(f"等級: {result['level']}")
print(f"\n回饋:")
print(scorer.generate_feedback(result))

4.4.2 評分視覺化

# visualization.py
def create_score_chart(metrics: dict) -> str:
    """生成評分圖表(使用 ASCII 藝術或 Plotly)"""

    overall = metrics['overall_accuracy']
    character = metrics['character_accuracy']
    tone = metrics['tone_accuracy']

    # 簡易長條圖
    chart = f"""
    整體準確度: {'█' * int(overall/5):<20} {overall:.1f}%
    字元準確度: {'█' * int(character/5):<20} {character:.1f}%
    聲調準確度: {'█' * int(tone/5):<20} {tone:.1f}%
    """

    return chart

五、實作階段規劃

階段一:環境建置與模型測試(Week 1-2)

📋 任務清單

Week 1
- [ ] Day 1-2: 環境設定
```bash
# 建立專案目錄
mkdir taiwanese-pronunciation-scorer
cd taiwanese-pronunciation-scorer

# 建立虛擬環境
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows

# 安裝核心套件
pip install torch transformers
pip install sounddevice scipy
pip install python-Levenshtein
pip install gradio

# 建立專案結構
mkdir -p {data/texts,recordings,logs,tests}
touch {main.py,text_manager.py,recorder.py,whisper_model.py,scorer.py}
```

print("⏳ 開始下載模型...")

pipe = pipeline(
"automatic-speech-recognition",
model="NUTN-KWS/Whisper-Taiwanese-model-v0.5"
)

print("✅ 模型下載完成")

# 測試辨識(需準備測試音檔)
# result = pipe("test_audio.wav")
# print(f"辨識結果: {result['text']}")
```

# 準備 3 種測試音檔:
# 1. 清晰台語發音
# 2. 含噪音的音檔
# 3. 不同說話速度

test_files = [
"test_clear.wav",
"test_noisy.wav",
"test_fast.wav"
]

for audio_file in test_files:
result = pipe(audio_file)
text = result['text']

  # 判斷輸出格式
  is_tailo = check_if_tailo(text)

  print(f"\n檔案: {audio_file}")
  print(f"辨識: {text}")
  print(f"格式: {'台羅' if is_tailo else '漢字'}")

# ⚠️ 關鍵決策點: 根據結果決定後續開發策略
```

Week 2
- [ ] Day 8-10: 準備測試資料
- 建立 30 筆文稿(easy: 20, medium: 8, hard: 2)
- 錄製 10 個測試音檔(不同性別/年齡/口音)
- 建立評分基準(邀請台語專家)

🎯 階段一成功標準


階段二:核心功能開發(Week 3-4)

Week 3: 基礎模組實作

Day 15-16: 文稿管理系統

# 實作內容:
# 1. TextManager 類別
# 2. texts.json 資料結構
# 3. CRUD 操作
# 4. 統計功能

# 驗收標準:
assert len(text_manager.texts) >= 30
assert text_manager.get_random_text('easy') is not None

Day 17-18: 錄音模組

# 實作內容:
# 1. AudioRecorder 類別
# 2. 音訊品質驗證
# 3. WAV 檔案儲存
# 4. 音訊分析工具

# 測試項目:
# - 錄音 10 次,成功率 100%
# - 品質驗證能正確識別低音量/削波

Day 19-20: 文字處理工具

# 實作內容:
# 1. TextProcessor 類別
# 2. 格式判斷 (is_tailo)
# 3. 文字正規化
# 4. (可選) 漢字轉台羅

# 測試案例:
test_cases = [
    ("Lí hó", True),   # 應判斷為台羅
    ("你好", False),   # 應判斷為漢字
    ("  Lí  ", "lí")   # 正規化測試
]

Day 21: 整合測試與除錯

Week 4: 評分引擎與辨識整合

Day 22-24: 評分引擎

# 實作內容:
# 1. TaiwaneseScorer 類別
# 2. 多維度評分計算
# 3. 錯誤分析
# 4. 回饋生成

# 驗收標準:
# - 與專家評分相關係數 > 0.75
# - 能正確識別聲調/音節錯誤

Day 25-27: Whisper 整合

# 實作內容:
# 1. WhisperTaiwanese 類別
# 2. RecognitionPipeline
# 3. 錯誤處理
# 4. 效能優化(模型快取)

# 測試項目:
# - 連續辨識 10 次,無記憶體洩漏
# - 平均推理時間 < 5 秒

Day 28: 端對端測試

# 完整流程測試
def test_e2e():
    # 1. 選擇文稿
    text = text_manager.get_random_text()

    # 2. (人工)錄音
    audio = recorder.record(10)
    path = recorder.save(audio)

    # 3. 辨識
    result = pipeline.recognize(path)

    # 4. 評分
    score = scorer.score(text['tailo'], result['normalized'])

    # 5. 驗證
    assert 0 <= score['final_score'] <= 100
    assert score['level'] in ["優秀", "良好", "尚可", "待改進", "需重學"]

    print("✅ 端對端測試通過")

階段三:介面開發(Week 5-6)

Week 5: Gradio 介面實作

Day 29-30: 基礎介面

# app.py
import gradio as gr

with gr.Blocks(theme=gr.themes.Soft()) as demo:

    gr.Markdown("# 🎙️ 台語朗讀自動評分系統")

    with gr.Row():
        difficulty = gr.Dropdown(
            choices=["easy", "medium", "hard"],
            label="選擇難度",
            value="easy"
        )
        generate_btn = gr.Button("📝 生成文稿", variant="primary")

    text_display = gr.Textbox(
        label="請朗讀以下文本",
        lines=4,
        interactive=False,
        scale=2
    )

    with gr.Row():
        audio_input = gr.Audio(
            source="microphone",
            type="filepath",
            label="🎤 開始錄音"
        )

    submit_btn = gr.Button("🎯 提交評分", variant="primary", size="lg")

    result_display = gr.Markdown(label="評分結果")

    # 事件綁定
    generate_btn.click(
        fn=generate_text,
        inputs=[difficulty],
        outputs=[text_display]
    )

    submit_btn.click(
        fn=process_audio,
        inputs=[audio_input, text_display],
        outputs=[result_display]
    )

demo.launch()

Day 31-32: 功能完善
- 加入載入動畫
- 錯誤提示優化
- 音訊波形顯示
- 重新錄製按鈕

Day 33-35: 結果展示優化

# 美化評分結果顯示
def format_result(score_data):
    result_md = f"""
    ## 📊 評分結果

    ### 🎯 總分: **{score_data['final_score']}** 分

    **等級**: {get_emoji(score_data['level'])} {score_data['level']}

    ---

    ### 📈 詳細指標

    | 項目 | 分數 | 進度 |
    |------|------|------|
    | 整體準確度 | {score_data['metrics']['overall_accuracy']:.1f}% | {progress_bar(score_data['metrics']['overall_accuracy'])} |
    | 字元準確度 | {score_data['metrics']['character_accuracy']:.1f}% | {progress_bar(score_data['metrics']['character_accuracy'])} |
    | 聲調準確度 | {score_data['metrics']['tone_accuracy']:.1f}% | {progress_bar(score_data['metrics']['tone_accuracy'])} |

    ---

    ### 📝 對照

    **標準答案**: `{score_data['reference']}`

    **辨識結果**: `{score_data['recognized']}`

    **編輯距離**: {score_data['edit_distance']}

    ---

    ### 💡 改進建議

    {generate_feedback(score_data)}
    """

    return result_md

Week 6: 測試與優化

Day 36-38: 使用者測試
- 邀請 10 位使用者測試
- 收集回饋問卷(SUS 量表)
- 記錄使用問題

Day 39-41: 優化改進
- 修復發現的 Bug
- 優化使用流程
- 改善錯誤訊息
- 效能調校

Day 42: 功能凍結與文件


階段四:測試與部署(Week 7-8)

Week 7: 系統測試

Day 43-44: 功能測試

測試項目 測試案例數 通過標準
文稿載入 100 全部正確
錄音功能 50 成功率 > 95%
辨識準確率 100 平均 > 85%
評分準確性 50 相關係數 > 0.8
介面響應 20 無卡頓/錯誤

Day 45-46: 效能測試

# performance_test.py
import time
import psutil

def test_performance():
    """效能測試"""

    # 1. 模型載入時間
    start = time.time()
    model = get_whisper_model()
    load_time = time.time() - start
    assert load_time < 30, "模型載入過慢"

    # 2. 辨識速度
    start = time.time()
    result = model.transcribe("test.wav")
    infer_time = time.time() - start
    assert infer_time < 5, "辨識過慢"

    # 3. 記憶體使用
    memory = psutil.Process().memory_info().rss / 1024 / 1024
    assert memory < 3000, f"記憶體使用過高: {memory}MB"

    print(f"""
    ✅ 效能測試通過
    - 載入時間: {load_time:.2f}s
    - 辨識時間: {infer_time:.2f}s
    - 記憶體: {memory:.0f}MB
    """)

Day 47-49: 壓力測試
- 連續使用 1 小時無崩潰
- 同時 5 個使用者無延遲
- 處理異常輸入(過長/過短音檔)

Week 8: 文件與部署

Day 50-52: 文件撰寫

📄 待完成文件:
- README.md (專案說明)
- INSTALL.md (安裝指南)
- USER_GUIDE.md (使用手冊)
- API.md (API 文件)
- CONTRIBUTING.md (貢獻指南)
- CHANGELOG.md (版本記錄)

Day 53-54: 部署準備

# requirements.txt
torch==2.0.1
transformers==4.30.0
sounddevice==0.4.6
scipy==1.10.1
python-Levenshtein==0.21.1
gradio==4.0.0

# Docker 配置 (可選)
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Day 55-56: 正式發布
- GitHub Release
- 部署至雲端平台(Hugging Face Spaces / Render)
- 公告與推廣


六、資料準備計劃

6.1 文稿資料庫建置

分類與數量規劃

分類 難度分布 總數 來源
問候語 E:10, M:4, H:1 15 日常用語
自我介紹 E:8, M:6, H:1 15 教材改編
日常對話 E:15, M:12, H:3 30 生活情境
購物 E:5, M:4, H:1 10 市場對話
家庭 E:5, M:4, H:1 10 家庭對話
俗諺語 E:2, M:8, H:5 15 台語諺語
文學 E:0, M:2, H:3 5 詩詞散文
總計 45, 40, 15 100 -

文稿範例(按難度)

Easy 級別 (5-15字)
1. 你好( .)
2. 食飽未(Tsia̍h- buē?)
3. 我愛你(Guá ài .)
4. 多謝你(To-siā .)
5. 你叫啥名( kiò siáⁿ-miâ?)
6. 我欲去學校(Guá beh khì ha̍k-hāu.)
7. 今仔日天氣真好(Kin-á-ji̍t thinn-khì tsin .)
8. 這个幾錢(Tsit ê guī-tsé tsînn?)
9. 我毋知(Guá m̄-tsai.)
10. 請坐(Tshiánn tsē.)
Medium 級別 (15-30字)
1. 我的名叫阿明今年二十歲佇台北讀冊
   (Guá ê miâ kiò A-bîng, kin- -tsa̍p huè,  Tâi-pak tha̍k-chheh.)

2. 阮兜有五个人阿爸阿母阮佮兩个小弟
   (Guán tau ū gōo ê lâng, a-pah, a-, guán kah nn̄g ê sió-.)

3. 這條路一直行過去看著紅綠燈就倒手仔爿
   (Tsit tiâu lōo it-ti̍t kiânn kuè-khì, khuànn-tio̍h âng-li̍k-ting  -tshiú-á-pîng.)
Hard 級別 (30+字)
1. 一枝草一點露天無絕人之路日頭赤焱焱隨人顧性命
   (Tsi̍t ki tsháu, tsi̍t tiám lōo, thinn  tsa̍t jîn tsi lo̍o; ji̍t-thâu tshiah-iānn-iānn, suî lâng kòo sìng-miā.)

2. 阮阿公講伊細漢的時陣生活真艱苦逐工愛去田裡做穡日時做甲烏暗才轉來兜
   (Guán a-kong kóng, i -hàn ê -tsūn, sing-ua̍h tsin kan-khóo, ta̍k-kang ài khì tshân- tsò-sit, ji̍t- tsò kah oo-àm tsiah tńg-lâi tau.)

文稿品質控管流程

Step 1: 初稿撰寫
  ↓
Step 2: 台語專家校驗
  ├─ 檢查漢字用字
  ├─ 確認台羅拼音正確性
  └─ 驗證聲調標記
  ↓
Step 3: 難度評估
  ├─ 字數統計
  ├─ 罕見詞彙檢查
  └─ 語法複雜度分析
  ↓
Step 4: 標籤分類
  ↓
Step 5: 錄入資料庫
  ↓
Step 6: 測試驗證

6.2 測試音檔準備

錄製規範

環境要求:
  - 地點: 安靜室內空間
  - 背景噪音: < 40 dB
  - 迴音: 最小化(可用軟材料吸音)

設備規格:
  - 麥克風: 中等品質(非專業錄音室級)
  - 距離: 15-30 公分
  - 採樣率: 16000 Hz
  - 格式: WAV (PCM)

錄製標準:
  - 語速: 正常(每分鐘 150-200 字)
  - 音量: 適中(RMS 5000-15000)
  - 清晰度: 咬字清楚,不含糊

錄音者配置

ID 性別 年齡 口音 台語程度 錄製數量
S01 25-35 台北腔 流利 20 篇
S02 25-35 台北腔 流利 20 篇
S03 40-50 南部腔 母語 15 篇
S04 40-50 南部腔 母語 15 篇
S05 男/女 18-25 混合 中等 10 篇

音檔標註格式

{
  "audio_id": "audio_001_S01_easy",
  "text_id": "text_001",
  "speaker_id": "S01",
  "difficulty": "easy",
  "recording_date": "2025-01-10",
  "duration": 3.2,
  "sample_rate": 16000,
  "quality_check": {
    "rms": 8500,
    "snr_db": 25,
    "is_clipping": false
  },
  "human_score": 95,  // 人工評分(用於驗證)
  "notes": ""
}

6.3 評分標準建立

專家評分指引

## 台語發音評分準則

### 評分維度 (總分 100)

1. **音節正確性 (50 分)**
   - 聲母正確: 20 分
   - 韻母正確: 20 分
   - 音節完整: 10 分

2. **聲調準確度 (30 分)**
   - 調值正確: 30 分
   - (台語7調+輕聲)

3. **流暢度 (20 分)**
   - 斷句自然: 10 分
   - 速度適中: 5 分
   - 無停頓: 5 分

### 評分細則

**優秀 (90-100)**
- 發音標準,無明顯錯誤
- 聲調準確,符合變調規則
- 流暢自然

**良好 (80-89)**
- 發音清晰,1-2處小錯
- 聲調大致正確
- 稍有停頓

**尚可 (70-79)**
- 基本可理解
- 3-4處錯誤
- 聲調偶有錯誤

**待改進 (60-69)**
- 多處明顯錯誤
- 聲調不穩定
- 影響理解

**需重學 (<60)**
- 無法理解
- 嚴重錯誤
- 需重新學習

七、風險評估與應對

7.1 技術風險矩陣

風險項目 機率 影響 風險等級 應對策略
Whisper 輸出格式不符預期 🔴 高 備用漢字轉台羅方案
辨識準確率低於預期 🟠 中高 音訊前處理 + 模型微調
錄音品質不穩定 🟡 中 品質檢查機制
評分標準爭議 🟡 中 專家委員會制定
模型推理速度慢 🟢 低 GPU加速/模型量化
系統穩定性問題 🟡 中 完善錯誤處理

7.2 應對措施詳解

🔴 風險1: Whisper 輸出格式不符

情境: 模型輸出漢字而非台羅

方案A: 整合台灣言語工具

from tai5_uan5_gian5_gi2_kang1_ku7 import TaiBunTaigiHuanLoGiSuKhiPhing

def hanzi_to_tailo_v1(hanzi):
    """使用台灣言語工具轉換"""
    converter = TaiBunTaigiHuanLoGiSuKhiPhing()
    try:
        tailo = converter.chu_im(hanzi)
        return tailo
    except Exception as e:
        logging.error(f"轉換失敗: {e}")
        return None

方案B: 自建漢字-台羅對照表

# 建立常用詞對照字典
HANZI_TAILO_DICT = {
    "你好": "Lí hó",
    "食飽未": "Tsia̍h-pá buē",
    "多謝": "To-siā",
    # ... 擴充至 1000+ 詞條
}

def hanzi_to_tailo_v2(hanzi):
    """字典查詢轉換"""
    return HANZI_TAILO_DICT.get(hanzi, hanzi)

方案C: 降級為漢字比對

def fallback_scoring(ref_hanzi, rec_hanzi):
    """漢字比對(精度較低)"""
    # 僅比對漢字,無法評估發音細節
    return levenshtein_distance(ref_hanzi, rec_hanzi)

🟠 風險2: 辨識準確率不足

策略1: 音訊前處理

import noisereduce as nr
import librosa

def preprocess_audio(audio_path):
    """音訊前處理流程"""
    # 1. 載入音訊
    y, sr = librosa.load(audio_path, sr=16000)

    # 2. 降噪
    y_denoised = nr.reduce_noise(y=y, sr=sr)

    # 3. 音量正規化
    y_normalized = librosa.util.normalize(y_denoised)

    # 4. 靜音移除
    y_trimmed, _ = librosa.effects.trim(y_normalized)

    return y_trimmed, sr

策略2: 收集錯誤案例

# 建立錯誤案例資料庫
class ErrorCaseDB:
    """錯誤案例追蹤"""

    def log_error(self, audio_path, expected, recognized, score):
        """記錄辨識錯誤"""
        error_case = {
            'timestamp': datetime.now(),
            'audio': audio_path,
            'expected': expected,
            'recognized': recognized,
            'score': score,
            'edit_distance': levenshtein_distance(expected, recognized)
        }

        # 儲存至資料庫
        self._save_to_db(error_case)

        # 如果累積100個案例,觸發分析
        if self._count() >= 100:
            self.analyze_patterns()

    def analyze_patterns(self):
        """分析錯誤模式"""
        # 找出常見錯誤類型
        # 用於改進模型或評分機制
        pass

策略3: 提示使用者改善錄音

RECORDING_TIPS = """
📌 錄音小技巧:
1. 找一個安靜的環境
2. 麥克風距離嘴巴 15-30 公分
3. 音量適中,不要太大聲或太小聲
4. 咬字清楚,語速正常
5. 避免背景音樂或電視聲
"""

🟡 風險3: 評分標準爭議

應對: 建立評分委員會

組成:
- 台語教師 x 2
- 台語研究者 x 1  
- 技術開發者 x 1
- 使用者代表 x 1

職責:
1. 制定詳細評分準則
2. 標註測試集標準答案
3. 定期檢視評分爭議案例
4. 調整評分權重

🟢 風險4: 效能優化

方案1: 模型量化

# 使用 INT8 量化減少模型大小
from optimum.onnxruntime import ORTModelForSpeechSeq2Seq

quantized_model = ORTModelForSpeechSeq2Seq.from_pretrained(
    MODEL_NAME,
    export=True,
    provider="CPUExecutionProvider"
)

方案2: GPU 加速

# 確保安裝 CUDA 版本的 PyTorch
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

方案3: 非同步處理

import asyncio

async def async_recognize(audio_path):
    """非同步辨識"""
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        None, 
        whisper.transcribe,
        audio_path
    )
    return result

八、時程與里程碑

8.1 詳細甘特圖

2025年1月 - 2

Week 1  [====環境建置====]
  Mon   ███ Python環境配置
  Tue   ███ 套件安裝
  Wed   ███ 專案結構建立
  Thu   ███ Whisper模型下載
  Fri   ███ 模型測試

Week 2  [====驗證實驗====]
  Mon   ███ 輸出格式測試
  Tue   ███ 準備測試資料
  Wed   ███ 錄音功能驗證
  Thu   ███ 辨識準確率測試
  Fri   ███ 技術可行性報告
         里程碑1: 技術驗證完成

Week 3  [====核心開發====]
  Mon   ███ TextManager開發
  Tue   ███ 文稿資料庫建立
  Wed   ███ AudioRecorder開發
  Thu   ███ 錄音測試
  Fri   ███ TextProcessor開發

Week 4  [====評分引擎====]
  Mon   ███ TaiwaneseScorer開發
  Tue   ███ 評分演算法實作
  Wed   ███ Whisper整合
  Thu   ███ 端對端測試
  Fri   ███ Bug修復
         里程碑2: 核心功能完成

Week 5  [====介面開發====]
  Mon   ███ Gradio基礎介面
  Tue   ███ 事件綁定
  Wed   ███ 結果展示設計
  Thu   ███ UI/UX優化
  Fri   ███ 錯誤處理

Week 6  [====測試優化====]
  Mon   ███ 使用者測試
  Tue   ███ 收集回饋
  Wed   ███ Bug修復
  Thu   ███ 效能優化
  Fri   ███ 功能凍結
         里程碑3: MVP完成

Week 7  [====系統測試====]
  Mon   ███ 功能測試
  Tue   ███ 效能測試
  Wed   ███ 壓力測試
  Thu   ███ 回歸測試
  Fri   ███ 測試報告

Week 8  [====部署發布====]
  Mon   ███ 文件撰寫
  Tue   ███ README完成
  Wed   ███ 部署準備
  Thu   ███ 雲端部署
  Fri   ███ 正式發布
         里程碑4: 正式上線

8.2 關鍵里程碑

日期 里程碑 交付物 驗收標準
Week 2 技術驗證完成 可行性報告 ✅ 模型可運作
✅ 辨識率>70%
✅ 確認輸出格式
Week 4 核心功能完成 CLI 可執行版本 ✅ 錄音正常
✅ 辨識正常
✅ 評分正常
Week 6 MVP 完成 Web 介面版本 ✅ 介面可用
✅ 端對端流程完整
✅ SUS>70
Week 8 正式發布 生產版本 ✅ 通過所有測試
✅ 文件完整
✅ 已部署上線

8.3 彈性調整機制

風險緩衝時間: 每個階段預留 10% 時間

調整原則:
1.  Week 2 輸出格式測試發現問題
    延長 1 週開發轉換工具

2.  Week 4 核心功能延遲
    壓縮 Week 5 介面開發時間
    或延後發布 1 

3.  Week 6 使用者測試發現重大問題
    立即修復,延後非關鍵功能

緊急應變:
- 技術阻礙  啟動備案方案
- 資源不足  縮減功能範圍至核心MVP
- 時程壓力  砍掉非必要功能(如歷史記錄)

九、預期成果與評估

9.1 量化指標

類別 指標 目標值 測量方式 重要性
效能 辨識準確率 (WER) ≥ 85% 100筆測試音檔 ⭐⭐⭐
效能 系統回應時間 ≤ 5秒 平均處理時間 ⭐⭐⭐
效能 錄音成功率 ≥ 95% 100次錄音操作 ⭐⭐
準確 評分相關係數 ≥ 0.80 與專家評分比較 ⭐⭐⭐
準確 評分一致性 ≥ 0.85 重複測試變異數 ⭐⭐
品質 代碼覆蓋率 ≥ 60% pytest-cov ⭐⭐
體驗 使用者滿意度 (SUS) ≥ 70分 SUS問卷 ⭐⭐⭐
體驗 任務完成率 ≥ 90% 可用性測試 ⭐⭐

9.2 質化成果

功能完整性

技術成果

產出清單

程式碼

taiwanese-pronunciation-scorer/
├── README.md                    ✅ 專案說明
├── requirements.txt             ✅ 依賴套件
├── main.py                      ✅ 主程式
├── text_manager.py              ✅ 文稿管理
├── recorder.py                  ✅ 錄音模組
├── whisper_model.py             ✅ 語音辨識
├── scorer.py                    ✅ 評分引擎
├── text_processor.py            ✅ 文字處理
├── config.py                    ✅ 配置檔
├── utils.py                     ✅ 工具函式
├── tests/                       ✅ 單元測試
│   ├── test_text_manager.py
│   ├── test_recorder.py
│   ├── test_scorer.py
│   └── ...
├── data/
│   └── texts.json               ✅ 文稿資料庫 (100篇)
├── docs/
│   ├── INSTALL.md               ✅ 安裝指南
│   ├── USER_GUIDE.md            ✅ 使用手冊
│   ├── API.md                   ✅ API文件
│   └── ARCHITECTURE.md          ✅ 架構說明
└── examples/                    ✅ 使用範例

資料集
- ✅ 台語文稿庫: 100+ 篇(分級分類)
- ✅ 測試音檔集: 50+ 個(多說話者)
- ✅ 評分標準文件
- ✅ 錯誤案例資料庫

文件
- ✅ 技術白皮書
- ✅ 評分準則手冊
- ✅ 開發者指南
- ✅ 測試報告
- ✅ 使用者手冊

9.3 評估方法

A. 技術評估

辨識準確率測試

def evaluate_recognition_accuracy():
    """評估辨識準確率"""
    test_set = load_test_audios()  # 100個測試音檔

    results = []
    for audio, ground_truth in test_set:
        recognized = whisper.transcribe(audio)
        wer = calculate_wer(ground_truth, recognized)
        results.append(wer)

    avg_wer = np.mean(results)
    accuracy = 100 * (1 - avg_wer)

    print(f"辨識準確率: {accuracy:.2f}%")
    return accuracy

評分準確性測試

def evaluate_scoring_accuracy():
    """評估評分準確性"""
    test_cases = load_expert_scored_cases()  # 50個專家已評分案例

    system_scores = []
    expert_scores = []

    for case in test_cases:
        sys_score = scorer.score(case['reference'], case['recognized'])
        system_scores.append(sys_score['final_score'])
        expert_scores.append(case['expert_score'])

    # 計算皮爾森相關係數
    correlation = np.corrcoef(system_scores, expert_scores)[0, 1]

    print(f"評分相關係數: {correlation:.3f}")
    return correlation

B. 使用者評估

SUS 問卷(System Usability Scale)

1. 我認為我會經常使用這個系統
    強烈不同意   不同意   中立   同意   強烈同意

2. 我覺得這個系統太複雜了
    強烈不同意   不同意   中立   同意   強烈同意

3. 我覺得這個系統很容易使用
    強烈不同意   不同意   中立   同意   強烈同意

4. 我需要技術人員的幫助才能使用這個系統
    強烈不同意   不同意   中立   同意   強烈同意

5. 我覺得這個系統的各項功能整合得很好
    強烈不同意   不同意   中立   同意   強烈同意

... (共10題)

計分公式:
SUS分數 = [(奇數題總分 - 5) + (25 - 偶數題總分)] × 2.5

評估標準:
- 90-100: 優秀 (A+)
- 80-89:  良好 (A)
- 70-79:  尚可 (B)
- 60-69:  待改進 (C)
- <60:    不及格 (F)

任務完成率測試

測試任務:
1. 選擇一個簡單難度的文稿
2. 錄製朗讀音檔
3. 提交並查看評分結果
4. 理解評分報告內容
5. 重新練習一次

成功標準:
- 能獨立完成 5 個任務
- 完成時間 < 5 分鐘
- 無需求助

目標:  90% 使用者完成率

十、後續擴展規劃

10.1 短期優化(3個月內)

功能增強

技術改進

10.2 中期功能(6個月內)

互動功能

額外功能:
- 離線使用(模型本地化)
- 推播通知提醒練習
- 社群分享
```

進階評分

def analyze_prosody(audio):
"""韻律分析"""
# 音高 (Pitch)
pitch = librosa.yin(audio, sr=16000)

  # 能量 (Energy)
  energy = librosa.feature.rms(y=audio)[0]

  # 語速 (Speaking Rate)
  tempo = estimate_tempo(audio)

  return {
      'pitch_variation': np.std(pitch),
      'avg_energy': np.mean(energy),
      'speaking_rate': tempo
  }

```

實作:
- 每種方言獨立的評分標準
- 方言特徵標註
- 自動識別方言類型
```

10.3 長期願景(1年內)

AI 語音教練

智能化功能:
1. 發音診斷
   - 自動識別常見錯誤
   - 針對性練習推薦

2. 個人化課程
   - 根據程度自動調整難度
   - 智能生成練習內容

3. 語音合成 (TTS)
   - 提供標準發音示範
   - 逐字播放功能
   - 調整語速功能

多語言擴展

新增語言:
- 客家語(四縣腔、海陸腔)
- 原住民語(阿美語、排灣語等)
- 其他閩南語方言(廈門話、潮州話)

技術挑戰:
- 各語言專用 Whisper 模型
- 羅馬拼音系統差異
- 評分標準制定

社群平台

功能設計:
1. 使用者社群
   - 分享學習心得
   - 互相批改練習
   - 問答討論區

2. 教師資源
   - 教案分享
   - 測驗卷生成
   - 班級管理工具

3. 內容貢獻
   - 使用者上傳文稿
   - 社群審核機制
   - 貢獻者排行榜

與教育機構合作

合作模式:
1. 學校授權版
   - 客製化功能
   - 學生資料管理
   - 成績匯出報表

2. 教材整合
   - 搭配課本使用
   - 同步課程進度
   - 考試模擬

3. 師資培訓
   - 教師使用指導
   - 教學工作坊
   - 技術支援

10.4 商業化可能性

免費增值模式 (Freemium)

免費版:
- 每日 10 次練習
- 基礎文稿庫 (100篇)
- 簡易評分報告

付費版 (NT$ 99/月):
- 無限練習次數
- 完整文稿庫 (1000+篇)
- 詳細分析報告
- 學習歷程追蹤
- 無廣告
- 離線使用

企業/學校授權

方案:
- 學校授權: NT$ 10,000/年 (50人以下)
- 企業方案: 客製報價

附加服務:
- 技術支援
- 客製化功能
- 資料分析報告
- 使用培訓

十一、附錄

A. 開發環境需求

硬體需求

最低配置:
- CPU: Intel i5 第8代 / AMD Ryzen 5
- RAM: 8GB
- 硬碟: 10GB 可用空間
- 麥克風: 任何可用的輸入裝置

建議配置:
- CPU: Intel i7 第10代+ / AMD Ryzen 7
- RAM: 16GB+
- 硬碟: 20GB SSD
- GPU: NVIDIA GTX 1660 以上 (可選)
- 麥克風: 中等品質外接麥克風

理想配置 (開發/測試):
- CPU: Intel i9 / AMD Ryzen 9
- RAM: 32GB
- 硬碟: 50GB NVMe SSD
- GPU: NVIDIA RTX 3060+
- 麥克風: 專業級 USB 麥克風

軟體需求

作業系統:
- Windows: 10 / 11 (64-bit)
- macOS: 11+ (Big Sur, Monterey, Ventura)
- Linux: Ubuntu 20.04+ / Debian 11+

Python:
- 版本: 3.9, 3.10, 3.11 (推薦 3.10)
- 不支援: < 3.9, 3.12+ (套件相容性)

CUDA (使用GPU時):
- 版本: 11.8 或 12.1
- cuDNN: 8.9+

其他工具:
- Git: 最新版本
- FFmpeg: 最新版本 (音訊處理)

B. 套件依賴清單

# requirements.txt

# 核心套件
torch==2.0.1
transformers==4.30.2
sounddevice==0.4.6
scipy==1.10.1
numpy==1.24.3

# 文字處理
python-Levenshtein==0.21.1

# 介面
gradio==4.0.2

# 音訊處理 (可選)
librosa==0.10.0
noisereduce==3.0.0

# 資料處理
pandas==2.0.3

# 工具
python-dotenv==1.0.0
pyyaml==6.0

# 測試
pytest==7.4.0
pytest-cov==4.1.0

# 文件
mkdocs==1.5.0
mkdocs-material==9.1.0

# 開發工具
black==23.7.0
pylint==2.17.5
mypy==1.4.1

C. 參考資源

台語學習資源

技術文件

學術論文

1. "Robust Speech Recognition via Large-Scale Weak Supervision"
   - Radford et al., 2022
   - Whisper 原始論文

2. "Taiwanese Hokkien Speech Recognition"
   - 台語語音辨識相關研究

3. "Automatic Pronunciation Assessment for Language Learning"
   - 自動發音評分系統綜述

4. "Tonal Languages and Speech Recognition"
   - 聲調語言的語音辨識挑戰

開源專案參考

D. 常見問題 FAQ

Q1: 為什麼選擇台羅拼音而不是其他拼音系統?

A: 台羅(台灣閩南語羅馬字拼音)是教育部公告的官方標準,具有:
- 標準化: 統一的拼寫規則
- 完整性: 能精確標示所有音素
- 教育性: 學校教材採用
- 可讀性: 拉丁字母易於電腦處理

Q2: 系統能處理不同的台語腔調嗎?

A: MVP 階段以標準腔為主,但設計上預留擴展性:
- 目前: 以教育部標準音為基準
- 未來: 可新增方言模式(泉州腔、漳州腔等)
- 實作: 透過不同的評分權重檔調整

Q3: 辨識準確率如何提升?

A: 多管齊下提升準確率:
1. 音訊前處理: 降噪、正規化
2. 提示使用者: 錄音環境建議
3. 模型微調: 收集資料fine-tune
4. 後處理: 常見錯誤校正

Q4: 如何確保評分的公平性?

A: 評分機制設計考量:
- 客觀演算法: 基於編輯距離
- 專家驗證: 與人工評分比對
- 透明化: 提供詳細評分依據
- 可調整: 權重可根據使用回饋調整

Q5: 系統需要網路連線嗎?

A: 分階段實作:
- MVP階段: 需要網路(模型首次下載)
- 執行時: 可離線使用
- 未來: 完整離線模式(行動APP

E. 團隊協作指南

建議團隊組成

角色 人數 職責 所需技能
專案經理 1 進度管控、風險管理、協調溝通 專案管理、敏捷開發
後端工程師 1-2 核心系統開發、模型整合 Python, ML, APIs
前端工程師 1 介面設計與實作 Web開發, Gradio/Streamlit
台語專家 1 文稿校驗、評分標準制定 台語專業、語言學
測試工程師 1 品質保證、測試自動化 測試方法論、自動化
UX設計師 0-1 使用者體驗設計(可選) UI/UX、使用者研究

協作工具建議

版本控制: Git + GitHub
專案管理: Trello / Notion / Jira
文件協作: Google Docs / Notion
溝通工具: Slack / Discord
設計工具: Figma (UI設計)
測試管理: TestRail (可選)

Git 工作流程

# 主分支
main           # 穩定版本
develop        # 開發分支

# 功能分支
feature/text-manager
feature/audio-recorder
feature/scoring-engine
feature/web-interface

# 工作流程
git checkout develop
git checkout -b feature/新功能
# ... 開發 ...
git add .
git commit -m "feat: 新增xxx功能"
git push origin feature/新功能
# 發起 Pull Request

Commit 訊息規範

格式: <type>(<scope>): <subject>

Type:
- feat: 新功能
- fix: Bug修復
- docs: 文件更新
- style: 格式調整
- refactor: 重構
- test: 測試
- chore: 雜項

範例:
feat(scorer): 新增聲調評分功能
fix(recorder): 修復音量過低問題
docs(readme): 更新安裝說明

十二、結語

專案願景

本台語朗讀自動評分系統不僅是一個技術專案,更承載著本土語言傳承科技賦能教育的使命。透過 AI 技術的應用,我們期望:

  1. 降低學習門檻 - 讓任何人都能輕鬆學習台語
  2. 提升學習效率 - 即時回饋取代傳統的延遲批改
  3. 促進語言保存 - 以數位方式記錄與推廣台語
  4. 創新教學模式 - 為教育工作者提供有力工具

關鍵成功因素

技術可行性驗證 - 務必在 Week 2 確認 Whisper 模型輸出格式
高品質文稿庫 - 100+ 篇經專家校驗的標準文稿
合理評分標準 - 與專家共同制定,持續優化
使用者中心設計 - 介面簡潔、流程順暢、回饋即時
持續迭代改進 - 收集使用資料,不斷優化系統

下一步行動

立即行動(本週內)

  1. 📋 組建開發團隊或確認個人開發計劃
  2. 💻 建置開發環境(Python + 套件安裝)
  3. 🔬 執行 Whisper 模型測試(最關鍵!)
  4. 📝 準備 10-20 篇初始文稿

短期目標(2週內)

  1. ✅ 完成技術驗證報告
  2. ✅ 確定技術方案(根據模型輸出格式)
  3. ✅ 建立基礎文稿庫(30篇)
  4. ✅ 開始核心模組開發

中期目標(2個月內)

  1. ✅ 完成 MVP 開發
  2. ✅ 使用者測試與回饋
  3. ✅ 優化與bug修復
  4. ✅ 正式發布 v1.0

社會影響期待

這個專案若能成功,將可能產生以下影響:

🌱 教育普及 - 讓偏鄉或缺乏台語環境的學生也能學習
👨‍👩‍👧‍👦 世代傳承 - 協助年輕世代重新連結母語
🏫 教師增能 - 減輕教師負擔,提供教學輔助工具
🔬 學術貢獻 - 累積台語語音資料,促進研究發展
🌍 語言保存 - 為台語數位化保存盡一份心力

致謝與鼓勵

感謝您對本土語言教育的關注與投入!

這是一個有意義且具挑戰性的專案,需要耐心與毅力。遇到困難時,請記住:

"一枝草,一點露,天無絕人之路"
每一步努力都在為台語的未來鋪路。

讓我們一起用科技的力量,為台語學習開創新的可能!


專案資訊

參與貢獻
歡迎任何形式的貢獻:
- 🐛 回報問題
- 💡 功能建議
- 📝 文稿貢獻
- 🔧 程式碼改進
- 📖 文件優化

預祝專案成功! 🎉


本計劃案提供完整的開發藍圖,從技術選型、功能設計到實作步驟均有詳細規劃。實際執行時可根據資源狀況與遇到的問題靈活調整,保持敏捷開發的彈性。