Qwen3-ASR-1.7B模型微调实战:方言识别优化

如果你正在做一个方言相关的语音识别项目,比如识别粤语、四川话或者闽南语,可能会发现直接用通用的语音识别模型效果总差那么点意思。发音习惯、特有词汇、语速语调,这些方言特有的东西,通用模型处理起来确实有点吃力。

好消息是,Qwen3-ASR-1.7B这个开源模型原生就支持22种中文方言,底子相当不错。但如果你想让它在你特定的方言场景下表现更精准,比如识别某个地区的口音,或者某个行业的专业术语,那就需要对它进行专门的“训练”了。

今天我就带你走一遍完整的微调流程,从准备数据到训练模型,再到评估效果,最后部署上线。整个过程我会尽量用大白话讲清楚,即使你之前没怎么接触过模型微调,跟着做也能跑通。

1. 准备工作:环境与数据

微调模型就像教一个已经会说话的人学习新的方言,你需要准备两样东西:合适的“教室”(环境)和专门的“教材”(数据)。

1.1 环境搭建

首先得把环境准备好。我建议用Python 3.9以上的版本,然后安装必要的库。

# 创建虚拟环境(可选但推荐)
python -m venv qwen_asr_finetune
source qwen_asr_finetune/bin/activate  # Linux/Mac
# 或者
qwen_asr_finetune\Scripts\activate  # Windows

# 安装核心库
pip install torch torchaudio transformers datasets
pip install accelerate peft  # 用于高效微调
pip install soundfile librosa  # 处理音频文件

如果你有GPU,建议安装对应版本的PyTorch,这样训练会快很多。没有GPU也没关系,CPU也能跑,就是慢一点。

1.2 数据准备

这是最关键的一步。微调的效果很大程度上取决于你的数据质量。

你需要准备什么格式的数据?

简单来说,就是“音频文件+对应的文字”。比如你有一段10秒的粤语录音,内容是“今日天气好好”,那么你需要一个音频文件(比如.wav格式)和一个文本文件,里面写着“今日天气好好”。

数据从哪里来?

  1. 自己录制:如果你有明确的方言场景,比如客服录音、会议记录,这是最直接的数据源。
  2. 公开数据集:网上有一些方言语音数据集,比如AISHELL-3包含部分方言数据。
  3. 合成数据:用文本转语音工具生成,但要注意合成语音和真实语音的差异。

数据要处理成什么样?

我建议按这个结构组织你的数据:

你的方言数据集/
├── train/
│   ├── audio/
│   │   ├── sample1.wav
│   │   ├── sample2.wav
│   │   └── ...
│   └── transcripts.txt  # 每行:音频文件名,对应文字
├── dev/   # 验证集,结构同train
└── test/   # 测试集,结构同train

transcripts.txt文件的内容像这样:

sample1.wav,今日天气好好,适合出门散步
sample2.wav,请问这个商品有货吗
sample3.wav,帮我查一下明天的航班信息

数据量要多少?

对于方言微调,我觉得有个几百条到几千条就够开始尝试了。当然数据越多效果越好,但也要考虑收集和标注的成本。关键是数据要有代表性,要覆盖你实际场景中会出现的各种情况。

2. 开始微调:代码实战

环境准备好了,数据也准备好了,现在可以开始写代码了。

2.1 加载预训练模型

首先把Qwen3-ASR-1.7B模型加载进来。

from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq
import torch

# 加载模型和处理器
model_name = "Qwen/Qwen3-ASR-1.7B"

print("正在加载模型...")
processor = AutoProcessor.from_pretrained(model_name)
model = AutoModelForSpeechSeq2Seq.from_pretrained(
    model_name,
    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
    low_cpu_mem_usage=True,
)

# 如果有GPU就放到GPU上
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
print(f"模型已加载到 {device}")

这里有个小细节:如果有GPU,我用torch.float16半精度,这样能省内存跑得更快;如果没有GPU就用全精度torch.float32

2.2 准备数据集

接下来要把你的音频数据转换成模型能理解的格式。

from datasets import Dataset, Audio
import pandas as pd
import os

