본문으로 건너뛰기

RNNoise와 Silero: 받아쓰기를 위한 음성 활동 감지기 선택하기

RNNoise는 노이즈를 억제하며 부산물로 음성 확률을 알려 줍니다. Silero는 음성을 감지만 하고 오디오는 그대로 둡니다. Voice Type가 Silero로 게이트를 거는 이유와 실제로 배포하는 임계값을 소개합니다.

핵심 요약

결론부터
  • 세그먼트 분할이 인식기에 무엇을 줄지 결정합니다. 침묵을 받은 모델도 텍스트를 돌려줍니다. 텍스트를 돌려주는 것이 모델의 일이기 때문입니다.
  • 게이트는 16 kHz의 512샘플 프레임으로 동작하며 음성 임계값은 0.30입니다.
  • 음성 구간에는 첫 구절에 1,000 ms의 프리롤이 붙고, 이후에는 양쪽에 150 ms가 붙어서 경계에서 단어가 잘리지 않습니다.
  • 5초보다 짧은 배치는 일시 정지가 있어도 전송되지 않습니다. 다음 구절을 기다립니다.

인식기는 키보드 소리도, 에어컨 소리도, 생각하는 동안의 3초간의 침묵도 들을 필요가 없습니다.

Voice Type는 스트리밍 방식의 Silero 음성 활동 감지기(VAD)를 사용해 마이크 스트림의 어느 부분이 음성인지 판단합니다. 각 배치의 길이와 모델이 실행되는 시점을 포함한 모든 하위 단계는 이 게이트가 무엇을 음성으로 표시하느냐에 따라 정해집니다.

정정합니다. 이 페이지의 이전 버전에는 Voice Type가 RNNoise를 사용한다고 쓰여 있었습니다. 사용하지 않으며, 사용한 적도 없습니다. 앱이 RNNoise를 배포한 적은 한 번도 없고, 그 주장은 게시 당시에도 틀렸습니다. 게이트는 Silero이며, 아래 설정이 실제로 배포되는 값입니다. RNNoise를 검색하다 이곳에 오셨다면 다음 섹션이 도움이 될 것입니다.

RNNoise, 그리고 그것이 게이트가 아닌 이유

RNNoise는 Xiph.Org가 공개하고 BSD 라이선스로 배포한, 노이즈 억제를 위한 순환 신경망입니다. 작고 빠르며, 본래의 역할을 아주 잘합니다. 잡음이 섞인 음성을 받아 더 깨끗한 음성을 돌려줍니다. 많은 음성 소프트웨어에 들어 있으며, 가장 많이 접하는 버전은 OBS에 포함된 것입니다.

Voice Type에 들어 있지 않은 이유는 노이즈 억제와 음성 활동 감지가 서로 다른 일이기 때문입니다.

  • 노이즈 억제는 오디오를 바꿉니다. 노이즈로 판단한 것을 약화시키고 나머지를 통과시키므로, 하위 단계에 넘어가는 신호는 캡처된 신호와 다릅니다.
  • 음성 활동 감지는 아무것도 바꾸지 않습니다. 오디오를 읽고 어느 구간에 음성이 있는지만 표시합니다. 인식기는 원래의 샘플을 그대로 받습니다.

RNNoise는 수행하는 억제 작업의 부산물로 음성 활동 확률을 제공하는데, 그래서 VAD를 찾는 검색에 등장합니다. 그 출력을 게이트로 쓴다는 것은 주된 목적이 아닌 신호를 얻기 위해 원하지 않는 억제기를 돌린다는 뜻입니다. Silero는 감지기이므로 감지라는 일을 직접 수행하고 오디오는 그대로 둡니다.

이 차이는 통화보다 받아쓰기에서 더 중요합니다. 통화에서는 더 깨끗하게 들리는 것이 목표이고, 억제기가 팬 소리와 함께 실제 음성 일부를 지워도 듣는 사람이 그 틈을 메워 주기 때문에 받아들일 만한 절충입니다. 인식기는 틈을 메우지 않습니다. 주어진 것을 그대로 받아 적기 때문에, 인식 전에 오디오가 변형되면 억제기가 잡음과 구별하기 가장 어려워하는 자음에서 정확도를 잃을 수 있습니다.

RNNoise VAD 임계값을 찾고 계신다면, 그 값은 어떤 래퍼를 쓰는지에 따라 다릅니다. RNNoise 자체는 0에서 1 사이의 확률을 반환하고 각 통합 구현이 자체 기준값을 정하기 때문입니다. 참고로 이 앱의 Silero 게이트에서 이에 해당하는 값은 음성 상태를 유지하는 0.30과 음성에 진입하는 0.22이며, 두 값의 이유는 아래 섹션에 있습니다. 이 값들은 Silero의 값이지 RNNoise의 값이 아니므로 그대로 옮길 수 없습니다.

RNNoise는 훌륭한 소프트웨어입니다. 다만 우리에게는 없는 문제를 풀고 있을 뿐입니다.

요약

  • 세그먼트 분할이 인식기에 무엇을 줄지 결정합니다. 침묵을 받은 모델도 텍스트를 돌려줍니다. 텍스트를 돌려주는 것이 모델의 일이기 때문입니다.
  • 게이트는 16 kHz의 512샘플 프레임으로 동작하며 음성 임계값은 0.30입니다.
  • 음성 구간에는 첫 구절에 1,000 ms의 프리롤이 붙고, 이후에는 양쪽에 150 ms가 붙어서 경계에서 단어가 잘리지 않습니다.
  • 5초보다 짧은 배치는 일시 정지가 있어도 전송되지 않습니다. 다음 구절을 기다립니다.

정확도의 상한을 정하는 것은 모델이 아니라 세그먼트 분할입니다

