Talk is cheap, here is the code

先上代码和 demo 效果:

https://github.com/Chivier/easy-gpt4o/

GPT-4 Trace on 计划

OpenAI 昨晚的发布会相比大家应该都或多或少看到了相关的新闻。昨晚本月最重磅的科技圈新闻,已经引起了所有人的关注。凭借其令人印象深刻的功能,我们看到了多模态的人机交互的未来好像已经到来了。然而,经过仔细检查,GPT-4o 似乎并没有比其前身 GPT-4 更具显著优势。事实上,我相信我可以使用 GPT-4 Turbo 和其他 OpenAI API 的组合来创建一个类似于 GPT-4o 的系统。而且,我设法在不到200行的代码中实现了这一点!

(PS: Trace on 这个名字的出处比较暴露成分,不过问题不大)

How to Make It Happen

首先,让我们更仔细地看一下 GPT-4o 的能力。在最近的 OpenAI 春季新闻发布会上,GPT-4o 展示了其与实时视频互动的卓越能力。为了复制这个功能,我们可以采用分而治之的方法,将复杂的问题拆分成更小、更容易调试的部分。基本上,较大的问题可以分解为以下较小的问题:

  1. 理解视频的内容
  2. 理解视频文件中的问题和音频
  3. 促进与视频文件的交互,进行聊天
  4. 阅读生成的答案并将其转换为音频

通过解决每一个较小的问题,我们可以使用GPT-4 Turbo和其他OpenAI API的组合来实现类似于GPT-4o的系统。

以下是流程图:

e44587022ef4b91975d45da279ae63773c39c9a7

下面我们将逐个实现上述模块。

视频理解

为了解决从视频中生成片段的任务,我们可以利用 OpenAI 的视觉理解 API。一个简单直接的方法是逐帧生成描述,然后将它们组合成一个包含详细信息的综合性文本。

为了完成这个任务,我求助于 GPT-4 Turbo 来辅助我生成对应片段。这是我向 GPT-4 Turbo 提供的 prompt 提示词:

Help me write a Python code. My final target is a function.  
Input: path of a video file  
Ouput: A list of dictionay

1. The program read the video file, generate an image every 1 second in the video. And I don't like too large image, help me compress the image to fixed size, width and height are limit to 1024 pixels, don't change image scale at the same time.
2. Give me a dictionary, [{index, image path}]

现在,我已经准备好了代码的第一部分。下一步是为每个图像生成描述。然而,由于GPT-4 Turbo的知识限制,它无法直接提供答案。为了克服这个问题,我决定给 GPT-4 Turbo 一些 OpenAI API 作为参考。这样,它可以更好地理解期望的输出,并提供更准确的描述。

OK, the first step is done. Thank you.

The next step is. I wanna use GPT4-Preview API to generate description for each frame I just get. So I need a function:
Input: image path
Output: a string, description

I will give you an example about how to use GPT4-Preview image api:

import os # Included to Python
from openai import OpenAI # OpenAI official Python package
from IPython.display import Audio # Included to Python
client = OpenAI(
    api_key=os.getenv("openaikey"))
response = client.chat.completions.create(
    model="gpt-4-vision-preview",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What you think the person in the image is doing?"},
                {
                    "type": "image_url",
                    "image_url": "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/AA18Lnc8.img?w=1920&h=1080&q=60&m=2&f=jpg",
                },
            ],
        }
    ],
    max_tokens=300,
)

print(response.choices[0].message.content)

Local image read:

import base64
from mimetypes import guess_type
# Function to encode a local image into data URL 
def local_image_to_data_url(image_path):
    # Guess the MIME type of the image based on the file extension
    mime_type, _ = guess_type(image_path)
    if mime_type is None:
        mime_type = 'application/octet-stream'  # Default MIME type if none is found

    # Read and encode the image file
    with open(image_path, "rb") as image_file:
        base64_encoded_data = base64.b64encode(image_file.read()).decode('utf-8')

    # Construct the data URL
    return f"data:{mime_type};base64,{base64_encoded_data}"

# Example usage
image_path = '<path_to_image>'
data_url = local_image_to_data_url(image_path)
print("Data URL:", data_url)

And you need use local image read replace the image url above:

…
"type": "image_url",
"image_url": {
   "url": "data:image/jpeg;base64,<your_image_data>"
}
…

Please give me the function I need.

目前为止,视觉部分我们暂时解决了(真的吗?)
 

音频转写

好的,现在让我们专注于音频部分。我使用以下提示来帮助我构建一个独立模块:

Good job. Next step is the audio. I wanna extract the voice in the video and convert it to text.
The input is a video path. The output is the transcription:

Here is the API reference:

from openai import OpenAI
client = OpenAI()

audio_file= open("/path/to/file/audio.mp3", "rb")
transcription = client.audio.transcriptions.create(
  model="whisper-1", 
  file=audio_file
)
print(transcription.text)

对话和总结

当我们获取到视觉描述文本和音频转录之后,我们可以将它们结合在一起。利用提示技巧,我们可以利用GPT-4 Turbo生成一个综合性回答,将视觉和听觉信息融合在一起。然后,我们可以利用一个TTS(文本转语音)模型为回答生成相应的声音。通过采用这种方法,我们可以创建一个流畅而身临其境的体验。

