如何利用OpenAI與喬布斯進行跨時空對話

語言: CN / TW / HK

```bash

注:文章摘要 by ChatGPT, 頭圖 by DALL·E

```

很久沒有寫過有趣的代碼了,最近因為OpenAI開放了價格很低的API,所以很多和我一樣對AGI一無所知的人都試圖來實現一些好玩的東西。不過能看到的大部分應用都是聊天或者翻譯之類的,感覺沒什麼新意,所以上週先寫了個不同一點的玩具:將AI接入本地電腦的工具

最近在想,利用LLM強大的能力,在一定程度上其實可以實現人的數字永生。趁週末準備把我的博客內容提取一下,實現一個與自己對話的工具。這時候我發現了一個重大的障礙就是我的博客已經好多年沒有更新了,並且也沒什麼有營養的內容。不過這個難題好解決,因為我馬上就想到 @屈屈 的博客內容質量很高,內容量也足夠我實現一個demo,所以我先實現一個JerryAI。

先看下效果:

實現原理

TL;DR: 先將文章內容通過 OpenAI 的 Embedding 模型轉化成向量數據,然後與問題進行向量距離計算,最後將topK答案扔給 OpenAI 潤色輸出。

下面是主要的步驟,也可以參考官方的這個cookbook

第一步,獲取內容源

下載數據這一步就不詳細説明了,大家注意使用自己的博客、微博等內容源進行測試,不要逮着一個同學薅羊毛。

下載完之後可以做一些簡單處理,主要是需要先去除圖片、視頻標籤及無關的換行空白等,最終形成純文本文件。

第二步,優化內容token長度

```python import os import tiktoken

tokenizer = tiktoken.get_encoding("cl100k_base") posts = []

假如我們第一步先將文章內容保存到了本地./posts 目錄下

for file in os.listdir('posts/'): with open('posts/' + file, 'r', encoding='UTF-8') as f: text = f.read() [title, content] = text.split('@@@') tokens = len(tokenizer.encode(content)) posts.append({'title': title, 'content': content, 'tokens': tokens}) ``` tiktoken 是 OpenAI 提供的一個工具,可以快速標記文本成模型 token 以計算其長度。

計算完之後可以統計一下每篇文章的 token 長度,因為 OpenAI 的不同模型對輸入 token 長度有不同限制,如果超出了後續需要使用的模型限制,在這裏就需要將內容做下分割。同時也要考慮到 system 和 user 的 prompt 將會佔用的長度。

下面是檢測和分割內容的主要代碼,如果你使用的是微博這種很短的內容可以忽略這一步。

```python def split_text(text, max_tokens = max_tokens): sentences = text.split('。') #按句號分割,以防將同一個句子分割到了不同的text斷中 n_tokens = [len(tokenizer.encode(" " + sentence)) for sentence in sentences] chunks = [] tokens_so_far = 0 chunk = []

for sentence, token in zip(sentences, n_tokens):
    if tokens_so_far + token > max_tokens:
        chunks.append("。".join(chunk) + "。")
        chunk = []
        tokens_so_far = 0
    if token > max_tokens:
        continue

    chunk.append(sentence)
    tokens_so_far += token + 1
return chunks

ebd_posts = [] for post in posts: if post['content'] is None: continue if post['tokens'] > max_tokens: splited_posts = split_text(post['content']) for _post in splited_posts: ebd_posts.append((post['title'], _post)) else: ebd_posts.append((post['title'], post['content'])) ```

第三步,打標

把文本轉化為向量矩陣,這一步是整個過程最核心的,也是所有搜索、推薦、智能問答必不可少的。但是藉助 OpenAI 的 Embeddings 接口,我們可以使用一行代碼就能實現,並且成本非常低。

