MINICPM-V2_6图像得到embedding-代码解读

news2025/4/21 16:55:14

目的

基于上一篇MINICPM-V2_6图像预处理流程-代码解读将输入图片得到了input_ids、attention_mask、pixel_values、image_sizes、image_bound、tgt_sizes,但是要怎么通过这些得到图片对应的embedding呢?
这里接着从MINICPM-V2_6入手,了解如何从图像得到embedding的过程

随机位置编码

Randomized Positional Encodings Boost Length Generalization of Transformers
随机位置编码
因为图片的像素不统一,所以位置编码需要设置的比较大(L=2000)。假设图片对应的长度为N(N=40),训练阶段原本长度为N的序列对的位置序列是[0,1,⋯,N−2,N−1],现在改为从{0,1,⋯,L−2,L-1}中均匀地选N个点(0,50,100,。。。),作为当前序列的位置序列。这就解决了预测阶段的位置编码没有被训练过的问题。
这里的代码里用了这个思想,但是这里会复杂得多,因为这里是将2D的位置均匀的映射到[70*70]上面

代码

基本变量

import torch
from torch import nn

# 继承上篇的结果
# 图片块大小
tgt_sizes = torch.tensor([[28, 37],
        [39, 26],
        [39, 26]])
        
# 图片在inputs_id中的位置
image_bound = [torch.tensor([[ 18,  82],
        [ 84, 148],
        [150, 214]])]

# pixel_values,即最后得到的images
img1 = torch.randn(3,14,14504)
img2 = torch.randn(3,14,14196)
img3 = torch.randn(3,14,14196)
pixel_values_list = [[img1, img2, img3]]

max_patches = torch.max(tgt_sizes[:, 0] * tgt_sizes[:, 1])# 最大的块尺寸=28*37=1036,这里对应的是上一篇中的原始图片大小

# 将图片patch补齐到统一尺寸,作为一个batch
all_pixel_values = []
for pixel_values in pixel_values_list:
    all_pixel_values.extend([i.flatten(end_dim=1).permute(1, 0) for i in pixel_values])
    # pixel_values[0] 3*14*14504
    # i.flatten(end_dim=1) 把前两维铺平 42,14504
    # i.flatten(end_dim=2) 把前三维铺平 609168
    # i.flatten(end_dim=1).permute(1, 0) 转置 14504,42
    # all_pixel_values包含三个矩阵[14504*42,14196*42,14196*42]

all_pixel_values = torch.nn.utils.rnn.pad_sequence(all_pixel_values, batch_first=True, padding_value=0.0)# [3, 14504, 42] 会将不够的位置后面补齐0,这里的3指的是

B, L, _ = all_pixel_values.shape
all_pixel_values = all_pixel_values.permute(0, 2, 1).reshape(B, 3, -1, L)# [3, 3, 14, 14504] 第一个3是图片数量,第二个3是通道数,14是patch_size,14504是最大的块尺寸*14
# 因为有些patch的像素点是经过pad的,所以需要找到哪些块是经过pad的
patch_attn_mask = torch.zeros((B, 1, max_patches), dtype=torch.bool)# 3,1,1306
for i in range(B):# 将图片块的位置填充为True
    patch_attn_mask[i, 0, :tgt_sizes[i][0] * tgt_sizes[i][1]] = True
# 即第一张图片对应的patch_attn_mask[0,0,:]=True
# 第二、三张图片对应的patch_attn_mask[1:,0,39*26:]均为False

到这里就得到all_pixel_values,patch_attn_mask, tgt_sizes
可以进行下一步去得到embedding了

函数定义

