yusiwen commited on
Commit
2d43356
·
unverified ·
1 Parent(s): 1ef272c

feat(training): add evaluation with label masking and padding collator

Browse files

- Add eval_strategy and eval_steps to training config
- Split dataset into train/eval subsets
- Implement label masking: only assistant responses contribute to loss
- Replace default data collator with MedicalCollator for proper padding
- Set pad_token to eos_token if missing

qwen_domain_expansion/device_config.py CHANGED
@@ -184,6 +184,8 @@ def select_training_config(
184
  "learning_rate": 2e-4,
185
  "num_train_epochs": 1,
186
  "logging_steps": 5,
 
 
187
  "save_strategy": "no",
188
  "report_to": "none",
189
  **half_kwargs,
 
184
  "learning_rate": 2e-4,
185
  "num_train_epochs": 1,
186
  "logging_steps": 5,
187
+ "eval_strategy": "steps",
188
+ "eval_steps": 50,
189
  "save_strategy": "no",
190
  "report_to": "none",
191
  **half_kwargs,
qwen_domain_expansion/train_medical_vocab.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
 
3
- from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForLanguageModeling
 
4
  from peft import LoraConfig, get_peft_model
5
  from datasets import load_dataset
6
 
@@ -27,6 +28,8 @@ print_config(cfg)
27
  # 1. 设置模型和本地数据集
28
  model_id = cfg["model_id"]
29
  tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
 
 
30
 
31
  # 2. 扩增医学核心专属词汇(防止它们被拆成破碎的单字)
32
  medical_tokens = ["[并发症_心肌炎]", "阿司匹林肠溶片", "脉弦滑", "靶向免疫治疗"]
@@ -41,17 +44,39 @@ dataset = load_dataset(
41
  split="train[:1000]"
42
  )
43
 
44
- # 4. 格式化为 Qwen 的 ChatML 对话格式
 
 
 
 
 
45
  def format_medical_prompts(batch):
46
- texts = []
 
 
47
  for i, o in zip(batch["input"], batch["output"]):
48
- # 模拟拼接,让模型在上下文里深刻理解医学问答
49
- text = f"<|im_start|>user\n{i}<|im_end|>\n<|im_start|>assistant\n{o}<|im_end|>"
50
- texts.append(text)
51
-
52
- return tokenizer(texts, truncation=True, max_length=cfg["seq_len"], padding=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- tokenized_dataset = dataset.map(format_medical_prompts, batched=True, remove_columns=dataset.column_names)
 
55
 
56
  # 5. 加载 Qwen 基础模型并调整嵌入层结构
57
  print(f"正在加载 {model_id} 权重 ({cfg['dtype']})...")
@@ -75,11 +100,40 @@ model.print_trainable_parameters()
75
  # 7. 使用根据设备内存自动选择的训练参数
76
  training_args = TrainingArguments(**cfg["training_args"])
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  trainer = Trainer(
79
  model=model,
80
- train_dataset=tokenized_dataset,
 
81
  args=training_args,
82
- data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
83
  )
84
 
85
  print("\n--- 启动医学专有词表扩展 LoRA 训练 ---")
 
1
  import os
2
 
3
+ import torch
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
5
  from peft import LoraConfig, get_peft_model
6
  from datasets import load_dataset
7
 
 
28
  # 1. 设置模型和本地数据集
29
  model_id = cfg["model_id"]
30
  tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
31
+ if tokenizer.pad_token is None:
32
+ tokenizer.pad_token = tokenizer.eos_token
33
 
34
  # 2. 扩增医学核心专属词汇(防止它们被拆成破碎的单字)
35
  medical_tokens = ["[并发症_心肌炎]", "阿司匹林肠溶片", "脉弦滑", "靶向免疫治疗"]
 
44
  split="train[:1000]"
45
  )
46
 
47
+ split = dataset.train_test_split(test_size=100, seed=42)
48
+ train_dataset_raw = split["train"]
49
+ eval_dataset_raw = split["test"]
50
+ print(f"训练集: {len(train_dataset_raw)} 条,验证集: {len(eval_dataset_raw)} 条")
51
+
52
+ # 4. 格式化为 Qwen 的 ChatML 对话格式,并对 user 部分做 label mask
53
  def format_medical_prompts(batch):
54
+ max_len = cfg["seq_len"]
55
+ input_ids_list = []
56
+ labels_list = []
57
  for i, o in zip(batch["input"], batch["output"]):
58
+ prompt = f"<|im_start|>user\n{i}<|im_end|>\n<|im_start|>assistant\n"
59
+ completion = f"{o}<|im_end|>"
60
+
61
+ prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
62
+ completion_ids = tokenizer(completion, add_special_tokens=False)["input_ids"]
63
+
64
+ # 截断:优先保留完整 prompt,剩余空间给 completion
65
+ if len(prompt_ids) > max_len:
66
+ prompt_ids = prompt_ids[:max_len]
67
+ completion_ids = completion_ids[: max_len - len(prompt_ids)]
68
+
69
+ ids = prompt_ids + completion_ids
70
+ # prompt 部分不计入 loss,只有 assistant 回答参与
71
+ labels = [-100] * len(prompt_ids) + completion_ids
72
+
73
+ input_ids_list.append(ids)
74
+ labels_list.append(labels)
75
+
76
+ return {"input_ids": input_ids_list, "labels": labels_list}
77
 
78
+ tokenized_train = train_dataset_raw.map(format_medical_prompts, batched=True, remove_columns=train_dataset_raw.column_names)
79
+ tokenized_eval = eval_dataset_raw.map(format_medical_prompts, batched=True, remove_columns=eval_dataset_raw.column_names)
80
 
81
  # 5. 加载 Qwen 基础模型并调整嵌入层结构
82
  print(f"正在加载 {model_id} 权重 ({cfg['dtype']})...")
 
100
  # 7. 使用根据设备内存自动选择的训练参数
101
  training_args = TrainingArguments(**cfg["training_args"])
102
 
103
+
104
+ class MedicalCollator:
105
+ """Pad input_ids / labels to the longest sample in the batch.
106
+
107
+ Padding positions use pad_token_id for input_ids, 0 for attention_mask,
108
+ and -100 for labels so they are ignored by the loss.
109
+ """
110
+
111
+ def __init__(self, pad_token_id: int):
112
+ self.pad_token_id = pad_token_id
113
+
114
+ def __call__(self, features):
115
+ max_len = max(len(f["input_ids"]) for f in features)
116
+ input_ids, attention_mask, labels = [], [], []
117
+ for f in features:
118
+ ids = f["input_ids"]
119
+ lbl = f["labels"]
120
+ pad_len = max_len - len(ids)
121
+ input_ids.append(ids + [self.pad_token_id] * pad_len)
122
+ attention_mask.append([1] * len(ids) + [0] * pad_len)
123
+ labels.append(lbl + [-100] * pad_len)
124
+ return {
125
+ "input_ids": torch.tensor(input_ids, dtype=torch.long),
126
+ "attention_mask": torch.tensor(attention_mask, dtype=torch.long),
127
+ "labels": torch.tensor(labels, dtype=torch.long),
128
+ }
129
+
130
+
131
  trainer = Trainer(
132
  model=model,
133
+ train_dataset=tokenized_train,
134
+ eval_dataset=tokenized_eval,
135
  args=training_args,
136
+ data_collator=MedicalCollator(tokenizer.pad_token_id),
137
  )
138
 
139
  print("\n--- 启动医学专有词表扩展 LoRA 训练 ---")