前沿重器[33] | 试了试简单的prompt

news2024/12/28 3:27:54

前沿重器

栏目主要给大家分享各种大厂、顶会的论文和分享,从中抽取关键精华的部分和大家分享,和大家一起把握前沿技术。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。(算起来,专项启动已经是20年的事了!)

2022年的文章合集,累积起来有60w字,在这:CS的陋室60w字原创算法经验分享-2022版。

往期回顾

  • 前沿重器[28] | 前沿的向量召回都是怎么做的

  • 前沿重器[29] | ERNIE-Search:向交互式学习的表征式语义匹配代表作

  • 前沿重器[30] | 聊综述-预训练模型在信息检索中的应用

  • 前沿重器[31] | 理性聊聊ChatGPT

  • 前沿重器[32] | 域外意图检测——解决“没见过”的问题

prompt这个东西在现阶段应该不算新东西了,大家的关注点也到了后续的别的研究上了,前段时间拓展工具库的想法,于是开始想尝试一下prompt的效果。

懒人目录:

  • 先说下原理

  • 代码

  • 代码细节

  • 有关实验后的一些有意义的结论

  • 小结

  • 参考文章

先说下原理

所谓的prompt,简单而又笼统地说,其实就是把传统的NLP问题转化为一个类似我们以前做的“完形填空”一样,然后用MLM任务来预测对应的结果。以文本分类为例,例如分好评和差评的二分类,常规的方式是把句子输入到模型中,让模型预测正负,而在prompt中,我们对句子补充些内容,然后预测挖的空来判断正负。

例如一个句子“我觉得这个商品是我买的最对的商品了”,此时给句子进行一些补充,例如改成“句子:我觉得这个商品是我买的最对的商品了。这是一个[MASK]评”,此时我们只需要对比这里填“好”的概率和“差”的概率其实就能分析出最终的结果了。

是不是觉得原理超级简单,那后面就上代码了。

代码

首先是一些比较常规的提前配置,包括一些超参数和预训练模型的加载。

# 超参数
hidden_dropout_prob = 0.3
num_labels = 2
learning_rate = 1e-5
weight_decay = 1e-2
epochs = 15
batch_size = 16
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
prefix = "这也太[MASK]了吧,"  # prompt的配置在合理,为了简单化我直接用的是前缀的形式,其实大家要有时间改一下也可以成为后缀。
maskpos = 4

# 预训练模型路径
ptm_path = "./data/ptms/bert-base-chinese/"
vocab_file = ptm_path +"vocab.txt"               # 词汇表
tokenizer = BertTokenizer(vocab_file)
config = BertConfig.from_pretrained(ptm_path + "config.json")
device = torch.device("cuda:0" if  torch.cuda.is_available() else "cpu")
model = Bert_Model(bert_path=ptm_path + "pytorch_model.bin", config_file=config).to(device)

# 提前提取正负类代表的词汇的id,方便后面提取概率。
pos_id = tokenizer.convert_tokens_to_ids('棒')
neg_id = tokenizer.convert_tokens_to_ids('差')

# 只是做实验,所以这里的训练配置都是用的非常默认而且简单的。
loss_func = nn.CrossEntropyLoss(ignore_index=-1)
optimizer = AdamW(model.parameters(),lr=2e-5,weight_decay=1e-4)  #使用Adam优化器

此处的预训练模型,用的是这个结构,这里使用的是BertForMaskedLM,就是一个MLM模型的结构,相信大家都是比较熟悉的了,transformers库里面集成了很多任务类型的基础结构,用起来都很方便。

from transformers import BertForMaskedLM
class Bert_Model(nn.Module):
    def __init__(self, bert_path, config_file):
        super(Bert_Model, self).__init__()
        print(bert_path)
        self.bert = BertForMaskedLM.from_pretrained(bert_path,config=config_file)  # 加载预训练模型权重

    def forward(self, input_ids, attention_mask, token_type_ids):
        outputs = self.bert(input_ids, attention_mask, token_type_ids)
        logit = outputs.logits  # 池化后的输出 [bs, config.hidden_size]

        return logit

