Qwen3-ASR-0.6B模型微调实战:医疗专业术语识别优化
如果你在医疗领域工作,或者接触过医疗相关的语音识别项目,可能会发现一个挺头疼的问题:通用语音识别模型在识别专业术语时,经常出错。比如“阿司匹林”被识别成“阿司匹林吗”,“CT扫描”变成“CT少描”,这些错误在医疗场景下可不是小事。
今天我就来分享一个实战经验:如何用医疗领域的数据集,对Qwen3-ASR-0.6B这个轻量级语音识别模型进行微调,专门提升它在医疗专业术语上的识别准确率。整个过程我会手把手带你走一遍,从数据准备到训练配置,再到最后的评估优化,保证你跟着做就能出效果。
1. 为什么选择Qwen3-ASR-0.6B?
你可能听说过Qwen3-ASR系列,这是阿里开源的语音识别模型家族。其中0.6B版本虽然参数不多(大约9亿),但在效率和性能之间找到了很好的平衡点。
我选择它有几个原因:
轻量高效:0.6B的参数量意味着它不需要特别强大的硬件就能跑起来,我的实验在一张RTX 3090上就能完成,这对大多数开发者来说门槛不高。
多语言支持:原生支持52种语言和方言,包括22种中国方言,这意味着它本身就有不错的语音理解基础。
开源完整:不仅模型权重开源,连推理框架、微调脚本都一并提供了,这对我们做定制化开发特别友好。
最重要的是,Qwen3-ASR-0.6B在通用场景下表现已经很不错了,但就像所有通用模型一样,它在特定专业领域(比如医疗)还有提升空间。我们的目标就是通过微调,让它在这个特定领域变得“更专业”。
2. 环境准备与快速部署
2.1 基础环境搭建
我们先从环境搭建开始。建议使用Python 3.10或更高版本,这样能更好地兼容各种依赖。
# 创建虚拟环境 conda create -n qwen3-asr-medical python=3.10 -y conda activate qwen3-asr-medical # 安装基础包 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets accelerate如果你打算用vLLM后端来加速推理(推荐,特别是训练后的推理阶段),可以这样安装:
# 安装vLLM及相关依赖 pip install vllm pip install "vllm[audio]"2.2 安装Qwen3-ASR专用包
Qwen团队提供了一个专门的Python包,封装了很多实用功能:
pip install qwen-asr这个包会自动处理很多底层依赖,包括音频处理、模型加载等,让我们能更专注于微调本身。
2.3 验证环境
安装完成后,我们可以写个简单的脚本来验证环境是否正常:
import torch from qwen_asr import Qwen3ASRModel # 尝试加载基础模型 try: model = Qwen3ASRModel.from_pretrained( "Qwen/Qwen3-ASR-0.6B", dtype=torch.bfloat16, device_map="auto", ) print(" 环境配置成功!模型加载正常。") except Exception as e: print(f" 环境配置失败:{e}")如果看到成功提示,说明基础环境已经准备好了。
3. 医疗领域数据准备
数据准备是微调成功的关键。医疗语音数据有它的特殊性:专业术语多、发音可能不标准(比如医生快速口述)、背景可能有医疗设备噪音等。
3.1 数据来源建议
我建议从以下几个渠道获取或构建医疗语音数据集:
公开数据集:
- MIMIC-III/IV中的语音部分(需要申请权限)
- 医疗教学录音(很多医学院有公开资源)
- 医疗播客和讲座录音
自建数据集: 如果你有资源,可以自己录制或收集:
- 医疗术语朗读录音
- 模拟医患对话
- 医疗报告口述录音
数据增强: 医疗场景下,数据增强很重要:
- 添加背景噪音(心电图机、呼吸机等声音)
- 调整语速(模拟快速口述)
- 添加混响(模拟诊室环境)
3.2 数据格式整理
Qwen3-ASR微调需要特定的数据格式。我准备了一个示例数据集结构:
medical_asr_dataset/ ├── train/ │ ├── audio/ │ │ ├── patient_001.wav │ │ ├── doctor_consult_002.wav │ │ └── ... │ └── transcripts.jsonl ├── validation/ │ ├── audio/ │ └── transcripts.jsonl └── test/ ├── audio/ └── transcripts.jsonltranscripts.jsonl文件每行是一个JSON对象:
{ "audio": "train/audio/patient_001.wav", "text": "患者主诉头痛三天,伴恶心呕吐", "language": "Chinese" }3.3 数据预处理脚本
我写了一个数据预处理脚本,帮你把原始数据转换成适合微调的格式:
import json import os from pathlib import Path import soundfile as sf def prepare_medical_dataset(raw_data_dir, output_dir): """ 准备医疗ASR微调数据集 Args: raw_data_dir: 原始数据目录 output_dir: 输出目录 """ output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 假设原始数据是CSV格式:audio_path, transcript, language import pandas as pd # 读取原始数据 train_df = pd.read_csv(os.path.join(raw_data_dir, "train.csv")) val_df = pd.read_csv(os.path.join(raw_data_dir, "val.csv")) # 准备训练集 train_data = [] for _, row in train_df.iterrows(): # 检查音频文件是否存在 audio_path = Path(raw_data_dir) / "audio" / row["audio_path"] if not audio_path.exists(): print(f"警告:音频文件不存在 {audio_path}") continue # 验证音频格式 try: data, samplerate = sf.read(audio_path) duration = len(data) / samplerate if duration > 1200: # 超过20分钟,跳过 print(f"警告:音频过长 {duration}s,跳过") continue except Exception as e: print(f"音频文件读取失败 {audio_path}: {e}") continue train_data.append({ "audio": str(audio_path), "text": row["transcript"], "language": row.get("language", "Chinese") }) # 保存训练集 with open(output_dir / "train.jsonl", "w", encoding="utf-8") as f: for item in train_data: f.write(json.dumps(item, ensure_ascii=False) + "\n") print(f" 训练集准备完成:{len(train_data)} 条数据") # 类似处理验证集... # ... return len(train_data) # 使用示例 if __name__ == "__main__": data_count = prepare_medical_dataset( raw_data_dir="./raw_medical_data", output_dir="./medical_asr_dataset" ) print(f"总共处理了 {data_count} 条训练数据")4. 微调配置与训练
4.1 微调策略选择
对于医疗领域的微调,我建议采用渐进式微调策略:
- 第一阶段:在通用医疗文本上微调,让模型熟悉医疗术语
- 第二阶段:在带噪音的医疗语音数据上微调,提升鲁棒性
- 第三阶段:在特定子领域(如放射科、心内科)数据上微调
这样做的好处是避免模型"忘记"原有的通用能力,同时逐步适应医疗领域的特殊性。
4.2 训练配置文件
创建一个训练配置文件finetune_config.yaml:
# 基础配置 model_name_or_path: "Qwen/Qwen3-ASR-0.6B" output_dir: "./output/medical_asr_0.6b" logging_dir: "./logs" # 数据配置 train_file: "./medical_asr_dataset/train.jsonl" validation_file: "./medical_asr_dataset/validation.jsonl" max_train_samples: 10000 # 根据你的数据量调整 max_eval_samples: 1000 # 训练参数 num_train_epochs: 10 per_device_train_batch_size: 8 per_device_eval_batch_size: 8 gradient_accumulation_steps: 4 learning_rate: 1e-5 warmup_steps: 100 logging_steps: 50 eval_steps: 200 save_steps: 500 save_total_limit: 3 # 优化器 optim: "adamw_torch" lr_scheduler_type: "cosine" weight_decay: 0.01 # 音频处理 audio_sample_rate: 16000 max_audio_length: 1200 # 最大20分钟 min_audio_length: 1 # 最小1秒 # 特殊配置 language: "Chinese" # 主要语言,可以设为null让模型自动检测 forced_aligner: null # 微调阶段不需要强制对齐器4.3 微调训练脚本
这是核心的训练脚本,我加了详细的注释:
import os import torch from dataclasses import dataclass, field from typing import Optional from transformers import ( HfArgumentParser, TrainingArguments, Trainer, AutoConfig, ) from qwen_asr import Qwen3ASRModel, Qwen3ASRProcessor from datasets import load_dataset import json @dataclass class ModelArguments: """模型相关参数""" model_name_or_path: str = field( default="Qwen/Qwen3-ASR-0.6B", metadata={"help": "模型名称或路径"} ) language: Optional[str] = field( default=None, metadata={"help": "目标语言,None表示自动检测"} ) @dataclass class DataArguments: """数据相关参数""" train_file: str = field( default=None, metadata={"help": "训练数据文件"} ) validation_file: str = field( default=None, metadata={"help": "验证数据文件"} ) max_audio_length: float = field( default=1200.0, metadata={"help": "最大音频长度(秒)"} ) min_audio_length: float = field( default=1.0, metadata={"help": "最小音频长度(秒)"} ) def load_and_preprocess_data(data_args, processor): """加载和预处理数据""" # 加载数据集 data_files = {} if data_args.train_file is not None: data_files["train"] = data_args.train_file if data_args.validation_file is not None: data_files["validation"] = data_args.validation_file raw_datasets = load_dataset("json", data_files=data_files) # 音频预处理函数 def preprocess_function(examples): # 加载音频 audio_paths = examples["audio"] audio_inputs = [] for audio_path in audio_paths: try: # 这里简化处理,实际应该用soundfile等库加载音频 # 假设音频已经预处理成正确格式 audio_inputs.append(audio_path) except Exception as e: print(f"音频加载失败 {audio_path}: {e}") audio_inputs.append(None) # 准备模型输入 model_inputs = processor( audio=audio_inputs, text=examples["text"], language=[examples.get("language", "Chinese")] * len(audio_inputs), padding=True, truncation=True, max_length=int(data_args.max_audio_length * 100), # 转换为帧数 return_tensors="pt", ) return model_inputs # 应用预处理 tokenized_datasets = raw_datasets.map( preprocess_function, batched=True, remove_columns=raw_datasets["train"].column_names, ) return tokenized_datasets def main(): # 解析参数 parser = HfArgumentParser((ModelArguments, DataArguments, TrainingArguments)) model_args, data_args, training_args = parser.parse_args_into_dataclasses() # 加载处理器和模型 print(" 加载模型和处理器...") processor = Qwen3ASRProcessor.from_pretrained(model_args.model_name_or_path) model = Qwen3ASRModel.from_pretrained( model_args.model_name_or_path, torch_dtype=torch.bfloat16, device_map="auto", ) # 加载和预处理数据 print(" 加载和预处理数据...") tokenized_datasets = load_and_preprocess_data(data_args, processor) # 创建Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_datasets["train"] if "train" in tokenized_datasets else None, eval_dataset=tokenized_datasets["validation"] if "validation" in tokenized_datasets else None, tokenizer=processor, ) # 训练 print(" 开始训练...") train_result = trainer.train() # 保存最终模型 trainer.save_model() trainer.save_state() # 保存训练指标 metrics = train_result.metrics trainer.log_metrics("train", metrics) trainer.save_metrics("train", metrics) print(" 训练完成!") if __name__ == "__main__": main()4.4 启动训练
运行训练脚本:
# 单GPU训练 python finetune_medical.py \ --model_name_or_path Qwen/Qwen3-ASR-0.6B \ --train_file ./medical_asr_dataset/train.jsonl \ --validation_file ./medical_asr_dataset/validation.jsonl \ --output_dir ./output/medical_finetuned \ --num_train_epochs 10 \ --per_device_train_batch_size 8 \ --per_device_eval_batch_size 8 \ --learning_rate 1e-5 \ --warmup_steps 100 \ --logging_steps 50 \ --save_steps 500 \ --eval_steps 200 \ --save_total_limit 3 \ --fp16 # 如果GPU支持,使用混合精度训练训练过程中,你可以通过TensorBoard监控训练进度:
tensorboard --logdir ./logs5. 评估与优化
5.1 评估脚本
训练完成后,我们需要评估模型在医疗术语上的表现。我写了一个专门的评估脚本:
import torch from qwen_asr import Qwen3ASRModel from datasets import load_dataset import jiwer import json from pathlib import Path def evaluate_medical_asr(model_path, test_data_path): """评估医疗ASR模型""" print(f" 加载微调后的模型: {model_path}") model = Qwen3ASRModel.from_pretrained( model_path, torch_dtype=torch.bfloat16, device_map="auto", ) # 加载测试数据 test_data = [] with open(test_data_path, 'r', encoding='utf-8') as f: for line in f: test_data.append(json.loads(line)) print(f" 测试数据量: {len(test_data)}") # 医疗术语分类 medical_categories = { "drug_names": ["阿司匹林", "青霉素", "胰岛素", "布洛芬", "头孢"], "medical_procedures": ["CT扫描", "MRI检查", "心电图", "血液透析", "手术"], "symptoms": ["头痛", "发热", "咳嗽", "呼吸困难", "腹痛"], "anatomy": ["心脏", "肺部", "肝脏", "肾脏", "大脑"] } results = { "overall": {"total": 0, "correct": 0, "wer": 0.0}, "by_category": {cat: {"total": 0, "correct": 0, "wer": 0.0} for cat in medical_categories.keys()} } # 逐条评估 for i, item in enumerate(test_data): if i % 10 == 0: print(f"进度: {i}/{len(test_data)}") try: # 转录 transcription = model.transcribe( audio=item["audio"], language=item.get("language", "Chinese") ) predicted_text = transcription[0].text reference_text = item["text"] # 计算WER wer = jiwer.wer(reference_text, predicted_text) # 更新总体统计 results["overall"]["total"] += 1 results["overall"]["wer"] += wer # 检查是否完全正确 if predicted_text == reference_text: results["overall"]["correct"] += 1 # 按类别统计 for category, terms in medical_categories.items(): # 检查文本中是否包含该类术语 contains_term = any(term in reference_text for term in terms) if contains_term: results["by_category"][category]["total"] += 1 results["by_category"][category]["wer"] += wer # 检查该术语是否识别正确 term_correct = True for term in terms: if term in reference_text: # 检查预测文本中是否包含该术语 if term not in predicted_text: term_correct = False break if term_correct: results["by_category"][category]["correct"] += 1 except Exception as e: print(f"处理第{i}条数据时出错: {e}") continue # 计算平均值 if results["overall"]["total"] > 0: results["overall"]["wer"] /= results["overall"]["total"] results["overall"]["accuracy"] = results["overall"]["correct"] / results["overall"]["total"] for category in results["by_category"]: if results["by_category"][category]["total"] > 0: results["by_category"][category]["wer"] /= results["by_category"][category]["total"] results["by_category"][category]["accuracy"] = ( results["by_category"][category]["correct"] / results["by_category"][category]["total"] ) return results # 使用示例 if __name__ == "__main__": # 评估微调后的模型 finetuned_results = evaluate_medical_asr( model_path="./output/medical_finetuned", test_data_path="./medical_asr_dataset/test.jsonl" ) # 评估原始模型作为对比 original_results = evaluate_medical_asr( model_path="Qwen/Qwen3-ASR-0.6B", test_data_path="./medical_asr_dataset/test.jsonl" ) print("\n" + "="*50) print(" 评估结果对比") print("="*50) print(f"\n原始模型:") print(f" 总体准确率: {original_results['overall']['accuracy']:.2%}") print(f" 总体WER: {original_results['overall']['wer']:.4f}") print(f"\n微调后模型:") print(f" 总体准确率: {finetuned_results['overall']['accuracy']:.2%}") print(f" 总体WER: {finetuned_results['overall']['wer']:.4f}") print(f"\n准确率提升: {(finetuned_results['overall']['accuracy'] - original_results['overall']['accuracy']):.2%}") print(f"WER降低: {(original_results['overall']['wer'] - finetuned_results['overall']['wer']):.4f}") # 打印各类别改进情况 print("\n 各医疗类别改进情况:") for category in finetuned_results["by_category"]: if original_results["by_category"][category]["total"] > 0: orig_acc = original_results["by_category"][category].get("accuracy", 0) fine_acc = finetuned_results["by_category"][category].get("accuracy", 0) improvement = fine_acc - orig_acc print(f" {category}: {orig_acc:.2%} → {fine_acc:.2%} ({improvement:+.2%})")5.2 常见问题与优化建议
在实际微调过程中,你可能会遇到一些问题。这里我总结了一些常见问题和解决方案:
问题1:过拟合
- 现象:在训练集上表现很好,但在验证集上表现差
- 解决方案:
- 增加数据增强(添加医疗环境噪音)
- 使用更小的学习率(如5e-6)
- 早停(early stopping)
- 增加Dropout率
问题2:医疗术语识别不准确
- 现象:通用词汇识别正常,但专业术语错误率高
- 解决方案:
- 在训练数据中增加术语出现的频率
- 创建术语词典,在解码时加入偏置
- 使用术语特定的语言模型重打分
问题3:推理速度变慢
- 现象:微调后模型推理速度明显下降
- 解决方案:
- 使用vLLM进行推理优化
- 量化模型(INT8/INT4)
- 使用更小的批处理大小
5.3 优化后的推理脚本
这里提供一个优化后的推理脚本,加入了医疗术语偏置:
import torch from qwen_asr import Qwen3ASRModel import json class MedicalASRWithBias: """带医疗术语偏置的ASR模型""" def __init__(self, model_path, medical_terms_file=None): self.model = Qwen3ASRModel.from_pretrained( model_path, torch_dtype=torch.bfloat16, device_map="auto", ) # 加载医疗术语词典 self.medical_terms = set() if medical_terms_file: with open(medical_terms_file, 'r', encoding='utf-8') as f: terms_data = json.load(f) for category, terms in terms_data.items(): self.medical_terms.update(terms) print(f" 加载了 {len(self.medical_terms)} 个医疗术语") def transcribe_with_bias(self, audio_path, language="Chinese"): """带术语偏置的转录""" # 基础转录 results = self.model.transcribe( audio=audio_path, language=language, ) # 简单的术语校正(实际应用中可以用更复杂的方法) original_text = results[0].text corrected_text = original_text # 检查并校正术语 for term in self.medical_terms: # 这里简化处理,实际可以用编辑距离等更智能的方法 if self._similar_to_term(original_text, term): # 如果原文与某个术语相似,尝试替换 corrected_text = corrected_text.replace( self._find_similar_part(original_text, term), term ) # 如果做了校正,记录日志 if corrected_text != original_text: print(f"术语校正: '{original_text}' → '{corrected_text}'") return corrected_text def _similar_to_term(self, text, term): """简单判断文本是否与术语相似""" # 这里可以用更复杂的相似度算法 # 简化版:检查是否有重叠字符 if len(term) < 2: return False # 检查术语是否以子串形式出现 if term in text: return True # 检查编辑距离(简化) if len(text) >= len(term) - 1 and len(text) <= len(term) + 1: # 简单重叠检查 overlap = sum(1 for a, b in zip(text, term) if a == b) if overlap >= len(term) - 1: return True return False def _find_similar_part(self, text, term): """找到文本中与术语相似的部分""" # 简化实现:返回第一个可能匹配的子串 for i in range(len(text) - len(term) + 1): substring = text[i:i+len(term)] if self._similar_to_term(substring, term): return substring return text # 使用示例 if __name__ == "__main__": # 初始化带偏置的模型 medical_asr = MedicalASRWithBias( model_path="./output/medical_finetuned", medical_terms_file="./medical_terms.json" ) # 转录医疗音频 result = medical_asr.transcribe_with_bias( audio_path="./test_audio/patient_recording.wav", language="Chinese" ) print(f" 转录结果: {result}")6. 实际应用与部署
6.1 部署为API服务
训练好的模型可以部署为API服务,方便集成到医疗系统中。这里用vLLM部署:
# 使用vLLM部署微调后的模型 vllm serve ./output/medical_finetuned \ --gpu-memory-utilization 0.8 \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 4096然后可以用类似OpenAI API的方式调用:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY" ) # 准备医疗音频 audio_url = "http://your-server/medical_recording.wav" # 调用转录API response = client.audio.transcriptions.create( model="./output/medical_finetuned", file=open("medical_recording.wav", "rb"), language="zh" # 指定中文 ) print(f"医疗转录结果: {response.text}")6.2 集成到医疗系统
在实际医疗系统中,你可能需要一些额外的处理:
class MedicalASRSystem: """医疗ASR系统集成类""" def __init__(self, model_path): self.model = self._load_model(model_path) self.quality_checker = MedicalQualityChecker() self.term_validator = MedicalTermValidator() def transcribe_medical_audio(self, audio_path, context=None): """转录医疗音频,包含质量控制""" # 1. 质量检查 quality_result = self.quality_checker.check(audio_path) if not quality_result["pass"]: return { "success": False, "error": f"音频质量不合格: {quality_result['reason']}", "suggestion": quality_result["suggestion"] } # 2. ASR转录 try: transcription = self.model.transcribe( audio=audio_path, language="Chinese" ) text = transcription[0].text # 3. 术语验证 validation_result = self.term_validator.validate(text) # 4. 上下文融合(如果有的话) if context: text = self._apply_context(text, context) return { "success": True, "text": text, "confidence": transcription[0].confidence, "term_validation": validation_result, "quality_check": quality_result } except Exception as e: return { "success": False, "error": f"转录失败: {str(e)}" } def _load_model(self, model_path): """加载模型""" from qwen_asr import Qwen3ASRModel import torch return Qwen3ASRModel.from_pretrained( model_path, torch_dtype=torch.bfloat16, device_map="auto", ) def _apply_context(self, text, context): """应用上下文信息""" # 例如,如果上下文提到"患者有糖尿病史" # 可以将"血糖高"纠正为"血糖升高" # 这里实现具体的上下文逻辑 return text6.3 性能监控与持续优化
部署后,建议建立监控系统:
import time import logging from datetime import datetime from collections import defaultdict class MedicalASRMonitor: """医疗ASR性能监控""" def __init__(self): self.metrics = defaultdict(list) self.logger = logging.getLogger("medical_asr_monitor") def record_transcription(self, audio_duration, processing_time, success, error_type=None, term_accuracy=None): """记录一次转录的指标""" timestamp = datetime.now() # 记录基础指标 self.metrics["total_requests"].append({ "timestamp": timestamp, "audio_duration": audio_duration, "processing_time": processing_time, "success": success, "error_type": error_type }) # 记录术语准确率 if term_accuracy is not None: self.metrics["term_accuracy"].append({ "timestamp": timestamp, "accuracy": term_accuracy }) # 实时计算并报告 self._report_realtime_metrics() def _report_realtime_metrics(self): """报告实时指标""" if len(self.metrics["total_requests"]) % 100 == 0: total = len(self.metrics["total_requests"]) successes = sum(1 for r in self.metrics["total_requests"] if r["success"]) success_rate = successes / total if total > 0 else 0 avg_processing_time = sum( r["processing_time"] for r in self.metrics["total_requests"] ) / total if total > 0 else 0 self.logger.info( f"实时指标 - 总请求: {total}, " f"成功率: {success_rate:.2%}, " f"平均处理时间: {avg_processing_time:.2f}s" ) def generate_daily_report(self): """生成日报""" # 这里实现详细的报告生成逻辑 pass7. 总结
通过这次实战,我们完成了Qwen3-ASR-0.6B在医疗领域的微调全流程。从数据准备、环境搭建,到模型训练、评估优化,再到最后的部署应用,每一步我都尽量给出了可操作的代码和建议。
微调后的模型在医疗术语识别上会有明显提升,但也要注意,这只是一个起点。实际医疗应用中还需要考虑更多因素:数据隐私、模型可解释性、错误处理机制等。
我建议在实际部署前,一定要进行充分的测试,特别是在各种边缘情况下(比如带口音的医生、嘈杂的急诊室环境、罕见的疾病名称等)。医疗应用容错率低,每个错误都可能带来严重后果。
另外,模型需要定期更新和维护。医疗知识在不断发展,新的药物、新的治疗方法、新的术语不断出现,我们的模型也需要与时俱进。可以考虑建立自动化的数据收集和模型更新流程。
最后,虽然我们这次聚焦在医疗领域,但这个微调框架可以很容易地适配到其他专业领域,比如法律、金融、教育等。核心思路是一样的:准备领域数据、设计合适的训练策略、评估优化、部署应用。
希望这个实战指南对你有帮助。在实际操作中如果遇到问题,或者有更好的优化建议,欢迎交流分享。医疗AI是一个很有意义的领域,我们的每一点改进,都可能帮助到医生和患者。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。