def get_embedding(all_pixel_values):
    """
    输入:all_pixel_values 经过拼接后的像素点
    输出:embeddings
    demo:
    batch_size = 3
    num_channels = 3
    patch_size = 14
    h,w = 28,37
    num = patch_size * h * w
    all_pixel_values = torch.randn(batch_size, num_channels, patch_size, num)
    embeddings = get_embedding(all_pixel_values)
    # batch_size,1 * h * w,1152
    """
    num_channels = 3
    embed_dim = 1152
    patch_size = 14
    batch_size = 1
    patch_embedding = nn.Conv2d(
                in_channels=num_channels,# 3
                out_channels=embed_dim,# 1152
                kernel_size=patch_size,# 14
                stride=patch_size,# 14
                padding="valid",
            )# 像素点到embedding的过程是通过这个卷积操作完成的
    
    patch_embeds = patch_embedding(all_pixel_values)# patch_embeds是卷积后的patch [3, 1152, 1, 1036] 1036 = 14504/14 1 = 14/14
    embeddings = patch_embeds.flatten(2).transpose(1, 2)# batch_size,1*1036,1152
    # 到这里,像素点就完成了到embedding到过程
    return embeddings

def get_position_embedding(all_pixel_values, patch_attn_mask, tgt_sizes):
    """
    输入:all_pixel_values 经过拼接后的像素点
         patch_attn_mask mask矩阵
         tgt_sizes 图片尺寸大小
    输出:位置embeddings
    demo:
    batch_size = 3
    num_channels = 3
    patch_size = 14
    h,w = 28,37
    num = patch_size * h * w
    all_pixel_values = torch.randn(batch_size, num_channels, patch_size, num)
    tgt_sizes = torch.tensor([[28, 37],
        [39, 26],
        [39, 26]])
    patch_attn_mask = torch.zeros((B, 1, max_patches), dtype=torch.bool)# 3,1,1306
    for i in range(B):# 将图片块的位置填充为True
        patch_attn_mask[i, 0, :tgt_sizes[i][0] * tgt_sizes[i][1]] = True
    embeddings = get_position_embedding(all_pixel_values, patch_attn_mask, tgt_sizes)
    # batch_size,1 * h * w,1152
    """
    embed_dim = 1152
    num_patches_per_side = 70
    num_positions = num_patches_per_side**2
    position_embedding = nn.Embedding(num_positions, embed_dim)# 4900*1152
    batch_size = all_pixel_values.size(0)# all_pixel_values原始图片大小 batch_size=3
    max_im_h, max_im_w = all_pixel_values.size(2), all_pixel_values.size(3)# max_im_h=14,max_im_w=14504
    max_nb_patches_h, max_nb_patches_w = max_im_h // patch_size, max_im_w // patch_size# 1 1036
    position_ids = torch.full(
                size=(
                    batch_size,
                    max_nb_patches_h * max_nb_patches_w,
                ),
                fill_value=0,
            )# 3,1 * 1036 全0
    boundaries = torch.arange(1 / num_patches_per_side, 1.0, 1 / num_patches_per_side)
    # 从1/70开始到1,间隔是1/70,共69个数,注意torch.arange是左闭右开的,不包含1
    # [0.0143, 0.0286,...,0.9714, 0.985]    
    for batch_idx, p_attn_mask in enumerate(patch_attn_mask):
        if tgt_sizes is not None:
            nb_patches_h = tgt_sizes[batch_idx][0]# 28
            nb_patches_w = tgt_sizes[batch_idx][1]# 37
        else:
            nb_patches_h = patch_attn_mask[:, 0].sum()
            nb_patches_w = patch_attn_mask[0].sum()
        fractional_coords_h = torch.arange(0, 1 - 1e-6, 1 / nb_patches_h)# 生成从0到1 - 1e-6,间隔是1 / nb_patches_h,共28个数
        fractional_coords_w = torch.arange(0, 1 - 1e-6, 1 / nb_patches_w)# 生成从0到1 - 1e-6,间隔是1 / nb_patches_w,共37个数
        bucket_coords_h = torch.bucketize(fractional_coords_h, boundaries, right=True)
        # 从boundaries中找到fractional_coords_h该差值的地方
        # 根据boundaries序列返回fractional_coords_h中每个元素的区间索引
        # boundaries = [0.0143, 0.0286, 0.0429, 0.0571, 0.0714, 0.0857, 0.1000, 0.1143, 0.1286...]
        # fractional_coords_h = [0.0000, 0.0357, 0.0714, 0.1071, 0.1429, 0.1786, 0.2143, 0.2500, 0.2857...]
        # 0.0000<0.0143,找到索引0
        # 0.0286<0.0357<0.0429,找到索引2
        # 0.0714=0.0714<0.0857,找到索引5
        # 0.1000=0.1071<0.1143,找到索引7
        # [ 0,  2,  5,  7, 10, 12, 15, 17, 20, 22, 25, 27, 30, 32, 35, 37, 40, 42, 45, 47, 50, 52, 55, 57, 60, 62, 65, 67]
        bucket_coords_w = torch.bucketize(fractional_coords_w, boundaries, right=True)
        # [ 0,  1,  3,  5,  7,  9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 35, 37, 39, 41, 43, 45, 47, 49, 51, 52, 54, 56, 58, 60, 62, 64, 66, 68]
        pos_ids = (bucket_coords_h[:, None] * num_patches_per_side + bucket_coords_w).flatten()
        # bucket_coords_h[:, None] * num_patches_per_side得到0 140 350...
        # bucket_coords_h[:, None] * num_patches_per_side + bucket_coords_w 
        # [   0,    1,    3,  ...,   64,   66,   68],
        # [ 140,  141,  143,  ...,  204,  206,  208],
        # [ 350,  351,  353,  ...,  414,  416,  418] 。。。
        # (bucket_coords_h[:, None] * num_patches_per_side + bucket_coords_w).flatten()得到
        # tensor([   0,    1,    3,  ..., 4754, 4756, 4758])
        # 这样把位置id就映射到4900上了
        position_ids[batch_idx][p_attn_mask.view(-1).cpu()] = pos_ids
        # 这个看起来复杂的过程是将[28,37]找到对应的位置id
        # 这里使用的是随机位置编码的方法
        # 这里是将2D均匀映射到[70,70]上面
        """
        tensor([[   0,    1,    3,  ..., 4754, 4756, 4758],
                [   0,    2,    5,  ...,    0,    0,    0],
                [   0,    2,    5,  ...,    0,    0,    0]])
        """
    embeddings = position_embedding(position_ids)# 3,1036,1152
    return embeddings

