Motivation
前几天搭建了一个对牛客网每天最新的工作信息进行爬取的程序,见牛客网爬虫,但从网上爬取下来的帖子有很多不是工作信息,需要把这部分干扰信息给排除掉,否则很影响使用心情。之前使用关键词与正则表达式进行了简单过滤,但总是有一些漏网之鱼,且容易误伤,如果能训练一个NLP分类模型来进行过滤,那就再好不过了,正好本人的研究方向是NLP,就想试着构建一个模型玩玩了。
数据准备
但一般情况下要训练一个NLP模型需要几千到几万条有标注好的数据,而本项目没有现成的数据,这也是构建模型最困难的地方了。通过爬虫,获取了4万条左右的历史数据,包含id、用户昵称、标题、正文等内容,如下图所示,但没有标签。通过观察,可以把这些帖子大致分成 【招聘信息、经验贴、求助帖】三类,接下来就该考虑如何进行标注了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传[外链图片转存中
人工标注太费时费力了,而且非常的不优雅,我们还是希望找到一个自动标注的方法,这里首先想到的就是最近两年在学术界比较火的few-shot、zero-shot技术了,且一般模型越大,效果越好。目前能访问到的大模型有: openAI的GPT3及最近大火的chatGPT,百度文心的 ERNIE 3.0大模型,已经一些机构开源在huggingface 和 魔搭社区的大模型,我使用prompt进行了一轮zero-shot尝试。
prompt格式示例如下:
请问下面文本属于 招聘信息、 经验贴、 求助贴 三者中的哪一类?
秋招大结局(泪目了)。家人们泪目了,一波三折之后获得的小奖状,已经准备春招了,没想到被捞啦,嗐,总之是有个结果,还是很开心的[掉小珍珠了][掉小珍珠了]请问下面文本属于哪一类帖子?
秋招大结局(泪目了)。家人们泪目了,一波三折之后获得的小奖状,已经准备春招了,没想到被捞啦,嗐,总之是有个结果,还是很开心的[掉小珍珠了][掉小珍珠了]
选项:招聘信息, 经验贴, 求助贴
答案:
经过一轮测试,发现他们的效果如下: chatGPT > 百度文心 >> others
chatGPT表现较好,绝大本分都预测的比较准确,百度文心也基本可用,大部分都能答正确,之后就准备使用API来调用这两个大模型来标数据了,但百度文心每天只能访问200次,我很快超出次数限制,现阶段还不能直接付费购买服务,只能填合作申请表,然后等待。
chatGPT不对中国用户开放,无法直接注册账户,通过特殊方法也是可以注册上的。前段时间翻墙后还能正常访问chatGPT的页面,但现在访问不了了,API在国内也访问不了,但可以采用“东数西算”的思想,把数据拿到国外的服务器上计算就行了,最简单的方法就是使用google的colab,免费创建一个notebook,并把数据传到google drive 或 GitHub,然后访问openAI的api。调用api需要先到官网上申请一个API key,然后再调用,使用pyhton调用API的代码如下:
import openai
openai.api_key = "your api key"s = '''请问下面文本属于 招聘信息、 经验贴、 求助贴 三者中的哪一类?\n秋招大结局(泪目了)。家人们泪目了,一波三折之后获得的小奖状,已经准备春招了,没想到被捞啦,嗐,总之是有个结果,还是很开心的[掉小珍珠了][掉小珍珠了]'''rst = openai.Completion.create(model="text-davinci-003", prompt= s,max_tokens=15,temperature=0
)print(rst['choices'][0]["text"])# output: \n\n这是求助贴
直接进去还没有chatGPT的API,但有 text-davinci-003 这一强大的模型,它基于GPT3大模型,使用了跟chatGPT相似的instruction训练,亲测效果很好,跟chatGPT差不多,甚至可以说就是chatGPT了。最终,用API标注了500条左右的数据,然后又人工标注了100条数据作为测试集。
模型与训练
训练的基本策略为使用伪标签技术,即先使用少量数据训练一个模型,让这个模型去标数据,然后用其标注的数据集进行训练,最后结果往往会超过原来那个标注的模型。
由于500条数据仍然很小,属于few-shot的范围了,因此希望使用尽量大的模型,一般模型也大,表现往往越好,大模型的few-shot能力也强,我在AutoDL上租了个24GB显存的A5000GPU,最大也就能训练1.3B大小的模型,但经过一系列实验后发现,居然是roberta-large表现最好,在我那个100数据的小测试集上F1 score超过了90%,然后用它对剩下的3万多条数据进行预测,生成标注数据集,最后使用该数据集训练一个新模型。
由于后期要在cpu上运行,因此希望使用尽量小的模型,这里选择了腾讯的 uer/chinese_roberta_L-4_H-512 模型进行训练,训练结果出人意料的好(也许是测试集太小,不准确),如下图所示:
训练完成后的模型在roberta4h512文件夹中,可通过huggingface本地读取,读取示例如下:
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "roberta4h512"
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
预测过滤
我们把爬回来的帖子中预测为招聘信息的帖子留下来,其他的过滤掉即可。爬虫程序一天执行一次,可以采用类似懒加载的方式加载模型,为了性能,需要分batch进行计算。预测代码如下:
from transformers import AutoTokenizer, AutoModelForSequenceClassification
def batch_generate(texts, tokenizer, model, id2label = {0: '招聘信息', 1: '经验贴', 2: '求助贴'}, max_length = 128):inputs = tokenizer( texts, return_tensors="pt", max_length=128, padding=True, truncation=True)outputs = model(**inputs).logits.argmax(-1).tolist()return [id2label[x] for x in outputs]def model_predict(text_list, model_name = "roberta4h512", batch_size = 16):if not text_list: return []model = AutoModelForSequenceClassification.from_pretrained(model_name)tokenizer = AutoTokenizer.from_pretrained(model_name)model.eval()result, start = [], 0while(start < len(text_list)):result.extend(batch_generate(text_list[start : start + batch_size], tokenizer, model))start += batch_sizereturn result
使用示例如下:
ss = ['秋招大结局(泪目了)。家人们泪目了,一波三折之后获得的小奖状,已经准备春招了,没想到被捞啦,嗐,总之是有个结果,还是很开心的[掉小珍珠了][掉小珍珠了]','找到工作之后还要继续找吗。5k 加班严重 春招还想继续找 大家有什么好的建议 #我的求职思考# ...双非应届本科 拿了一个广州嵌入式offer 待遇9.'
]print(model_predict(ss))# output: ['经验贴', '求助贴']
项目guthub地址:https://github.com/chadqiu/newcoder-crawler