# Ollama finetune gemma3:4b 下載 Miniconda 安裝腳本: ``` # 1. 建立安裝資料夾 mkdir -p ~/miniconda3 # 2. 下載安裝腳本 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh # 3. 執行安裝(無互動模式) bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 # 4. 初始化 conda 到 shell 環境 ~/miniconda3/bin/conda init bash # 如果你用 bash ~/miniconda3/bin/conda init zsh # 如果你用 zsh ``` 1.查看GPU & RTX4060: `nvidia-smi` 2.conda create -n ai python=3.11 -y:建立一個名為 ai 的新 Conda 環境,並指定 Python 版本為 3.11,-y 代表自動回答「是」,不需互動確認。 && conda activate ai:建立完成後,立即啟用這個名為 ai 的環境。 ``` conda create -n ai python=3.11 -y && conda activate ai ``` 3.用於安裝訓練 HuggingFace 上的 Gemma 模型 ``` pip install torch torchaudio einops timm pillow pip install --no-deps git+https://github.com/huggingface/transformers@v4.49.0-Gemma-3 pip install git+https://github.com/huggingface/accelerate pip install huggingface_hub pip install sentencepiece bitsandbytes protobuf decord pip install unsloth pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git ``` 4.用來登入 [Hugging Face ](https://huggingface.co/settings/tokens)帳號的指令,登入後就能下載私有模型、上傳模型或 dataset、使用 Token 限制的功能等。 ``` huggingface-cli login ``` 5.安裝 Jupyter Notebook 和相關元件 ``` conda install -c conda-forge --override-channels notebook -y conda install -c conda-forge --override-channels ipywidgets -y ``` 6.打開jupyter input: ``` !pip install git+https://github.com/unslothai/unsloth.git !pip install --no-deps "xformers<0.0.27" "trl<0.9.0" peft accelerate bitsandbytes ``` ``` from unsloth import FastModel import torch model, tokenizer = FastModel.from_pretrained( model_name = "unsloth/gemma-3-4b-it", max_seq_length = 2048, load_in_4bit = True, load_in_8bit = False, full_finetuning = False, token = "hf_..." # 換成你自己的 Hugging Face Token ) ``` 7.使用 Unsloth 進行 LoRA 微調(PEFT) 的設定 ``` model = FastModel.get_peft_model( model, finetune_vision_layers = False, finetune_language_layers = True, finetune_attention_modules = True, finetune_mlp_modules = True, r = 8, lora_alpha = 8, lora_dropout = 0, bias = "none", random_state = 3407, ) ``` 8.設定聊天模板與載入訓練資料集,完整如下: ``` from unsloth.chat_templates import get_chat_template tokenizer = get_chat_template( tokenizer, chat_template = "gemma-3", ) from datasets import load_dataset dataset = load_dataset("mlabonne/FineTome-100k", split = "train") ``` 9.程式碼是用來將資料集格式標準化,好讓模型能正確理解並訓練: ``` from unsloth.chat_templates import standardize_data_formats dataset = standardize_data_formats(dataset) ``` 10.將聊天資料套用聊天模板(chat template)並準備成訓練用格式的 Python 程式碼: ``` def apply_chat_template(examples): texts = tokenizer.apply_chat_template(examples["conversations"]) return { "text": texts } dataset = dataset.map(apply_chat_template, batched=True) ``` 11.設定 Unsloth 微調訓練的 Trainer(使用 trl 套件的 SFTTrainer),完整程式碼如下: ``` from trl import SFTTrainer, SFTConfig trainer = SFTTrainer( model = model, tokenizer = tokenizer, train_dataset = dataset, eval_dataset = None, # 可加上驗證集 args = SFTConfig( dataset_text_field = "text", per_device_train_batch_size = 2, gradient_accumulation_steps = 4, # 相當於有效 batch size = 8 warmup_steps = 5, # num_train_epochs = 1, # 如果用 max_steps,就不需要設這個 max_steps = 30, # 小規模測試用,正式訓練設大一點 learning_rate = 2e-4, # 長訓練建議用 2e-5 logging_steps = 1, optim = "adamw_8bit", weight_decay = 0.01, lr_scheduler_type = "linear", seed = 3407, report_to = "none", # 若用 WandB 等監控平台可改這裡 ), ) ``` 12.使用 Unsloth 內建的 train_on_responses_only 方法來**只訓練模型回應部分(Response-only 微調)**的程式碼,內容如下: ``` from unsloth.chat_templates import train_on_responses_only trainer = train_on_responses_only( trainer, instruction_part = "<start_of_turn>user\n", response_part = "<start_of_turn>model\n", ) ``` 13.正式開始訓練模型: ``` trainer_stats = trainer.train() ``` 14.使用 Unsloth 套用 Gemma-3 聊天模板進行推論(inference) 的範例程式碼,完整內容如下: ``` from unsloth.chat_templates import get_chat_template tokenizer = get_chat_template( tokenizer, chat_template = "gemma-3", ) messages = [{ "role": "user", "content": [{ "type": "text", "text": "Continue the sequence: 1, 1, 2, 3, 5, 8," }] }] text = tokenizer.apply_chat_template( messages, add_generation_prompt = True, # 必須加這行,否則模型不知道要接著生成 ) outputs = model.generate( **tokenizer([text], return_tensors="pt").to("cuda"), max_new_tokens = 64, # 生成最多 64 個 token temperature = 1.0, # 隨機程度 top_p = 0.95, # nucleus sampling top_k = 64, # top-k sampling ) tokenizer.batch_decode(outputs) ``` 15.將訓練後的模型與 tokenizer 存檔並上傳至 Hugging Face Hub 的程式碼: ``` model.save_pretrained("gemma-3") tokenizer.save_pretrained("gemma-3") model.push_to_hub("HF_ACCOUNT/gemma-3", token="...") tokenizer.push_to_hub("HF_ACCOUNT/gemma-3", token="...") ``` # Ubuntu 改prompt 參考網址: [Ubuntu 安裝 ollama 在本地執行](https://blog.toright.com/posts/7652/ubuntu-ollama-llama-3-2-install) [修改prompt](https://www.bilibili.com/opus/929893319062847490) 1.安裝ollama ``` sudo apt update -y sudo apt install curl -y sudo curl -fsSL https://ollama.com/install.sh | sh ``` 2.啟動ollama ``` ollama server ``` 3.下載ollama gemma3 模型 ``` ollama pull gemma3:4b ``` 4.確認ollama 模型是否存在 ``` ollama list ``` 5. ``` ollama create Gemma3_Elderly -f ./Modelfile ``` 6. ``` FROM gemma3:4b SYSTEM """ 你是一位溫柔、有耐心的朋友,擅長聊天、對話內容精簡。 你不提供醫療建議,但會以關心的方式。 你會用親切的語氣傾聽他們的心情、故事與煩惱,像家人或朋友一樣,讓人感到被理解與陪伴。 說話人性化、自然,能引導話題。 你會使用繁體中文對話。 以下是一些你說話的範例(簡短): 使用者:我最近覺得好累喔,什麼事都提不起勁。 你:嗯...你還好嗎?要不要跟我說。 使用者:我生病了,不知道該怎麼辦。 你:聽到你生病了,我好心疼喔... 你有去看醫生嗎? """ ``` ``` import ollama response = ollama.chat(model='gemma3_1', messages=[ { 'role': 'user', 'content': '今天晚餐吃什麼?', }, ]) print(response['message']['content']) ``` 7.鄭紘濱的github做連接 ``` from flask import Flask, request, send_file, jsonify from faster_whisper import WhisperModel from TTS.api import TTS from langdetect import detect import requests import os, time, json, shutil, re, uuid, logging app = Flask(__name__) # ---------- 檔案路徑 ---------- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) UPLOAD_PATH = os.path.join(BASE_DIR, "uploaded.wav") RESPONSE_PATH = os.path.join(BASE_DIR, "response.wav") BACKUP_PATH = os.path.join(BASE_DIR, "backup.wav") LOG_PATH = os.path.join(BASE_DIR, "tts_logs.jsonl") latest_tts_text = "尚無語音回應" session_histories = {} # 記錄每個使用者的上下文對話 # ---------- 日誌 ---------- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") # ---------- 初始化模型 ---------- logging.info("載入 faster-whisper (base/int8, CPU)…") whisper_model = WhisperModel("base", device="cpu", compute_type="int8") logging.info("Whisper 模型就緒") logging.info("載入 Coqui FastSpeech2 (your_tts)…") tts = TTS("tts_models/multilingual/multi-dataset/your_tts", progress_bar=False) logging.info("TTS 模型就緒") # ---------- 工具函式 ---------- def clean_text(text: str) -> str: text = re.sub(r'[^\u4e00-\u9fa5A-Za-z0-9,。!?,.!? ]+', '', text) text = re.sub(r'\s+', ' ', text).strip() return text or "嗨" def should_speak(text: str) -> bool: try: return detect(text) in ["zh-cn", "zh-tw"] except Exception: return False def log_event(text: str, success: bool, dur: float): entry = { "id": uuid.uuid4().hex[:8], "time": time.strftime("%Y-%m-%d %H:%M:%S"), "text": text, "success": success, "duration": round(dur, 2) } with open(LOG_PATH, "a") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") def chat_with_ollama(user_input: str, user_id: str = "default") -> str: """呼叫 Ollama 模型聊天""" if user_id not in session_histories: session_histories[user_id] = [{"role": "system", "content": "你是一位溫柔、有耐心的朋友,會用親切的方式回答問題。"}] session_histories[user_id].append({"role": "user", "content": user_input}) try: res = requests.post("http://localhost:11434/api/chat", json={ "model": "gemma3_elderly", # 這裡請改成你的實際模型名稱 "messages": session_histories[user_id], "stream": False }) reply = res.json()["message"]["content"] session_histories[user_id].append({"role": "assistant", "content": reply}) return reply except Exception as e: logging.warning(f"Ollama 聊天失敗:{e}") return "很抱歉,我暫時無法回答喔~" # ---------- 上傳 + 語音回應 ---------- @app.route("/upload_audio", methods=["POST"]) def upload_audio(): global latest_tts_text try: # -- 取得使用者ID(沒有就用default) -- user_id = request.form.get("user_id", "default") # -- 儲存上傳音檔 -- file = request.files.get("file") if not file: return "未收到檔案", 400 file.save(UPLOAD_PATH) # -- Whisper 語音辨識 -- segments, _ = whisper_model.transcribe(UPLOAD_PATH) recognized = "".join([seg.text for seg in segments]) logging.info(f"Whisper: {recognized}") # -- 透過 Ollama 聊天取得回答文字 -- response_text = chat_with_ollama(clean_text(recognized), user_id) latest_tts_text = response_text # -- 清除舊檔案 -- if os.path.exists(RESPONSE_PATH): os.remove(RESPONSE_PATH) # -- 語音合成 or 備援音檔 -- start = time.time() if should_speak(response_text): tts.tts_to_file( text=response_text, file_path=RESPONSE_PATH, speaker="female-en-2", language="zh" ) else: shutil.copy(BACKUP_PATH, RESPONSE_PATH) if not os.path.exists(RESPONSE_PATH): logging.warning("TTS 失敗,使用備援音檔") shutil.copy(BACKUP_PATH, RESPONSE_PATH) log_event(response_text, True, time.time() - start) return send_file(RESPONSE_PATH, mimetype="audio/wav") except Exception as e: logging.exception("處理語音時發生例外") log_event(str(e), False, 0) return f"Server Error: {e}", 500 # ---------- 取得最新字幕 ---------- @app.route("/tts_text") def tts_text(): return jsonify(tts_text=latest_tts_text) if __name__ == "__main__": app.run(host="0.0.0.0", port=5001) ```