ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 음성을 텍스트로, 목소리를 사람으로 — Whisper와 pyannote가 풀어낸 두 가지 문제
    IT 2026. 3. 19. 21:00

    녹음을 다시 듣고 있다. 1시간짜리 음성 파일. 핵심 내용이 어디쯤이었는지 기억이 안 나서 처음부터 재생한다. 빨리감기. 되감기. 또 빨리감기.

    이 경험이 익숙하다면, "녹음은 했는데 다시 듣기가 귀찮다" 는 인류 공통의 문제에 공감할 것이다.

    이 문제를 해결하려면 두 가지가 필요하다.

    1. 음성을 텍스트로 바꾸기 (Speech-to-Text)
    2. 누가 말한 건지 구분하기 (Speaker Diarization)

    이 글에서는 이 두 문제를 각각 해결하는 오픈소스 모델 — OpenAI Whisperpyannote-audio — 을 다룬다. 어떤 문제를 풀기 위해 나왔고, 어떻게 작동하며, 실제로 어떻게 설치하고 사용하는지까지.


    문제 1: 음성을 텍스트로 — OpenAI Whisper

    왜 이 모델이 필요한가

    음성인식(STT)은 오래된 문제다. 시리, 구글 어시스턴트, 네이버 클로바 — 이미 잘 되고 있지 않나?

    문제는 "잘 된다"의 기준이다.

    • 조용한 방에서 또박또박 말하면? 잘 된다.
    • 여러 사람이 자유롭게 토론하는 자리에서는? 엉망이 된다.
    • 한국어로 말하다가 갑자기 영어 용어를 쓰면? 더 엉망이 된다.

    기존 음성인식 시스템의 한계는 크게 두 가지였다.

    첫째, 언어별로 따로 만들어야 했다. 한국어 모델, 영어 모델, 일본어 모델... 각각 별도의 데이터로 학습하고 별도로 배포했다.

    둘째, 학습 데이터가 "깨끗한 음성"에 편향되어 있었다. 스튜디오에서 녹음한 뉴스 앵커의 음성으로 학습하니, 실제 현장의 웅성거림 속에서는 정확도가 급락했다.

    Whisper는 어떻게 해결했나

    OpenAI는 2022년에 Whisper를 발표하면서 완전히 다른 접근을 했다.

    68만 시간의 인터넷 음성 데이터로 학습했다. YouTube 영상의 자막 데이터가 핵심이었다. 99개 언어가 섞여 있고, 배경 소음이 있고, 발음이 불명확한 — 그야말로 "지저분한" 실제 음성 데이터다.

    이 접근의 효과는 명확하다.

    • 다국어를 하나의 모델로 처리한다. 한국어, 영어, 일본어를 동일한 모델이 인식한다. 한국어로 말하다 "transformer architecture"라고 말해도 자연스럽게 전사된다.
    • 실제 환경에서도 정확하다. 학습 데이터 자체가 실제 환경 음성이므로, 세미나장의 배경 소음이나 에어컨 소리에 강하다.
    • 무료 오픈소스다. MIT 라이선스. 내 서버에서 돌릴 수 있다. 클라우드 API 비용도, 개인정보 유출 걱정도 없다.

    작동 원리를 쉽게 설명하면

    Whisper의 구조는 의외로 직관적이다.

    음성 → [인코더] → 음성의 "의미 벡터" → [디코더] → 텍스트
    

    인코더는 음성 파형을 받아서 "이 구간에서 이런 소리가 났다"를 숫자 벡터로 압축한다. 사람으로 치면, 귀로 소리를 듣고 뇌에서 "아, 이건 '안녕하세요'라는 소리구나"라고 인식하는 과정이다.

    디코더는 이 벡터를 보고 텍스트를 하나씩 생성한다. ChatGPT가 글자를 하나씩 출력하는 것과 같은 방식이다. 이전에 생성한 텍스트를 참고하면서 다음 글자를 예측한다.

    이 구조 덕분에 Whisper는 문맥을 이해한다. "나는 오늘 ___ 에 갔다"에서 빈칸이 "학교"인지 "학꾜"인지 — 음성이 불명확해도 문맥으로 올바른 단어를 선택할 수 있다.

    모델 크기별 선택

    Whisper는 여러 크기의 모델을 제공한다.

    모델 파라미터 VRAM 용도
    tiny 39M ~1GB 빠른 테스트, 실시간 자막
    base 74M ~1GB 가벼운 용도
    small 244M ~2GB 괜찮은 정확도
    medium 769M ~5GB 좋은 정확도
    large-v3 1.55B ~10GB 최고 정확도

    정확도가 최우선이라면 large-v3가 답이다. GPU 메모리가 10GB 이상이면 충분히 구동할 수 있다.


    문제 2: 누가 말한 건지 — pyannote-audio

    왜 이 모델이 필요한가

    Whisper가 음성을 텍스트로 바꿔줬다. 하지만 결과물은 이렇다.

    오늘 세미나 주제는 AI 활용입니다.
    네 그 부분에 대해서 질문이 있는데요.
    추가로 말씀드리면 작년 사례를 보시면...
    

    누가 한 말인지 모른다. 3명이 대화했는데 전부 한 사람이 한 것처럼 보인다. 기록으로 쓸 수 없다.

    이 문제를 화자 분리(Speaker Diarization) 라고 한다. "이 음성에서 몇 명이 말했고, 각 구간에서 누가 말했는지"를 알아내는 것이다.

    pyannote는 어떻게 해결했나

    pyannote-audio는 프랑스 CNRS(국립과학연구센터)에서 개발한 오픈소스 화자 분리 파이프라인이다. 내부적으로 두 개의 모델을 조합한다.

    ① 세그멘테이션 모델 (pyannote/segmentation-3.0)

    이 모델은 음성을 아주 짧은 구간(수십 밀리초)으로 쪼개서, 각 구간마다 세 가지를 판단한다.

    • 여기서 누군가 말하고 있나? (Voice Activity Detection)
    • 화자가 바뀌었나? (Speaker Change Detection)
    • 두 사람이 동시에 말하고 있나? (Overlap Detection)

    사람으로 치면, 눈을 감고 소리만 듣면서 "지금 말이 바뀌었다", "지금 두 사람이 겹쳐서 말한다"를 감지하는 과정이다.

    ② 화자 임베딩 모델 (wespeaker-voxceleb-resnet34)

    세그멘테이션이 구간을 잘라주면, 이 모델은 각 구간의 "목소리 지문" 을 추출한다. 사람마다 성대 구조, 발성 습관, 말투가 다르므로 — 이 차이를 숫자 벡터(임베딩)로 표현한다.

    같은 사람의 목소리는 비슷한 벡터를 갖고, 다른 사람은 다른 벡터를 갖는다. 이 벡터들을 클러스터링하면 "Speaker 1", "Speaker 2"... 라벨이 만들어진다.

    두 모델의 협업

    음성 파일
      ↓
    세그멘테이션: "0:00~0:15 누군가 말함, 0:15~0:30 다른 사람 말함, 0:28~0:35 겹침"
      ↓
    임베딩 추출: 각 구간의 목소리 지문 벡터 생성
      ↓
    클러스터링: "벡터 A, C, E는 비슷 → Speaker 1 / 벡터 B, D는 비슷 → Speaker 2"
      ↓
    결과: 시간별 화자 라벨
    

    세그멘테이션만으로는 "언제" 누군가 말하는지는 알지만 "누구" 인지 모른다. 임베딩만으로는 "누구" 인지는 알지만 "언제" 말하는지 모른다. 둘을 결합해야 완전한 화자 분리가 된다.


    실전: 설치부터 실행까지

    환경 준비

    Python 가상환경을 만들고 필요한 패키지를 설치한다.

    # 가상환경 생성
    python3 -m venv ~/voice-env
    source ~/voice-env/bin/activate
    
    # Whisper 설치
    pip install openai-whisper
    
    # pyannote 설치
    pip install pyannote.audio
    
    # GPU 사용 시 PyTorch CUDA 버전 필요
    pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128
    

    pyannote 사전 준비

    pyannote 모델은 HuggingFace에서 제공되며, 라이선스 동의가 필요하다.

    1. huggingface.co 계정 생성
    2. Settings → Tokens에서 토큰 발급
    3. 아래 두 페이지에서 "Agree and access repository" 클릭
    4. pyannote/speaker-diarization-3.1
    5. pyannote/segmentation-3.0

    Whisper로 전사하기

    import whisper
    
    # 모델 로드 (최초 실행 시 자동 다운로드, ~3GB)
    model = whisper.load_model("large-v3", device="cuda")
    
    # 전사
    result = model.transcribe("meeting.m4a", language="ko")
    
    # 결과 출력
    for segment in result["segments"]:
        start = segment["start"]
        end = segment["end"]
        text = segment["text"]
        print(f"[{start:.1f}s ~ {end:.1f}s] {text}")
    

    출력 예시:

    [0.0s ~ 3.2s]  오늘 세미나 주제는 AI 활용입니다.
    [3.5s ~ 7.8s]  네 그 부분에 대해서 질문이 있는데요.
    [8.1s ~ 15.3s] 추가로 말씀드리면 작년 사례를 보시면...
    

    language="ko"를 지정하면 한국어로 고정되어 더 정확하다. 생략하면 Whisper가 자동으로 언어를 감지한다.

    pyannote로 화자 분리하기

    from pyannote.audio import Pipeline
    
    # 파이프라인 로드 (HuggingFace 토큰 필요)
    pipeline = Pipeline.from_pretrained(
        "pyannote/speaker-diarization-3.1",
        use_auth_token="hf_YOUR_TOKEN_HERE"
    )
    
    # GPU 사용
    import torch
    pipeline.to(torch.device("cuda"))
    
    # 화자 분리 실행
    diarization = pipeline("meeting.m4a")
    
    # 결과 출력
    for turn, _, speaker in diarization.itertracks(yield_label=True):
        print(f"[{turn.start:.1f}s ~ {turn.end:.1f}s] {speaker}")
    

    출력 예시:

    [0.0s ~ 3.2s] SPEAKER_00
    [3.5s ~ 7.8s] SPEAKER_01
    [8.1s ~ 15.3s] SPEAKER_00
    

    두 결과를 합치면

    Whisper의 전사 결과(텍스트 + 타임스탬프)와 pyannote의 화자 분리 결과(화자 + 타임스탬프)를 타임스탬프 기준으로 매핑하면 완성이다.

    # Whisper 세그먼트의 중간 시점이 어떤 화자 구간에 속하는지 매핑
    for seg in whisper_segments:
        mid = (seg["start"] + seg["end"]) / 2
        for turn, _, speaker in diarization.itertracks(yield_label=True):
            if turn.start <= mid <= turn.end:
                seg["speaker"] = speaker
                break
    

    최종 결과:

    [00:00] Speaker 1: 오늘 세미나 주제는 AI 활용입니다.
    [00:03] Speaker 2: 네 그 부분에 대해서 질문이 있는데요.
    [00:08] Speaker 1: 추가로 말씀드리면 작년 사례를 보시면...
    

    이제 이 텍스트를 LLM에 넘기면 자동 요약, 액션 아이템 추출, 기록 정리까지 가능하다.


    실제 운영에서의 팁

    성능

    내 DGX Spark(NVIDIA GB10, 128GB 통합 메모리) 환경에서의 실측:

    • Whisper large-v3: 45분 녹음 파일 전사에 약 6분 소요 (GPU)
    • pyannote 화자 분리: 같은 파일에 약 2분 소요 (GPU)
    • 전체 파이프라인 (전사 + 화자분리 + LLM 요약): 약 10분

    CPU만 사용하면 Whisper만 30분 이상 걸린다. GPU가 있다면 반드시 활용하자.

    m4a 포맷 주의

    갤럭시 기본 녹음 앱은 m4a(AAC) 포맷으로 저장한다. Whisper는 m4a를 직접 읽을 수 있지만, pyannote의 내부 오디오 로더(soundfile)는 m4a를 지원하지 않는다. ffmpeg로 wav 변환이 필요하다.

    ffmpeg -i recording.m4a -ar 16000 -ac 1 recording.wav
    

    화자 수 자동 감지

    pyannote는 화자 수를 자동으로 감지한다. 미리 알고 있다면 명시할 수도 있다.

    # 화자 수를 모를 때 (자동 감지)
    diarization = pipeline("meeting.m4a")
    
    # 화자 수를 알 때 (더 정확)
    diarization = pipeline("meeting.m4a", num_speakers=3)
    

    마무리

    Whisper는 "음성을 텍스트로" 문제를, pyannote는 "누가 말했는지" 문제를 각각 해결한다. 둘 다 오픈소스이고, GPU가 있으면 로컬에서 충분히 돌릴 수 있다.

    이 두 모델을 조합하면 녹음 파일 → 화자별 전사 텍스트까지 완전 자동화된다. 여기에 LLM 요약을 붙이면, 1시간짜리 녹음이 2분 만에 깔끔한 요약 문서가 된다.

    더 이상 녹음 파일을 처음부터 다시 들을 필요가 없다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.