数据集其实是一个挺部分,我把拼接放在了这一步,具体函数就在这个prompt_dataset里面。

# 训练集整理
Inputid, Labelid, sid, atid = prompt_dataset(x_train, y_train, prefix, tokenizer, maskpos)
Inputid = np.array(Inputid)
Labelid = np.array(Labelid)
sid = np.array(sid)
atid = np.array(atid)
# 偷个懒,验证集和训练集一样
input_ids_train,  input_ids_valid  = Inputid, Inputid
input_masks_train,  input_masks_valid = atid, atid
input_types_train, input_types_valid = sid, sid
label_train, y_valid = Labelid, Labelid

来看看prompt_dataset的具体怎么写,这里比较特别的其实就是text_ = prefix + x[i],就是把前缀和句子拼接在一起,然后后面就是比较常规的转化了。

def prompt_dataset(x, y, prefix, tokenizer, maskpos):
    # prompt x: 原始数据输入, y: 输出,prefix: 前缀,tokenizer: 转换器
    Inputid = []
    Labelid = []
    sid = []
    atid = []
    for i in range(len(x)):
        text_ =  prefix + x[i]
        encode_dict = tokenizer.encode_plus(text_, max_length=200, padding='max_length', truncation=True, add_special_tokens=True)

        id = encode_dict["input_ids"]
        segmentid = encode_dict["token_type_ids"]
        attid = encode_dict["attention_mask"]
        labelid, inputid = id[:], id[:]
        if y[i] == 0:
            labelid[maskpos] = neg_id
            labelid[: maskpos ] = [-1]*len(labelid[: maskpos ])
            labelid[maskpos + 1 : ] = [-1]*len(labelid[maskpos + 1 : ])
            inputid[maskpos] = tokenizer.mask_token_id
        else:
            labelid[maskpos] = pos_id
            labelid[: maskpos] = [-1] * len(labelid[: maskpos])
            labelid[maskpos + 1:] = [-1] * len(labelid[maskpos + 1:])
            inputid[maskpos] = tokenizer.mask_token_id
        Labelid.append(labelid)
        Inputid.append(inputid)
        sid.append(segmentid)
        atid.append(attid)
    
    return Inputid, Labelid, sid, atid

为了方便,我这里还写了一个预测单个case的函数,在平时自测啥的,都会挺方便,这里用的概率

def pred_single(model, data_info, maskpos, pos_id, neg_id):
    ids, att, tpe= list2cuda(data_info["Inputid"]), list2cuda(data_info["atid"]), list2cuda(data_info["sid"])
    out  = model(ids, att, tpe)
    tout_train_mask = out[:, maskpos, :] # 预测值,这里是这个位置所有token的概率。
    pos_score = tout_train_mask[:,pos_id].cpu().detach().numpy().tolist() # 正类关键词的概率
    neg_score = tout_train_mask[:,neg_id].cpu().detach().numpy().tolist() # 负类关键词的概率
    # print(pos_score, neg_score)
    pred = cal_pred(pos_score, neg_score)
    return pred

def list2cuda(data):
    return torch.from_numpy(np.array(data)).long().to(device)

def cal_pred(pos_score, neg_score):
    # 计算正负类概率,取高
    # print(pos_score, neg_score)
    pred = []
    for idx in range(len(pos_score)):
        if pos_score[idx] >= neg_score[idx]:
            pred.append(1)
        else:
            pred.append(0)
    return pred

然后是还比较关键的训练代码,开始之前需要专门说的是,这个训练不着急做,可以在无监督的情况下先直接试试效果,其实只要设计到比较好的prompt,我的实验是能达到80%这个水平,这个水平不算高,但是在无监督没什么数据的情况下,已经是一个很高的baseline了,这点就非常值得我们吸收学习了。

