paint-brush
Cách xây dựng trợ lý giọng nói của riêng bạn và chạy nó cục bộ bằng Whisper + Ollama + Barktừ tác giả@vndee
1,741 lượt đọc
1,741 lượt đọc

Cách xây dựng trợ lý giọng nói của riêng bạn và chạy nó cục bộ bằng Whisper + Ollama + Bark

từ tác giả Duy Huynh13m2024/04/02
Read on Terminal Reader

dài quá đọc không nổi

Tương tác dựa trên giọng nói: Người dùng có thể bắt đầu và dừng ghi âm giọng nói đầu vào của họ và trợ lý sẽ phản hồi bằng cách phát lại âm thanh đã tạo. Bối cảnh hội thoại: Trợ lý duy trì bối cảnh của cuộc trò chuyện, cho phép phản hồi mạch lạc và phù hợp hơn. Việc sử dụng mô hình ngôn ngữ Llama-2 cho phép trợ lý đưa ra phản hồi ngắn gọn và tập trung.
featured image - Cách xây dựng trợ lý giọng nói của riêng bạn và chạy nó cục bộ bằng Whisper + Ollama + Bark
Duy Huynh HackerNoon profile picture

Sau bài đăng mới nhất của tôi về cách xây dựng RAG của riêng bạn và chạy nó cục bộ, hôm nay, chúng tôi sẽ tiến thêm một bước nữa bằng cách không chỉ triển khai khả năng đàm thoại của các mô hình ngôn ngữ lớn mà còn bổ sung thêm khả năng nghe và nói. Ý tưởng rất đơn giản: chúng tôi sẽ tạo một trợ lý giọng nói gợi nhớ đến Jarvis hoặc Friday trong các bộ phim Iron Man mang tính biểu tượng, có thể hoạt động ngoại tuyến trên máy tính của bạn.


Vì đây là hướng dẫn giới thiệu nên tôi sẽ triển khai nó bằng Python và giữ nó đủ đơn giản cho người mới bắt đầu. Cuối cùng, tôi sẽ cung cấp một số hướng dẫn về cách mở rộng quy mô ứng dụng.

Techstack

Trước tiên, bạn nên thiết lập môi trường Python ảo. Bạn có một số tùy chọn cho việc này, bao gồm pyenv, virtualenv, thơ và những tùy chọn khác phục vụ mục đích tương tự. Cá nhân tôi sẽ sử dụng Thơ cho hướng dẫn này do sở thích cá nhân của tôi. Dưới đây là một số thư viện quan trọng bạn sẽ cần cài đặt:


  • phong phú : Để có đầu ra bảng điều khiển hấp dẫn trực quan.
  • openai-whisper : Một công cụ mạnh mẽ để chuyển đổi giọng nói thành văn bản.
  • suno-bark : Thư viện tiên tiến để tổng hợp văn bản thành giọng nói, đảm bảo đầu ra âm thanh chất lượng cao.
  • langchain : Một thư viện đơn giản để giao tiếp với Mô hình ngôn ngữ lớn (LLM).
  • sounddevice , pyaudionhận dạng giọng nói : Cần thiết để ghi và phát lại âm thanh.


Để biết danh sách chi tiết các phụ thuộc, hãy tham khảo liên kết tại đây .


Thành phần quan trọng nhất ở đây là phần phụ trợ Mô hình Ngôn ngữ Lớn (LLM), mà chúng tôi sẽ sử dụng Ollama. Ollama được công nhận rộng rãi là một công cụ phổ biến để chạy và phục vụ LLM ngoại tuyến. Nếu Ollama là người mới đối với bạn, tôi khuyên bạn nên xem bài viết trước của tôi về RAG ngoại tuyến: "Xây dựng RAG của riêng bạn và chạy nó cục bộ: Langchain + Ollama + Streamlit". Về cơ bản, bạn chỉ cần tải xuống ứng dụng Ollama, lấy mô hình ưa thích của bạn và chạy nó.

