문제 상황기존 Speaking 채점 서비스 Flow점수 계산 정확도 문제1. 비교군의 불일치 문제 2. 음악의 특징으로 인해 정확도가 제대로 측정되지 않는 문제해결STT에서 정확한 문장 단위로 인식하도록 전처리가설검증해결음악의 특징으로 정확도가 감소되지 않도록 텍스트 후처리한계후기 및 배운점
문제 상황
MelLearn 프로젝트를 하던 중 Speaking 서비스에서 사용자의 음성 파일을 채점할 수 없는 문제가 발생했다.
Speaking 서비스란?
MelLearn에는 사용자의 언어 학습을 위해 여러가지 카테고리의 학습을 지원한다. 그 중 Speaking은 사용자가 노래를 따라부른 음성 파일을 제출하면 정확도와 틀린 단어들의 위치를 알려주는 서비스이다. Speaking 서비스를 통해 사용자는 노래를 따라 부르며 흥미롭게 공부를 할 수 있다.

기존 Speaking 채점 서비스 Flow
문제가 발생했던 당시, 서버에서 사용자 음성을 처리하는 흐름을 설명하자면
- 사용자는 음악을 따라 부른 뒤, 녹음된 음성 파일을 서버에 보내 채점을 요청한다.
- 서버는 사용자 음성을 STT를 사용해 텍스트로 변환
- 서버는 원본 가사와 사용자의 음성을 변환한 텍스트를 비교해 정확도를 100점 만점 기준으로 반환해준다.
사용자의 음성을 STT를 통해 텍스트로 변환하는 것까진 문제가 없었다.
하지만 변환된 텍스트와 가사를 채점할 때 점수가 정확하게 계산되지 않는 버그가 빈번히 발생했다.
점수 계산 정확도 문제
정확한 점수 측정이 불가능했던 많은 이유들이 있는데 그 중 몇가지 이유들을 살펴보자.
1. 비교군의 불일치 문제
사용자 음성의 정확도를 측정하려면 STT로 변환한 텍스트와 원본 가사를 비교해서 점수를 측정해야 한다.
사용자의 음성을 변환한 텍스트와 원본 가사를 비교하여 정확도를 측정하는 방식은 여러가지가 있는데, 각 상황에서의 문제점을 알아보자.
예시
# Justin Bieber - Sorry 가사 중 일부
You gotta go and get angry at all of my honesty
You know I try
but I don't do too well with apologies
I hope I don't run out of time
could someone call a referee?
Cause I just need one more shot at forgiveness
I know you know that I made those mistakes
maybe once or twice
By once or twice
I mean maybe a couple of hundred times
So let me oh let me redeem oh redeem oh myself
tonight
정확도를 측정하려면 기준을 잡아야 한다. 어떤 단위로 기준을 잡아야 할지 위의 예시를 보며 상황별 문제 상황을 알아보자.
전체 문장으로 기준을 잡았을 때
- 전체를 기준으로 사용자가 발음한 단어의 카운트를 계산하는 방식이다
- 문장을 특정할 수 없는 문제가 발생한다.
You
라는 단어는 가사에서 여러번 등장한다.- 사용자가 틀린 발음의 정확한 위치를 특정하기 힘들다.
- 음악 가사의 특성상 한 단어가 여러번 사용되는 특징이 있어서 문제가 발생할 확률이 매우 높다.
줄바꿈 기준(문장 기준)으로 기준을 잡았을 때
- 줄바꿈을 기준으로 하여 발음한 단어의 카운트를 계산하는 방식이다.
- 그 당시 STT(Open AI Whisper 기준)에서는 변환된 텍스트를 제공할 때 줄바꿈을 제공해주지 않았다.
- 그래서 변환된 텍스트는 문장 기준(온점 기준), 원본 가사는 줄바꿈을 기준으로 정확도를 검증하였다.
- STT로 변환한 텍스트와 실제 가사의 문장 갯수 불일치 문제 발생
- 위 가사를 예시로, 원본 가사에서는 2개의 단위로 분할이 되고, 이를 녹음한 음성파일이 변환된 텍스트는 하나의 문장으로 인식되는 문제가 발생한다. →
I mean maybe a couple of hundred times, so let me oh let me redeem oh redeem oh myself
I mean maybe a couple of hundred times
So let me oh let me redeem oh redeem oh myself
정리하자면 정확도를 측정하기 위해서는 한 문장을 기준으로 정확도를 측정해야 한다. 하지만 한 문장의 기준이 STT로 변환한 텍스트와 실제 가사의 문장의 갯수가 달라져버려 제대로 된 측정이 불가했기 때문에, 이를 해결해야 한다.
2. 음악의 특징으로 인해 정확도가 제대로 측정되지 않는 문제
음악의 특징으로 정확도가 제대로 측정되지 않는 문제도 발생했다.
- 가사에서 후렴부분에 Oh와 같은 바이브레이션을 표현할 때 Ohhhh와 같이 마지막 음절을 늘어트리는 경우가 있었다. 이 때의 정확도를 측정하는 경우가 애매하였다.
- 가사에 특수문자나 이모지가 포함된 경우도 있었다.
- 가사에 사전에 없는 단어들이 포함된 경우가 있었다.
위의 상황에서 채점할 필요가 없는 토큰들은 서버에서 제외해야 제대로 된 정확도를 측정할 수 있다.
해결
위에서 본 문제 상황처럼, 전체 문장으로 기준을 잡았을 때 사용자가 어떤 위치를 발음하고 있는지 판단하기 힘들다. 그래서 채점 기준을 문장으로 잡고, 발생했던 이슈를 해결해야한다.
해결해야 하는 이슈는 다음과 같다.
- STT에서 문장 갯수를 인식했을 때, 원본 가사와 불일치했던 문제
- 음악의 특징으로 정확도가 감소하는 문제
STT에서 정확한 문장 단위로 인식하도록 전처리
STT에서 원본 가사와 같은 문장 개수로 반환하게 한다면, 이 이슈를 해결할 수 있다.
어떻게 문장 개수를 일치시킬 수 있을까? STT에서 어떻게 문장을 구별할 지 생각해보자
가설
I mean maybe a couple of hundred times.
So let me oh let me redeem oh redeem oh myself.
저 문장을 STT에서는 어떻게 두 개의 문장으로 나누는 걸까?
오디오 파일을 문장으로 구분하여 온점을 찍는 방식은 두가지가 있을 것이다.
- 오디오의 쉬는 시간으로 구분
- 1번째 문장의 마지막 단어인 times와 두번째 문장의 첫번째 단어인 So를 발음하는 시점 사이 공백 시간을 체크하여 문장의 끝을 확인하는 방법
- 문장 구조를 분석하기
- 발화를 텍스트로 변환하고 문법적 요소를 파악하여 문장을 결정하는 방법
검증
두번째 방법은 확률이 많이 낮다고 생각해서 제외했다.
문법적으로 문장의 최소 구성 요소는 주어와 동사로 이루어져야 한다.
하지만 음악의 특성상 단어 하나로만 이루어진 경우도 있고, 현대인이 일상생활에서 대화를 할때도 저 최소 문장구조를 지키지 않기 때문이다. 때문에 가능성이 희박하다고 생각했다.
그래서 오디오의 쉬는 시간으로 구분한다는 첫번째 가설을 테스트 해봤다.
실제 두개의 문장의 쉬는시간을 달리 발음하여 텍스트로 변환해보는 테스트를 해보았고, 실제 두 상황에서 문장의 개수가 달라지는 것을 확인했다.
해결
사용자의 음성 파일에서 문장이 끝났을 때의 기준을 정확하게 해주면 된다.
- 어떻게 문장이 끝난 시점을 알 수 있을까?
- 음악 스트리밍을 할 때, 가사의 현재 재생 위치를 알려주는 UI/UX를 떠올렸다.
- 알아보니 LRC라는 파일 포맷이 존재했고 이를 통해서 해결할 수 있었다.
LRC란?
LRC는 Lyrics의 약자로, MP3, Vorbis, MIDI 등의 오디오 파일과 노래 가사를 동기화하는 파일 형식호환되는 플레이어에서 노래와 동기화하여 노래 가사를 표시할 수 있다.
- 음악에서 가사의 시작과, 끝 부분의 타임스탬프를 같이 첨부하는 방식으로, 음원에서 발화 시점과 가사를 연결할 수 있는 포맷이다.
- Spotify에서는 LRC를 지원하고, OpenAI사의 Whisper도 Segment라는 서비스를 지원하는데, 이는 LRC처럼 발화 시점에 타임스탬프를 같이 지원하는 서비스이다.
LRC를 이용해서 어떻게 STT와 가사의 문장개수를 일치시키지?
- LRC파일에서 각 문장이 끝나는 발화 시점을 확인해서 오디오를 자른다.
- 자른 오디오 파일의 끝에 2초의 공백 오디오를 삽입한다.
- 문장이 끝났다는 것을 2초의 공백 오디오를 통해 STT가 알 수 있게 된다.
- 분할된 오디오 파일을 다시 하나로 병합한다.
- 이렇게 전처리 된 오디오 파일을 텍스트로 변환하면, 원본 가사와 똑같은 문장 개수를 가지게 된다.
이를 흐름도로 나타내면 다음 과정과 같다.
결과
이렇게 사용자의 음성을 전처리 하여, STT의 결과가 가사와 똑같은 포맷을 갖도록 하여 정확한 문장을 기준으로 채점할 수 있었다.
또한 다른 요인에서의 정확도도 개선되었는데, 음악에서 간주와 같이 사용자가 발음하지 않아야 되는 시점의 음성이 변환되어 정확도가 망가지는 문제도 개선할 수 있었다.
음악의 특징으로 정확도가 감소되지 않도록 텍스트 후처리
음악의 특징으로 정확도가 감소하는 문제
- 가사에서 후렴부분에 Oh와 같은 바이브레이션을 표현할 때 Ohhhh와 같이 마지막 음절을 늘어트리는 경우가 있었다. 이 때의 정확도를 측정하는 경우가 애매하였다.
- 가사에 특수문자나 이모지가 포함된 경우도 있었다.
- 가사에 사전에 없는 단어들이 포함된 경우가 있었다.
위와 같은 문제를 해결하기 위해 후처리를 진행했다.
- 가사에서 후렴부분에 Oh와 같은 바이브레이션을 표현할 때 Ohhhh와 같이 마지막 음절을 늘어트리는 경우가 있었다. 이 때의 정확도를 측정하는 경우가 애매하였다.
- 감탄사와 수사등과 같은 경우는 채점에서 제외하였다.
- 감탄사와 수사의 판별은 StanfordCoreNLP 라이브러리를 통해 판별하였다.
- 가사에 특수문자나 이모지가 포함된 경우도 있었다.
- 특수문자나 이모지를 없애고 대소문자 구분을 하지 않았다.
- 가사에 사전에 없는 단어들이 포함된 경우가 있었다.
- 단어들이 사전에 없을 경우에는 정확도를 측정하지 않았다.
한계
전/후처리로 Speaking서비스의 정확도를 높혔지만 한계가 존재한다.
- 모든 언어를 채점할 수는 없다.
- 일본어와 같은 경우 띄어쓰기가 존재하지 않기 때문에 분리해 낼 수 없다.
- LRC 포맷을 지원하지 않는경우는 채점이 불가하다.
- 옛날 노래와 같은 경우에는 리메이크 되지 않는 이상 LRC를 지원하지 않는다.
- LRC를 지원하지 않는다면 음성파일에서 문장을 기준으로 토큰화할 수 없어서 채점이 불가하다
- 후처리에서 사전에 없는 단어들을 제외하지 못했다.
- WordNet과 같은 내장형 사전 데이터베이스 도입 불가
- 따라서 WordNet과 같은 내장형 사전 데이터베이스를 도입하려고 했지만 WordNet은 영어 하나밖에 지원하지 않는다는 큰 단점이 있었다.
- 또한 음악은 최신 트렌드를 반영한다는 특징이 있는데, WordNet은 신조어가 등록되지 않는다는 단점이 있다.
- 사전 API 지원하지 않음
- 학생 프로젝트 수준에서 옥스포드와 같은 큰 사전 API를 사용하는데 허가를 받을 수 없었다.
- 다른 Api들은 금액적으로 부담이 됐다.
- 많아지는 api call
- Speaking 서비스를 통해 노래 한번의 채점에서 100번 이상의 api call이 발생했다.
- 음악 가사 한번에 보통적으로 100~200개의 단어가 존재한다. 단어의 숫자만큼 api call이 발생하게 된다
- 시간, 금액적으로 많은 비용이 발생하여 제외했다.
사전에 없는 단어를 제거하고 채점하려고 계획했지만 여러가지 문제로 인해 도입하지 못했다.
환경적 문제
기술적 문제
후기 및 배운점
처음 서비스를 구상할 때 이렇게 많은 사항을 고려해야 할 줄은 몰랐지만, 실제로 개발을 진행하면서 초기 기획과 설계의 중요성을 깊이 체감할 수 있었다. 충분한 사전 준비가 되어 있어야 개발 단계에서도 효율적으로 작업을 진행할 수 있다는 점을 이번 프로젝트를 통해 배웠다.
문제 해결을 위해 직접 가설을 세우고 검증해본 결과, 예상이 맞아떨어졌을 때의 성취감은 이루 말할 수 없을 정도로 컸다. 또한 가설을 바탕으로 전처리 및 후처리 작업을 진행하며, 기존에 사용해본 적 없던 StanfordNLP와 같은 NLP 라이브러리를 활용해보는 흥미로운 경험도 할 수 있었다. 오디오 전처리를 위해 ffmpeg 프로그램을 사용하며 음성 데이터를 직접 다뤄보는 과정 역시 새로웠고, 개발자로서 기술 스택의 폭을 넓히는 계기가 되었다.
캡스톤 디자인 프로젝트였기 때문에 일정이 촉박했고, 해당 이슈로 전체 일정에 차질이 생기지 않도록 2주간 밤낮으로 몰입해 새로운 기술을 익히고 적용했다. 잘 때조차도 전체 흐름을 머릿속으로 되짚으며, 이후에 발생할 수 있는 예외 상황이나 기술적 문제들을 미리 정리하고 대응 방안을 고민했다.
최종적으로 코드를 Git에 푸시하고 Jenkins를 통해 서버에 배포되었을 때 큰 보람을 느꼈다. 하지만 실제 배포 환경에서는 예기치 못한 문제가 발생했다. 개발 환경은 Windows였던 반면, 배포 환경은 Linux였고, 이로 인해 ffmpeg가 정상적으로 작동하지 않는 이슈가 생긴 것이다. Jenkins에서 환경 테스트가 통과되었을 때만 코드가 배포되도록 Job을 구성했어야 했지만, 시간이 부족해 테스트 코드를 작성하지 못한 점이 아쉽게 남았다.
이번 프로젝트를 통해 단순한 기능 구현을 넘어, 실제 서비스 환경에서 발생할 수 있는 다양한 문제를 예측하고 대응하는 역량을 키울 수 있었다. 또한 새로운 기술을 빠르게 익히고 적용하는 데 자신감을 얻게 되었으며, 무엇보다 실사용자 관점에서 서비스를 고민하고 개선해나가는 개발 과정의 즐거움을 다시 한 번 느낄 수 있었던 소중한 경험이었다.
Share article