下面就是重头戏了,训练,其实训练的部分也比较简单,损失函数就是交叉熵(前文已经定义了),我们是希望对应类目的关键词,在这个句子中的概率尽可能高,通过这种方式训练的,剩下就看代码理解吧:

def train(model, epoch, optimizer, dataset, device, loss_func):
    starttime_train = datetime.now()
    start = time.time()
    correct = 0
    train_loss_sum = 0.0
    model.train()
    schedule = get_cosine_schedule_with_warmup(optimizer,num_warmup_steps=len(dataset),num_training_steps=epoch*len(dataset))
    logger.info("***** Running training epoch {} *****".format(epoch + 1))
    for idx, (ids, att, tpe, y) in enumerate(tqdm(dataset)):
        ids, att, tpe, y = ids.to(device), att.to(device), tpe.to(device), y.to(device)
        out_train = model(ids, att, tpe)
        # print(out_train.view(-1, 21128).shape, y.view(-1).shape)
        loss = loss_func(out_train.view(-1, 21128), y.view(-1))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        schedule.step()
        train_loss_sum += loss.item()

        if (idx + 1) % 100 == 0:
            logger.info("Epoch {:04d} | Step {:06d}/{:06d} | Loss {:.4f} | Time {:.0f}".format(
                epoch + 1, idx + 1, len(dataset), train_loss_sum / (idx + 1), time.time() - start))

        truelabel = y[:, maskpos]
        out_train_mask = out_train[:, maskpos, :]

        predicted = torch.max(out_train_mask.data, 1)[1]
        correct += (predicted == truelabel).sum()
        correct = np.float(correct)
    acc = float(correct / len(label_train))

其实看代码会发现非常常规,就是一般的MLM任务的训练流程了,让模型预测的token尽可能接近label。

代码细节

在写这个代码过程中,其实还挺波折,这里面还是有关注到挺多细节的。

  • Transformers所封装的几种常见的模型结构,分类、句子对等,都是需要熟悉了解的,包括这次用到的BertForMaskedLM。大家可以通过文档等方式直接学习。

  • 各种数据类型、设备的转化,熟练度似乎不太够,就是tout_train_mask[:,pos_id].cpu().detach().numpy().tolist()

  • 另外注意,我这种只是为了跑通做玩具的脚本,这个代码风格不值得学习,参考技术方案就好了。

  • 训练并非必须,可以尝试直接不训练地直接预测,好的prompt会有一个还不错的baseline。

有关实验后的一些有意义的结论

实验结果我就不摆在这里了,但又一些有意义的发现直接列举给大家,供大家做实验的参考:

  • 不训练的情况下,多换几种prompt,能得到一个不差的结果,这让我们在比较困难的环境下,也可以得到一个不错的baseline。(我的实验上限F1在80%左右)

  • 不训练的情况下,不同的prompt,对结果的差距非常大,下限能到55%,所以如果不训练,需要花点时间在prompt的设计上。

  • 训练情况,可能是我的数据都偏简单,所以prompt和其他模型,例如bert-cls,差距不是很大,没有显著变好,和数据有关。

  • 训练情况,不同的prompt对最终的效果也会有影响,但不会那么大,收官时间调一调还可以,早期不要花太多时间。

  • 令人惊喜的是,强行压缩训练集,我这里压缩到100条,此时prompt方案仍然能够达到接近全量数据的水平(当然数据量少了epoch就要增加不少,不过收敛的其实挺快的),大概能达到较差的prompt的水平。

  • 这个压缩数据集的fewshot的场景下能有这个效果,是bert-cls、textcnn等经典方法都办不到的,所以小数据集下,可以试试这个方案的。

  • 上述效果是建立在大型预训练模型的基础上的,CNN等的一些比较小的结果似乎没有这个效果,大家需要注意。

