LangChain 怎麼玩?用 Hugging Face 結合 LangChain,做個英文對話機器人模擬面試吧

Posted on  Mar 20, 2024  in  LangChain , Python 程式設計 - 高階  by  Amo Chen  ‐ 7 min read

LangChain 作為 1 個框架,讓開發者可以很輕鬆地開發語言模型相關的應用,不過語言模型還是有其極限,有些功能仍須仰賴傳統的程式設計、機器學習等領域,譬如語音辨識、語音合成等等,所幸這些功能可以藉由 Hugging Face 等平台輕鬆實現。

本文將教導如何使用 Hugging Face 上的模型,並結合 LangChain 做出可以用英文對話的機器人,讓語言模型的應用跨出文字以外的領域!

本文環境

$ brew install portaudio
$ pip install langchain transformers sentencepiece "datasets[audio]" torchaudio pytorch pydub pyaudio SpeechRecognition openai-whisper

本文需要 Ollama 指令與 Meta 公司提供的 llama2 模型(model),ollama 指令請至 Ollama 官方頁面下載安裝,安裝完成之後即可執行指令安裝 llama2 模型,其安裝指令如下:

$ ollama run llama2

p.s. 執行上述模型需要至少 8GB 記憶體, 3.8G 硬碟空間

Hugging Face 🤗 簡介

Hugging Face 是 1 個機器學習(Machine Learning)與資料科學(data science)的平台,該平台不僅擁有各種開源的 AI/ML 模型(model)、資料集(dataset),還提供使用者部署/使用模型的功能,使用者可以使用 Hugging Face 的 API, 或者選擇部署到 Google Cloud, Azure ML, Amazon SageMaker 等平台,甚至如果要自行訓練模型的話,也可以在 Hugging Face 上進行(需支付費用)。

目前 Hugging Face 也將各種 AI/ML 模型分為以下類別:

  1. Multimodal 多模模型
  2. Computer Vision 電腦視覺
  3. Natural Language Processing 自然語言處理
  4. Audio 聲音
  5. Tabular 表格
  6. Reinforcement Learning 強化學習
  7. Other 其他

每個類別適合應用的情境不同,譬如特斯拉的自動駕駛 AI 模型就是屬於 Computer Vision 的 1 種(當然, Hugging Face 上面沒有特斯拉的自駕模型),大家很愛用的 text to image 影像生成也屬於 Computer Vision 類別,大家可以視需求在 Hugging Face 上面找看看是否有合適的模型可以使用。

如何使用 Hugging Face 上的各種模型?

首先註冊 Hugging Face 帳號,並至 https://huggingface.co/settings/tokens 建立 1 組 Access Tokens, 這是在使用 Hugging Face 所提供的 API 時需要的 Token (請保護它免於外流)。

使用模型的方法十分簡單,主要分為 3 步驟:

  1. 搜尋
  2. 測試
  3. 整合
搜尋

首先,可以在首頁搜尋框或點選 Models 瀏覽各類 Models:

hugging-face-models-index.png

如果你明確知道需要何種類型的 model, 可以進一步點選標籤過濾,例如我們需要 text-to-image 文字生成圖片的 stable-diffusion-v1-5 :

hugging-face-models-filter.png

測試

進到 model 的頁面之後,通常可以在右邊 Infererce API 的區塊進行測試(有些模型不提供),例如我們輸入 a man is driving a car 測試生成圖片:

hugging-face-test-inference-api.png

如果測試結果符合需求/預期,那就可以進一步整合。

整合

Model 頁面右上角的 DeployUse in ** 是整合模型的方法。

Deploy 是使用 API 進行整合:

hugging-face-deploy-menu.png

上圖可以看到有 serverless, dedicated, Azure ML 等選項,其中 serverless 只適合 prototyping 使用,如果是自己在測試,僅有少少幾個 requests, 可以選擇 serverless 就好;但如果是 production 環境,最好使用其他選項(需付費)。

選擇使用 serverless 就會出現如何呼叫 API 的程式碼,左上角還可以選擇程式語言或者 cURL 指令;右上角可以選擇我們一開始申請的 Access Token, 此 Token 對應的是下圖中的 xxxxxxxxxxxxx... 字串,也可以勾選 Show API token 將程式碼中的 xxxxxxxxxxxxx... 字串換為 Access Token:

hugging-face-serverless-api.png

這是使用 API 進行整合的方法。

再來看看 Use in **Use in ** 是使用 Hugging Face 所提供的函式進行整合的方法,多數可以將預先訓練好的模型下載到本機(local)環境執行(缺點是本機很容易因為硬體規格較低,執行速度慢):

hugging-face-use-library-01.png

stable-diffusion-v1-5 為例,它提供使用 Diffusers 進行整合的方式,它可以下載預先訓練好的模型執行並產生圖片:

hugging-face-use-library-02.png

你可以試著用以下方式生成圖片,詳情請參考 Diffusers 文件

image = pipeline("a man is driving a car").images[0]