def prepare_dataset(data_dir):
    """准备训练数据集"""
    
    # 读取标注文件
    transcript_path = os.path.join(data_dir, "transcripts.txt")
    df = pd.read_csv(transcript_path, header=None, names=["file", "text"])
    
    # 构建完整音频路径
    audio_dir = os.path.join(data_dir, "audio")
    df["audio_path"] = df["file"].apply(lambda x: os.path.join(audio_dir, x))
    
    # 创建数据集
    dataset = Dataset.from_pandas(df)
    
    # 加载音频
    dataset = dataset.cast_column("audio_path", Audio())
    
    return dataset

# 加载训练集、验证集
train_dataset = prepare_dataset("你的方言数据集/train")
dev_dataset = prepare_dataset("你的方言数据集/dev")

print(f"训练集样本数: {len(train_dataset)}")
print(f"验证集样本数: {len(dev_dataset)}")

2.3 数据预处理

模型不能直接吃音频文件,需要先转换成特征。

def preprocess_function(examples):
    """预处理函数:音频转特征"""
    
    # 提取音频数组和采样率
    audio_arrays = [x["array"] for x in examples["audio_path"]]
    sampling_rates = [x["sampling_rate"] for x in examples["audio_path"]]
    
    # 统一采样率(如果需要)
    target_sampling_rate = processor.feature_extractor.sampling_rate
    
    # 提取特征
    inputs = processor(
        audio_arrays,
        sampling_rate=target_sampling_rate,
        text=examples["text"],
        padding=True,
        return_tensors="pt",
        max_length=480000,  # 最长30秒音频
        truncation=True,
    )
    
    return inputs

# 应用预处理
train_dataset = train_dataset.map(
    preprocess_function,
    batched=True,
    batch_size=4,
    remove_columns=train_dataset.column_names
)

dev_dataset = dev_dataset.map(
    preprocess_function,
    batched=True,
    batch_size=4,
    remove_columns=dev_dataset.column_names
)

2.4 配置训练参数

现在来设置训练的参数。这些参数就像烹饪时的火候和时间,调好了效果才好。

from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="./qwen_asr_finetuned",  # 保存模型的目录
    evaluation_strategy="steps",  # 按步数评估
    eval_steps=500,  # 每500步评估一次
    save_strategy="steps",
    save_steps=500,
    learning_rate=5e-5,  # 学习率,新手建议就用这个
    per_device_train_batch_size=2,  # 批量大小,根据显存调整
    per_device_eval_batch_size=2,
    num_train_epochs=3,  # 训练轮数
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=100,
    report_to="none",  # 不报告到外部平台
    push_to_hub=False,  # 不推送到Hugging Face Hub
    gradient_accumulation_steps=4,  # 梯度累积,模拟更大的批量
    predict_with_generate=True,
    generation_max_length=128,  # 生成的最大长度
)

参数解释

  • learning_rate:学习率,太大容易“学过头”,太小学得慢。5e-5是个比较安全的值。
  • per_device_train_batch_size:一次训练多少条数据。如果你的GPU显存小(比如8G),可以设为1或2。
  • num_train_epochs:整个数据集训练几轮。3轮通常够用,数据少可以多几轮。
  • gradient_accumulation_steps:如果显存不够,用这个模拟更大的批量。

2.5 开始训练

一切就绪,开始训练!

from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=dev_dataset,
    tokenizer=processor.tokenizer,
)

print("开始训练...")
trainer.train()

训练时间取决于你的数据量和硬件。如果有GPU,几千条数据训练3轮大概需要几小时。训练过程中你会看到损失值(loss)在下降,这说明模型正在学习。

训练完成后,模型会自动保存到./qwen_asr_finetuned目录。

3. 评估效果:看看学得怎么样

训练完了,得看看效果怎么样。不能光看训练时的损失值,要用没见过的数据来测试。

3.1 加载测试集

# 加载测试集
test_dataset = prepare_dataset("你的方言数据集/test")
test_dataset = test_dataset.map(
    preprocess_function,
    batched=True,
    batch_size=4,
    remove_columns=test_dataset.column_names
)

