![]() |
![]() |
---|
过年期间,deepseek-R1火出了圈,各家媒体都在狂轰乱炸宣传deepseek,以及技术圈各种文章来介绍其实现原理。那这里仅从“高质量数据”角度作为入口来阐述对其的理解。
这不废话吗,没有高质量数据集怎么训练高质量模型。对的,这个回答完全没有问题,从目前大模型能力角度来讲,其回答已经持平或者某些方面已经高于绝大多数人的认知,这是其一。其二是在垂直领域或者具体业务,更多、更贴和的真实数据会给模型带来更好的效果以及降低一个量级的参数量,这也是下游能够应用大模型的主要原因。
那其三呢,我们无法构建一个真实环境,来给大模型进行交互,让其不断试错和学习,所以我们需要将人类对于各种问题的理解以及自然规律等等,用其简单明了直接的方式告诉大模型,这个就是答案,所以出现了SFT。其他文章在介绍到SFT时,喜欢用与人类能够交互的方式来解释这里,我觉得这里的原因是同等重要的,如果模型能够进化到具身智能,那么人类知识作为其主要部分,仍是绕不开与人类交互的。
那自然伸展到这里了,或者说高质量数据集应该包含哪些可能,以及什么样的高质量数据集可以更大可能的激发出大模型的能力(aha moment或者是能力涌现)。aha moment是指经过先思考再回答,think step by step,对于推理问题,例如数学、代码方面表现出了优秀的结果。能力涌现是在pretrained model到sft model,指少量标注的QA可以激发出Pretrained model按照人类交互的方式回答用户问题。至此总结了两种高质量数据集的可能表达形式。这也是OpenAI O1、deepseek目前所带来的新的能力。那多模态大模型和agent交互所带来的,我认为也会是接下来的发展方向。
下面展示一个对话demo,经过这种reason dataset来让经过sft model训练后所带来的思考能力。
![]() |
![]() |
---|
Llava是一个多模态大模型,本文以如下代码大致介绍下。
1 | import os |
1 |
|
一共三部分:
图像部分走的是CLIP处理流程,resize到336*336,所以pixel_values shape为(3, 336, 336),其他没啥特殊。文字部分走的是Llama,这个就很熟悉了。
![]() |
![]() |
---|
image走ViT,kernel_size为14,所以计算过程和结果如下:
1 | (336-14)/14+1 = 24 |
由于CLIP输出是1024,经过multi_modal_projector
后维度为(1,576,4096),这个也是下面image_features的维度。
至此同一个维度4096。
原prompt如下:
1 | prompt = "USER: <image>\nWhat's the content of the image? ASSISTANT:" |
<image>
是插在指定位置的,那么这里图文对齐和之前的对齐就增添了另外一个含义:不仅要能图文对齐,还要考虑图像插入位置
。
参考上图中_merge_input_ids_with_image_features
函数以及结合上图中的信息,那么不难得出如下结论:
1 | (final_embedding[:, 5:576+5, :] == image_features[:, :, :]).all() |
由于后续target task为VQA、Image Caption之类的,先到此为止。
最近有个需求,能够对不同排版格式的繁体信息进行抽取,所以从传统的版面分析+文字检测、识别+阅读顺序+NLP到现在发展的多模态大模型综合调研。
此处以文字区域检测+识别做个demo,来直观感受多模态大模型的结果。
注:实际比下面还复杂些,此处只做简单demo展示。
简体横排(没有这样训练语料仍能泛化,模型本身具备OCR能力):
![]() |
![]() |
---|
![]() |
![]() |
---|
手写体(基本仍符合横排):
繁体竖排:
![]() |
![]() |
---|
![]() |
---|
对于更复杂场景的排版,泛化效果不理想,官方解释是原始训练语料包含少。
1 | FROM ubuntu:18.04 |
1 | from pprint import pprint |
最近面试了一北京候选者,之前使用电子病例以及CT图像两种模态信息,训练ViLT多模态预测模型,提高肺结节良恶性预测准确率。
正好我对多模态如何对齐也比较感兴趣,以Transformers-Tutorials提供的代码为例,来看下其内部是如何实现的。
数据集我没有从VQA下载,太大了,这里也强烈安利huggingface提供的lmms-lab/VQAv2
dataset。
剩下就改下VQADataset
部分,其他保持不变。
这里记录比较有趣的几个点。
答案:在第二维对齐。
具体来说,text部分使用的是BertTokenizer,max_position为40(所以如果有长文本,这里就坑了),假设batch_size为4,text embedding出来后就是(4, 40, 768)。
image部分使用的也是ViT,所以整体思想和CLIP是一样的,得到patch后flatten,然后concat到一起即可, 即embeddings = torch.cat([text_embeds, image_embeds], dim=1)
。
相比CLIP,不同之处在于支持不同宽高的image,CLIP默认只支持(224, 224)宽高的图像。所以ViLT多了个visual pad操作,另外kernel size也不一样。如果深究的话,可以看看visual_embed部分。
ViltForTokenClassification
?是的,上面既然已经对齐了,那么前面都是text部分,所以做文本相关例如token classification等这部分也是没问题的。
ViltForQuestionAnswering
是QA吗?先入为主来讲,这个A
呢,是不固定的,比如使用generation
来生成。但是这里呢,是有限的!!!。
怎么说呢,人家提供了个类似[CLS]
,然后你可以指定自己的类别,所以他是个分类器。
这里作者使用了finetuned的label来作为演示,可以看看”dandelin/vilt-b32-finetuned-vqa”下config.json中的label2id查看标签数量。
1 |
|
排序
新操作正如作者所说的那样,VQAv2是个多标签分类数据集,什么意思呢,例如下图。
它的question:where is he looking?
它的标注结果annotations:
1 |
|
answer是有多个的,也就是说它的answers是个众包的,有的人认为是down,有的人认为是table。
例如上面标注结果,首先统计answer次数,得到下面结果:
1 | answers_count = {'down': 7, 'at table': 1, 'skateboard': 1, 'table': 1} |
接着对每个answer count使用get_score
进行平滑处理,get_score有三种可能,那每种可能就认为是每种答案的权重。
1 | def get_score(count: int) -> float: |
在ViLT出来后,接了个分类器,这个分类器的标签数量来自自己实际业务指定,比如answers的总数量。假设一共有10个类别,down类别是1,at table是2,依次类推,得到label如下:
1 | label = [1.0, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0, 0, 0, 0, 0, 0] |
接着计算loss如下:
1 | loss = nn.functional.binary_cross_entropy_with_logits(logits, labels) * labels.shape[1] |
这里又乘了个labels总数量,可以简单理解成loss scale。
作者使用topk进行解码:
1 | probs, classes = torch.topk(predicted_classes, 5) |
至此,大致理解了整体流程。
这里是get到一种新的实现方式了哈,那接下来我想对其扩展下,看看还有哪些方式可以做这种。
1、rank loss。排序中常用到。
2、目前大模型中的打分模型(score model),在最开始我没看其实现的时候,我曾深深纠结于这又是什么新的技术,等我看完后,我感觉更应该关注的是“打分”俩字,即强化学习思想中的critic model。本质技术层面没有太大变化,作为打分模型为Actor提供优化方向。
3、DPO。对,你没看错,强化学习算法DPO,本质思想就在于好的要逐渐和坏的拉开差距,使其更符合人类偏好。
看的越多,会发现还是有共通之处的。
分享个模型可解释的库SHAP,其可以对XGBoost等机器学习库进行结果解释。
更多还有类似Paddle中的TrustAI。
SHAP(SHapley Additive exPlanations)是用来解释机器学习模型的工具。它告诉我们每个特征对模型预测结果的贡献有多大。想象一下你和几个朋友一起干活,最后得到了一笔报酬。你想知道每个人应该分多少钱,SHAP就是在做类似的事情,只不过它是在告诉你每个特征对模型预测结果的贡献。
想象一个团队在完成任务后得到一笔奖金,我们想公平地分配这笔奖金。Shapley值就是一种分配方法,告诉我们每个成员(在机器学习中是每个特征)应得的报酬(对预测结果的贡献)。
假设我们在玩一个游戏,每次加入一个新成员(特征),看看他们对团队成绩(模型预测)的贡献有多大。边际贡献就是每次新加入的成员对总成绩的额外贡献。
SHAP计算每个特征的贡献时,会考虑所有可能的加入顺序。这是因为不同的加入顺序可能会导致不同的贡献。举个例子:
对于每个特征,计算它加入不同特征子集时,对模型预测结果的边际贡献。
对于每个特征,计算它在所有可能子集中的平均贡献,这就是这个特征的Shapley值。
假设我们有一个模型在预测一个人是否会购买某产品,模型用到了年龄、收入和是否有孩子三个特征。SHAP会做以下事情:
对于每个特征,计算它在所有顺序中的平均贡献,这个平均值就是该特征的Shapley值。
SHAP通过考虑每个特征在不同组合中的贡献,并取平均值,来告诉我们每个特征对模型预测的影响有多大。这样,我们就能清楚地知道模型是如何做出预测的,每个特征在其中起了多大作用。
演示代码:https://github.com/geasyheart/algo/blob/m2/python/shap_demo.ipynb
DPO全称Direct Preference Optimization,它是RLHF算法的一种,相比PPO算法来讲,它只需要actor和ref model,少了critic和reward model。其核心期望为good loss - bad loss越来越大, 这点和排序模型中的rank loss很相似,但是又不希望和ref model偏差太多。
1 |
|
值函数希望学习一个价值函数,这个值可以用于评估当前决策的分值。策略函数希望学习一个策略函数,拿到其动作的概率分布。
Actor-Critic是在策略函数的基础上,额外引入学习价值函数,来帮助策略函数更好地学习。
下面这个图很好表示了两者关系。
重点看actor-critic算法中update函数log_probs
部分。actor采用策略,critic来进行评价。
这个是RLHF系列中的策略梯度部分,在看了Hands-on-RL和parl两者实现后,感觉整体难度并不是很高,但是当自己从零实现时还是会莫名其妙多一些问题,相比深度学习来讲,还是有蛮多小细节是需要额外注意的。
这里是指learn阶段中的获取最大期望阶段,如下代码所示:
1 | output = self.model(obs_bs) |
在最开始自己实现时,我没有加log进行平滑,发现模型没法收敛(CarPole-v0 reward最大得分为200),一直是8,9徘徊。后来我看了上述实现,发现这里多了个log,这里让我觉得很困惑,因为我觉得这一步是不必要的,原因有以下几个方面:
但是呢,如果不加log这一步,模型就无法收敛。
这里是指在每一次done,产生了一批state、reward、action之后,在进行计算loss时,下一步的reward还要考虑当前步reward的结果,即下一步的reward一定要小于当前步的reward。也就是calc_reward_to_go
函数这里。
这里同样也会觉得很困惑,因为如果希望期望最大,那就reward * prob使其概率最大即可。
如果看Hands-on-RL他的实现,不会感到任何困惑,因为他是用for来做的。但是呢,parl的实现在考虑了reward * prob使其概率最大
这一步之后,又添加了calc_reward_to_go
函数,从而本来是个离散的东西,强生生的给变成了一个连续状态的事情,关键呢,怎么看都不像是连续的,因为当前步在计算loss时也并没有跟上一步扯上直接的关系。
这一步还好,从理解角度来讲,我会更倾向Hands-on-RL的实现,容易理解。
关于第一点,我的感觉是步子不能迈太大,宁可慢慢收敛,也要比无法收敛更强,例如model部分我尝试改成如下:
1 | class PolicyNet(torch.nn.Module): |
即forward部分不用softmax,剩下代码保持不变,也同样处于无法收敛状态,似乎来看,softmax+log才是这个算法成功的关键。
这里让我想到一个事情,在深度学习Layer参数初始化的过程,比如:
1 | a = nn.Linear(2222, 11) |
我们会是这么写,基本不会关心a这个linear的参数是如何初始化的。是因为框架内部已经考虑了kaiming、xavier、uniform等各种初始化技术。如果不加这些参数初始化技术,模型基本也很难收敛。
所以如果应用的话,可以采用现成的实现,如果研究的话,其中一些细节可以慢慢调整。
1 | import random |
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true