以上是使用 Hugging Face 的各式模型的方法簡介。

英文對話機器人流程圖

以下是本文的英文對話機器人流程圖:

langchain-hugging-face.png

流程是輸入一段聲音之後,我們透過語音識別(Speech Recognition),將聲音轉為 1 段文字作為輸入,並組成 prompt 輸入給語言模型,由語言模型生成文字回應之後,使用語音合成(TTS, Text to Speech)將文字轉為 1 段語音訊息。

從 prompt 到語言模型對我們來說已經駕輕就熟,所以整個流程關鍵是如何做到語音識別(Speech Recognition)以及語音合成(Text to Speech)。

在以往,無論是語音識別或者語音合成都是需要花費時間、資源進行開發的功能。但現在有 Hugging Face 之後,我們可以輕易的整合 Hugging Face 所提供的各種開源/付費模型,以完成我們需要的功能。

本文選擇以下 2 種模型分別用於語音辨識與語音合成:

語音辨識程式碼

雖然 Hugging Face 有 OpenAI Whisper 可以使用,不過本文使用包裝更為易用、完整的 SpeechRecognition 套件實作語音辨識的功能,其原因在於:

  1. 該套件整合多種語音辨識服務,包含 Google Speech Recognition, Microsoft Azure Speech, OpenAI whisper 等等,其中 OpenAI whisper 也可以在本機端使用。
  2. 該套件整合麥克風(Microphone)作為輸入裝置
  3. 該套件能自動偵測說話者結束談話的狀態(超過一定秒數未說話即視為結束)

綜合上述 3 個原因,使用 SpeechRecognition 會更容易實作我們想要的功能,體驗也會更好。

SpeechRecognition 所提供的程式碼範例如下:

import speech_recognition as sr

# obtain audio from the microphone
r = sr.Recognizer()
with sr.Microphone() as source:
    print("Say something!")
    audio = r.listen(source)

try:
    text = r.recognize_whisper(audio, language="english")
    print("Whisper thinks you said " + text)
except sr.UnknownValueError:
    print("Whisper could not understand audio")
except sr.RequestError as e:
    print("Could not request results from Whisper")

p.s. 更多範例請參考 speech_recognition/examples at master · Uberi/speech_recognition

前述範例可以看到在 with sr.Microphone() as source: 區塊並不需要設定錄音的時長(duration), 這是因為 SpeechRecognition 已經實作自動偵測說話者結束談話的功能,不僅解決我們需要知道使用者何時結束談話的問題,也讓程式碼看起來更佳簡潔。

當然,自動偵測機制是可以透過參數調整,詳情可以閱讀 Speech Recognition Library References

最後,當我們收到來自麥克風的資料之後,就可以使用呼叫 recognize_whisper() 方法,對語音進行辨識,並轉為文字。

語音合成程式碼

語音合成的部分,我們使用 Microsoft SpeechT5 進行。

它提供的範例使用方式很簡單:

# Use a pipeline as a high-level helper
from transformers import pipeline

pipe = pipeline("text-to-speech", model="microsoft/speecht5_tts")

只要給 pipe 一段文字以及演講者的聲音特徵 embedding 即可生成一段 speech, 我們只要用 soundfilepydub 2 個套件即可播放這段語音:

import io
import soundfile as sf
import torch
from pydub import AudioSegment
from pydub.playback import play
from transformers import pipeline
from datasets import load_dataset

embeddings_dataset = load_dataset("Matthijs/cmu-arctic-xvectors", split="validation")
speaker_embedding = torch.tensor(embeddings_dataset[7306]["xvector"]).unsqueeze(0)

pipe = pipeline("text-to-speech", model="microsoft/speecht5_tts")

speech = pipe("Good morning!", forward_params={"speaker_embeddings": speaker_embedding})
virtual_file = io.BytesIO()
sf.write(
    virtual_file,
    speech['audio'],
    samplerate=speech['sampling_rate'],
    format='wav'
)
virtual_file.seek(0)
sound = AudioSegment.from_file(virtual_file, fromat='wav')
play(sound)

上述範例比較多需要說明的部分,以下分段說明。

首先用 load_dataset() 載入 CMU 語音合成的資料集,該資料集共有 7,391 筆資料,每 1 筆都是長度 512 的 embedding, embeddings_dataset[7306] 對應是 1 名美國女性的聲音特徵,你也可以試著改編號換掉聲音:

embeddings_dataset = load_dataset("Matthijs/cmu-arctic-xvectors", split="validation")
speaker_embedding = torch.tensor(embeddings_dataset[7306]["xvector"]).unsqueeze(0)

再來用 pipe() 生成 1 個含有 speech 資料的 dictionary:

speech = pipe(
    "Good morning!",
    forward_params={"speaker_embeddings": speaker_embedding}
)

接著將 speech 的資料寫到 Python 的 BytesIO object 之中,如此就不用特地將音訊存成檔案再播放,可以存在記憶體中直接播放:

