Wyszeptaj do ucha sztucznej inteligencji

Niezwykle interesująco wygląda model Whisper dostarczany przez OpenAI przetwarzające pliki dźwiękowe na tekst.

Zapraszam na pierwszy wpis, który nie jest tylko rozważaniem, ale konkretnym przykładem zastosowania AI.

Koncept

Pomysł na wykorzystanie modelu Whisper jest prosty. Mam film – nagranie ze spotkania. Robię ekstrakcję ścieżki dźwiękowej, następnie przesyłam ją do modelu, który generuje z tego transkrypcję. Następnie przesyłam transkrypcję do GPT w celu wygenerowania notatki podsumowującej spotkanie.

Teamsy mają wbudowaną transkrypcję, jednak działa ona tragicznie z językiem polskim. Zobaczmy jak poradzi sobie Whisper.

Krok 1. Ekstrakcja ścieżki dźwiękowej

Na tę chwilę robię POC, więc robię to ręcznie. Instaluję pakiet FFMPEG i odpalam komendę:

% ffmpeg -i video.mp4 -vn -q:a 0 -map a audio.mp3

W moim przypadku plik audio ma nieco ponad 18MB więc na razie nie muszę się martwić dzieleniem go na porcje (maksymalny rozmiar dla modelu Whisper to 25MB). Niestety wysyłane audio nie może być zbyt długie, aby generowanie transkrypcji nie zajęło zbyt dużo czasu.

Wyciągając ścieżkę dźwiękową odrazu dzielę audio na porcje (na razie nie przejmuję się, że utnę audio w pół słowa).

# pierwsze 10 minut
% ffmpeg -i video.mp4 -ss 00:00:00 -t 00:10:00  -vn -q:a 0 -map a audio1.mp3

# kolejne 10 minut
% ffmpeg -i video.mp4 -ss 00:10:00 -t 00:10:00  -vn -q:a 0 -map a audio2.mp3

# kolejne 10 minut
% ffmpeg -i video.mp4 -ss 00:20:00 -t 00:10:00  -vn -q:a 0 -map a audio3.mp3

# kolejne 10 minut
% ffmpeg -i video.mp4 -ss 00:30:00 -t 00:10:00  -vn -q:a 0 -map a audio4.mp3

# kolejne 10 minut
% ffmpeg -i video.mp4 -ss 00:40:00 -t 00:10:00  -vn -q:a 0 -map a audio5.mp3

# w moim przypadku ostatnie wywołanie zwróciło informację o błędzie, ponieważ nagranie się zakończyło

Krok 2. Transkrypcja

Do wywoływania OpenAI API wykorzystuję paczkę: openai-php/client

Tworzę prostą metodę i jej wywołanie. Na razie nie przejmuję się zwracanym formatem, ani zapętleniem odpytania. Chcę po prostu dostać transkrypcję pierwszych 10 minut nagrania.

    public function execute(InputInterface $input, OutputInterface $output)
    {
        $transcription = $this->getTranscription('./audio1.mp3');

        dump($transcription);

        return 0;
    }

    private function getTranscription(string $path): \OpenAI\Responses\Audio\TranscriptionResponse
    {

        $transcribe = $this->client->audio()->transcribe(
            [
                "model" => 'whisper-1',
                'file' => fopen($path, 'rb'),
                'response_format' => 'verbose_json',
            ]
        );

        return $transcribe;
    }

Odpalam kod i po chwili dostaję transkrypcję (wstawiam tylko kawałek):

 +text: "Cześć Tomku. Cześć. Wreszcie mi się udało złapać czas. Super. Poprzednie tylko oglądałem. Co myślisz o inicjatywie? Super. Bardzo mi się podoba. Fajne rzeczy poruszane. Cieszę się. 

[..]

Dobra, przechodzimy powoli do, dołączacie jeszcze, witam wszystkich jeszcze raz. I zaczynam powoli. Więc tak, jak ja podszedłem do robienia prezentacji, jak polecam podejść wam. Pierwsze co, to zaczynamy tutaj, to pewnie wiecie. 

[..]

Tego nie było na mojej prezentacji. Ostatnio to znalazłem, fajny mód. I większość zaczyna się od kształtów. Gdzie wrzucamy sobie jakiś kształt. I na przykład ja sobie wrzucę o taki kształt. Sprawia, żeby on był bardziej zaokrąglony. Może trochę bardziej go wyrównam. No i teraz Ctrl D, duplikuję sobie, tworzę sobie tych obiekcików więcej. 

[..]
"

Transkrypcja nie jest idealna, ale mimo wszystko jest naprawdę niezła.

Krok 3. Podsumowanie

Do powyższego kodu dodaję metodę, umożliwiającą mi wygenerowanie podsumowania danego fragmentu:

    private function getChunkSum(
        string $chunk,
        string $meetingInfo,
        string $meetingLead,
        string $previousChunkSum = ""
    ): string
    {
        $prompt = [
            'model' => 'gpt-4-1106-preview',
            'messages' => [
                [
                    'role' => 'system',
                    'content' => $chunk,
                ],
                [
                    'role' => 'user',
                    'content' => <<<EOT
                    Otrzymałeś część transkrypcji spotkania:
                    $meetingInfo
                    Prowadzącym spotkanie był $meetingLead
                    Napisz krótkie podsumowanie tego spotkania. 
                    Podsumowanie powinno stanowić kompendium wiedzy na temat tego spotkania. Maksymalnie 100 słów.
                    EOT
                ],
            ],
            'max_tokens' => 500,
        ];

        if ($previousChunkSum) {
            $prompt['messages'][] = [
                'role' => 'assistant',
                'content' => $previousChunkSum,
            ];
            $prompt['messages'][] = [
                'role' => 'user',
                'content' => 'Kontunuuj podsumowywanie dla tego fragmentu',
            ];
        }

        $response = $this->client->chat()->create($prompt);

        return $response->choices[0]->message->content ?? "";
    }