Ngành kiến trúc

Được rồi, nếu mọi thứ đã được thiết lập, hãy chuyển sang bước tiếp theo. Dưới đây là kiến trúc tổng thể của ứng dụng của chúng tôi, về cơ bản bao gồm 3 thành phần chính:


  • Nhận dạng giọng nói : Bằng cách sử dụng Whisper của OpenAI , chúng tôi chuyển đổi ngôn ngữ nói thành văn bản. Quá trình đào tạo của Whisper về các bộ dữ liệu đa dạng đảm bảo khả năng thành thạo của nó trên nhiều ngôn ngữ và phương ngữ khác nhau.


  • Chuỗi đàm thoại : Đối với khả năng đàm thoại, chúng tôi sẽ sử dụng giao diện Langchain cho mô hình Llama-2 , được cung cấp bằng Ollama. Thiết lập này hứa hẹn một luồng trò chuyện liền mạch và hấp dẫn.


  • Bộ tổng hợp giọng nói : Việc chuyển đổi văn bản thành giọng nói được thực hiện thông qua Bark , một mô hình tiên tiến của Suno AI, nổi tiếng với khả năng tạo ra giọng nói giống như thật.


Quy trình làm việc rất đơn giản: ghi lại lời nói, chép thành văn bản, tạo phản hồi bằng LLM và phát âm phản hồi bằng Bark.

Sơ đồ trình tự cho trợ lý giọng nói với Whisper, Ollama và Bark.

Thực hiện

Quá trình triển khai bắt đầu bằng việc tạo TextToSpeechService dựa trên Bark, kết hợp các phương pháp tổng hợp giọng nói từ văn bản và xử lý các văn bản đầu vào dài hơn một cách liền mạch như sau:

 import nltk import torch import warnings import numpy as np from transformers import AutoProcessor, BarkModel warnings.filterwarnings( "ignore", message="torch.nn.utils.weight_norm is deprecated in favor of torch.nn.utils.parametrizations.weight_norm.", ) class TextToSpeechService: def __init__(self, device: str = "cuda" if torch.cuda.is_available() else "cpu"): """ Initializes the TextToSpeechService class. Args: device (str, optional): The device to be used for the model, either "cuda" if a GPU is available or "cpu". Defaults to "cuda" if available, otherwise "cpu". """ self.device = device self.processor = AutoProcessor.from_pretrained("suno/bark-small") self.model = BarkModel.from_pretrained("suno/bark-small") self.model.to(self.device) def synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"): """ Synthesizes audio from the given text using the specified voice preset. Args: text (str): The input text to be synthesized. voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1". Returns: tuple: A tuple containing the sample rate and the generated audio array. """ inputs = self.processor(text, voice_preset=voice_preset, return_tensors="pt") inputs = {k: v.to(self.device) for k, v in inputs.items()} with torch.no_grad(): audio_array = self.model.generate(**inputs, pad_token_id=10000) audio_array = audio_array.cpu().numpy().squeeze() sample_rate = self.model.generation_config.sample_rate return sample_rate, audio_array def long_form_synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"): """ Synthesizes audio from the given long-form text using the specified voice preset. Args: text (str): The input text to be synthesized. voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1". Returns: tuple: A tuple containing the sample rate and the generated audio array. """ pieces = [] sentences = nltk.sent_tokenize(text) silence = np.zeros(int(0.25 * self.model.generation_config.sample_rate)) for sent in sentences: sample_rate, audio_array = self.synthesize(sent, voice_preset) pieces += [audio_array, silence.copy()] return self.model.generation_config.sample_rate, np.concatenate(pieces)
  • Khởi tạo ( __init__ ) : Lớp này lấy một tham số device tùy chọn, chỉ định thiết bị sẽ được sử dụng cho kiểu máy ( cuda nếu có GPU hoặc cpu ). Nó tải mô hình Bark và bộ xử lý tương ứng từ mô hình được đào tạo trước suno/bark-small . Bạn cũng có thể sử dụng phiên bản lớn bằng cách chỉ định suno/bark cho trình tải mô hình.


  • Tổng hợp ( synthesize ) : Phương thức này lấy đầu vào text và tham số voice_preset , chỉ định giọng nói sẽ được sử dụng để tổng hợp. Bạn có thể kiểm tra giá trị voice_preset khác tại đây . Nó sử dụng processor để chuẩn bị văn bản đầu vào và cài sẵn giọng nói, sau đó tạo mảng âm thanh bằng phương thức model.generate() . Mảng âm thanh được tạo sẽ được chuyển đổi thành mảng NumPy và tốc độ mẫu được trả về cùng với mảng âm thanh.


  • Tổng hợp dạng dài ( long_form_synthesize ) : Phương pháp này được sử dụng để tổng hợp các đầu vào văn bản dài hơn. Đầu tiên, nó mã hóa văn bản đầu vào thành các câu bằng cách sử dụng hàm nltk.sent_tokenize . Với mỗi câu, nó gọi phương thức synthesize để tạo ra mảng âm thanh. Sau đó, nó nối các mảng âm thanh được tạo ra với một khoảng im lặng ngắn (0,25 giây) được thêm vào giữa mỗi câu.


