Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 70137aa

Browse files
committed
[speech] use api to calculate metric
1 parent 4cf13bd commit 70137aa

6 files changed

Lines changed: 364 additions & 424 deletions

File tree

applications/speech-cmd-analysis/README.md

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,17 @@ pip install pydub
4949
## 3. 数据准备
5050

5151
本应用来自于语音报销工单信息录入场景,即员工向公司报销部门提出交通费报销的口头申请,在传统场景下,报销审核人员需要人工将语音转换为文字信息,并从中抽取记录报销需要的``时间````出发地````目的地````费用``字段,而在本应用可以端到端的完成这一工作。相应的数据集为[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl),共50条标注数据,用于信息抽取模型在交通费报销场景下的优化,示例数据如下:
52+
5253
```json
5354
{"id": 39, "text": "10月16日高铁从杭州到上海南站车次d5414共48元", "relations": [], "entities": [{"id": 90, "start_offset": 0, "end_offset": 6, "label": "时间"}, {"id": 77, "start_offset": 9, "end_offset": 11, "label": "出发地"}, {"id": 91, "start_offset": 12, "end_offset": 16, "label": "目的地"}, {"id": 92, "start_offset": 24, "end_offset": 26, "label": "费用"}]}
5455
```
5556

57+
其中抽取的目标(schema)表示为:
58+
59+
```python
60+
schema = ['出发地', '目的地', '费用', '时间']
61+
```
62+
5663
标注数据保存在同一个文本文件中,每条样例占一行且存储为``json``格式,其包含以下字段
5764
- ``id``: 样本在数据集中的唯一标识ID。
5865
- ``text``: 语音报销工单的原始文本数据。
@@ -98,9 +105,8 @@ python audio_to_wav.py --audio_file ./audios_raw/ --save_dir ./audios_wav/
98105

99106
#### 自定义数据标注
100107

