从视觉问答到多语言翻译:InternVL的多模态“全能”应用图鉴

在人工智能领域,多模态大模型的发展正以前所未有的速度改变着我们与技术的交互方式。从视觉到语言,从图像到文本,多模态模型正逐渐成为连接人类感知与机器理解的桥梁。今天,我们将深入探讨一个备受瞩目的开源多模态大模型——InternVL。由OpenGVLab推出,InternVL凭借其强大的技术架构和广泛的应用场景,正在成为多模态领域的重要力量。
一、项目概述
InternVL 是上海人工智能实验室 OpenGVLab推出的一款开源多模态大模型,旨在通过融合视觉与语言信息,实现高效、灵活的多模态交互。它采用了先进的“ViT-MLP-LLM”架构,通过动态高分辨率处理、像素洗牌等技术,能够高效处理高分辨率图像和视频数据,同时支持多语言处理和多种模态输入输出。

InternVL在视觉问答、文档理解、多语言翻译等任务上表现出色,广泛应用于教育、电商、智能客服等领域。其开源特性促进了学术界和工业界的交流与合作,推动了多模态技术的创新和应用。
二、技术原理
(一)核心架构
InternVL采用了先进的多模态融合架构,其核心组件包括视觉编码器(Vision Encoder)、MLP投影器(MLP Projector)和语言模型(LLM)。
1. 视觉编码器(Vision Encoder):InternVL使用改进的Vision Transformer(ViT)模型作为视觉编码器,例如InternViT。这种模型能够将输入的图像或视频转换为高维特征向量,从而提取出丰富的视觉信息。
2. MLP投影器(MLP Projector):该组件的作用是将视觉特征映射到与语言模型相同的特征空间,使得视觉信息和语言信息能够有效地融合。
3. 语言模型(LLM):基于InternLM的语言模型是InternVL的核心部分,负责处理文本输入和生成文本输出。
(二)创新技术
InternVL在技术上引入了多项创新,以提升模型的性能和效率。
1. 动态高分辨率:通过将图像分割成多个小块(瓦片),并动态调整分辨率,InternVL能够高效处理高分辨率图像,同时保持计算效率。
2. 像素洗牌:该技术通过减少视觉标记的数量,降低计算复杂度,同时保留图像的细节信息。
3. 渐进式训练策略:InternVL采用了一种渐进式训练策略,先使用小模型在大量带噪数据上进行预训练,再用大模型在精选数据上进行对齐。这种策略能够有效减少训练资源的消耗。
三、功能特点
(一)多模态输入与输出
InternVL支持多种模态的输入和输出,包括文本、图像、视频等。它可以处理复杂的多模态任务,例如视觉问答(VQA)、文档理解(DocVQA)和信息图表问答(ChartQA)。
(二)多语言支持
InternVL支持多语言处理,能够处理和生成多种语言的文本。这一特性使得InternVL在跨语言交流和国际商务中具有广阔的应用前景。
(三)高效处理高分辨率图像
通过动态高分辨率技术和像素洗牌技术,InternVL能够高效处理高分辨率图像,同时保持计算效率。
(四)强大的视觉和语言理解能力
InternVL在多个基准测试中表现出色,例如在MMMU benchmark上得分超过70,显示了其强大的视觉和语言理解能力。
四、应用场景
(一)视觉问答(VQA)
InternVL能够处理与图像或视频内容相关的问题,广泛应用于教育、电子商务和客户服务等领域。
(二)文档和图表理解
InternVL在文档理解(DocVQA)和信息图表问答(ChartQA)任务中表现出色,能够提取文档中的关键信息,解析表格和图表,生成文档摘要或图表解释。
(三)多语言翻译和理解
InternVL支持多语言处理,能够处理和生成多种语言的文本,在跨语言交流和国际商务中具有广阔的应用前景。
(四)图像和视频分析
InternVL可用于自动标注、分类和理解图像和视频内容。在安防监控领域,它可以实时分析监控视频,识别异常行为;在内容审核方面,能够快速识别违规内容。
(五)智能客服
InternVL可以作为智能客服的核心技术,支持多模态交互。用户可以通过上传图片或视频描述问题,模型能够理解并提供解决方案。
五、部署使用
(一)环境准备
在部署InternVL之前,需要确保系统满足以下基本要求:
– 硬件环境:推荐使用具有高性能GPU的服务器,以加速模型的推理过程。
– 软件环境:需要安装Python、PyTorch等基础软件,并确保版本兼容。
(二)安装步骤
1. 克隆项目仓库:通过以下命令克隆InternVL的GitHub仓库:
git clone https://github.com/OpenGVLab/InternVL.git
2. 安装依赖:进入项目目录后,运行以下命令安装所需的Python依赖:
pip install -r requirements.txt
3. 下载模型权重:
git lfs install git clone https://huggingface.co/OpenGVLab/InternVL2_5-8B
(三)推理 示例
以InternVL2_5-8B模型为例推理如下:
import numpy as np import torch import torchvision.transforms as T from decord import VideoReader, cpu from PIL import Image from torchvision.transforms.functional import InterpolationMode from transformers import AutoModel, AutoTokenizer IMAGENET_MEAN = (0.485, 0.456, 0.406) IMAGENET_STD = (0.229, 0.224, 0.225) def build_transform(input_size): MEAN, STD = IMAGENET_MEAN, IMAGENET_STD transform = T.Compose([ T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img), T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC), T.ToTensor(), T.Normalize(mean=MEAN, std=STD) ]) return transform def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size): best_ratio_diff = float('inf') best_ratio = (1, 1) area = width * height for ratio in target_ratios: target_aspect_ratio = ratio[0] / ratio[1] ratio_diff = abs(aspect_ratio - target_aspect_ratio) if ratio_diff < best_ratio_diff: best_ratio_diff = ratio_diff best_ratio = ratio elif ratio_diff == best_ratio_diff: if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]: best_ratio = ratio return best_ratio def dynamic_preprocess(image, min_num=1, max_num=12, image_size=448, use_thumbnail=False): orig_width, orig_height = image.size aspect_ratio = orig_width / orig_height # calculate the existing image aspect ratio target_ratios = set( (i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if i * j <= max_num and i * j >= min_num) target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1]) # find the closest aspect ratio to the target target_aspect_ratio = find_closest_aspect_ratio( aspect_ratio, target_ratios, orig_width, orig_height, image_size) # calculate the target width and height target_width = image_size * target_aspect_ratio[0] target_height = image_size * target_aspect_ratio[1] blocks = target_aspect_ratio[0] * target_aspect_ratio[1] # resize the image resized_img = image.resize((target_width, target_height)) processed_images = [] for i in range(blocks): box = ( (i % (target_width // image_size)) * image_size, (i // (target_width // image_size)) * image_size, ((i % (target_width // image_size)) + 1) * image_size, ((i // (target_width // image_size)) + 1) * image_size ) # split the image split_img = resized_img.crop(box) processed_images.append(split_img) assert len(processed_images) == blocks if use_thumbnail and len(processed_images) != 1: thumbnail_img = image.resize((image_size, image_size)) processed_images.append(thumbnail_img) return processed_images def load_image(image_file, input_size=448, max_num=12): image = Image.open(image_file).convert('RGB') transform = build_transform(input_size=input_size) images = dynamic_preprocess(image, image_size=input_size, use_thumbnail=True, max_num=max_num) pixel_values = [transform(image) for image in images] pixel_values = torch.stack(pixel_values) return pixel_values # If you have an 80G A100 GPU, you can put the entire model on a single GPU. # Otherwise, you need to load a model using multiple GPUs, please refer to the `Multiple GPUs` section. path = 'OpenGVLab/InternVL2_5-8B' model = AutoModel.from_pretrained( path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True, trust_remote_code=True).eval().cuda() tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True, use_fast=False) # set the max number of tiles in `max_num` pixel_values = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda() generation_config = dict(max_new_tokens=1024, do_sample=False)
1、纯文本对话
question = 'Hello, who are you?' response, history = model.chat(tokenizer, None, question, generation_config, history=None, return_history=True) print(f'User: {question}\nAssistant: {response}') question = 'Can you tell me a story?' response, history = model.chat(tokenizer, None, question, generation_config, history=history, return_history=True) print(f'User: {question}\nAssistant: {response}')
2、单图单轮对话
question = '<image>\nPlease describe the image shortly.' response = model.chat(tokenizer, pixel_values, question, generation_config) print(f'User: {question}\nAssistant: {response}')
3、单图多轮对话
question = '<image>\nPlease describe the image in detail.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=None, return_history=True) print(f'User: {question}\nAssistant: {response}') question = 'Please write a poem according to the image.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=history, return_history=True) print(f'User: {question}\nAssistant: {response}')
4、多图多轮对话,拼接图像
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda() pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda() pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0) question = '<image>\nDescribe the two images in detail.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=None, return_history=True) print(f'User: {question}\nAssistant: {response}') question = 'What are the similarities and differences between these two images.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=history, return_history=True) print(f'User: {question}\nAssistant: {response}')
5、多图多轮对话,独立图像
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda() pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda() pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0) num_patches_list = [pixel_values1.size(0), pixel_values2.size(0)] question = 'Image-1: <image>\nImage-2: <image>\nDescribe the two images in detail.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, num_patches_list=num_patches_list, history=None, return_history=True) print(f'User: {question}\nAssistant: {response}') question = 'What are the similarities and differences between these two images.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, num_patches_list=num_patches_list, history=history, return_history=True) print(f'User: {question}\nAssistant: {response}')
6、单图批处理
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda() pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda() num_patches_list = [pixel_values1.size(0), pixel_values2.size(0)] pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0) questions = ['<image>\nDescribe the image in detail.'] * len(num_patches_list) responses = model.batch_chat(tokenizer, pixel_values, num_patches_list=num_patches_list, questions=questions, generation_config=generation_config) for question, response in zip(questions, responses): print(f'User: {question}\nAssistant: {response}')
7、视频多轮对话
def get_index(bound, fps, max_frame, first_idx=0, num_segments=32): if bound: start, end = bound[0], bound[1] else: start, end = -100000, 100000 start_idx = max(first_idx, round(start * fps)) end_idx = min(round(end * fps), max_frame) seg_size = float(end_idx - start_idx) / num_segments frame_indices = np.array([ int(start_idx + (seg_size / 2) + np.round(seg_size * idx)) for idx in range(num_segments) ]) return frame_indices def load_video(video_path, bound=None, input_size=448, max_num=1, num_segments=32): vr = VideoReader(video_path, ctx=cpu(0), num_threads=1) max_frame = len(vr) - 1 fps = float(vr.get_avg_fps()) pixel_values_list, num_patches_list = [], [] transform = build_transform(input_size=input_size) frame_indices = get_index(bound, fps, max_frame, first_idx=0, num_segments=num_segments) for frame_index in frame_indices: img = Image.fromarray(vr[frame_index].asnumpy()).convert('RGB') img = dynamic_preprocess(img, image_size=input_size, use_thumbnail=True, max_num=max_num) pixel_values = [transform(tile) for tile in img] pixel_values = torch.stack(pixel_values) num_patches_list.append(pixel_values.shape[0]) pixel_values_list.append(pixel_values) pixel_values = torch.cat(pixel_values_list) return pixel_values, num_patches_list video_path = './examples/red-panda.mp4' pixel_values, num_patches_list = load_video(video_path, num_segments=8, max_num=1) pixel_values = pixel_values.to(torch.bfloat16).cuda() video_prefix = ''.join([f'Frame-{i+1}: <image>\n' for i in range(len(num_patches_list))]) question = video_prefix + 'What is the red panda doing?' # Frame1: <image>\nFrame2: <image>\n...\nFrame8: <image>\n{question} response, history = model.chat(tokenizer, pixel_values, question, generation_config, num_patches_list=num_patches_list, history=None, return_history=True) print(f'User: {question}\nAssistant: {response}') question = 'Describe this video in detail.' response, history = model.chat(tokenizer, pixel_values, question, generation_config, num_patches_list=num_patches_list, history=history, return_history=True) print(f'User: {question}\nAssistant: {response}')
六、结语
InternVL作为OpenGVLab推出的一个开源多模态大模型,凭借其强大的技术架构和广泛的应用场景,正在成为多模态领域的重要力量。它不仅在视觉问答、文档理解等任务上表现出色,还在多语言支持、图像生成等方面具备强大的能力。未来,随着技术的不断进步和应用场景的不断拓展,InternVL有望在更多领域发挥更大的作用。
七、项目地址
GitHub仓库:https://github.com/OpenGVLab/InternVL
arXiv技术论文:https://arxiv.org/pdf/2312.14238
在线体验Demo:https://huggingface.co/spaces/OpenGVLab/InternVL
(文:小兵的AI视界)