인식기에 깨끗한 음성 세그먼트를 주면 음성을 받아 적습니다. 단어 하나가 묻혀 있는 3초간의 에어컨 소음을 주어도 디코더는 텍스트를 만들어 냅니다. 이 실패는 모델 오류처럼 보이지만 입력 오류입니다.

그래서 게이트가 흥미로운 구성 요소입니다. 사람에게 소리를 더 좋게 들리게 하려는 것이 아닙니다. 모델에게는 질문이 담긴 오디오에 대해서만 묻도록 하기 위한 것입니다.

배포되는 설정

다음은 균형 모드(balanced) 받아쓰기의 현재 기본값이며, 원본 캡처(raw capture)로 바꾸지 않는 한 이 값으로 동작합니다.

| 설정 | 값 | | --- | --- | | 프레임 크기 | 16 kHz에서 512샘플 | | 음성 임계값 | 0.30 | | 조용한 시작 임계값 | 0.22 | | 첫 구절 앞 프리롤 | 1,000 ms | | 이후 구절의 프리롤 | 150 ms | | 포스트롤 | 150 ms | | 구절을 닫는 무음 (실시간) | 400 ms | | 구절을 닫는 무음 (정지 시) | 350 ms |

이해할 만한 부분은 두 임계값입니다. 하나의 임계값은 조용한 첫 음절을 잡을 만큼 민감하면서 동시에 팬 소리를 무시할 만큼 엄격해야 합니다. 이 둘은 서로 다른 일이므로, 게이트는 음성이 시작되기를 기다릴 때는 더 낮은 기준(0.22)을, 음성이 진행 중일 때는 일반 기준(0.30)을 사용합니다.

비대칭 프리롤은 시간 영역에서 같은 방법을 씁니다. 발화의 첫 단어는 가장 자주 잘리는 부분인데, 게이트가 그 앞에서 참고할 것이 없기 때문입니다. 그래서 감지 지점 앞에 1초 분량의 오디오를 둡니다. 이후 구절은 게이트가 이미 사용자를 추적하고 있으므로 150 ms면 충분합니다.

짧은 배치가 기다리는 이유

배처는 음성 구간을 통째로 덧붙이고, 구간을 반으로 자르지 않습니다. 또한 디코딩할 가치가 없을 만큼 짧은 배치는 보내지 않습니다.

현재 배치에 선택된 오디오가 5초 미만이고 사용자가 말을 멈추면 아무것도 전송되지 않습니다. 배치는 멈춤을 넘어 유지되고 다음 구절이 합쳐집니다. 이는 의도된 동작입니다. 1.5초짜리 조각을 혼자 디코딩하면 주변 문맥이 없고, Whisper 계열 모델은 문맥을 활용합니다. 두 조각을 함께 디코딩하는 편이 따로 디코딩하는 것보다 낫습니다.

5초에 도달하면 일반 규칙이 다시 적용됩니다. 닫힌 구절 뒤에 400 ms의 멈춤이 있으면 배치가 플러시되고, 배치가 29초 목표를 넘기게 되는 구절이 오는 경우에도 마찬가지입니다. 단축키를 놓으면 남은 모든 것이 길이와 관계없이 플러시됩니다.

Voice Type 음성 게이트의 흐름도. 16 kHz 마이크 스트림이 GPU를 끈 채 단일 CPU 스레드에서 ggml로 실행되는 Silero 음성 활동 감지기에 들어간다. 감지기가 음성 구간을 표시하고, 배처는 구간이 목표 길이(15초에서 29초 사이로 제한되며 구절 중간에서 나뉘지 않음)에 이를 때까지 보관한 뒤에야 Metal GPU에서 실행되는 whisper.cpp 인식기에 배치를 넘긴다.

게이트는 하나의 CPU 스레드에서 실행되므로 인식기와 GPU를 두고 경쟁하지 않습니다. 출처: StreamingSileroSpeechGate.swift.

배치가 거치는 경로

배치가 플러시되면 컨디셔닝을 거친 뒤 디코딩됩니다.

  1. DC 오프셋을 제거합니다.
  2. 활성화된 필터와 음량 제어를 적용합니다. 기본적으로 50 Hz 하이패스가 포함됩니다.
  3. 캡처 기기가 아직 16 kHz가 아니었다면 Whisper가 기대하는 16 kHz로 리샘플링합니다.
  4. 메인 액터 밖에서 모델을 실행합니다.
  5. 결과 텍스트를 트랜스크립트에 이어 붙이고, 배치 경계에서 반복된 단어를 중복 제거합니다.

5단계는 보이는 것보다 중요합니다. 배치는 독립적으로 디코딩되므로 같은 단어가 한 배치의 끝과 다음 배치의 시작에 모두 나타날 수 있습니다. 마지막으로 확정된 단어들은 프롬프트 문맥으로도 전달되므로, 모델은 이전 오디오를 다시 디코딩하지 않고도 방금 무슨 말을 했는지 알 수 있습니다.

게이트가 실패할 때

음성 활동 감지도 모델이므로 틀릴 수 있습니다. Silero가 완전히 실패하면 파이프라인은 캡처된 오디오를 대체 세그먼트로 표시하여 사용할 수 있습니다.

의도된 예외가 하나 있습니다. 게이트가 정상적으로 실행되었고 음성이 전혀 없다고 보고하면, 앱은 에너지 임계값으로 대체하지 않습니다. 팬 소리, 타이핑, 정지 키의 클릭 소리가 트랜스크립트가 되는 실패 모드이기 때문이며, 잘못된 트랜스크립트는 트랜스크립트가 없는 것보다 나쁩니다.

관련 글: Core ML 인코더를 껐던 이유 · 16 kHz로 리샘플링