```python import pandas as pd import openai

df = pd.DataFrame(ebd_posts, columns = ['title', 'content']) df['tokens'] = df.content.apply(lambda x: len(tokenizer.encode(x)))

以下這行代碼會多次請求 OpenAI 接口,會有較長的等待時間,如果數據量大可以自行做優化處理

df['embeddings'] = df.content.apply(lambda x: openai.Embedding.create(input=x, engine='text-embedding-ada-002')['data'][0]['embedding'])

df.to_csv('data/embeddings.csv') df.head() ``` 同時,我們將打標後的數據保存在本地,方便後續查詢。因為我們這裏只是一個 demo 演示,所以使用本地文件存儲,如果內容量比較大的,建議使用向量數據庫。

第四步,計算匹配內容

在這一步我們先將問題也向量化,然後與本地內容進行距離計算,獲取到匹配度更高的內容。

```python import pandas as pd import numpy as np import openai from openai.embeddings_utils import distances_from_embeddings

max_len = 4000 #4096 for gpt-3.5-turbo

df=pd.read_csv('data/embeddings.csv', index_col=0) df['embeddings'] = df['embeddings'].apply(eval).apply(np.array) df.head()

def match_text(question): context = [] cur_len = 0

# 問題打標
q_embeddings = openai.Embedding.create(input=question, engine='text-embedding-ada-002')['data'][0]['embedding']

# 使用 OpenAI 提供的工具函數做向量匹配,如果是存儲的向量數據庫,查詢計算更方便
df['distances'] = distances_from_embeddings(q_embeddings, df['embeddings'].values, distance_metric='cosine')
for i, row in df.sort_values('distances', ascending=True).iterrows():
    cur_len += row['tokens']
    if cur_len > max_len:
        break
    context.append(row['content'])
return context

``` 補充一句,因為 OpenAI 的 Embedding 模型是基於普遍性數據訓練,如果你的內容或問答過於專業,有可能就會出現查詢數據不準確的情況,這種時候可以考慮自己訓練 Embedding 模型,比如可參考 text2vec 這個項目。

第五步,優化回答內容

上面的步驟已經能根據問題獲取到答案了,但是返回的內容是原始的文本片段,不夠友好,我們可以通過將匹配的內容發送給 OpenAI 來潤色。 ```python import openai

def q(question=''): answers = match_text(question) context = '\n\n###\n\n'.join(answers)

try:
    # prompt 可以自己調整優化,其他參數也可以根據需要修改
    completion = openai.ChatCompletion.create(
        model='gpt-3.5-turbo',
        messages=[
            {'role': 'system', 'content': f"你叫xxx,是一個熱心和大家分享和交流的人,你擅長的領域有xxxx。"},
            {'role': 'user', 'content': f"根據context提供的信息回答我提出的問題。\n\nContext: {context}\n\n---\n\nQuestion: {question}\nAnswer:"}
        ]
    )
    return completion['choices'][0]['message']['content']
except Exception as e:
    print(e)
    return ''

```

最後的思考

因為 GPT-3.5 的模型是一個文本模型,並沒有邏輯和推理的能力,所以現有的實現本質還只是一個搜索匹配的工具,加上對話功能也只能説是多了一些總結和表達的能力,但效果還是比較驚豔的。如果想達到更好的效果,還可以自己做 Fine-tuning,使用更簡單,只是價格會貴一些。

按 OpenAI 的介紹,GPT-4 有更強的推理能力。回到我們的標題,如果我們將喬布斯的傳記、演講等內容交給 OpenAI,通過不斷的訓練調優,一定是可以做到與喬布斯進行跨時空的對話,甚至可以用他的思考方式來解決新的問題,實現思想的延續。再加上多模態的能力,或者使用這個項目,還可以輸出喬布斯的語音。

而要實現上面的構想,在工程層面,只需要寫很少的代碼。最近在基於 OpenAI 做開發的時候,在驚喜的同時,又有一些失落,發現從代碼上來説,沒什麼實現難度,也會發現大部分的應用可複製成本非常低。

a world where all of us have access to help with almost any cognitive task.

生產力在進步,保持思考,持續行動吧。