函数调用

ori_embeddings = get_embedding(all_pixel_values)# batch_size,1 * h * w,1152
pos_embeddings = get_position_embedding(all_pixel_values, patch_attn_mask, tgt_sizes)# 3,h*w,1152
embeddings = ori_embeddings + pos_embeddings# 3,h*w,1152

额外说几句

为什么这里得到pos_ids会这么复杂呢?
将2D的[h,w]对应到2D的[70,70],按照我一开始想的,那将[h,w]拉平到h*w,直接映射到70*70多简单啊,但是看了代码就发现不是这么做的
代码中是将每一行单独映射到70,每一列也单独映射到70,这么说有点空
给个demo:
按照第一张图片的宽是37,高是28
每一行的尺寸是37,均匀映射到70对应的位置是[0 1 3 5 …]
每一列的尺寸是28,均匀映射到70对应的位置是[0 2 5 7…]
因为每一行都有70个位置,所以每一列的位置id都需要乘上70得到这一列的真实列位置id
[ 0 2 5 7 . . . 67 ] ∗ 70 = [ 0 140 350 490 . . . 4690 ] \begin{bmatrix} &0\\ &2\\ &5\\ &7\\ &...\\ &67 \end{bmatrix} *70=\begin{bmatrix} &0\\ &140\\ &350\\ &490\\ &...\\ &4690 \end{bmatrix} 0257...67 70= 0140350490...4690
那真实列id+每一行的映射id就得到了2D位置编码
[ 0 140 350 490 . . . 4690 ] + [ 0 1 3 5 . . . 68 ] = [ 0 1 3 . . . 68 140 141 143 . . . 208 350 351 353 . . . 418 490 491 493 . . . 558 . . . 4690 4691 4693 . . . 4758 ] \begin{bmatrix} &0\\ &140\\ &350\\ &490\\ &...\\ &4690 \end{bmatrix} +\begin{bmatrix} &0 &1 &3 &5 &... &68 \end{bmatrix}=\begin{bmatrix} &0&1&3&...&68\\ &140&141&143&...&208\\ &350&351&353&...&418\\ &490&491&493&...&558\\ &...\\ &4690&4691&4693&...&4758 \end{bmatrix} 0140350490...4690 +[0135...68]= 0140350490...46901141351491469131433534934693...............682084185584758
将这个位置编码拉平就是pos_ids了,随后就可以根据pos_ids得到对应的位置编码了