Bây giờ chúng ta đã thiết lập TextToSpeechService , chúng ta cần chuẩn bị máy chủ Ollama để phân phối mô hình ngôn ngữ lớn (LLM). Để thực hiện việc này, bạn cần thực hiện theo các bước sau:


  • Kéo mô hình Llama-2 mới nhất : Chạy lệnh sau để tải xuống mô hình Llama-2 mới nhất từ kho lưu trữ Ollama: ollama pull llama2 .


  • Khởi động máy chủ Ollama : Nếu máy chủ chưa khởi động, hãy thực hiện lệnh sau để khởi động nó: ollama serve .


Khi bạn đã hoàn thành các bước này, ứng dụng của bạn sẽ có thể sử dụng máy chủ Ollama và mô hình Llama-2 để tạo phản hồi cho thông tin đầu vào của người dùng.


Tiếp theo, chúng ta sẽ chuyển sang logic ứng dụng chính. Đầu tiên chúng ta cần khởi tạo các thành phần sau:

  • Rich Console : Chúng tôi sẽ sử dụng thư viện Rich để tạo bảng điều khiển tương tác tốt hơn cho người dùng trong thiết bị đầu cuối.


  • Whisper Speech-to-Text : Chúng tôi sẽ khởi tạo mô hình nhận dạng giọng nói Whisper, một hệ thống nhận dạng giọng nói nguồn mở tiên tiến do OpenAI phát triển. Chúng tôi sẽ sử dụng mô hình tiếng Anh cơ bản ( base.en ) để sao chép dữ liệu đầu vào của người dùng.


  • Chuyển văn bản thành giọng nói của Bark : Chúng tôi sẽ khởi tạo một phiên bản tổng hợp chuyển văn bản thành giọng nói của Bark, đã được triển khai ở trên.


  • Chuỗi hội thoại : Chúng tôi sẽ sử dụng Chuỗi ConversationalChain tích hợp sẵn từ thư viện Langchain, cung cấp mẫu để quản lý luồng hội thoại. Chúng tôi sẽ định cấu hình nó để sử dụng mô hình ngôn ngữ Llama-2 với phần phụ trợ Ollama.
 import time import threading import numpy as np import whisper import sounddevice as sd from queue import Queue from rich.console import Console from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain from langchain.prompts import PromptTemplate from langchain_community.llms import Ollama from tts import TextToSpeechService console = Console() stt = whisper.load_model("base.en") tts = TextToSpeechService() template = """ You are a helpful and friendly AI assistant. You are polite, respectful, and aim to provide concise responses of less than 20 words. The conversation transcript is as follows: {history} And here is the user's follow-up: {input} Your response: """ PROMPT = PromptTemplate(input_variables=["history", "input"], template=template) chain = ConversationChain( prompt=PROMPT, verbose=False, memory=ConversationBufferMemory(ai_prefix="Assistant:"), llm=Ollama(), )