# 加载微调后的模型
finetuned_model = AutoModelForSpeechSeq2Seq.from_pretrained("./qwen_asr_finetuned")
finetuned_model.to(device)

3.2 计算识别准确率

语音识别通常用WER(词错误率)来评估,WER越低越好。

from evaluate import load
import numpy as np

wer_metric = load("wer")

def compute_metrics(pred):
    """计算评估指标"""
    pred_ids = pred.predictions
    label_ids = pred.label_ids
    
    # 将预测和标签转换成文字
    pred_str = processor.batch_decode(pred_ids, skip_special_tokens=True)
    label_str = processor.batch_decode(label_ids, skip_special_tokens=True)
    
    # 计算WER
    wer = wer_metric.compute(predictions=pred_str, references=label_str)
    
    return {"wer": wer}

# 在测试集上评估
results = trainer.evaluate(test_dataset)
print(f"测试集WER: {results['eval_wer']:.4f}")

3.3 对比微调前后效果

为了直观感受微调的效果,我们可以对比一下:

def transcribe_audio(model, audio_path):
    """转录单个音频"""
    # 加载音频
    import librosa
    audio, sr = librosa.load(audio_path, sr=16000)
    
    # 预处理
    inputs = processor(audio, sampling_rate=sr, return_tensors="pt")
    inputs = inputs.to(device)
    
    # 生成转录
    with torch.no_grad():
        generated_ids = model.generate(**inputs, max_length=128)
    
    transcription = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
    return transcription

# 测试几个样例
test_samples = [
    ("sample1.wav", "今日天气好好,适合出门散步"),
    ("sample2.wav", "请问这个商品有货吗"),
]

print("对比微调前后效果:")
print("-" * 50)

for audio_file, true_text in test_samples:
    audio_path = f"你的方言数据集/test/audio/{audio_file}"
    
    # 原始模型
    original_transcription = transcribe_audio(model, audio_path)
    
    # 微调后模型
    finetuned_transcription = transcribe_audio(finetuned_model, audio_path)
    
    print(f"音频: {audio_file}")
    print(f"真实文本: {true_text}")
    print(f"原始模型识别: {original_transcription}")
    print(f"微调后识别: {finetuned_transcription}")
    print(f"改进: {'✓' if finetuned_transcription == true_text else '✗'}")
    print("-" * 50)

4. 实用技巧与常见问题

在实际操作中,你可能会遇到一些问题。这里分享一些经验。

4.1 数据不够怎么办?

如果方言数据很少,可以试试这些方法:

  1. 数据增强:对音频做点小改动,生成新数据
import numpy as np

def augment_audio(audio, sr):
    """简单的音频增强"""
    # 添加轻微噪声
    noise = np.random.randn(len(audio)) * 0.005
    augmented = audio + noise
    
    # 稍微改变语速(这里简化处理)
    return augmented
  1. 迁移学习:先用相近方言的数据预训练,再用你的数据微调

  2. 少样本学习:用Prompt Engineering技巧,在输入中给模型一些提示

4.2 训练时显存不够?