参考

modeling_minicpmv
modeling_navit_siglip

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

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

相关文章

在 Windows 系统上,文件传输到虚拟机(VM)可以通过 VS Code 的图形界面(GUI)或命令行工具进行操作

在 Windows 系统上&#xff0c;文件传输到虚拟机&#xff08;VM&#xff09;可以通过 VS Code 的图形界面&#xff08;GUI&#xff09;或命令行工具进行操作。以下是几种方法&#xff1a; ### 方法 1: 使用 VS Code 图形界面 1. **连接到远程 VM**&#xff1a; - 在 VS Cod…

eNUM 原理概述(VoNR VoLTE适用) eNUM 报文解析

目录 1. eNUM 原理概述(VoNR VoLTE适用) 1.1 主要内容 1.2 什么是 eNUM 及 FAQ 1.3 eNUM 的主要信令场景 1.4 eNUM 查询结果为空&#xff0c;意味着什么&#xff1f; 1.5 eNUM 典型流程举例(VoNR 呼叫流程) 1.6 案例&#xff1a;因 eNUM 配置错误导致呼叫失败&#xff…

【STM32】BH1750光敏传感

1.BH1750介绍 BH1750是一个光敏传感&#xff0c;采用I2C协议&#xff0c;对于I2C的从机&#xff0c;都有自己的地址&#xff0c;用来主机选择和哪个从机通信&#xff0c;对于OLED来说&#xff0c;只有单片机通过I2C往OLED中写数据。而BH1750来说&#xff0c;有单片机往BH1750写…

illusionX——一个从理解情感到改变学习、创新教育体验集成情感计算的混合现实系统

概述 论文地址&#xff1a;https://arxiv.org/pdf/2402.07924.pdf 近年来&#xff0c;情感计算在丰富人类与计算机和机器的交互方式方面备受关注。这一创新领域旨在通过理解和响应用户的情绪和心理状态&#xff0c;将人机交互转变得更加自然和直观。无论是情感识别、面部表情分…

DAY74

#ifndef WIDGET_H #define WIDGET_H#include <QWidget>#include <QPainter> //画家类 #include <QTimer> //定时器类 #include <QTime> //时间类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : …

C#使用TCP-S7协议读写西门子PLC(五)-测试程序

上面四篇我们进行封装连接PLC以及读写PLC内存地址表 C#使用TCP-S7协议读写西门子PLC(一)-CSDN博客 C#使用TCP-S7协议读写西门子PLC(二)-CSDN博客 C#使用TCP-S7协议读写西门子PLC(三)-CSDN博客 C#使用TCP-S7协议读写西门子PLC(四)-CSDN博客 这里我们进行测试操作 西门子PLC-…

高并发内存池项目(3)——项目框架介绍与实现线程池

一&#xff0c;项目的整体架构 这个高并发内存池的主要分为三层&#xff0c;分别是TheradCache层&#xff0c;CentralCache层&#xff0c;PageCache层。如下图所示&#xff1a; 二&#xff0c;原理讲解 当我们来了一个任务要申请内存时&#xff0c;先经过第一层ThreadCache。…

基于AI+多技术融合在流域生态系统服务评价、水文水生态分析、碳收支、气候变化影响、制图等领域中的应用

流域生态系统服务在环境保护与资源管理中具有不可替代的重要性。随着全球气候变化和人类活动对自然环境的压力日益增大&#xff0c;流域生态系统的稳定性和健康状况面临严峻挑战。水资源短缺、洪水频发、水质污染、生物多样性减少等问题&#xff0c;正在威胁流域内及其下游区域…

