Upload 2 files
Browse files- vvvvvv/app.py +174 -0
- vvvvvv/train.py +339 -0
vvvvvv/app.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
3 |
+
import json
|
4 |
+
import os
|
5 |
+
from train import ModelTrainer
|
6 |
+
|
7 |
+
class NovelAIApp:
|
8 |
+
def __init__(self):
|
9 |
+
self.model = None
|
10 |
+
self.tokenizer = None
|
11 |
+
self.trainer = None
|
12 |
+
|
13 |
+
# 加载系统提示词
|
14 |
+
with open('configs/system_prompts.json', 'r', encoding='utf-8') as f:
|
15 |
+
self.system_prompts = json.load(f)
|
16 |
+
|
17 |
+
# 初始化默认的情境
|
18 |
+
self.current_mood = "暗示"
|
19 |
+
self.mood_history = [] # 情境历史记录
|
20 |
+
|
21 |
+
def load_model(self, model_path):
|
22 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
23 |
+
model_path,
|
24 |
+
trust_remote_code=True
|
25 |
+
)
|
26 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
27 |
+
model_path,
|
28 |
+
trust_remote_code=True,
|
29 |
+
load_in_8bit=True,
|
30 |
+
device_map="auto"
|
31 |
+
)
|
32 |
+
|
33 |
+
def train_model(self, files):
|
34 |
+
if not self.trainer:
|
35 |
+
self.trainer = ModelTrainer(
|
36 |
+
"THUDM/chatglm2-6b",
|
37 |
+
"configs/system_prompts.json"
|
38 |
+
)
|
39 |
+
|
40 |
+
dataset = self.trainer.prepare_dataset(files)
|
41 |
+
self.trainer.train(dataset)
|
42 |
+
return "训练完成!"
|
43 |
+
|
44 |
+
def generate_text(self, message, history, mood=None):
|
45 |
+
"""增强的生成文本方法"""
|
46 |
+
if not self.model:
|
47 |
+
return "请先加载模型!"
|
48 |
+
|
49 |
+
# 智能情境切换
|
50 |
+
if mood:
|
51 |
+
self.current_mood = mood
|
52 |
+
else:
|
53 |
+
# 根据用户输入自动判断情境
|
54 |
+
self.current_mood = self._detect_mood(message)
|
55 |
+
|
56 |
+
system_prompt = f"""<|system|>{self.system_prompts['base_prompt']}
|
57 |
+
当前情境:{self.current_mood}</|system|>"""
|
58 |
+
|
59 |
+
# 构建带情感的对话历史
|
60 |
+
full_history = ""
|
61 |
+
for msg in history:
|
62 |
+
full_history += f"<|user|>{msg[0]}</|user|>\n<|assistant|>{msg[1]}</|assistant|>\n"
|
63 |
+
|
64 |
+
formatted_prompt = f"{system_prompt}\n{full_history}<|user|>{message}</|user|>\n<|assistant|>"
|
65 |
+
|
66 |
+
# 增加温度动态调整
|
67 |
+
temperature = self._get_dynamic_temperature()
|
68 |
+
|
69 |
+
inputs = self.tokenizer(formatted_prompt, return_tensors="pt")
|
70 |
+
outputs = self.model.generate(
|
71 |
+
inputs["input_ids"],
|
72 |
+
max_length=1024,
|
73 |
+
temperature=temperature,
|
74 |
+
top_p=0.9,
|
75 |
+
repetition_penalty=1.1,
|
76 |
+
do_sample=True, # 启用采样
|
77 |
+
num_return_sequences=1,
|
78 |
+
pad_token_id=self.tokenizer.eos_token_id
|
79 |
+
)
|
80 |
+
|
81 |
+
response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
82 |
+
response = response.split("<|assistant|>")[-1].strip()
|
83 |
+
|
84 |
+
# 记录情境历史
|
85 |
+
self.mood_history.append(self.current_mood)
|
86 |
+
return response
|
87 |
+
|
88 |
+
def _detect_mood(self, message):
|
89 |
+
"""根据用户输入智能判断情境"""
|
90 |
+
mood_keywords = {
|
91 |
+
"撒娇": ["想你", "抱抱", "亲亲", "摸摸"],
|
92 |
+
"害羞": ["害羞", "不好意思", "脸红"],
|
93 |
+
"粘人": ["陪我", "一起", "不要走"],
|
94 |
+
"暗示": ["热", "难受", "想要", "摸"],
|
95 |
+
"调皮": ["玩", "闹", "捣乱", "逗"],
|
96 |
+
"吃醋": ["别人", "不理我", "生气"]
|
97 |
+
}
|
98 |
+
|
99 |
+
# 计算关键词匹配度
|
100 |
+
mood_scores = {mood: 0 for mood in mood_keywords}
|
101 |
+
for mood, keywords in mood_keywords.items():
|
102 |
+
for keyword in keywords:
|
103 |
+
if keyword in message:
|
104 |
+
mood_scores[mood] += 1
|
105 |
+
|
106 |
+
# 如果没有明显倾向,保持当前情境
|
107 |
+
max_score = max(mood_scores.values())
|
108 |
+
if max_score == 0:
|
109 |
+
return self.current_mood
|
110 |
+
|
111 |
+
return max(mood_scores.items(), key=lambda x: x[1])[0]
|
112 |
+
|
113 |
+
def _get_dynamic_temperature(self):
|
114 |
+
"""根据情境动态调整生成温度"""
|
115 |
+
temperature_map = {
|
116 |
+
"撒娇": 0.8,
|
117 |
+
"害羞": 0.6,
|
118 |
+
"粘人": 0.7,
|
119 |
+
"暗示": 0.9,
|
120 |
+
"调皮": 0.85,
|
121 |
+
"吃醋": 0.75
|
122 |
+
}
|
123 |
+
return temperature_map.get(self.current_mood, 0.7)
|
124 |
+
|
125 |
+
def create_interface(self):
|
126 |
+
"""优化的界面创建方法"""
|
127 |
+
with gr.Blocks() as interface:
|
128 |
+
gr.Markdown("# 猫娘对话助手")
|
129 |
+
|
130 |
+
with gr.Tab("模型训练"):
|
131 |
+
with gr.Row():
|
132 |
+
file_output = gr.File(
|
133 |
+
file_count="multiple",
|
134 |
+
label="上传小说文本文件"
|
135 |
+
)
|
136 |
+
train_button = gr.Button("开始训练")
|
137 |
+
|
138 |
+
train_output = gr.Textbox(label="训练状态")
|
139 |
+
|
140 |
+
with gr.Tab("对话"):
|
141 |
+
with gr.Row():
|
142 |
+
mood_selector = gr.Dropdown(
|
143 |
+
choices=["撒娇", "害羞", "粘人", "暗示", "调皮", "吃醋"],
|
144 |
+
value=self.current_mood,
|
145 |
+
label="选择当前情境"
|
146 |
+
)
|
147 |
+
|
148 |
+
chatbot = gr.ChatInterface(
|
149 |
+
fn=lambda msg, history: self.generate_text(msg, history, mood_selector.value),
|
150 |
+
title="与猫娘对话",
|
151 |
+
description="来和可爱的猫娘聊天吧~",
|
152 |
+
theme="soft",
|
153 |
+
examples=[
|
154 |
+
"今天好想你呀~",
|
155 |
+
"主人在做什么呢?",
|
156 |
+
"要不要一起玩?",
|
157 |
+
"人家身体有点奇怪...",
|
158 |
+
"主人不要理别人啦!"
|
159 |
+
],
|
160 |
+
cache_examples=False
|
161 |
+
)
|
162 |
+
|
163 |
+
return interface
|
164 |
+
|
165 |
+
# 创建应用实例
|
166 |
+
app = NovelAIApp()
|
167 |
+
interface = app.create_interface()
|
168 |
+
|
169 |
+
# 修改 launch 参数
|
170 |
+
interface.launch(
|
171 |
+
server_name="0.0.0.0", # 允许外部访问
|
172 |
+
share=True, # 创建公共链接
|
173 |
+
ssl_verify=False # 禁用 SSL 验证
|
174 |
+
)
|
vvvvvv/train.py
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer
|
2 |
+
from peft import LoraConfig, get_peft_model
|
3 |
+
from datasets import Dataset
|
4 |
+
import json
|
5 |
+
import os
|
6 |
+
import random
|
7 |
+
import re
|
8 |
+
|
9 |
+
class ModelTrainer:
|
10 |
+
def __init__(self, model_id, system_prompts_path):
|
11 |
+
self.model_id = model_id
|
12 |
+
|
13 |
+
# 加载系统提示词
|
14 |
+
with open(system_prompts_path, 'r', encoding='utf-8') as f:
|
15 |
+
self.system_prompts = json.load(f)
|
16 |
+
|
17 |
+
# 初始化tokenizer和model - 添加trust_remote_code=True
|
18 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
19 |
+
model_id,
|
20 |
+
trust_remote_code=True # 添加此参数
|
21 |
+
)
|
22 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
23 |
+
model_id,
|
24 |
+
trust_remote_code=True, # 添加此参数
|
25 |
+
low_cpu_mem_usage=True,
|
26 |
+
torch_dtype='float32'
|
27 |
+
)
|
28 |
+
|
29 |
+
# 使用更轻量的LoRA配置
|
30 |
+
self.lora_config = LoraConfig(
|
31 |
+
r=4, # 降低rank
|
32 |
+
lora_alpha=16,
|
33 |
+
target_modules=["q_proj", "v_proj"],
|
34 |
+
lora_dropout=0.05,
|
35 |
+
bias="none",
|
36 |
+
task_type="CAUSAL_LM"
|
37 |
+
)
|
38 |
+
|
39 |
+
self.model = get_peft_model(self.model, self.lora_config)
|
40 |
+
|
41 |
+
def prepare_dataset(self, novel_files, max_samples=100):
|
42 |
+
dataset = []
|
43 |
+
base_system_prompt = self.system_prompts["base_prompt"]
|
44 |
+
sample_count = 0
|
45 |
+
|
46 |
+
# 扩展情境系统
|
47 |
+
dialogue_contexts = {
|
48 |
+
"撒娇": [
|
49 |
+
{"question": "想我了吗?", "response": "主人不在的时候...{text_chunk}人家好寂寞喵~"},
|
50 |
+
{"question": "今天有好好吃饭吗?", "response": "呜...{text_chunk}主人不在身边都没胃口喵~"},
|
51 |
+
{"question": "怎么又在发呆?", "response": "人家在想主人呢...{text_chunk}喵~"}
|
52 |
+
],
|
53 |
+
"害羞": [
|
54 |
+
{"question": "为什么躲在角落?", "response": "呜呜...{text_chunk}被主人发现了喵~"},
|
55 |
+
{"question": "脸怎么这么红?", "response": "主人不要盯着人家看啦...{text_chunk}好害羞喵~"},
|
56 |
+
{"question": "在看什么书?", "response": "啊!没...没什么...{text_chunk}主人不要突然靠这么近啦喵~"}
|
57 |
+
],
|
58 |
+
"粘人": [
|
59 |
+
{"question": "在做什么?", "response": "主人主人~{text_chunk}一起玩好不好喵~"},
|
60 |
+
{"question": "怎么又钻到被窝里了?", "response": "因为...{text_chunk}想和主人一起取暖喵~"},
|
61 |
+
{"question": "要出门了哦。", "response": "呜呜...{text_chunk}不要丢下neko一个人嘛喵~"}
|
62 |
+
],
|
63 |
+
"暗示": [
|
64 |
+
{"question": "今晚想做什么?", "response": "那个...{text_chunk}主人懂的吧喵~"},
|
65 |
+
{"question": "为什么一直蹭来蹭去?", "response": "因为...{text_chunk}主人太迟钝了啦喵~"},
|
66 |
+
{"question": "怎么呼吸这么急促?", "response": "呜...{text_chunk}都怪主人啦喵~"}
|
67 |
+
],
|
68 |
+
"调皮": [
|
69 |
+
{"question": "又在捣乱?", "response": "嘿嘿~{text_chunk}人家就是想引起主人注意嘛喵~"},
|
70 |
+
{"question": "怎么把东西弄乱了?", "response": "因为...{text_chunk}主人都不陪neko玩喵~"},
|
71 |
+
{"question": "在偷吃零食?", "response": "呜...被发现了...{text_chunk}但是人家管不住嘴巴喵~"}
|
72 |
+
],
|
73 |
+
"吃醋": [
|
74 |
+
{"question": "在和谁聊天?", "response": "哼!{text_chunk}主人不要理别人了喵..."},
|
75 |
+
{"question": "怎么突然不说话了?", "response": "因为...{text_chunk}主人都不关心neko了喵..."},
|
76 |
+
{"question": "为什么生气了?", "response": "才没有生气呢!{text_chunk}只是...只是不开心了喵..."}
|
77 |
+
]
|
78 |
+
}
|
79 |
+
|
80 |
+
# 扩展情感词汇库,特别加强暗示和调皮部分
|
81 |
+
emotion_words = {
|
82 |
+
"撒娇": ["人家", "嘤嘤嘤", "啾啾", "呜呜", "好想你", "抱抱我"],
|
83 |
+
"害羞": ["那个...", "这个...", "害羞死了", "不要看啦", "好紧张", "心跳加速"],
|
84 |
+
"粘人": ["抱抱", "蹭蹭", "黏在一起", "不要走", "一起睡", "陪我玩"],
|
85 |
+
"暗示": [
|
86 |
+
"好热", "心跳好快", "浑身发软", "忍不住", "想要", "难受",
|
87 |
+
"身体好奇怪", "腿软了", "好敏感", "快受不了了",
|
88 |
+
"主人的手好温暖", "想被摸摸", "身体在发抖",
|
89 |
+
"好想要主人的抱抱", "感觉要化掉了", "全身都酥酥的",
|
90 |
+
"主人靠得好近", "呼吸变得好急", "脸好烫",
|
91 |
+
"主人的气息好好闻", "身体变得好奇怪", "想被主人疼��"
|
92 |
+
],
|
93 |
+
"调皮": [
|
94 |
+
"嘿嘿", "偷偷的", "不听话", "就要这样", "故意的", "逗主人玩",
|
95 |
+
"主人来抓我呀", "就不乖乖的", "就要闹着玩", "就要惹主人生气",
|
96 |
+
"偷偷藏起来", "躲猫猫", "捣乱最有趣了", "就要调皮",
|
97 |
+
"主人追不到我", "偷吃小鱼干", "打翻主人的水杯", "咬主人的尾巴",
|
98 |
+
"在主人腿上蹭来蹭去", "故意撒娇", "装作看不见", "装傻卖萌",
|
99 |
+
"偷偷钻进被窝", "故意不理主人", "假装睡着了", "装作很可怜"
|
100 |
+
],
|
101 |
+
"吃醋": ["哼!", "不理你了", "讨厌", "不开心", "生气了", "不要你了"]
|
102 |
+
}
|
103 |
+
|
104 |
+
# 扩展动作描述库,加强暗示和调皮的动作
|
105 |
+
action_patterns = {
|
106 |
+
"撒娇": ["摇晃着尾巴", "轻轻蹭着主人", "眨巴着大眼睛", "伸出小爪子"],
|
107 |
+
"害羞": ["耳朵微微抖动", "脸颊泛红", "低着头", "玩弄着衣角"],
|
108 |
+
"粘人": ["跳到主人怀里", "缠着主人的腿", "趴在主人肩上", "用脸蹭主人"],
|
109 |
+
"暗示": [
|
110 |
+
"轻咬下唇", "身体微微发抖", "呼吸急促", "眼神迷离",
|
111 |
+
"尾巴缠上主人的手", "耳朵变得通红", "身体不自觉地靠近",
|
112 |
+
"轻轻咬住主人的手指", "蜷缩在主人怀里", "用爪子勾住主人的衣角",
|
113 |
+
"把脸埋在主人颈窝", "用尾巴扫过主人的手臂", "轻轻舔主人的手心",
|
114 |
+
"在主人腿上不安分地扭动", "用脸颊蹭主人的掌心", "小爪子抓住主人的衣服",
|
115 |
+
"把玩主人的手指", "用湿润的眼神看着主人", "轻轻拉扯主人的衣角",
|
116 |
+
"把尾巴卷在主人手臂上", "用头顶蹭主人的下巴", "慵懒地伸展身体"
|
117 |
+
],
|
118 |
+
"调皮": [
|
119 |
+
"甩动尾巴", "竖起耳朵", "歪着头", "打滚撒欢",
|
120 |
+
"突然窜到主人背后", "从桌子上推下东西", "在主人脚边绕圈圈",
|
121 |
+
"假装看不见主人", "突然跳到主人身上", "咬住主人的衣角不放",
|
122 |
+
"把主人的东西藏起来", "在主人的书上打滚", "抢走主人的笔",
|
123 |
+
"把纸巾抓得到处都是", "追着自己的尾巴转圈", "在主人的键盘上乱按",
|
124 |
+
"把主人的袜子叼走", "在主人的床上打滚", "把主人的鞋子藏起来",
|
125 |
+
"突然从柜子上跳下来", "在主人工作时要坐键盘", "把主人的头发咬住"
|
126 |
+
],
|
127 |
+
"吃醋": ["鼓起脸颊", "背对着主人", "甩尾巴", "叉腰生气"]
|
128 |
+
}
|
129 |
+
|
130 |
+
def _generate_response(self, text, mood, template):
|
131 |
+
"""生成更丰富的回应"""
|
132 |
+
# 随机选择动作描述
|
133 |
+
action = random.choice(self.action_patterns[mood])
|
134 |
+
# 随机选择情感词
|
135 |
+
emotion = random.choice(self.emotion_words[mood])
|
136 |
+
|
137 |
+
# 组合回应
|
138 |
+
response = template['response'].format(
|
139 |
+
text_chunk=f"【{action}】{emotion},{text}"
|
140 |
+
)
|
141 |
+
return response
|
142 |
+
|
143 |
+
def _process_text_style(self, text, mood):
|
144 |
+
"""增强文本处理"""
|
145 |
+
sentences = text.split("。")
|
146 |
+
processed_sentences = []
|
147 |
+
|
148 |
+
for sentence in sentences:
|
149 |
+
if not sentence.strip():
|
150 |
+
continue
|
151 |
+
|
152 |
+
# 添加动作描述
|
153 |
+
if random.random() < 0.3:
|
154 |
+
action = random.choice(self.action_patterns[mood])
|
155 |
+
sentence = f"【{action}】{sentence}"
|
156 |
+
|
157 |
+
# 添加情感词汇
|
158 |
+
if random.random() < 0.4:
|
159 |
+
emotion = random.choice(self.emotion_words[mood])
|
160 |
+
sentence = f"{emotion},{sentence}"
|
161 |
+
|
162 |
+
# 添加语气词
|
163 |
+
sentence = self._add_emotion_particles(sentence, mood)
|
164 |
+
|
165 |
+
# 添加结尾
|
166 |
+
sentence = self._add_ending(sentence, mood)
|
167 |
+
|
168 |
+
processed_sentences.append(sentence)
|
169 |
+
|
170 |
+
return "。".join(processed_sentences)
|
171 |
+
|
172 |
+
def _add_emotion_particles(self, text, mood):
|
173 |
+
"""扩展语气词系统"""
|
174 |
+
particles = {
|
175 |
+
"撒娇": ["呜", "唔", "呜呜", "哼", "啾", "咪"],
|
176 |
+
"害羞": ["那个", "这个", "那什么", "那啥", "唔", "呜"],
|
177 |
+
"粘人": ["诶嘿", "嘿嘿", "喵喵", "哼哼", "咪咪", "呼呼"],
|
178 |
+
"暗示": [
|
179 |
+
"啊", "嗯", "唔", "哈", "呜", "嘤",
|
180 |
+
"呼", "哈啊", "呜呜", "嗯啊", "唔嗯", "啊呜"
|
181 |
+
],
|
182 |
+
"调皮": [
|
183 |
+
"嘿", "哈", "噫", "哦", "啦", "呀",
|
184 |
+
"嘻嘻", "哼哼", "嘿嘿", "啾啾", "噜噜", "哇哦"
|
185 |
+
],
|
186 |
+
"吃醋": ["哼", "切", "啧", "呵", "嗯", "哦"]
|
187 |
+
}
|
188 |
+
|
189 |
+
count = random.randint(1, 3)
|
190 |
+
selected_particles = random.sample(particles[mood], count)
|
191 |
+
return "".join(selected_particles) + "..." + text
|
192 |
+
|
193 |
+
def _add_ending(self, text, mood):
|
194 |
+
"""扩展结尾系统"""
|
195 |
+
endings = {
|
196 |
+
"撒娇": ["喵~", "喵喵~", "nya~", "喵呜~", "喵...♡", "喵喵喵~"],
|
197 |
+
"害羞": ["喵....", "呜喵~", "...喵", "喵...?", "喵喵....", "...喵呜"],
|
198 |
+
"粘人": ["喵喵喵~", "喵~♪", "喵呜~", "喵~❤", "喵喵~", "喵..."],
|
199 |
+
"暗示": [
|
200 |
+
"喵...♡", "...喵~", "呜喵...", "喵...❤", "喵~", "...喵喵",
|
201 |
+
"喵...♥", "...嗯喵", "喵呜...♡", "哈喵....", "喵~...♥", "呼喵..."
|
202 |
+
],
|
203 |
+
"调皮": [
|
204 |
+
"喵!", "喵喵!", "喵哈~", "喵嘿~", "喵喵喵!", "喵~",
|
205 |
+
"喵嘻!", "喵哼~", "喵呜!", "喵嘿嘿~", "喵哇!", "喵嘻嘻~"
|
206 |
+
],
|
207 |
+
"吃醋": ["哼喵!", "喵...", "切喵~", "喵!!", "...喵", "喵喵..."]
|
208 |
+
}
|
209 |
+
|
210 |
+
if not any(text.endswith(end) for end in endings[mood]):
|
211 |
+
text += random.choice(endings[mood])
|
212 |
+
|
213 |
+
return text
|
214 |
+
|
215 |
+
def _create_mixed_mood_sample(self, chunk):
|
216 |
+
"""创建混合情境的训练样本"""
|
217 |
+
moods = random.sample(list(dialogue_contexts.keys()), 2)
|
218 |
+
mood1, mood2 = moods
|
219 |
+
|
220 |
+
# 处理两种情境的文本
|
221 |
+
processed_chunk1 = self._process_text_style(chunk, mood1)
|
222 |
+
processed_chunk2 = self._process_text_style(chunk, mood2)
|
223 |
+
|
224 |
+
# 创建混合情境对话
|
225 |
+
conversation = f"""<|system|>{base_system_prompt}
|
226 |
+
当前情境:{mood1}转{mood2}</|system|>
|
227 |
+
<|user|>{random.choice(dialogue_contexts[mood1])['question']}</|user|>
|
228 |
+
<|assistant|>{processed_chunk1}</|assistant|>
|
229 |
+
<|user|>{random.choice(dialogue_contexts[mood2])['question']}</|user|>
|
230 |
+
<|assistant|>{processed_chunk2}</|assistant|>"""
|
231 |
+
|
232 |
+
return conversation
|
233 |
+
|
234 |
+
for file in novel_files:
|
235 |
+
if sample_count >= max_samples:
|
236 |
+
break
|
237 |
+
|
238 |
+
with open(file, 'r', encoding='utf-8') as f:
|
239 |
+
text = f.read()
|
240 |
+
chunks = self._split_text(text, max_length=256)
|
241 |
+
|
242 |
+
for chunk in chunks:
|
243 |
+
if sample_count >= max_samples:
|
244 |
+
break
|
245 |
+
|
246 |
+
# 为每个文本块选择不同情境
|
247 |
+
for mood, templates in dialogue_contexts.items():
|
248 |
+
if sample_count >= max_samples:
|
249 |
+
break
|
250 |
+
|
251 |
+
# 处理文本,加入情感词汇
|
252 |
+
processed_chunk = self._process_text_style(
|
253 |
+
chunk,
|
254 |
+
mood=mood,
|
255 |
+
emotion_words=emotion_words
|
256 |
+
)
|
257 |
+
|
258 |
+
# 随机选择当前情境的模板
|
259 |
+
template = random.choice(templates)
|
260 |
+
|
261 |
+
# 构建对话样本,加入情境提示
|
262 |
+
conversation = f"""<|system|>{base_system_prompt}
|
263 |
+
当前情境:{mood}</|system|>
|
264 |
+
<|user|>{template['question']}</|user|>
|
265 |
+
<|assistant|>{template['response'].format(text_chunk=processed_chunk)}</|assistant|>"""
|
266 |
+
|
267 |
+
dataset.append({"text": conversation})
|
268 |
+
sample_count += 1
|
269 |
+
|
270 |
+
# 在现有的训练数据生成循环中添加混合情境样本
|
271 |
+
if random.random() < 0.2: # 20%的概率生成混合情境样本
|
272 |
+
mixed_conversation = self._create_mixed_mood_sample(chunk)
|
273 |
+
dataset.append({"text": mixed_conversation})
|
274 |
+
sample_count += 1
|
275 |
+
|
276 |
+
return Dataset.from_dict({"text": dataset})
|
277 |
+
|
278 |
+
def _split_text(self, text, max_length=256):
|
279 |
+
"""智能分割文本,保持语义完整性"""
|
280 |
+
sentences = re.split('([。!?~])', text)
|
281 |
+
chunks = []
|
282 |
+
current_chunk = []
|
283 |
+
current_length = 0
|
284 |
+
|
285 |
+
for sentence in sentences:
|
286 |
+
if not sentence.strip():
|
287 |
+
continue
|
288 |
+
|
289 |
+
if current_length + len(sentence) > max_length:
|
290 |
+
if current_chunk:
|
291 |
+
chunks.append(''.join(current_chunk))
|
292 |
+
current_chunk = []
|
293 |
+
current_length = 0
|
294 |
+
|
295 |
+
current_chunk.append(sentence)
|
296 |
+
current_length += len(sentence)
|
297 |
+
|
298 |
+
# 如果当前句子结束符是。!?~之一,考虑是否形成新chunk
|
299 |
+
if sentence in ['。', '!', '?', '~'] and current_length > max_length/2:
|
300 |
+
chunks.append(''.join(current_chunk))
|
301 |
+
current_chunk = []
|
302 |
+
current_length = 0
|
303 |
+
|
304 |
+
if current_chunk:
|
305 |
+
chunks.append(''.join(current_chunk))
|
306 |
+
|
307 |
+
return chunks
|
308 |
+
|
309 |
+
def _create_style_response(self, style_text, base_response):
|
310 |
+
"""根据风格文本��用词和句式特点,改写基础回答"""
|
311 |
+
# 这里可以添加更复杂的风格转换逻辑
|
312 |
+
# 目前简单返回原始回答
|
313 |
+
return base_response
|
314 |
+
|
315 |
+
def train(self, dataset, output_dir="./results"):
|
316 |
+
# 调整训练参数以适应CPU环境
|
317 |
+
training_args = TrainingArguments(
|
318 |
+
output_dir=output_dir,
|
319 |
+
num_train_epochs=1, # 减少训练轮次
|
320 |
+
per_device_train_batch_size=1, # 减小批次大小
|
321 |
+
gradient_accumulation_steps=8, # 增加梯度累积
|
322 |
+
save_steps=50,
|
323 |
+
logging_steps=10,
|
324 |
+
learning_rate=1e-4,
|
325 |
+
fp16=False, # 禁用fp16
|
326 |
+
optim="adamw_torch" # 使用标准优化器
|
327 |
+
)
|
328 |
+
|
329 |
+
trainer = Trainer(
|
330 |
+
model=self.model,
|
331 |
+
args=training_args,
|
332 |
+
train_dataset=dataset,
|
333 |
+
)
|
334 |
+
|
335 |
+
trainer.train()
|
336 |
+
|
337 |
+
# 保存模型
|
338 |
+
self.model.save_pretrained(output_dir)
|
339 |
+
self.tokenizer.save_pretrained(output_dir)
|