背后的其他工作

乍一看,这个任务的故事可能看起来很简单,好像我只需要付出很少的努力。但事实是,我花了超过2个小时才完成。

你可能会想,一个本来应该在不到10分钟内完成的任务为什么会花费这么多时间呢?

嗯,虽然GPT帮助我生成了大部分的代码,但它并不总是准确的。例如,在"图像到文本"部分,它难以理解响应结果的类型,这就需要我进行手动修复。

此外,当涉及到文件大小时,OpenAI的语音转文本有一定的限制。直接发送小视频文件是可行的,但随着文件大小的增加,我不得不首先使用 ffmpeg 从视频中提取音频部分。

subprocess.run(["ffmpeg", "-i", video_path, "-vn", "-acodec", "pcm_s16le", "-ar", "44100", "-ac", "2", audio_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

此外,还涉及到许多复杂的调试和测试任务。然而,最终,一切似乎都很顺利。我已经在GitHub上更新了代码和演示供您参考:

https://github.com/Chivier/easy-gpt4o

项目规划和提升方案

性能提升

这个项目的目的并不是否认 GPT-4o,而是想说明一个简单的事情:GPT-4o 可能并没有我们想象中那么强大,实际上更多的是对已有技术的组合包装。OpenAI 基于自己的技术积累,成功的降低了多模型协作的延迟。如果我们有机会将这些部分逐个替换,我们理论上完全可以实现一个实时的端到端模型。

我们将三个模块进行拆解,在下图中标注了 A、B、C 三个模块。首先 A、B 两个模块是可以完全并行的,我们可以将这两个部分进行完全并行执行。视频抽帧之后,可以启用多个 CLIP 模型进行更快的标注,进一步提升并行速率。而音频转写,大文件也可以切分成多个音频转写任务并行执行,分别执行之后合成。

另一个层面显然容易想到的是,C 模块中,生成的文字结果转换成音频的时候,我们实际上可以使用 OpenAI 的 Streaming 生成模式,每当生成完一个句子的时候,我们就可以阶段输出到 TTS 模型中进行转换,并且逐条播放。这样我们语言模型推理的时间,实际上是可以隐藏在播放音频的时间里。

我们重新整理一下思路,升级版本的 Easy-GPT4o 可能会有这样的效果:

ed6d525d840f3970bd21f3c7967a19d1ed4b16aa

理论上,我们最短的等待的时间是

即,音视频处理一个切片的时间加上单句的推理时间。

那么全流程只剩下最后一个问题,也就是唯一的限制:交互时间。换而言之,我们的瓶颈集中在音视频内容序列和语言模型的交互上,语言模型的 input token 数量会成为唯一的限制。

效果提升

7d6ebff3b0992e1df3ed8ff987c72fc78e704b61

坦白来说,我以前几乎没有接触过 CV 领域的研究。唯一的认知也停留在了解 CLIP 模型上。整个项目中最不合理的部分就是 A 模块:将视频转换成文字描述。非常欢迎大家提出 issue 给我提供更好的解决方案。

在视频描述方面,我是对每一秒的视频等差抽帧,之后暴力提问 GPT 4 Turbo,让他对每一个抽出的图片进行描述,之后直接串联在一起。事实上,这部分的设计并不合理,对于连续录制的视频来说,这个方案可以被优化,相邻的图片之间应该使用「请问这两张图片之间发生了什么」或者「相比上一张图片,你看到了什么新的内容吗?」进行增量提问。这种方式可以极大的节约 token,从而减少对视频描述的 token 消耗。

音频描述上,我们忽略了时间信息和情感信息。这部分我们其实可以使用更复杂的转写方案,生成更详细的包含时间信息和情感信息的转写,进一步和视频信息进行整合。

最后一个模块,我们也可以使用自由度更高的方案,例如我们可以介入自己的 TTS 模型生成不同音色的语音。

总体而言,项目未来还有非常大的提升空间,但是,我至少提出来一个看起来好像勉强可行的方案。

项目意义

这个项目的意义并不是否定 GPT-4o 这一产品,事实上,我也承认,GPT-4o 是我使用过用户体验最好的多模态模型。但是,我们必须承认的是,GPT-4o 中,o 所代表的 Omnipotent 并不是真的那么强大。至少纯文本生成上,他应该是不如 GPT 4 Turbo Preview 模型的。

因此,我们首先需要更冷静地思考,我们真的充分挖掘已有模型的能力了吗?我觉得单单从上面的例子中就不难看出,实际上我们更早就应该做出类似 GPT-4o 的产品。而且是每一个普通开发者都可以用非常低成本快速实现的。我有一个习惯,每天起床的第一件事情是浏览一遍当天的 GitHub Trending。但我经常会因为同质化的项目填满屏幕而感到无聊,比如 Stable diffusion 的各种衍生产品填满整个页面的几个月,让我非常难受。我自己也因为自己从事科研领域和自己在开发上的一些理念产生感到割裂,科研领域让我更深刻了解模型的能力和 AI 的性能,我每一天都能看到这一条清晰的边界;另一方面,开发领域我会感觉很多 idea 距离落地有过多的非技术要素,例如网络安全,产品伦理,项目部署,域名签发等等一系列琐碎的事情。而且从这次的开发经验来看,AI 并不能真正写好一个程序。当项目结构更加复杂的时候,例如前后端需要通过跨域方案进行结合,app 的组件设计和功能设计需要有一定的关联或者添加动态功能,当你需要给注册产品的用户提供用户注册功能和收费功能,这些其实都应该交给 AI,让他结合诸多的 SaaS 产品帮我组合好一个框架,而我也应该专注在产品的心脏上,而不是为了落地产品付出高昂的维护成本。

其次,我在完成整个框架的时候做了一个假设:多模态之间的交互是使用人类语言的。这个假设实际上是项目中最不合理的地方,语言在视觉模型的冲击面前是苍白无力的,但是语言在描述画面感和情感上是非凡的。汉语中有一个概念叫作「通感」,描述了因为一种感受联想到另外一种感受的过程,例如「舞殿冷袖,风雨凄凄」,或是「大弦嘈嘈如急雨,小弦切切如私语」。某种程度上,这也算是一种比喻,但是确实是跨越了多种信息形式的联想。多模态模型中,我们很容易忽略这一特质,纯文本或者纯图像的 token 是不能很好描述这类隐形信息的,设计更精妙的 token 可能会极大的提升多模态模型的能力。同理,网络结构也直接串联和组合也不一定是这个问题最优的解法,短期内最可能实现高质量多模态可能会借鉴大语言模型中 MOE 的想法和设计思路。

此外,这个项目的一个重要意义是提出了我们自己定制类似 GPT-4o 这种组合模型的可能性。普通开发人员也可以通过拆解模块的方法实现极为复杂的多模态交互需求。上述模型中的模块我们可以自由组合和替换。例如我们可以使用自己带 TTS 模型。也可以在一些特殊场景下对视频内容提取提出定制化的需求,聚焦视频中的一些特殊对象。或者是在转写之后介入一个模型帮我们完成多语种的翻译。end-to-end 实际上是一把双刃剑,低延迟,效果好,但是我们也失去了对功能效果和功能定制的自由。AI 其实并不应该用来限制我们想象的自由。我应该算是个科幻小说的爱好者,在读凡尔纳,读莱姆,读阿西莫夫,读刘慈欣的时候,我会被他们天马行空的想象力震撼,会因为这些伟大的创意和灵感产生感动和共鸣,也是因为这些作品诱导我成为一个科研工作人员。但是我也慢慢感觉,就好像有一天,我们的思考上限被上锁了,因为我们有了 GPT,我们某些时候开始不思进取,不去思考那些边界线上,和边界线之外的事情。但是上述的几位科幻泰斗,我并不认为他们是顶尖的科研人员,但是他们提出的点子能给我们思考的方向。

最后,超长上下文模型的研究在当下仍然具有非常重要的意义。我们不妨做一个简单的计算,假设我们一帧视频用 500 个 token 描述,模型确实可以看得非常仔细,但是我们的视频时长就会被极大限制。如果一帧视频用 100 个 token 描述,我们的模型就不会有这么强视觉能力,但是他可以看到更长时间范围内的变化。如果我们使用变化的 token 描述,程序行为会变得复杂,从而影响整个流程的性能。如果要运用现有技术解决多模态问题,短期内最重要的研究方向还是如何实现超长文本的上下文。RAG 和类似的技术提供了一个可行的解法,但是实际上这个解法并不优秀,但是作为一种暴力美学,我非常喜欢向量数据库这种直观暴力的想法。目前的超长文本其实也会带来一些衍生问题,例如「如何实现记忆和遗忘」,「什么时候需要将 RAG 中的记忆内容送入 fine tune 阶段内化成模型的一部分」。这两个问题其实更像是人脑的机制,对应了人类的「遗忘」和「阶段性学习」。在多模态记忆被引入的时候,我觉得「让 AI 更像真实人类」这个课题,算是真正意义上全面启动了。

不过,对于上面几个问题,我们课题组恰好都有一些对应的解决方案,我目前的领域是降低端到端推理的延迟的方案。欢迎大家关注我们的 repo!这里也就是简单讨论一下,近期也会更新一下对上面每个问题的一些深度思考(欢迎催更)。也欢迎私信我进行讨论。我业余也是开源爱好者,也有丰富地将 AI 产品落地的经验,非常欢迎大家和我一起整活。

我们需要数据集!

唔,其实评估整个产品的效果还是非常困难的一件事情。或者说整个产品的每一个模块都缺少了对应的数据集。这个月的晚些时候我打算启动一个开源数据库计划(先要搞个大点的共享网盘)。GPT-4o 已经发布,我们不应该默认这就是我们想要的产品,我们的判断价值和审美不应该被一个单一产品蒙蔽,我们对真实世界的触感和热爱还是应当作为我们评价世界的第一手标准。对于评估类似 GPT-4o 数据集组件的讨论方案需要大家的帮助。欢迎随时私信联系。