Bây giờ, hãy xác định các chức năng cần thiết:

  • record_audio : Hàm này chạy trong một luồng riêng biệt để thu thập dữ liệu âm thanh từ micrô của người dùng bằng cách sử dụng sounddevice.RawInputStream . Hàm gọi lại được gọi bất cứ khi nào có dữ liệu âm thanh mới và đưa dữ liệu vào data_queue để xử lý thêm.


  • transcribe : Hàm này sử dụng phiên bản Whisper để phiên âm dữ liệu âm thanh từ data_queue thành văn bản.


  • get_llm_response : Hàm này cung cấp ngữ cảnh hội thoại hiện tại cho mô hình ngôn ngữ Llama-2 (thông qua Langchain ConversationalChain ) và truy xuất phản hồi văn bản đã tạo.


  • play_audio : Chức năng này lấy dạng sóng âm thanh được tạo bởi công cụ chuyển văn bản thành giọng nói Bark và phát lại cho người dùng bằng thư viện phát lại âm thanh (ví dụ: sounddevice ).
 def record_audio(stop_event, data_queue): """ Captures audio data from the user's microphone and adds it to a queue for further processing. Args: stop_event (threading.Event): An event that, when set, signals the function to stop recording. data_queue (queue.Queue): A queue to which the recorded audio data will be added. Returns: None """ def callback(indata, frames, time, status): if status: console.print(status) data_queue.put(bytes(indata)) with sd.RawInputStream( samplerate=16000, dtype="int16", channels=1, callback=callback ): while not stop_event.is_set(): time.sleep(0.1) def transcribe(audio_np: np.ndarray) -> str: """ Transcribes the given audio data using the Whisper speech recognition model. Args: audio_np (numpy.ndarray): The audio data to be transcribed. Returns: str: The transcribed text. """ result = stt.transcribe(audio_np, fp16=False) # Set fp16=True if using a GPU text = result["text"].strip() return text def get_llm_response(text: str) -> str: """ Generates a response to the given text using the Llama-2 language model. Args: text (str): The input text to be processed. Returns: str: The generated response. """ response = chain.predict(input=text) if response.startswith("Assistant:"): response = response[len("Assistant:") :].strip() return response def play_audio(sample_rate, audio_array): """ Plays the given audio data using the sounddevice library. Args: sample_rate (int): The sample rate of the audio data. audio_array (numpy.ndarray): The audio data to be played. Returns: None """ sd.play(audio_array, sample_rate) sd.wait()

Sau đó, chúng tôi xác định vòng lặp ứng dụng chính. Vòng lặp ứng dụng chính hướng dẫn người dùng thực hiện tương tác đàm thoại như sau:


  1. Người dùng được nhắc nhấn Enter để bắt đầu ghi dữ liệu đầu vào của họ.


  2. Sau khi người dùng nhấn Enter, hàm record_audio sẽ được gọi trong một chuỗi riêng biệt để thu âm thanh đầu vào của người dùng.


  3. Khi người dùng nhấn Enter lần nữa để dừng ghi, dữ liệu âm thanh sẽ được phiên âm bằng chức năng transcribe .


  4. Sau đó, văn bản được phiên âm sẽ được chuyển đến hàm get_llm_response , hàm này tạo ra phản hồi bằng mô hình ngôn ngữ Llama-2.


  5. Phản hồi được tạo sẽ được in ra bảng điều khiển và phát lại cho người dùng bằng hàm play_audio .

 if __name__ == "__main__": console.print("[cyan]Assistant started! Press Ctrl+C to exit.") try: while True: console.input( "Press Enter to start recording, then press Enter again to stop." ) data_queue = Queue() # type: ignore[var-annotated] stop_event = threading.Event() recording_thread = threading.Thread( target=record_audio, args=(stop_event, data_queue), ) recording_thread.start() input() stop_event.set() recording_thread.join() audio_data = b"".join(list(data_queue.queue)) audio_np = ( np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0 ) if audio_np.size > 0: with console.status("Transcribing...", spinner="earth"): text = transcribe(audio_np) console.print(f"[yellow]You: {text}") with console.status("Generating response...", spinner="earth"): response = get_llm_response(text) sample_rate, audio_array = tts.long_form_synthesize(response) console.print(f"[cyan]Assistant: {response}") play_audio(sample_rate, audio_array) else: console.print( "[red]No audio recorded. Please ensure your microphone is working." ) except KeyboardInterrupt: console.print("\n[red]Exiting...") console.print("[blue]Session ended.")