virtual_file = io.BytesIO()
sf.write(
    virtual_file,
    speech['audio'],
    samplerate=speech['sampling_rate'],
    format='wav'
)

寫完資料之後,要將檔案指標指回開始的位置,才能順利從頭播放:

virtual_file.seek(0)

最後,用 pydub 播放 speech:

sound = AudioSegment.from_file(virtual_file, fromat='wav')
play(sound)

完整程式碼

至此我們已經利用 Hugging Face 上的 models 攻克實作語音對話機器人的 2 大難關,最後可以用 LangChain 結合語言模型,拼出一個可以模擬面試的機器人,完整程式碼如下:

import io
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_community.llms import Ollama
from langchain.prompts.prompt import PromptTemplate
from transformers import pipeline
from datasets import load_dataset
import torch
import soundfile as sf
from datasets import load_dataset
import speech_recognition as sr
from pydub import AudioSegment
from pydub.playback import play


def pause():
    input('\n[Press Enter to Continue]')


# load xvector containing speaker's voice characteristics from a dataset
embeddings_dataset = load_dataset("Matthijs/cmu-arctic-xvectors", split="validation")
speaker_embeddings = torch.tensor(embeddings_dataset[7306]["xvector"]).unsqueeze(0)
pipe = pipeline("text-to-speech", model="microsoft/speecht5_tts")


def text_to_speech(input):
    text = input['response']
    print(text)
    for sentence in text.split('. '):
        speech = pipe(sentence, forward_params={"speaker_embeddings": speaker_embeddings})
        virtual_file = io.BytesIO()
        sf.write(
            virtual_file,
            speech['audio'],
            samplerate=speech['sampling_rate'],
            format='wav'
        )
        virtual_file.seek(0)
        sound = AudioSegment.from_file(virtual_file, fromat='wav')
        play(sound)


llm = Ollama(model='llama2')

template = """You are a Staff Engineer working at Google. Currently, you are interviewing a candidate who has applied for the Senior Engineer position. Your task is to first ask the candidate to introduce themselves and then proceed to ask questions related to software engineering. No additional description is needed.

Current conversation:
{history}
Human: {input}
AI Assistant:"""

prompt = PromptTemplate(input_variables=["history", "input"], template=template)

conversation = ConversationChain(
    prompt=prompt,
    llm=llm,
    verbose=False,
    memory=ConversationBufferMemory(),
)
chain = conversation | text_to_speech
chain.invoke(input='Let\'s start!')
pause()

r = sr.Recognizer()
while True:
    # obtain audio from the microphone
    with sr.Microphone() as source:
        print("*Recognition of your speech is active. \n Please say something to the interviewer.*")
        audio = r.listen(source)

    try:
        input_text = r.recognize_whisper(audio, language="english")
        print(f'me: {input_text}')
    except sr.UnknownValueError:
        print("Whisper could not understand audio")
        continue
    except sr.RequestError as e:
        print("Could not request results from Whisper")
        continue

    if not input_text:
        continue

    chain.invoke(input=input_text)
    pause()

上述範例執行之後, AI 大概率會請你自我介紹:

Great, thank you for joining us today! Could you please start by introducing yourself and telling us a bit about your background and experience in software engineering?

在說話回答 AI 之前,會顯示 [Press Enter to Continue] ,給我們時間思考一下,按下 Enter 之後就會開始錄音,你可以回答 AI 面試官的問題。

前述範例需要說明的部分主要為 prompt, 請語言模型扮演 1 名在 Google 工作的 Staff Engineer, 然後告訴 AI 正在面試 Senior Engineer 的 candidate, 面試流程從自我介紹開始,然後會問面試者一些關於軟體工程的問題:

template = """You are a Staff Engineer working at Google. Currently, you are interviewing a candidate who has applied for the Senior Engineer position. Your task is to first ask the candidate to introduce themselves and then proceed to ask questions related to software engineering. No additional description is needed.

Current conversation:
{history}
Human: {input}
AI Assistant:"""

剩下的部分則是使用 ConversationChain 額外結合將 AI 生成的回應轉為 speech 的部分:

conversation = ConversationChain(
    prompt=prompt,
    llm=llm,
    verbose=False,
    memory=ConversationBufferMemory(),
)
chain = conversation | text_to_speech

以上就是 1 個語音對話機器人的原型。

這個原型其實還有不少缺點有待加強,包含語音合成的速度、太長的文字可能會導致語音生成失敗、各種 error 處理等等問題有待最佳化,又再次驗證要做好 1 個服務/軟體都不是一件簡單的事。

總結

這是 1 個對軟體開發者很友善的時代,在以往需要花費大量資源開發的功能,現在可以藉由 Hugging Face 等平台輕鬆實現,甚至結合語言模型,還可以做出更驚奇的應用。

以上!

Enjoy!

References

microsoft/speecht5_tts · Hugging Face

Speech Recognition Library References

soundfile

jiaaro/pydub: Manipulate audio with a simple and easy high level interface

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!