如果遇到CUDA out of memory错误:

  1. 减小per_device_train_batch_size,比如从4改成2或1
  2. 使用梯度累积(gradient_accumulation_steps),比如批量2累积4步相当于批量8
  3. 使用半精度训练(torch.float16
  4. 用PEFT(参数高效微调)技术,只训练部分参数

4.3 效果提升不明显?

如果微调后效果改善不大:

  1. 检查数据质量:标注是否正确?音频是否清晰?
  2. 增加数据量:特别是覆盖更多发音变化
  3. 调整学习率:试试1e-5或3e-5
  4. 增加训练轮数:从3轮增加到5轮或10轮
  5. 检查数据分布:训练集和测试集是否来自同一分布?

5. 部署使用:让模型真正用起来

训练好的模型要能用起来才有价值。这里给几个简单的部署方案。

5.1 本地API服务

用FastAPI快速搭建一个服务:

from fastapi import FastAPI, File, UploadFile
import torch
import librosa
import io

app = FastAPI()

# 加载模型(在实际应用中应该只加载一次)
model = None
processor = None

@app.on_event("startup")
async def load_model():
    global model, processor
    model = AutoModelForSpeechSeq2Seq.from_pretrained("./qwen_asr_finetuned")
    processor = AutoProcessor.from_pretrained("Qwen/Qwen3-ASR-1.7B")
    model.to("cuda" if torch.cuda.is_available() else "cpu")

@app.post("/transcribe")
async def transcribe(file: UploadFile = File(...)):
    """转录上传的音频文件"""
    
    # 读取音频
    audio_bytes = await file.read()
    audio, sr = librosa.load(io.BytesIO(audio_bytes), sr=16000)
    
    # 预处理
    inputs = processor(audio, sampling_rate=sr, return_tensors="pt")
    inputs = inputs.to(model.device)
    
    # 生成转录
    with torch.no_grad():
        generated_ids = model.generate(**inputs, max_length=128)
    
    transcription = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
    
    return {"text": transcription, "language": "dialect"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

运行后,你就可以通过HTTP请求来转录音频了。

5.2 批量处理脚本

如果需要处理大量音频文件:

import os
from tqdm import tqdm

def batch_transcribe(input_dir, output_file):
    """批量转录目录下的所有音频文件"""
    
    results = []
    audio_files = [f for f in os.listdir(input_dir) if f.endswith(('.wav', '.mp3', '.flac'))]
    
    for audio_file in tqdm(audio_files, desc="处理中"):
        audio_path = os.path.join(input_dir, audio_file)
        
        try:
            transcription = transcribe_audio(finetuned_model, audio_path)
            results.append(f"{audio_file}\t{transcription}")
        except Exception as e:
            print(f"处理 {audio_file} 时出错: {e}")
            results.append(f"{audio_file}\tERROR")
    
    # 保存结果
    with open(output_file, 'w', encoding='utf-8') as f:
        f.write("\n".join(results))
    
    print(f"处理完成,结果保存到 {output_file}")

# 使用
batch_transcribe("待处理的音频文件夹", "转录结果.txt")

5.3 集成到现有系统

如果你已经有其他系统,可以把模型集成进去:

class DialectASR:
    """方言语音识别器"""
    
    def __init__(self, model_path="./qwen_asr_finetuned"):
        self.model = AutoModelForSpeechSeq2Seq.from_pretrained(model_path)
        self.processor = AutoProcessor.from_pretrained("Qwen/Qwen3-ASR-1.7B")
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.model.to(self.device)
        self.model.eval()  # 设置为评估模式
    
    def transcribe(self, audio_path):
        """转录音频文件"""
        return transcribe_audio(self.model, audio_path)
    
    def transcribe_stream(self, audio_stream, sample_rate=16000):
        """转录音频流"""
        inputs = self.processor(
            audio_stream, 
            sampling_rate=sample_rate, 
            return_tensors="pt"
        )
        inputs = inputs.to(self.device)
        
        with torch.no_grad():
            generated_ids = self.model.generate(**inputs, max_length=128)
        
        return self.processor.batch_decode(generated_ids, skip_special_tokens=True)[0]

# 使用示例
asr = DialectASR()
result = asr.transcribe("test_audio.wav")
print(f"识别结果: {result}")

6. 总结

走完这一整套流程,你应该对如何微调Qwen3-ASR-1.7B进行方言识别有了比较清晰的认识。从准备数据到训练模型,再到评估和部署,每个环节都有需要注意的地方。

实际做下来,我觉得最关键的是数据质量。好的数据能让模型学得又快又好,差的数据再怎么调参数效果也有限。所以花时间在数据准备和清洗上是值得的。

另一个体会是,微调不是一蹴而就的,可能需要多次尝试。第一次效果不好很正常,调整学习率、增加数据、修改预处理方式,多试几次总能找到适合你场景的方案。

最后,微调后的模型在实际使用中确实能带来明显的效果提升。特别是在特定场景下,比如某个地区的方言客服系统,或者某个行业的专业术语识别,定制化的模型比通用模型要好用得多。

如果你刚开始接触,建议从小规模数据开始,先跑通整个流程,看到效果后再逐步扩大。遇到问题也不用慌,大部分问题都能通过调整参数或改进数据来解决。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