Kết quả

Sau khi mọi thứ đã được sắp xếp xong, chúng ta có thể chạy ứng dụng như trong video ở trên. Ứng dụng chạy khá chậm trên MacBook của tôi vì model Bark có kích thước lớn, ngay cả ở phiên bản nhỏ hơn. Vì vậy, tôi đã tăng tốc độ video một chút. Đối với những người có máy tính hỗ trợ CUDA, nó có thể chạy nhanh hơn. Dưới đây là các tính năng chính của ứng dụng của chúng tôi:


  • Tương tác dựa trên giọng nói : Người dùng có thể bắt đầu và dừng ghi âm giọng nói đầu vào của họ và trợ lý sẽ phản hồi bằng cách phát lại âm thanh đã tạo.


  • Bối cảnh hội thoại: Trợ lý duy trì bối cảnh của cuộc trò chuyện, cho phép phản hồi mạch lạc và phù hợp hơn. Việc sử dụng mô hình ngôn ngữ Llama-2 cho phép trợ lý đưa ra phản hồi ngắn gọn và tập trung.


Đối với những người muốn nâng ứng dụng này lên trạng thái sẵn sàng sản xuất, nên thực hiện các cải tiến sau:

  • Tối ưu hóa hiệu suất : Kết hợp các phiên bản được tối ưu hóa của các mô hình, chẳng hạn như thì thầm.cpp, llama.cpp và vỏ cây.cpp, được thiết kế để tăng hiệu suất, đặc biệt là trên các máy tính cấu hình thấp.


  • Lời nhắc của Bot có thể tùy chỉnh : Triển khai một hệ thống cho phép người dùng tùy chỉnh tính cách và lời nhắc của bot, cho phép tạo các loại trợ lý khác nhau (ví dụ: cá nhân, chuyên nghiệp hoặc theo miền cụ thể).


  • Giao diện người dùng đồ họa (GUI) : Phát triển GUI thân thiện với người dùng để nâng cao trải nghiệm người dùng tổng thể, giúp ứng dụng dễ truy cập hơn và hấp dẫn trực quan hơn.


  • Khả năng đa phương thức : Mở rộng ứng dụng để hỗ trợ các tương tác đa phương thức, chẳng hạn như khả năng tạo và hiển thị hình ảnh, sơ đồ hoặc nội dung trực quan khác ngoài phản hồi dựa trên giọng nói.


Cuối cùng, chúng ta đã hoàn thành ứng dụng trợ lý giọng nói đơn giản, mã đầy đủ có thể tìm thấy tại: https://github.com/vndee/local-talking-llm . Sự kết hợp giữa nhận dạng giọng nói, mô hình hóa ngôn ngữ và công nghệ chuyển văn bản thành giọng nói này chứng tỏ cách chúng tôi có thể tạo ra thứ gì đó nghe có vẻ khó khăn nhưng thực sự có thể chạy trên máy tính của bạn. Hãy cùng tận hưởng việc viết mã và đừng quên đăng ký vào blog của tôi để không bỏ lỡ những bài viết mới nhất về AI và lập trình.


Cũng được xuất bản ở đây