小结

这次尝试算是刷新了我的工具库了,是一个比较新的,有新的适配场景的方案了,在few-shot场景下,这个方案有非常令人惊喜的结果,这个可以说是比较大的卖点了,推荐大家也用来试试,除了分类任务外,ner等任务其实也可以尝试下的。

参考文章

  • 苏剑林:曾被嫌弃的预训练任务NSP,做出了优秀的Zero Shot效果,https://spaces.ac.cn/archives/8671

  • 谢立阳:Prompt 初探,https://zhuanlan.zhihu.com/p/464562384

  • 谢立阳:Prompt 中文分类任务工程尝试,https://zhuanlan.zhihu.com/p/464684532

5451bad96265785a2a0588b8c29bd68f.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/553360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

容器简单介绍

目录 一、容器简介​编辑 二、容器和虚拟化技术差异 三、容器基本概念 四、容器技术对企业优势 五、容器的工具 一、容器简介 docker只是容器工具,真正容器技术是LXC (linux container) 二、容器和虚拟化技术差异 虚拟机模式&#xff…

微服务开发系列 第二篇:Nacos

总概 A、技术栈 开发语言:Java 1.8数据库:MySQL、Redis、MongoDB、Elasticsearch微服务框架:Spring Cloud Alibaba微服务网关:Spring Cloud Gateway服务注册和配置中心:Nacos分布式事务:Seata链路追踪框架…

【腾讯云 Finops Crane 集训营】云架构成本大,浪费支出太高?何不试试Crane

一、前言 近年来,很多公司随着业务的发展都开始采用云原生的架构方式来部署服务系统,以便满足系统的弹性需求。但随着业务的进一步增长,k8s的节点数不断的增加,每个月消耗的费用也随之增加,导致了资源的利用率并不平均…

Python自动化办公对每个子文件夹的Excel表加个表头(Excel同名)

点击上方“Python爬虫与数据挖掘”,进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 玉容寂寞泪阑干,梨花一枝春带雨。 大家好,我是皮皮。 一、前言 前几天在Python粉丝【彩】问了一个Python自动化办公处理的问题&…

python整合合并两个excel文件,保留各自excel文件的样式,包含字体大小和字体颜色等格式

一、实现目标 现有两个excel文件data1.xlsx和data2.xlsx,要求将这两个excel文件合并为一个excel文件,同时保留这两个excel文件各自带有的样式,包括字体、颜色等格式需要保留。 data1.xlsx: …

装饰者设计模式解读

问题引进 星巴克咖啡订单项目(咖啡馆): 1) 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡) 2) 调料:Milk、Soy(豆浆)、Chocolate 3) 要求在扩展新的咖啡种类时&#x…

Redis主从集群搭建及其原理

Redis主从集群搭建及其原理 1.Redis主从1.1.搭建主从架构1.2.准备实例和配置1.3.启动1.4.开启主从关系1.5.测试 2.主从数据同步原理2.1.全量同步2.2.增量同步2.3.repl_backlog原理 3.主从同步优化4.小结 1.Redis主从 1.1.搭建主从架构 单节点Redis的并发能力是有上限的&#…

车辆合格证怎么转为结构化excel数据?

一、为何要将车辆合格证转为结构化excel? 车辆合格证是在车辆制造完成后,经过各项检测合格的证明。对于车辆行业来说,车辆合格证是一种重要的合规证明,在车辆的生产制造、售后服务、质量管理等各个环节中都有着重要的作用。同时&…

【架构】常见技术点--监控告警

导读:收集常见架构技术点,作为项目经理了解这些知识点以及解决具体场景是很有必要的。技术要服务业务,技术跟业务具体结合才能发挥技术的价值。 目录 1. 服务监控 2. 全链路监控 2.1 服务拨测 2.2 节点探测 2.3 告警过滤 2.4 告警去重 …

内网 monorepo 配置指南(PNPM、YARN、Rush.js)