Korzystam tutaj z najnowszego modelu gpt-4-1106-preview, który jest dostępny od kilku dni. Prompt nie jest idealny, na pewno warto by go jeszcze dopracować, ale na potrzeby testowe powinien wystarczyć.

Pozostaje jedynie uruchomić dla każdego fragmentu audio najpierw metodę do wygenerowania transkrypcji, a potem podsumowania:

    public function execute(InputInterface $input, OutputInterface $output)
    {
        $transcription = [];
        $summarize = [];

        for ($i = 1; $i < 5; $i++) {
            $chunkTranscription = $this->getTranscription("./audio{$i}.mp3");
            $transcription[] = $chunkTranscription;
            $summarize[$i] = $this->getChunkSum(
                $chunkTranscription,
                "Lunch&Learn - Fantastic PowerPoint",
                "Jan",
                $transcription[$i-1] ?? ""
            );

            dump($chunkTranscription, $summarize[$i]);
        }

        dump($summarize);

        return Command::SUCCESS;
    }

Liczbę i lokalizację plików audio na razie zaszywam na sztywno w kodzie. Podobnie zresztą jak tytuł spotkania oraz imię prowadzącego. Do generowania podsumowania przesyłam również poprzedni fragment, co powinno ułatwić modelowi kontynuację.

Krok 4. Efekty

Po uruchomieniu kodu prawidłowo udało się wygenerować transkrypcję, a następnie takie podsumowanie:

"Podczas spotkania Lunch&Learn poświęconego PowerPointowi, Jan podzielił się swoimi doświadczeniami i technikami tworzenia atrakcyjnych prezentacji. Zaznaczył ważność korzystania z dostępnych zasobów, takich jak firmowe szablony i stokowe zdjęcia. Pokazał praktyczne wskazówki dotyczące pracy z kształtami oraz ich wyrównywaniem, co pozwala na tworzenie estetycznie wyglądających slajdów. Podczas prezentacji uczestnicy mogli zadawać pytania, co umożliwiało interaktywną naukę i wymianę wiedzy na temat efektywnego wykorzystania PowerPointa. Jan podkreślił, że prezentacja ma być nie tylko efektywna, ale i luźniejsza, co przyczynia się do przyjemniejszego przebiegu spotkania."
"Uczestnicy nauczyli się, jak tworzyć ciekawe formy wizualne z kształtów i zdjęć, w tym dodawanie tekstu i stosowania efektów głębi. Jan pokazał także, jak wycinać tła ze zdjęć bez użycia zewnętrznych narzędzi, podkreślając skuteczność narzędzia "Usuń tło" dostępnego w PowerPoint. Uczestnicy zapoznali się z tworzeniem estetycznych list i wykorzystaniem cieni dla dodania wypukłości elementom graficznym, a Jan zakończył prezentację zapowiadając pokaz ciekawych tranzycji slajdów."
"Jan skupiał się na płynnych przejściach, dopasowywaniu obrazów, efektach wizualnych, a także na praktycznych patentach, jak np. wykorzystanie gotowych kształtów czy tekstur w prezentacjach. Demonstracja była improwizowana, zachęcająca do zadawania pytań. Jan podkreślił znaczenie estetyki i precyzji w tworzeniu prezentacji. Ukazał, jak za pomocą prostych narzędzi można wizualizować treść, dobierając efekty, które wzbogacają przekaz i utrzymują uwagę widzów."
"Jan pokazał również przykładowy slajd końcowy i zachęcał do tworzenia ciekawych slajdów tytułowych, zamiast standardowych. Na zakończenie omówił zasoby, takie jak kanał YouTube, skąd czerpał wiedzę i podzielił się linkami do materiałów pomocnych. Prosił o pytania i pomysły, oraz zapowiedział, gdzie znajdą się udostępnione prezentacje i nagrania z warsztatów."

Krok 5. Wnioski

Wynik tego prostego eksperymentu jest w sumie podobny do innych eksperymentów wcześniej podanych i ogólnego wrażenia z AI, które mam obecnie. Można by go podsumować stwierdzeniem „obiecujący”, niestety należy również dodać przymiotniki „przeciętny” oraz „wymagający dopracowania”.

Uzyskanie wyników zadowalających lub wręcz doskonałych wymaga (i tutaj AI narazie niewiele zmieniło) dużo więcej pracy. Dopasowanie promptów, dynamiczne ich dopracowywanie, może ponowna korekta podsumowania po jego wygenerowaniu (np w celu usunięcia powtórzeń).

Krok 5a. Koszty

Na koniec jeszcze słowo podsumowania kosztów powyższej zabawy. Model do transkrypcji audio wygenerował koszt na poziomie 0.27$ (transkrypcję generowałem kilka razy). Za użycie modelu GPT4-preview nie zostałem obciążony lub jeszcze nie widzę tego kosztu (zaktualizuję ten fragment jeśli się on pojawi).

Czy to drogo? Załóżmy, że transkrypcję wygenerowałem cztery razy (w działającym rozwiązaniu zrobiłbym to raz), a za model GPT-4 zapłaciłbym porównywalnie co za Whisper’a, to wychodziłoby około 50gr za notatkę z jednego trzydziestominutowego spotkania. Niby niedużo, ale gdy zaczniemy to mnożyć przez liczbę spotkań i czas, to z pewnością trzeba by tutaj popracować jeszcze nie tylko nad jakością, co również nad optymalizacją kosztów.