STMCuBeMX新建项目的两种匪夷所思的问题

错误一、保存地址名中有中文 错误&#xff1a;error1-haveCHinese_有中文\error1-haveCHinese_有中文.axf: error: L6002U: Could not open file error1-havechinese_???\stm32f1xx_it.o: No such file or directory 解决方法&#xff1a;重新导出&#xff0c;并且不要用中文…

Bandicam录制视频发白(过曝)如何解决?

Bandicam录制视频发白&#xff08;过曝&#xff09;如何解决&#xff1f; 一&#xff0c;问题现象二&#xff0c;解决方法 一&#xff0c;问题现象 录制视频时&#xff0c;视频播放的颜色比笔记本电脑上的颜色差别比较大&#xff0c;显示比实际的颜色发白。 二&#xff0c;解…

会计确认数据资产相关问题解读:权属和合规

会计确认数据资产相关问题中的权属和合规性是企业必须深入理解和重视的两个方面。 企业会计准则上明确了资产&#xff1a;企业合法拥有和控制的&#xff0c;预期会给企业带来经济利益的资源都叫资产。如何理解数据资产的权属及合规&#xff1f;如何确保企业合法拥有、控制数据…

Java 入门指南:Java 并发编程 —— 同步工具类 CyclicBarrier(循环屏障)

文章目录 同步工具类CyclicBarrier构造函数常用方法工作机制使用步骤适用场景CyclicBarrier与CountDownLatch的区别示例代码 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff…

泰勒斯威夫特是认真的:我已经做过研究我做出了选择,支持哈里斯 !呼吁粉丝赶紧投票!

泰勒说她将投票给哈里斯,因为"她为权利而战,我相信需要一个战士来捍卫他们。" 泰勒:这真的让我对人工智能产生了恐惧 当拥有3亿多粉丝的碧昂丝将主题曲授权给哈里斯的时候就已经暗示了她的倾向性 如果碧昂丝和斯威夫特6亿粉丝的哈里斯选票转化率为30%&#xff0c;…

全国各地身份证号开头6位数字及地区对照表

具体请前往&#xff1a;全国各地身份证号开头6位数字-省市县/区对照表

计算语言学(一)基础

概率论的几个概念 熵、互信息 神经网络基础 MLP CNN RNN Seq2Seq LSTM Transformer 语料库与知识库

L2线性回归模型

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 鸢尾花数据集的单变量与多变量预测 在这周学习如何使用 机器学习 模型对鸢尾花&#xff08;Iris&#xff09;数据集进行单变量与多变量预测。我们将使用鸢尾花…

北京中实新材料公司:安全筑基,共绘新材料产业新篇章

北京中实新材料有限责任公司(以下简称“北京中实”),作为中关村科技发展(控股)股份有限公司旗下的重要成员,近年来在安全生产、技术创新及企业合作等方面取得了显著进展。近期,公司围绕安全生产月及新材料研发中心成立等核心活动,展开了一系列富有成效的工作,进一步推动了企业的…

【Java算法】递归

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【算法工作坊】算法实战揭秘 &#x1f347;一.递归 概念 递归是一种解决问题的方法&#xff0c;其中函数通过调用自身来求解问题。这种方法的关键在于识别问题是否可以被分解为若干个相似但规模更小…

深入Redis:复杂的集群

广义的集群&#xff0c;可能说只要是多台机器组成了分布式系统&#xff0c;就可以称之为集群。 狭义的集群&#xff0c;指的是Redis提供的集群模式&#xff0c;这个集群模式之下&#xff0c;主要是解决存储空间不足的问题&#xff0c;以及如何拓展存储空间。 之前的哨兵模式&…

C++中string的简单实现

string的简单实现中一些函数的实现可以复用一些其他的函数来实现&#xff1b;比较重要的是在实现是深浅拷贝问题 目录 string的结构 实现构造和析构 reserve扩容 容量 push_back和append insert和erase的实现 swap的实现&#xff08;不是成员函数但是string类的友元&…