이 글은 Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle에 제출하는 글입니다.
제가 만든 것
프랑스 초등학교에서는 아이들이 시를 외우며 배웁니다. 몇 주마다 숙제 일지에 '시 배우기(apprendre la poésie)'라고 적혀 있고, 며칠 후에는 아이가 교실에 일어나서 그것을 낭송합니다. 집에서는 그 사이에 누군가가 책을 들고 들어줘야 합니다: 줄 하나하나, 저녁마다, 종종 요리하는 동안에도요.
Puesia가 듣는 역할을 합니다. _Puesia_는 코르시카어로 '시(poetry)'를 의미하며, 저는 아자시오에 삽니다.
아이가 가족 Mac에서 오늘 밤의 시를 열고 텍스트를 숨긴 다음 'Je récite'(제가 낭송합니다) 버튼을 누릅니다. 끝내면 Puesia가 아이의 실수가 표시된 시를 다시 보여줍니다: 빨간색으로 바뀐 단어와 그 옆에 들은 내용, 건너뛴 줄은 하이라이트하고, 빠진 단어는 밑줄을 칩니다. 짧은 요약도 소리 내어 읽어줍니다. 중간에 막히면 'Je bloque !'(멈췄어요!): 녹음이 일시 정지되고, Puesia가 어디서 멈췄는지 파악하여 다음 줄의 시작 부분을 읽어줍니다.
이것은 주간 학습에도 도움을 줍니다. 아이는 자신의 숙제 일지를 구술합니다(
이 스크린샷들은 합성 음성으로 실제 앱에서 가져온 것입니다. 왜냐하면 저희 딸의 시와 그녀의 목소리는 집에 있기 때문입니다. 이 시는 베를렌(Verlaine)의 <가을 노래 (Chanson d'automne)>이며, 파이퍼(Piper) 음성 톰(Tom)이 의도적으로 두 가지 실수를 저지른 버전으로 읽었습니다: 'mon cœur' 대신 'ton cœur'로 잘못 말했고, 9번째 줄은 건너뛰었습니다.
세 개의 숙제를 추가한 저녁 풍경입니다 (스크린샷을 위해 상자에 타이핑되었으며; 집에서는 구술로 전달되었습니다):
Puesia's evening view: tonight's steps, with the first stanza of Chanson d'automne, maths exercises and a lesson to reread
낭독 후의 시입니다. 바뀐 단어는 Whisper가 들은 내용으로 빨간색 처리되어 있으며, 건너뛴 줄은 강조 표시되어 있습니다:
Chanson d'automne with
그리고 소리 내어 읽는 요약입니다:
The feedback: 16 of 18 lines without a mistake, line 4
이 실행은 두 가지 실수만 포착했고 다른 것은 아무것도 잡지 못했습니다. 같은 낭독의 이전 실행에서는 마지막 줄도 플래그를 지정했습니다: Whisper는 'feuille' 대신 'fée'를 들었습니다. 음성은 'feuille'라고 말했습니다. 이것이 아래 벤치마크가 계산하는 종류의 오경보입니다.
코드
프랑스 초등학생을 위한 저녁 낭독 파트너로, 가족 Mac에서 실행됩니다: Whisper가 듣고, 코드가 단어별로 확인하며, Piper가 말하고, Gemma 4가 주간 계획을 세웁니다.
구축 방법 (How I Built It)
누가 무엇을 하는지 (Who does what)
Whisper large-v3-turbo (MLX, Mac에서)는 20초 분량의 음성을 약 1초 만에 텍스트로 변환합니다.
...
저는 언어 모델에게 시 낭송을 평가하도록 요청하지 않았습니다. 아이가 같은 낭송을 할 때마다 똑같은 판정을 받아야 하며, Puesia가 '9행을 건너뛰었어요'라고 말할 때, 저는 왜 그런지 보여줄 수 있기를 바랍니다. 따라서 평가는 코드로 이루어집니다: 아래의 모든 경우에 대한 테스트를 포함한 약 500줄의 Python 코드입니다.
프랑스어는 발음이 같은 단어가 많습니다
전사(transcription)는 철자이고, 낭송은 소리입니다. Whisper는 시가 'vers'라고 할 때 'vert'로, 'pleure'라고 할 때 'pleurent'로, 'tant'이라고 할 때 'temps'로 적을 수 있습니다: 아이는 올바른 말을 했지만, 철자가 다릅니다. 만약 Puesia가 이를 계산한다면, Whisper의 철자 때문에 아이를 꾸짖을 것입니다.
그래서 비교 과정은 먼저 모든 단어를 대략적인 소리 키(rough sound key)로 변환합니다: 프랑스어의 어미, 비음(nasals), 묵음(silent letters)에 대한 약 서른 가지 규칙입니다. 'Vers', 'vert', 'verre' 그리고 'ver'는 같은 키를 받습니다. 하지만 'Mon'과 'ton', 'un'과 'une', 'petit'과 'petite'는 그렇지 않습니다. 왜냐하면 그것들은 아이가 실제로 저지르는 실수이기 때문입니다. 그런 다음 시와 전사(an edit distance)를 정렬하고 (심지어 'deçà'에 대해 'de ça'도 허용하는 편집 거리), 망설임('euh')과 재시작을 용서합니다.
이 규칙들은 다음과 같은 쌍들을 대상으로 테스트되었으며, 여러 개는 벤치마크에서 가져온 것입니다: 거짓 경보가 원인별로 분류했을 때, 'maître'를 'mettre'로 듣거나, 또는 'au temps'을 'autant'으로 들은 것은 제 규칙의 잘못이지 Whisper의 잘못이 아니었습니다.
Whisper에게 도움을 많이 줄수록, 실수를 덜 듣게 됩니다
Whisper는 initial_prompt라는 것을 받아들입니다: 자신이 방금 들었다고 가정하는 텍스트입니다. 시를 제공하는 것은 명백한 개선처럼 보입니다. 그것은 모든 단어를 미리 알기 때문에, 적게 오해해야 합니다. 저는 이것을 신뢰하기 전에 측정했습니다.
벤치마크는 Piper 음성을 사용하여 5편의 퍼블릭 도메인 시(Verlaine, Hugo, Rimbaud 및 La Fontaine 우화 2편)를 낭독하는 것을 구축합니다. 여기에 60개의 실수(건너뛴 줄, 건너뛴 단어, 가깝거나 먼 단어로 바뀐 단어)에 더해 깨끗한 낭독과 망설이는 낭독을 주입하고, 일부에는 배경 소음을 추가하며, 모든 것을 프롬프트로 시를 사용하거나 사용하지 않은 세 가지 크기의 Whisper를 통해 실행합니다. 이로 인해 설정당 90개의 낭독이 생성되었으며, 모두 제 노트북에서 이루어졌습니다.
| Whisper | 포착한 실수 | 단어 100개당 오경보율 | 낭독 시간 |
|---|
turbo, 텍스트 없음 (Puesia가 사용하는 방식) | 57 / 60 | 3.5 | 2.0 s |
| ... | | | |
시를 사용했을 때, Whisper는 오경보율이 낮아지고 실제 실수를 숨깁니다. 음성은 _Blessent ton cœur_라고 말하는데, medium에 시를 함께 쓰면 _Blessent mon cœur_라고 적습니다. 단순히 듣기만 하는 존재가 아니라 읽는 존재가 됩니다. 구두점에서도 볼 수 있습니다: 주어진 _Le Corbeau et le Renard_의 경우, 프롬프트와 함께 사용된 turbo는 우화 자체의 대문자와 기유(guillemets)인 « Hé ! bonjour, Monsieur du Corbeau »를 작성하는데, 이는 음성이 발음할 수 없는 것입니다. 더 큰 모델일수록 더 순종적입니다: medium은 프롬프트 없이도 18개의 실수를 숨기면서 포착합니다.
아이의 무엇이 잘못되었는지 알아내는 것이 전적으로 임무인 도구에게는 그것 자체가 잘못된 거래입니다. Puesia는 Whisper에게 텍스트를 주지 않습니다.
저는 또한 오경보율에 대한 명백한 해결책도 시도했습니다: Whisper는 각 단어에 확률을 부여하므로, 낮은 신뢰도로 들린 잘못된 단어는
남아있는 오탐(false alarms)은 대부분 실제 잘못 들은 경우이며, 많은 경우가 프롬프트 문제와 같은 곳에서 발생합니다: Whisper가 프랑스어가 보통 어떻게 들리는지에 대한 감각 때문입니다. _Ni l'or du soir_는 _ni l'heure du soir_이 되고, _tenait en son bec_는 _dans son bec_이 됩니다. 목소리가 매우 중요합니다. Tom을 사용했을 때 turbo는 100단어당 0.8개의 오탐을 발생시키고, UPMC 음성을 사용할 때는 5.7개를 발생시킵니다.
큰 한계점: 이것들은 합성 성인 목소리이며, 아동의 목소리가 아닙니다. 이 벤치마크는 Puesia가 10세 아이의 목소리를 얼마나 잘 듣는지에 대한 설정 선택 방법을 알려줄 뿐입니다. 그건 제 딸에게서 얻은 것입니다.
Gemma가 제안하고, 코드가 확인합니다
숙제 일기는 언어 모델이 자신의 자리를 확보하는 곳입니다. 아이들은 가능한 모든 순서로 숙제를 지시합니다 («음, 금요일에는 시를, 그리고 월요일에 갈리아인들에 대한 수업을 배워야 해»와 같이), Gemma 4 e4b는 이를 목록으로 변환합니다: 과목, 활동, 그리고 받아쓰기가 나온 정확한 구절입니다.
그러면 코드가 모든 항목을 확인합니다:
- 해당 구절은 실제로 받아쓰기에 단어 그대로 순서대로 존재해야 합니다. 서로 다른 문장의 절반 두 개를 붙여 만든 과제는 거부됩니다.
- 코드는 그 구절 주변에서 아이의 말로 날짜를 자체적으로 읽습니다: demain (내일), jeudi (목요일), pour le 16 (16일에), la semaine prochaine (다음 주). Gemma는 날짜를 제공하지 않습니다. « 목요일에 시를 배우고 금요일에 연습을 하려면 »이라는 경우, 각 과제는 자신의 날짜를 유지합니다.
- 알려진 시 제목은 날짜를 읽을 때 마스킹됩니다. 따라서 _Demain, dès l'aube_가 내일 할당된 것은 아닙니다.
- 만약 Gemma가 세부 사항을 다시 작성한다면 (아이가 _4 et 5_라고 말했을 때 _exercices 6 et 5_로), 아이는 Gemma의 말이 아닌 자신의 말을 보게 됩니다.
- 일기에 언급된 시는 텍스트가 추가되는 즉시 그 텍스트와 연결되고, 계획은 밤마다 한 연씩이 됩니다.
제 노트북에서 Gemma는 로드된 후 6초에서 11초가 걸립니다. 첫날 저녁에는 아직 로드되지 않았고, 첫 받아쓰기는 느렸으며, 작동하는 동안 페이지를 다시 로드해야 했습니다. Puesia는 이제 시작 시점에 Gemma를 로드하고 기다리는 동안 카운터를 보여줍니다.
이번 주 시에 적용해 봤습니다
수요일 저녁, 저희 딸이 실제 숙제에 Puesia를 사용해 보았습니다. 금요일 제출용으로 학교에서 나눠준 종이에 옮겨 적은 3연의 가을 시였죠.
효과가 있었습니다. 요약 기능이 아이가 실수한 부분을 잡아냈고, 이후 Je bloque ! 기능을 사용하여 다시 시작할 수 있게 했습니다. 숙제 받아쓰기에도 Puesia를 사용했는데, 한 번 말실수가 있었어요. 'la poésie'를 Whisper로 읽었더니 'la poser'로 나왔는데, 이건 Puesia가 시와 연결할 수 있는 단어가 아니었습니다. 아이의 목소리는 성인의 목소리보다 Whisper에게 더 어렵고, 이 문장 때문에 제가 아동에 대한 정확도 수치를 발표하기 전까지는 측정된 자료가 없습니다.
같은 저녁에 두 가지 변화가 있었습니다. 첫째, 딸이 기본 음성을 마음에 들어 하지 않아서, Puesia가 이제 네 가지 프랑스어 목소리 옵션과 속도 및 톤을 제공하며 테스트 버튼도 생겼습니다. 둘째, 저희는 더 자연스러운 읽기를 원했습니다. Piper는 각 줄을 독립된 문장처럼 읽고 줄 끝마다 끊어졌어요. 이제는 한 연 전체를 한 번에 읽으며, 각 줄 끝에 짧은 간격을 두어 목소리가 시의 문장을 따라가게 합니다. Whisper로 확인해 보니, 이렇게 해도 시의 명료도는 변함이 없었습니다.
Open Innovation이 중요한 이유
아이의 목소리는 집에 머뭅니다. 녹음된 내용은 마이크에서 Mac 메모리로 이동하여 전사되고, 그리고 잊힙니다. 계정이나 업로드가 없습니다. 사용자 연령대가 6세에서 11세인 제품에 대해서는 아이들의 목소리가 어디로 가는지 설명할 필요가 없습니다.
'몇 유로'를 가능하게 합니다. 클라우드 음성 API는 분당 과금되며, 일주일에 시를 열 번 연습하는 아이에게는 몇 유로에 팔릴 도구치고는 엄청난 양의 시간이 소요됩니다. 여기서는 낭독 한 번당 비용이 전기의 아주 작은 양과 같습니다. 이것은 또한 제가 벤치마크를 감당할 수 있었던 방식이기도 합니다. 540개의 전사본, 그리고 제 규칙을 수정하는 동안 버렸던 모든 실행분들이요.
나는 경청하는 내부를 들여다볼 수 있었다. Puesia에게 가장 중요한 발견은 프롬프트가 Whisper를 실수에 대해 청각을 마비시킨다는 것이었고, 이는 Whisper가 디코딩하는 방식과 각 단어의 확률을 읽는 방식을 변경함으로써 얻게 되었다. 나는 모든 설정을 제어했고, 동일한 오디오를 세 가지 모델 크기를 거쳐 실행했으며, 코드가 필요로 하는 것을 유지했다.