101-
对于不同的应用场景,关键信息的配置多种多样,直接应用通用信息抽取模型的效果可能不够理想。这时可以标注少量场景相关的数据,利用few-shot learning技术来改进特定场景下的信息抽取效果。
108+
对于不同的应用场景,关键信息的配置多种多样,直接应用通用信息抽取模型的效果可能不够理想。这时可以标注少量场景相关的数据,利用few-shot learning技术来改进特定场景下的信息抽取效果。在本应用场景中,标注数据为[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl)。针对其他场景,可使用[doccano](https://github.com/doccano/doccano)平台标注并导出自定义数据。
102109

103-
自定义数据的格式应与[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl)相同,保存在``./data/``目录下。
104110

105111
## 4. 模型训练
106112

@@ -115,32 +121,35 @@ python audio_to_wav.py --audio_file ./audios_raw/ --save_dir ./audios_wav/
115121
├── preprocess.py # 数据预处理脚本
116122
├── finetune.py # 信息抽取模型 fine-tune 脚本
117123
├── model.py # 信息抽取模型(UIE)组网脚本
118-
├── metric.py # 信息抽取模型指标计算脚本
119124
└── utils.py # 辅助函数
120125
```
121126

122127
#### 数据预处理
123128

124-
准备好符合数据格式要求的自定义数据,放在``./data/data.txt``文件。执行以下脚本,按设置的比例划分数据集,同时构造负样本用于提升模型的学习效果。
129+
下载[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl),存储在``./data/``目录下。执行以下脚本,按设置的比例划分数据集,同时构造负样本用于提升模型的学习效果。
125130

126131
```shell
127132
python preprocess.py \
128-
--input_file ./data/data.txt \
133+
--input_file ./data/audio-expense-account.jsonl \
134+
--task_type "ext" \
129135
--save_dir ./data/ \
130136
--negative_ratio 5 \
131-
--splits 0.4 0.6
137+
--splits 0.2 0.8 0.0 \
138+
--seed 1000
132139
```
133140

134141
可配置参数包括
135142

136-
- ``input_file``: 原始数据文件名。文件内容应与[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl)的格式一致。
137-
- ``save_dir``: 训练数据的保存目录。若``splits``为空,则数据存储在``processed_data.txt``文件,若``splits``设置为长度为2的列表,则数据存储在目录下的``train.txt````dev.txt````test.txt``文件。
138-
- ``negative_ratio``: 负样本与正样本的比例。使用负样本策略可提升模型效果,负样本数量 = negative_ratio * 正样本数量。
139-
- ``splits``: 划分数据集时训练集、验证集所占的比例。默认为[0.4, 0.6]表示按照``4:6``的比例将数据划分为训练集和验证集。
140-
141-
#### 预训练模型参数
143+
- ``input_file``: 标注数据文件名。数据格式应与[语音报销工单数据](https://paddlenlp.bj.bcebos.com/datasets/erniekit/speech-cmd-analysis/audio-expense-account.jsonl)一致,即doccano平台导出格式。
144+
- ``save_dir``: 训练数据的保存目录,默认存储在``data``目录下。若``splits``为空,则数据存储在``train.txt``文件,若``splits``为长度为3的列表,则数据存储在目录下的``train.txt````dev.txt````test.txt``文件。
145+
- ``negative_ratio``: 负样本与正样本的比例,该参数只对抽取类型任务有效。使用负样本策略可提升模型效果,负样本数量 = negative_ratio * 正样本数量。
146+
- ``splits``: 划分数据集时训练集、验证集所占的比例。默认为[0.8, 0.1, 0.1]表示按照``8:1:1``的比例将数据划分为训练集、验证集和测试集。
147+
- ``task_type``: 选择任务类型,可选有抽取和分类两种类型的任务。本应用场景为抽取式任务``ext``
148+
- ``options``:(可选)指定分类任务的类别标签,该参数只对分类类型任务有效。
149+
- ``prompt_prefix``:(可选)声明分类任务的prompt前缀信息,该参数只对分类类型任务有效。
150+
- ``is_shuffle``: 是否对数据集进行随机打散,默认为True。
151+
- ``seed``: 随机种子,默认为1000.
142152

143-
下载预训练好的[UIE模型](https://bj.bcebos.com/paddlenlp/taskflow/information_extraction/uie_base/model_state.pdparams),放在`./uie_model/`目录下。
144153

145154
#### 定制化模型训练
146155

@@ -151,44 +160,48 @@ CUDA_VISIBLE_DEVICES=0 python finetune.py \
151160
--train_path ./data/train.txt \
152161
--dev_path ./data/dev.txt \
153162
--save_dir ./checkpoint \
163+
--model uie-base \
154164
--learning_rate 1e-5 \
155165
--batch_size 16 \
156166
--max_seq_len 512 \
157167
--num_epochs 50 \
158-
--init_from_ckpt ./uie_model/model_state.pdparams \
159168
--seed 1000 \
160169
--logging_steps 10 \
161-
--valid_steps 100 \
170+
--valid_steps 10 \
162171
--device gpu
163172
```
164173

165174
可配置参数包括
166175

167-
- ``train_path``: 训练集数据文件路径
168-
- ``dev_path``: 验证集数据文件路径
169-
- ``save_dir``: 保存训练模型的目录
176+
- `train_path`: 训练集文件路径
177+
- `dev_path`: 验证集文件路径
178+
- `save_dir`: 模型存储路径,默认为`./checkpoint`
170179
- ``init_from_ckpt``: 可选,模型参数路径,热启动模型训练。默认为None。
171-
- ``learning_rate``: 模型训练的学习率的大小。
172-
- ``batch_size``: 每次迭代每张卡上的样本数量。
173-
- ``max_seq_len``: 最大句子长度,超过该长度将被截断。
174-
- ``num_epochs``: 训练轮数。
175-
- ``logging_steps``: 日志打印间隔的step数。
176-
- ``valid_steps``: 在验证集上进行评估间隔的step数。
177-
- ``device``: 模型训练使用的设备,可选cpu或gpu。
178-
- ``seed``: 随机数种子,用于训练过程复现。
180+
- `learning_rate`: 学习率,默认为1e-5。
181+
- `batch_size`: 批处理大小,请结合显存情况进行调整,若出现显存不足,请适当调低这一参数,默认为16。
182+
- `max_seq_len`: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。
183+
- `num_epochs`: 训练轮数,默认为100。
184+
- `model`: 选择模型,程序会基于选择的模型进行模型微调,可选有`uie-base``uie-tiny`
185+
- `seed`: 随机种子,默认为1000.
186+
- `logging_steps`: 日志打印的间隔steps数,默认为10。
187+
- `valid_steps`: evaluate的间隔steps数,默认为100。
188+
- `device`: 模型训练使用的设备,可选cpu或gpu。
179189

180190

181191
## 5. 模型预测
182192

183-
预测时使用的schema应与finetune阶段训练数据的schema保持一致。在语音报销工单信息录入场景下,首先准备好``.wav``格式的音频文件,然后在BaiduAI开放平台创建语音识别应用以获取API Key和Secret Key,最后加载用场景数据finetune后的模型参数,执行语音指令解析脚本即可抽取报销需要的``时间````出发地````目的地````费用``字段。具体命令如下
193+
预测时使用的schema应与finetune阶段训练数据的schema保持一致以得到更好的效果。在语音报销工单信息录入场景下,
194+
- 首先准备好``.wav``格式的音频文件,例如下载[sample.wav](https://bj.bcebos.com/paddlenlp/applications/speech-cmd-analysis/sample.wav)放在``./audios_wav/``目录下。
195+
- 然后在BaiduAI开放平台创建语音识别应用以获取API Key和Secret Key。
196+
- 最后加载用场景数据finetune后的模型参数,执行语音指令解析脚本即可抽取报销需要的``时间````出发地````目的地````费用``字段。具体命令如下
184197

185198
```shell
186199
python pipeline.py \
187200
--api_key '4E1BG9lTnlSeIf1NQFlrxxxx' \
188201
--secret_key '544ca4657ba8002e3dea3ac2f5fxxxxx' \
189202
--audio_file ./audios_wav/sample.wav \
190-
--uie_model ./checkpoint/model_best/model_state.pdparams \
191-
--schema ['时间', '出发地', '目的地', '费用']
203+
--uie_model ./checkpoint/model_best/ \
204+
--schema '时间' '出发地' '目的地' '费用'
192205
```
193206

194207
可配置参数包括

applications/speech-cmd-analysis/finetune.py

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@
1515
import argparse
1616
import time
1717
import os
18-
import random
1918
from functools import partial
2019

21-
import numpy as np
2220
import paddle
2321
from paddle.utils.download import get_path_from_url
2422
from paddlenlp.datasets import load_dataset
2523
from paddlenlp.transformers import AutoTokenizer
24+
from paddlenlp.metrics import SpanEvaluator
2625

2726
from model import UIE
28-
from utils import set_seed, convert_example, reader, create_dataloader, evaluate, get_f1, get_metric
27+
from utils import set_seed, convert_example, reader, MODEL_MAP, evaluate, create_dataloader
2928

3029

3130
def do_train():
@@ -36,30 +35,36 @@ def do_train():
3635

3736
set_seed(args.seed)
3837

39-
tokenizer = AutoTokenizer.from_pretrained('ernie-1.0')
40-
model = UIE()
38+
encoding_model = MODEL_MAP[args.model]['encoding_model']
39+
hidden_size = MODEL_MAP[args.model]['hidden_size']
40+
url = MODEL_MAP[args.model]['url']
4141

42-
if args.init_from_ckpt:
43-
if not os.path.isfile(args.init_from_ckpt):
44-
raise Exception('%s is not a .pdparams file, please check it.' %
45-
args.init_from_ckpt)
46-
state_dict = paddle.load(args.init_from_ckpt)
47-
model.set_dict(state_dict)
48-
print('Init from: {}'.format(args.init_from_ckpt))
42+
tokenizer = AutoTokenizer.from_pretrained(encoding_model)
43+
model = UIE(encoding_model, hidden_size)
44+
45+
if args.init_from_ckpt is not None:
46+
pretrained_model_path = args.init_from_ckpt
4947
else:
50-
pretrained_uie_url = 'https://bj.bcebos.com/paddlenlp/taskflow/information_extraction/uie_base/model_state.pdparams'
51-
tmp_path = './_tmp_pretrained_uie/'
52-
if not os.path.exists(tmp_path):
53-
os.makedirs(tmp_path)
54-
get_path_from_url(pretrained_uie_url, tmp_path)
55-
state_dict = paddle.load(tmp_path + 'model_state.pdparams')
56-
model.set_dict(state_dict)
57-
print('Init from: {}'.format(pretrained_uie_url))
48+
pretrained_model_path = os.path.join(args.model, "model_state.pdparams")
49+
if not os.path.exists(pretrained_model_path):
50+
get_path_from_url(url, args.model)
51+
52+
state_dict = paddle.load(pretrained_model_path)
53+
model.set_dict(state_dict)
54+
print("Init from: {}".format(pretrained_model_path))
5855
if paddle.distributed.get_world_size() > 1:
5956
model = paddle.DataParallel(model)
6057

61-
train_ds = load_dataset(reader, data_path=args.train_path, lazy=False)
62-
dev_ds = load_dataset(reader, data_path=args.dev_path, lazy=False)
58+
train_ds = load_dataset(
59+
reader,
60+
data_path=args.train_path,
61+
max_seq_len=args.max_seq_len,
62+
lazy=False)
63+
dev_ds = load_dataset(
64+
reader,
65+
data_path=args.dev_path,
66+
max_seq_len=args.max_seq_len,
67+
lazy=False)
6368

6469
trans_func = partial(
6570
convert_example, tokenizer=tokenizer, max_seq_len=args.max_seq_len)
@@ -79,7 +84,8 @@ def do_train():
7984
optimizer = paddle.optimizer.AdamW(
8085
learning_rate=args.learning_rate, parameters=model.parameters())
8186

82-
loss_cal = paddle.nn.BCELoss()
87+
criterion = paddle.nn.BCELoss()
88+
metric = SpanEvaluator()
8389

8490
loss_list = []
8591
global_step = 0
@@ -93,8 +99,8 @@ def do_train():
9399
pos_ids)
94100
start_ids = paddle.cast(start_ids, 'float32')
95101
end_ids = paddle.cast(end_ids, 'float32')
96-
loss_start = loss_cal(start_prob, start_ids)
97-
loss_end = loss_cal(end_prob, end_ids)
102+
loss_start = criterion(start_prob, start_ids)
103+
loss_end = criterion(end_prob, end_ids)
98104
loss = (loss_start + loss_end) / 2.0
99105
loss.backward()
100106
optimizer.step()
@@ -105,52 +111,52 @@ def do_train():
105111
if global_step % args.logging_steps == 0 and rank == 0:
106112
time_diff = time.time() - tic_train
107113
loss_avg = sum(loss_list) / len(loss_list)
108-
num_correct, num_infer, num_label = get_metric(
109-
start_prob, end_prob, start_ids, end_ids)
110-
precision, recall, f1 = get_f1(num_correct, num_infer,
111-
num_label)
112114
print(
113-
'global step %d, epoch: %d, loss: %.5f, precision: %.5f, recall: %.5f, F1: %.5f, speed: %.2f step/s'
114-
% (global_step, epoch, loss_avg, precision, recall, f1,
115+
"global step %d, epoch: %d, loss: %.5f, speed: %.2f step/s"
116+
% (global_step, epoch, loss_avg,
115117
args.logging_steps / time_diff))
116118
tic_train = time.time()
117119

118120
if global_step % args.valid_steps == 0 and rank == 0:
119-
save_dir = os.path.join(args.save_dir, 'model_%d' % global_step)
121+
save_dir = os.path.join(args.save_dir, "model_%d" % global_step)
120122
if not os.path.exists(save_dir):
121123
os.makedirs(save_dir)
122-
save_param_path = os.path.join(save_dir, 'model_state.pdparams')
124+
save_param_path = os.path.join(save_dir, "model_state.pdparams")
123125
paddle.save(model.state_dict(), save_param_path)
124126

125-
precision, recall, f1 = evaluate(model, dev_data_loader)
126-
print('Evaluation precision: %.5f, recall: %.5f, F1: %.5f' %
127+
precision, recall, f1 = evaluate(model, metric, dev_data_loader)
128+
print("Evaluation precision: %.5f, recall: %.5f, F1: %.5f" %
127129
(precision, recall, f1))
128130
if f1 > best_f1:
131+
print(
132+
f"best F1 performence has been updated: {best_f1:.5f} --> {f1:.5f}"
133+
)
129134
best_f1 = f1
130-
save_dir = os.path.join(args.save_dir, 'model_best')
135+
save_dir = os.path.join(args.save_dir, "model_best")
131136
save_best_param_path = os.path.join(save_dir,
132-
'model_state.pdparams')
137+
"model_state.pdparams")
133138
paddle.save(model.state_dict(), save_best_param_path)
134139
tic_train = time.time()
135140

136141

137-
if __name__ == '__main__':
142+
if __name__ == "__main__":
138143
# yapf: disable
139144
parser = argparse.ArgumentParser()
140145

141-
parser.add_argument('--batch_size', default=16, type=int, help='Batch size per GPU/CPU for training.')
142-
parser.add_argument('--learning_rate', default=1e-5, type=float, help='The initial learning rate for Adam.')
143-
parser.add_argument('--train_path', default=None, type=str, help='The path of train set.')
144-
parser.add_argument('--dev_path', default=None, type=str, help='The path of dev set.')
145-
parser.add_argument('--save_dir', default='./checkpoint', type=str, help='The output directory where the model checkpoints will be written.')
146-
parser.add_argument('--max_seq_len', default=512, type=int, help='The maximum total input sequence length after tokenization. '
147-
'Sequences longer than this will be truncated, sequences shorter will be padded.')
148-
parser.add_argument('--num_epochs', default=50, type=int, help='Total number of training epochs to perform.')
149-
parser.add_argument('--init_from_ckpt', default=None, type=str, help='The path of checkpoint to be loaded.')
150-
parser.add_argument('--seed', default=1000, type=int, help='random seed for initialization')
151-
parser.add_argument('--logging_steps', default=10, type=int, help='The interval steps to logging.')
152-
parser.add_argument('--valid_steps', default=100, type=int, help='The interval steps to evaluate model performance.')
153-
parser.add_argument('--device', choices=['cpu', 'gpu'], default='gpu', help='Select which device to train model, defaults to gpu.')
146+
parser.add_argument("--batch_size", default=16, type=int, help="Batch size per GPU/CPU for training.")
147+
parser.add_argument("--learning_rate", default=1e-5, type=float, help="The initial learning rate for Adam.")
148+
parser.add_argument("--train_path", default=None, type=str, help="The path of train set.")
149+
parser.add_argument("--dev_path", default=None, type=str, help="The path of dev set.")
150+
parser.add_argument("--save_dir", default='./checkpoint', type=str, help="The output directory where the model checkpoints will be written.")
151+
parser.add_argument("--max_seq_len", default=512, type=int, help="The maximum input sequence length. "
152+
"Sequences longer than this will be truncated, sequences shorter will be padded.")
153+
parser.add_argument("--num_epochs", default=100, type=int, help="Total number of training epochs to perform.")
154+
parser.add_argument("--seed", default=1000, type=int, help="Random seed for initialization")
155+
parser.add_argument("--logging_steps", default=10, type=int, help="The interval steps to logging.")
156+
parser.add_argument("--valid_steps", default=100, type=int, help="The interval steps to evaluate model performance.")
157+
parser.add_argument('--device', choices=['cpu', 'gpu'], default="gpu", help="Select which device to train model, defaults to gpu.")
158+
parser.add_argument("--model", choices=["uie-base", "uie-tiny"], default="uie-base", type=str, help="Select the pretrained model for few-shot learning.")
159+
parser.add_argument("--init_from_ckpt", default=None, type=str, help="The path of model parameters for initialization.")
154160

155161
args = parser.parse_args()
156162
# yapf: enable

0 commit comments

Comments
 (0)