我们团队有一个老的NLP(自然语言处理)系统,用的是传统的机器学习方法,比如TF-IDF、SVM、CRF、规则引擎等,已经跑了好几年了,支撑着文本分类、命名实体识别、情感分析、关键词提取等多个NLP任务。
但是,随着业务的发展,老系统的问题越来越多,准确率上不去,很多边界情况处理不好,维护成本高,加新任务、新领域,都要重新写规则,调参数,很麻烦,扩展性差。而且,这几年,深度学习在NLP领域,发展很快,效果比传统方法好很多,所以,我们决定,把NLP系统,迁移到新的深度学习框架上,用深度学习的方法,比如CNN、RNN、LSTM、Attention等,提升准确率和效果。
整个迁移过程,花了大概三个月的时间,踩了很多坑,也积累了一些经验,今天,就来分享一下,我们NLP系统迁移的实战经验,希望能给大家一些参考。
一、为什么要迁移
先说说,我们为什么要迁移NLP系统。
1. 老系统准确率上不去: 老系统,用的是传统的机器学习方法,TF-IDF + SVM做文本分类,CRF做命名实体识别,规则引擎做情感分析和关键词提取,这些方法,在简单的场景下,效果还可以,但是,随着业务的发展,文本越来越复杂,领域越来越多,老系统的准确率,就上不去了,很多边界情况,处理不好,比如,网络用语、谐音、歧义、长文本、多领域混合等,老系统,经常识别错误,用户反馈很多。
而且,传统方法,对特征工程的依赖很大,要人工设计特征,调参数,很费时间,效果,也有天花板,很难再提升了。
2. 维护成本高: 老系统,是好几年前写的,代码很老,文档不全,很多规则,都是当时的开发写的,现在,人都走了,没人知道,那些规则,是为什么这么写的,改一个规则,可能会影响其他的地方,很容易出问题,维护成本很高。
而且,加新任务、新领域,都要重新写规则,调参数,很麻烦,要花很多时间,跟不上业务的发展速度。
3. 扩展性差: 老系统,是单体架构,所有的NLP任务,都在一个系统里,耦合度很高,加一个新任务,要改很多地方,而且,性能也不好,高并发的时候,响应很慢,扩展性很差。
而且,老系统,用的是Python 2,现在,Python 2已经停止维护了,很多新的库,都不支持Python 2了,继续用老系统,技术债越来越多。
4. 深度学习效果更好: 这几年,深度学习在NLP领域,发展很快,Word2Vec、GloVe、CNN、RNN、LSTM、GRU、Attention、Seq2Seq、Transformer等,不断涌现,在文本分类、命名实体识别、情感分析、机器翻译、问答系统等任务上,效果都比传统方法好很多,而且,不需要人工设计特征,端到端训练,很方便。
我们做了一些实验,用深度学习的方法,在我们的数据集上,文本分类的准确率,比老系统,提高了5-8个百分点,命名实体识别的F1值,提高了3-5个百分点,效果提升很明显,所以,我们决定,迁移到深度学习框架上。
二、迁移前的准备
迁移之前,我们做了充分的准备,不打无准备之仗。
1. 梳理老系统的功能和接口: 首先,我们梳理了老系统,所有的功能和接口,包括,文本分类、命名实体识别、情感分析、关键词提取、摘要生成等,每个任务,有哪些接口,输入输出是什么,调用方有哪些,QPS是多少,响应时间要求是多少,等等,都梳理清楚,做成文档,这样,迁移的时候,就知道,要迁移哪些功能,要兼容哪些接口,要满足哪些性能要求。
2. 整理数据集: 深度学习,需要大量的标注数据,所以,我们整理了老系统,所有的数据集,包括,历史的标注数据,线上的日志数据,用户反馈的bad case等,然后,对数据进行清洗、去重、标注,整理成,标准的训练集、验证集、测试集。
这个过程,花了很多时间,因为,老的数据,格式不统一,质量参差不齐,很多数据,标注不准确,要重新标注,我们组织了团队的人,一起标注数据,花了大概一个月,才整理出,比较高质量的数据集。
3. 技术选型: 然后,我们做了技术选型,深度学习框架,选哪个?当时,主流的深度学习框架,有TensorFlow、PyTorch、Keras、MXNet等,我们对比了一下,最后,选了TensorFlow,因为,TensorFlow生态最完善,文档最多,社区最活跃,生产环境部署,也最成熟,而且,我们团队,对TensorFlow比较熟悉,所以,选了TensorFlow。
模型方面,文本分类,我们选了TextCNN和BiLSTM+Attention,命名实体识别,我们选了BiLSTM+CRF,情感分析,我们选了TextCNN,关键词提取,我们选了基于词向量的方法,摘要生成,我们选了Seq2Seq+Attention。
4. 制定迁移计划: 最后,我们制定了详细的迁移计划,分阶段,分步骤,每个阶段,做什么,谁来做,什么时候完成,都规划好,而且,每个阶段,都有验收标准,确保迁移的质量。
我们的迁移计划,分为以下几个阶段:
- 数据准备和技术选型(2周)
- 模型训练和调优(4周)
- 模型评估和对比(2周)
- 新系统开发和部署(2周)
- 灰度切换和验证(2周)
- 全量上线和老系统下线(2周)
总共,大概三个月的时间,我们预留了一些缓冲时间,防止出现意外情况。
三、新旧系统的对比
迁移之前,我们也做了新旧系统的对比,明确了,新系统,要在哪些方面,比老系统好。
| 对比项 | 老系统 | 新系统 |
|---|---|---|
| 技术方法 | 传统机器学习(TF-IDF、SVM、CRF、规则) | 深度学习(CNN、LSTM、Attention、Seq2Seq) |
| 特征工程 | 人工设计特征,依赖经验 | 端到端学习,自动提取特征 |
| 准确率 | 一般,有天花板 | 更高,还有提升空间 |
| 维护成本 | 高,规则多,难维护 | 低,模型训练,自动学习 |
| 扩展性 | 差,加新任务麻烦 | 好,加新任务,训练新模型就行 |
| 性能 | 一般,高并发响应慢 | 更好,GPU加速,批量处理 |
| 技术栈 | Python 2,老框架 | Python 3,TensorFlow,新框架 |
通过对比,我们明确了,新系统的目标,就是,在准确率、维护成本、扩展性、性能等方面,都要比老系统好,而且,要兼容老系统的接口,让调用方,无感知迁移。
四、迁移的步骤
下面,详细说说,我们迁移的步骤。
第一步:数据准备
数据准备,是迁移的第一步,也是最重要的一步,深度学习,数据是基础,数据质量不好,模型效果,肯定不好。
我们做了以下几件事:
- 数据收集: 收集老系统,所有的历史数据,包括,标注数据、线上日志、用户反馈的bad case等,还有,公开的数据集,也收集了一些,作为补充。
- 数据清洗: 对收集到的数据,进行清洗,去重,去噪,过滤掉,质量不好的数据,比如,标注错误的,文本太短的,内容无关的,等等。
- 数据标注: 对清洗后的数据,进行标注,我们组织了团队的人,一起标注,制定了标注规范,每个人,按照规范标注,标注完,还要交叉检查,确保标注的质量。
- 数据划分: 把标注好的数据,划分为训练集、验证集、测试集,比例大概是8:1:1,而且,保证,各个类别,各个领域的数据,分布均匀,避免数据倾斜。
- 数据增强: 对于数据量少的类别和领域,我们做了数据增强,比如,同义词替换、回译、随机插入、随机删除等,增加数据量,提升模型的泛化能力。
数据准备,花了大概两周的时间,虽然,很枯燥,但是,很重要,数据质量好,模型效果,才有保障。
第二步:模型训练和调优
数据准备好之后,就开始模型训练和调优了。
我们用TensorFlow,搭建了各个任务的模型,文本分类,用了TextCNN和BiLSTM+Attention,命名实体识别,用了BiLSTM+CRF,情感分析,用了TextCNN,关键词提取,用了基于词向量的方法,摘要生成,用了Seq2Seq+Attention。
词向量,我们用了Word2Vec,在我们的语料上,预训练了词向量,然后,作为模型的输入,也可以,在训练的时候,微调词向量。
训练的时候,我们用了GPU加速,用的是阿里云的GPU服务器,一块Tesla P100,训练速度,比CPU快很多。
调优的时候,我们主要调了,以下这些超参数:
- 学习率: 学习率太大,容易震荡,不收敛,太小,收敛太慢,我们用了Adam优化器,学习率,从0.001开始调,最后,选了0.0005。
- 批次大小: 批次大小,影响训练的稳定性和速度,我们从32开始调,最后,选了64。
- 隐藏层大小: LSTM的隐藏层大小,影响模型的容量,我们从128开始调,最后,选了256。
- Dropout: 为了防止过拟合,我们加了Dropout,比例,从0.2开始调,最后,选了0.5。
- 正则化: 我们也加了L2正则化,防止过拟合,系数,选了0.0001。
- 训练轮数: 训练轮数,根据验证集的效果,早停,验证集的效果,连续5轮没有提升,就停止训练,防止过拟合。
调优的过程,很费时间,要不断地实验,对比效果,我们花了大概四周的时间,才把各个任务的模型,调优到,比较好的效果。
第三步:模型评估和对比
模型训练好之后,我们做了详细的评估和对比,和老系统,在同一个测试集上,对比效果。
评估指标,文本分类和情感分析,我们用了准确率、精确率、召回率、F1值;命名实体识别,我们用了精确率、召回率、F1值;关键词提取和摘要生成,我们用了人工评估,结合ROUGE、BLEU等指标。
对比结果,让我们很满意:
- 文本分类: 新系统的准确率,比老系统,提高了6.2个百分点,F1值,提高了5.8个百分点。
- 命名实体识别: 新系统的F1值,比老系统,提高了4.5个百分点。
- 情感分析: 新系统的准确率,比老系统,提高了7.1个百分点。
- 关键词提取: 新系统的效果,人工评估,比老系统,好很多,提取的关键词,更准确,更全面。
- 摘要生成: 新系统生成的摘要,更通顺,更准确,ROUGE值,比老系统,提高了不少。
而且,新系统,在边界情况的处理上,比老系统,好很多,比如,网络用语、谐音、歧义、长文本等,老系统,经常识别错误,新系统,大部分都能正确识别。
我们还做了bad case分析,把新系统,识别错误的case,都挑出来,分析原因,然后,针对性地,补充数据,调优模型,进一步提升效果。
模型评估和对比,花了大概两周的时间,确认,新系统的效果,全面超过老系统,满足业务要求,才进入下一步。
第四步:新系统开发和部署
模型效果达标之后,我们开始开发新系统,部署上线。
新系统,我们用了微服务架构,每个NLP任务,都是一个独立的服务,独立部署,独立扩展,耦合度低,扩展性好。
技术栈,我们用了Python 3 + TensorFlow + Flask + Gunicorn + Docker + Kubernetes,模型,用TensorFlow Serving部署,性能更好,也更方便。
接口方面,我们完全兼容老系统的接口,输入输出,和老系统,一模一样,这样,调用方,不需要改代码,就能切换到新系统,无感知迁移。
性能方面,我们做了优化:
- 批量处理: 支持批量请求,一次处理多个文本,提高吞吐量。
- GPU加速: 用GPU做推理,速度比CPU快很多。
- 模型优化: 用了TensorFlow的模型优化工具,比如,量化、剪枝,减小模型大小,提高推理速度。
- 缓存: 对高频请求,做了缓存,相同的文本,直接返回缓存的结果,提高响应速度。
- 负载均衡: 用Kubernetes做负载均衡,自动扩缩容,应对高并发。
部署方面,我们用了Docker + Kubernetes,容器化部署,方便扩展和管理,而且,支持灰度发布,蓝绿部署,回滚也很方便。
新系统开发和部署,花了大概两周的时间,开发完,我们做了详细的测试,单元测试、集成测试、性能测试,都通过了,才进入下一步。
第五步:灰度切换和验证
新系统部署好之后,我们没有直接全量切换,而是,做了灰度切换,逐步把流量,从老系统,切到新系统,验证新系统的稳定性和效果。
灰度切换的步骤:
- 1%流量: 先切1%的流量,到新系统,观察一天,看有没有问题,错误率、响应时间、效果,是不是正常。
- 5%流量: 1%没问题,再切到5%,观察一天。
- 10%流量: 5%没问题,再切到10%,观察一天。
- 30%流量: 10%没问题,再切到30%,观察两天。
- 50%流量: 30%没问题,再切到50%,观察两天。
- 80%流量: 50%没问题,再切到80%,观察两天。
- 100%流量: 80%没问题,再切到100%,全量上线。
整个灰度切换的过程,花了大概两周的时间,很谨慎,每一步,都观察清楚,没有问题,才进入下一步。
灰度期间,我们做了双写和对比,老系统和新系统,同时处理请求,然后,对比两个系统的结果,如果,新系统的结果,和老系统的不一样,就记录下来,分析原因,看看,是新系统的问题,还是老系统的问题,如果是新系统的问题,就及时修复。
而且,灰度期间,我们也收集了用户的反馈,如果,有用户反馈,新系统的效果不好,就及时排查,处理。
灰度切换,是很重要的一步,能有效降低迁移的风险,即使新系统有问题,影响的,也只是一小部分流量,不会影响整个业务,而且,能及时发现问题,修复问题。
第六步:全量上线和老系统下线
灰度切换,100%流量,都切到新系统,观察了一周,没有问题,效果和性能,都满足要求,用户也没有负面反馈,我们就宣布,新系统,全量上线了。
全量上线之后,我们没有马上,把老系统下线,而是,保留了一段时间,作为回滚预案,如果,新系统,出现重大问题,可以随时,切回老系统,保证业务的稳定。
又观察了一个月,新系统,运行稳定,没有出现重大问题,效果和性能,都很稳定,我们才正式,把老系统下线,释放资源。
老系统下线之后,我们也做了总结,把迁移过程中的经验和教训,都记录下来,做成文档,方便以后参考。
五、踩过的坑
整个迁移过程,我们踩了很多坑,下面,分享一下,我们踩过的坑,以及,怎么解决的。
坑1:数据不一致,导致模型效果不好 一开始,我们整理数据的时候,没有注意,数据的一致性,训练集、验证集、测试集,有重复的数据,而且,标注的标准,也不统一,有的人,标注得严,有的人,标注得松,导致,训练出来的模型,效果不好,验证集上,效果很好,但是,测试集和线上,效果很差。
后来,我们重新整理了数据,去重,统一标注标准,交叉检查,确保数据的一致性和质量,重新训练模型,效果,才好起来。
经验: 数据是深度学习的基础,数据质量,直接决定模型效果,一定要,重视数据的质量,确保数据的一致性,标注标准统一,去重,去噪,交叉检查。
坑2:模型效果不达标,调优很久 一开始,我们训练的模型,效果,没有老系统好,很着急,调了很久的超参数,效果,还是上不去。
后来,我们做了bad case分析,发现,主要是,数据量不够,特别是,一些小众领域和边界情况的数据,太少了,模型,学不好,而且,词向量,是用公开的语料训练的,和我们的领域,不太匹配。
后来,我们补充了数据,特别是,小众领域和边界情况的数据,而且,用我们自己的语料,重新训练了词向量,还做了数据增强,增加数据量,重新训练模型,效果,才超过了老系统。
经验: 模型效果不好,不要只调超参数,要做bad case分析,找到根本原因,是数据的问题,还是模型的问题,还是特征的问题,针对性地解决,数据量不够,就补数据,领域不匹配,就用领域语料训练词向量,模型不合适,就换模型。
坑3:性能不达标,推理太慢 模型效果达标之后,我们部署上线,发现,推理速度太慢了,单条请求,响应时间,要几百毫秒,高并发的时候,更慢,满足不了业务的性能要求。
后来,我们做了一系列的性能优化:
- 用TensorFlow Serving部署,比直接用Flask加载模型,速度快很多。
- 用GPU做推理,速度比CPU快好几倍。
- 做模型优化,量化、剪枝,减小模型大小,提高推理速度。
- 支持批量处理,一次处理多个请求,提高吞吐量。
- 加缓存,高频请求,直接返回缓存的结果。
- 用Kubernetes自动扩缩容,应对高并发。
经过这些优化,推理速度,提高了很多,单条请求,响应时间,降到了几十毫秒,高并发的时候,也能稳定响应,满足了业务的性能要求。
经验: 深度学习模型,推理速度,可能会比较慢,一定要,提前做性能测试,不满足要求,就要做优化,用GPU、模型优化、批量处理、缓存等方法,提高推理速度,满足业务的性能要求。
坑4:兼容性问题,调用方报错 灰度切换的时候,有几个调用方,报错了,说,接口返回的格式,和老系统不一样,我们查了一下,发现,有几个字段,我们的命名,和老系统,不一样,还有,空值的处理,也不一样,老系统,空值返回空字符串,我们返回null,导致,调用方解析报错。
后来,我们完全兼容了老系统的接口,字段命名、返回格式、空值处理、错误码等,都和老系统,一模一样,调用方,不需要改任何代码,就能切换,问题,才解决。
经验: 系统迁移,接口兼容性,很重要,一定要,完全兼容老系统的接口,输入输出、字段命名、返回格式、空值处理、错误码等,都要和老系统一致,让调用方,无感知迁移,不然,会影响调用方的业务。
坑5:没有回滚预案,差点出大事 灰度切换的时候,有一次,我们切到30%流量,新系统,出现了一个Bug,导致,部分请求,返回错误,我们当时,没有准备好回滚预案,切回老系统,花了一些时间,影响了一部分用户,虽然,影响不大,但是,很危险。
后来,我们完善了回滚预案,一键就能切回老系统,而且,灰度切换的时候,每一步,都观察清楚,没有问题,才进入下一步,而且,保留老系统,作为回滚预案,全量上线之后,也保留了一个月,确保,新系统稳定,才下线老系统。
经验: 系统迁移,一定要有回滚预案,随时能切回老系统,而且,灰度切换,要谨慎,逐步切换,每一步,都观察清楚,没有问题,才进入下一步,全量上线之后,也不要马上,下线老系统,保留一段时间,作为回滚预案,确保,新系统稳定,才下线。
六、迁移后的效果
迁移完成之后,新系统,运行了一段时间,效果,让我们很满意。
1. 准确率大幅提升: 各个NLP任务的准确率,都比老系统,提高了5-8个百分点,边界情况的处理,也好了很多,用户反馈,识别错误的情况,少了很多,满意度,提高了不少。
2. 维护成本降低: 新系统,用深度学习,端到端训练,不需要人工设计特征,写规则,加新任务、新领域,只需要,准备数据,训练新模型,就行,维护成本,比老系统,低了很多,开发效率,也提高了不少。
3. 扩展性更好: 新系统,微服务架构,每个任务,独立部署,独立扩展,加新任务,很方便,而且,用Kubernetes,自动扩缩容,应对高并发,扩展性,比老系统,好很多。
4. 性能更好: 新系统,用GPU加速,批量处理,缓存,性能,比老系统,好很多,响应时间,更短,吞吐量,更大,高并发的时候,也能稳定响应。
5. 技术栈更新: 新系统,用Python 3 + TensorFlow,技术栈更新了,不再有技术债,能用上,最新的技术和模型,持续提升效果。
总的来说,这次迁移,是成功的,达到了我们预期的目标,虽然,过程中,踩了很多坑,但是,也积累了很多经验,为以后,更多的系统迁移,打下了基础。
七、经验总结和最佳实践
最后,总结一下,这次NLP系统迁移的经验和最佳实践。
1. 充分准备,不打无准备之仗: 系统迁移,是一件有风险的事情,迁移之前,一定要,充分准备,梳理老系统的功能和接口,整理数据,做技术选型,制定详细的迁移计划,不打无准备之仗。
2. 数据是基础,重视数据质量: 深度学习,数据是基础,数据质量,直接决定模型效果,一定要,重视数据的质量,收集足够的数据,清洗、去重、标注,确保标注标准统一,数据一致,还要做数据增强,提升模型的泛化能力。
3. 充分评估,确保新系统效果达标: 迁移之前,一定要,充分评估新系统的效果,和老系统,在同一个测试集上,对比,确保,新系统的效果,全面超过老系统,满足业务要求,才能上线,不要,效果没达标,就急着上线。
4. 接口兼容,让调用方无感知迁移: 系统迁移,接口兼容性,很重要,一定要,完全兼容老系统的接口,输入输出、字段命名、返回格式、空值处理、错误码等,都要和老系统一致,让调用方,无感知迁移,减少迁移的阻力和风险。
5. 性能优化,满足业务要求: 深度学习模型,推理速度,可能会比较慢,一定要,提前做性能测试,不满足要求,就要做优化,用GPU、模型优化、批量处理、缓存、负载均衡等方法,提高推理速度,满足业务的性能要求。
6. 灰度切换,逐步迁移,降低风险: 不要,直接全量切换,要做灰度切换,逐步把流量,从老系统,切到新系统,1%、5%、10%、30%、50%、80%、100%,每一步,都观察清楚,没有问题,才进入下一步,有效降低迁移的风险。
7. 双写对比,及时发现问题: 灰度期间,要做双写和对比,老系统和新系统,同时处理请求,对比结果,发现不一致,及时记录,分析原因,修复问题,确保,新系统的效果,稳定可靠。
8. 回滚预案,随时能切回老系统: 系统迁移,一定要有回滚预案,一键就能切回老系统,而且,全量上线之后,也不要马上,下线老系统,保留一段时间,作为回滚预案,确保,新系统稳定,才下线老系统。
9. 充分测试,确保系统稳定: 新系统,上线之前,一定要,充分测试,单元测试、集成测试、性能测试、灰度测试,都要做,确保,系统稳定,没有重大问题,才能上线。
10. 总结经验,持续优化: 迁移完成之后,要总结经验和教训,记录下来,做成文档,方便以后参考,而且,新系统上线之后,也要持续优化,收集bad case,补充数据,调优模型,持续提升效果。
写在最后
NLP自然语言处理迁移实战:从旧系统到新系统。
这次NLP系统迁移,从传统机器学习,迁移到深度学习,花了大概三个月的时间,踩了很多坑,也积累了很多经验,最后,迁移成功,新系统的效果、性能、维护成本、扩展性,都比老系统,好很多,达到了我们预期的目标。
系统迁移,是一件有风险的事情,特别是核心系统,一定要谨慎,要充分准备,重视数据质量,充分评估效果,接口兼容,性能优化,灰度切换,双写对比,回滚预案,充分测试,才能,保证迁移的顺利进行,降低迁移的风险。
希望我们的经验,能给大家一些参考,也希望大家,做系统迁移的时候,都能顺利,少踩坑,迁移成功。
最后,用一句话结尾:
"系统迁移,不是一蹴而就的事情,需要充分的准备,谨慎的步骤,完善的预案,才能,平稳地,从旧系统,迁移到新系统,实现技术的升级和业务的持续发展。"
祝大家,系统迁移顺利,工作顺利,少踩坑,多成功!
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录