此处的内网是指没办法连接互联网进行依赖下载的环境,本文以windows平台为例 背景说明 绝大部分政府机关、国有企业都是在内网开发,无法从互联网同步依赖,就需要另辟蹊径解决项目依赖的问题。 传统的单包项目还好,从互联网机器将…

音视频技术开发周刊 | 294

每周一期,纵览音视频技术领域的干货。 新闻投稿:contributelivevideostack.com。 五问「ChatGPT医学影像」:新一代的 AI 能否成为放射科医生的一把利器? 在医学等专业性较强的领域内,ChatGPT的表现还不够好&#xff0c…

Linux 防火墙 SNAT DNAT

SNAT原理与应用 SNAT 应用环境 局域网主机共享单个公网IP地址接入Internet (私有IP地址不能在Internet中正常路由) SNAT原理 修改数据包的源地址 SNAT可以认为是路由器NAT中的easy ip DNAT可以认为是路由器NAT中的 nat server SNAT将 内网源地址 转化为网…

【Vue2.0源码学习】虚拟DOM篇-Vue中的DOM-Diff

1. 前言 在上一篇文章介绍VNode的时候我们说了,VNode最大的用途就是在数据变化前后生成真实DOM对应的虚拟DOM节点,然后就可以对比新旧两份VNode,找出差异所在,然后更新有差异的DOM节点,最终达到以最少操作真实DOM更新…

FasterRCNN训练自己的数据集

2016年提出的Faster RCNN目标检测模型是深度学习现代目标检测算法的开山之作,也是第一个真正全流程都是神经网络的目标检测模型。 其主要步骤如下: 1,使用CNN对输入图片提取feature map. 2,对feature map上的每个点设计一套不同大…

Roboflow的使用

文章目录 前言一、使用labelimg标注数据集二、导入roboflow1.注册roboflow账户2.导入图片2.1 创建工作区workspace(非必须)2.2 创建项目 project2.3 导入 3、导出图片4、同一个数据集可以导出不同类型 前言 我自己也是一个小白不是很会,如果…

ASO优化之怎么做好关键词本地化覆盖

如果想要我们的应用走向国际化,被多个国家/地区使用,那么做好关键词本地化覆盖至关重要。我们可以主要针对中文和英文进行设置(准备两套元数据),这样能够迅速增加应用商店ASO关键词覆盖数量。 那么我们要在哪里设置&a…

小白也能懂的薛斯通道抄底指标以及公式(附源码)

什么是薛斯通道? 上个世纪70年代,美国人薛斯最早发明了薛斯通道。 他本人曾是研究火箭运行的。 薛斯通道包括两组通道指标,分别是长期大通道指标(100天)和短期小通道指标(10天)。 股价实际上是被…

Netflix 团队解决了 Linux 内核中的 FUSE 死锁

Laf 公众号已接入了 AI 绘画工具 Midjourney&#xff0c;可以让你轻松画出很多“大师”级的作品。同时还接入了 AI 聊天机器人&#xff0c;支持 GPT、Claude 以及 Laf 专有模型&#xff0c;可通过指令来随意切换模型。欢迎前来调戏&#x1f447; <<< 左右滑动见更多 &…

Go与神经网络:张量运算

0. 背景 2023年年初&#xff0c;我们很可能是见证了一次新工业革命的起点&#xff0c;也可能是见证了AGI(Artificial general intelligence&#xff0c;通用人工智能)[1]孕育的开始。ChatGPT应用以及后续GPT-4大模型的出现&#xff0c;其震撼程度远超当年AlphaGo战胜人类顶尖围…

微信小程序-页面跳转wxAPI

官方文档地址&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateTo.html wx.navigateTo(Object object) 更改首页代码&#xff0c;添加一个按钮&#xff0c;绑定一个事件的点击&#xff1a; <!--index.wxml--> <text>首页</t…