디지털 시대의 음성은 회의부터 콘텐츠, 고객센터 등 어디에나 존재하는 정보입니다. 그러나 이런 음성 데이터를 효과적으로 활용하는 것은 생각보다 어려운 과제입니다. 음성 데이터를 활용하기 위해 자동 음성 인식(ASR)과 음성 인식 기술을 활용하여 분석 가능한 구조화된 형식으로 변환합니다.
본 글에서는 로컬 환경에서 사용할 수 있는 OpenAI에서 개발한 음성 인식 모델인 Whisper와 오디오 파일에서 화자를 식별하는 음성 인식 솔루션인 Pyannote에 대해서 살펴 봅니다. 이 두 기술을 결합하여 화자별로 세분화된 정확한 필사본을 생성하는 방법과 주요 활용 분야를 살펴보겠습니다.
자동 음성 인식(ASR) 이해하기
자동 음성 인식(ASR)은 사람의 음성을 텍스트로 변환하는 기술입니다. 오디오 신호를 분석하고 발화 내용을 추출할 수 있는 모델을 기반으로 합니다. ASR은 청각 장애인 접근성 향상, 회의 또는 인터뷰 내용의 녹취, 오디오 및 비디오 콘텐츠 요약 및 색인 생성 등 다양한 분야에 활용됩니다. ASR의 효과는 음질, 억양, 배경 소음 등 여러 요인에 따라 달라집니다.
ASR은 몇 가지 주요 단계를 거칩니다.
- 전처리: 오디오 신호를 정리하여 배경 소음을 줄이고 녹음 품질을 개선한 후, 모델에서 사용할 수 있는 형식으로 변환합니다.
- 음향 모델링: 주파수와 진폭과 같은 음향적 특징을 오디오 신호에서 추출하여 음성 언어의 기본 단위인 음소를 식별합니다.
- 언어 모델링: 알고리즘은 탐지된 음소와 맥락 정보를 기반으로 확률이 높은 단어와 구문을 예측합니다.
- 디코딩 및 수정: 잠재적 오류와 언어적 맥락을 고려하여 최종본을 조정합니다.
OpenAI에서 개발한 Whisper는 매우 유명한 ASR 모델입니다. 방대한 다국어 데이터를 기반으로 학습되어 다양한 언어, 억양, 맥락에서 효과적으로 작동합니다.
장정 및 특징은 아래와 같습니다.
- 높은 정확도: Whisper는 다양한 악센트와 소음이 많은 환경을 잘 처리하여 필사 품질을 향상 시킵니다.
- 다국어 지원: 여러 언어를 지원하고 자동으로 필사본을 번역할 수 있습니다.
- 자막 생성: 오디오 및 비디오 콘텐츠에 대한 동기화된 자막을 만들 수 있습니다.
- 소음 저향성: 배경 소음에 대한 견고성으로 인해 실제 오디오 필사에 매우 효과적입니다.
제한된 데이터 세트에 대한 특정 학습이 필요한 기존 ASR 모델과 다르게, Whisper는 대규모 학습을 기반으로 하여 더 나은 일반화를 가능하게 합니다. 따라서 정확하고 고품질의 트랜스크립션이 필요한 곳에 강력한 도구가 될 수 있습니다.
Hugging Face Transformers 라이브러리를 사용하면 Whisper를 Python 애플리케이션에 쉽게 통합할 수 있습니다. 아래는 Whisper Large-v3-Turbo 모델을 기반으로 한 접근 방식입니다.
- 모델 로딩: WhisperAudioTranscriber가 초기화되고 하드웨어 가용성에 따라 컴퓨팅 장치 (GPU, MPS 또는 CPU)가 자동으로 구성됩니다.
- 모델 및 프로세서 준비: Whisper 모델과 관련 프로세서는 AutoModelForSpeechSeq25eq 를 사용하여 로드욉니다.
- 파이프라인 구성: 파이프라인은 오디오를 1초 중복으로 5초 단위로 분할하고 타임스탬프를 활성화하는 특정 매개변수로 설정됩니다.
- 오디오 파일 트랜스크립션: transcribe(audio_path) 메소드를 호출하면 트랜스크립션이 실행되고 연관된 타임스템프와 함께 텍스트가 반환됩니다.
아래는 이 접근 방식을 보여주는 코드입니다.
https://gist.github.com/giljae/6de980451e522e3f5a8e12113f622d92import torch |
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline |
class WhisperAudioTranscriber(): |
def __init__(self, model_name="openai/whisper-large-v3-turbo"): |
# Configure the device for computation |
if torch.cuda.is_available(): |
self.device = "cuda:0" |
self.torch_dtype = torch.float16 |
elif torch.backends.mps.is_available(): |
self.device = "mps" |
self.torch_dtype = torch.float16 |
else: |
self.device = "cpu" |
self.torch_dtype = torch.float32 |
# Load the model and processor |
try: |
self.model = AutoModelForSpeechSeq2Seq.from_pretrained( |
model_name, |
torch_dtype=self.torch_dtype, |
low_cpu_mem_usage=True, |
use_safetensors=True, |
) |
self.model.to(self.device) |
self.processor = AutoProcessor.from_pretrained(model_name) |
# Configure the pipeline for automatic speech recognition |
self.pipe = pipeline( |
"automatic-speech-recognition", |
model=self.model, |
tokenizer=self.processor.tokenizer, |
feature_extractor=self.processor.feature_extractor, |
torch_dtype=self.torch_dtype, |
device=self.device, |
return_timestamps=True, |
generate_kwargs={"max_new_tokens": 400}, |
chunk_length_s=5, |
stride_length_s=(1, 1), |
) |
except Exception as e: |
raise |
def transcribe(self, audio_path: str) -> tuple: |
try: |
# Perform transcription with timestamps |
result = self.pipe(audio_path) |
transcription = result['text'] |
timestamps = result['chunks'] |
return transcription, timestamps |
except Exception as e: |
return None, None |
위 코드는 매개변수로 전달된 오디오 파일의 자동 트랜스크립션을 허용합니다. Whisper의 타임스펨프 기본 지원 기능은 오디오 세그먼트를 다른 도구와 쉽게 정렬할 수 있도록 하여, 대화에 대한 시간적 추적이 필요한 요건에 유용합니다.
Diarization: 화자 식별 하기
Diarization은 오디오 녹음을 분할하여 여러 화자를 식별하는데 사용되는 기술입니다. 대화를 구조화하고 "누가, 언제, 말하는가" 라는 질문에 답합니다.
화자 식별은 회의, 인터뷰, 전화, 영화 등 다양한 상황에서 유용합니다. 각 발화 부분을 해당 발화자와 연결하여 필사본의 가독성을 높이고 오디오 데이터의 심층 분석을 용이하게 합니다.
이 기법은 아래의 단계를 거칩니다.
- 세분화: 오디오는 화자 변경을 기준으로 더 작은 세그먼트로 나뉩니다.
- 특징 추출: 세그먼트를 분석하여 음향 매개변수(톤, 주파수, 강도)를 기반으로 고유한 음성 지문을 추출합니다.
- 식별: 일부 응용 프로그램에서는 음성 인식과 Diarization을 결합하여 각 화자에게 이름이나 역할을 부여할 수 있습니다.
일반적인 접근 방식은 신경망 기반 모델, 가우시안 혼합 모델(GMN), 스펙트럼 클러스터링과 같은 비지도 클러스터링 방법이 있습니다.
우리는 심층 신경망 모델을 활용하여 화자를 식별하는 Pyannote로 테스트를 진행합니다. 오디오 처리 파이프라인에 원활하게 통합되도록 설계되었고, 배경 소음이나 중복되는 음성을 포함된 오디오에서도 음성 분할 및 식별을 수행할 수 있습니다.
Pyannote는 아래의 특징을 지닙니다.
- 정확도: Pyannote는 딥러닝을 활용하여 화자 감지를 합니다.
- 적응성: 회의, 통화 등 다양한 유형에 사용할 수 있습니다.
- 모듈성: Pyannote는 Whisper와 같은 도구와 완벽하게 통합되어 화자 식별이 가능합니다.
- 호환성: 다양한 오디오 포맷을 지원하고, API 혹은 파이썬 스크립트를 통해 기존 애플리케이션에 통합할 수 있습니다.
Pyannote의 음성 분할 구현은 pyannote.audio 라이브러리를 사용하여 진행됩니다. 다음은 Pyannote Speaker Diarization 3.1 모델을 기반으로 한 접근 방식입니다.
- 모델 로링: 클래스 인스턴스느 ㄴHugging Face PyannoteDiarizer에서 분할 모델을 초기화하고 로드합니다.
- 분할 실행: diarize(audio_path) 메소드에서는 오디오 파일을 분석하여 서로 다른 화자를 식별하고 음성 기여도를 세분화합니다.
위 접근 방식을 구현한 코드는 아래와 같습니다.
https://gist.github.com/giljae/6de980451e522e3f5a8e12113f622d92
from pyannote.audio import Pipeline |
from pyannote.audio.pipelines.utils.hook import ProgressHook |
import torch |
class PyannoteDiarizer: |
def __init__(self, hf_token: str): |
try: |
self.pipeline = Pipeline.from_pretrained( |
"pyannote/speaker-diarization-3.1", |
use_auth_token=hf_token |
) |
self.device = torch.device("cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu") |
self.pipeline.to(self.device) |
except Exception as e: |
self.pipeline = None |
def diarize(self, audio_path: str): |
if self.pipeline is None: |
return None |
try: |
with ProgressHook() as hook: |
diarization = self.pipeline(audio_path, hook=hook) |
return diarization |
except Exception as e: |
return None |
위 코드는 오디오에서 화자를 세분화하여 Whisper에서 생성된 트랜스크립션을 해당 세그먼트에 맞추는 것을 더 쉽게 해줍니다.
오디오 세그먼트에 맞춰 트랜스크립션 정렬 하기
음성 텍스트 변환을 오디오 세그먼트에 맞추려면 변환된 각 단어나 구문을 해당 화자와 일치시켜야 합니다. 대화에서 중복, 중단 또는 독백 등은 이런 작업을 복잡하게 만들며, 변환 데이터와 화자 식별 간의 정확한 동기화를 요구합니다.
예를 들어서, Whisper는 타임스탬프가 포함된 대본을 생성합니다.
- (0초에서 3초까지) 안녕하세요.
- (3초에서 5초까지) 네 안녕하세요.
Pyannote는 아래와 같이 화자를 식별하고 오디오를 세분화 합니다.
- SPEAKER_00: (0초에서 3초까지)
- SPEAKER_01: (3초에서 5초까지)
정렬은 이런 정보를 병합하여 전사된 각 문장을 올바른 화자와 연결하고 대화를 정확하게 표현하는 것입니다.
이 단계를 정말 중요합니다. 그 이유는 아래와 같습니다.
- 향상된 이해력: 적절하게 정렬된 트랜스크립션은 누가 언제 말하는지 명확하게 식별합니다. 여러 사람이 대화하는 경우 텍스트를 더 이해하기 쉽게 만듭니다.
- 동기화된 자막 생성: 정확한 정렬은 일관된 자막을 만들고 접근성과 사용자 경험을 개선하는데 중요합니다. (비디오 서비스)
- 쉬운 인덱싱 및 검색: 잘 세분화된 텍스트를 통해 특정 화자나 대화의 특정 부분을 검색할 수 있어 미디어 분석에 유용합니다.
- 대화 분석 및 상호작용 추적: 정렬은 참가자 간의 대화 방향, 방해, 교환을 식별하여 토론의 역학을 더 잘 이해하는데 도움이 됩니다.
- 최적화된 자동 회의 요약: 회의나 컨퍼런스와 같은 전문적인 환경에서, 필사본을 오디오 세그먼트에 정확하게 연결하면 체계적이고 유용한 보고서를 쉽게 생성할 수 있습니다.
오디오 세그먼트에 맞춰 트랜스크립션을 정렬하는 것은 단순히 시간을 맞추는 것을 넘어 오디오 데이터를 효과적으로 활용하고 향상시키는데 중요한 역할을 합니다.
생각해보면 간단해 보일 수 있지만, 음성 텍스트와 오디오 세그먼트를 정렬하는 작업은 복잡하며, 자동 음성 인식(Whisper)과 화자 분류(Pyannote) 데이터를 병합하는데 필요합니다. 목표는 정확한 타임스탬프를 유지하면서 각 테스트 세그먼트를 해당 화자에게 할당하는 것입니다.
정렬은 Whisper가 제공한 세그먼트와 Pyannote가 감지한 세그먼트 간의 시간 교차 접근법으로 진행합니다.
- 전사 및 이중화 세그먼트 추출
- Whisper는 연관된 타임스탬프가 있는 세그먼트로 나뉜 트랜스크립션을 생성합니다.
- Pyannote는 오디오를 분할하고 각 시간 간격에 스피커 식별자를 할당합니다.
- 화자와 일치하는 전사 세그먼트
- Whisper에서 생성된 각 세그먼트에 대해 Pyannote에서 가장 잘 대응하는 세그먼트 식별
- 전사 세그먼트와 감지된 화자 세그먼트 간의 가장 큰 시간적 중복을 기반으로 합니다.
- 특수 사례 처리
- 전사 세그먼트 분할시 정의된 범위를 넘으면 마지막으로 감지된 세그먼트의 끝을 고려하여 정렬을 조정
- 여러 화자가 겹치는 경우, 알고리즘은 전사 세그먼트와 가장 긴 교차 기간을 가진 화자를 선택
- 동일한 스피커의 연속 세그먼트 병합
- 동일한 화자 속하는 연속된 세그먼트는 과도한 텍스트 단편화를 피하기 위해 병합됩니다.
위의 접근법을 파이썬으로 구현합니다.
- 각 요소 화자, 시작 및 종료 타임스탬프, 해당 텍스트가 align() 목록을 반환합니다.
- find_best_match() 는 주어진 전사 세그먼트와 시간적으로 가장 많이 중복되는 전사 세그먼트를 검색합니다.
- merge_consecutive_segments()는 동일한 화자에 속하는 인접한 세그먼트를 병합하여 정렬된 트랜스스크립션의 일관성을 개선합니다.
class SpeakerAligner(): |
def align(self, transcription, timestamps, diarization): |
speaker_transcriptions = [] |
# Find the end time of the last segment in diarization |
last_diarization_end = self.get_last_segment(diarization).end |
for chunk in timestamps: |
chunk_start = chunk['timestamp'][0] |
chunk_end = chunk['timestamp'][1] |
segment_text = chunk['text'] |
# Handle the case where chunk_end is None |
if chunk_end is None: |
# Use the end of the last diarization segment as the default end time |
chunk_end = last_diarization_end if last_diarization_end is not None else chunk_start |
# Find the best matching speaker segment |
best_match = self.find_best_match(diarization, chunk_start, chunk_end) |
if best_match: |
speaker = best_match[2] # Extract the speaker label |
speaker_transcriptions.append((speaker, chunk_start, chunk_end, segment_text)) |
# Merge consecutive segments of the same speaker |
speaker_transcriptions = self.merge_consecutive_segments(speaker_transcriptions) |
return speaker_transcriptions |
def find_best_match(self, diarization, start_time, end_time): |
best_match = None |
max_intersection = 0 |
for turn, _, speaker in diarization.itertracks(yield_label=True): |
turn_start = turn.start |
turn_end = turn.end |
# Calculate intersection manually |
intersection_start = max(start_time, turn_start) |
intersection_end = min(end_time, turn_end) |
if intersection_start < intersection_end: |
intersection_length = intersection_end - intersection_start |
if intersection_length > max_intersection: |
max_intersection = intersection_length |
best_match = (turn_start, turn_end, speaker) |
return best_match |
def merge_consecutive_segments(self, segments): |
merged_segments = [] |
previous_segment = None |
for segment in segments: |
if previous_segment is None: |
previous_segment = segment |
else: |
if segment[0] == previous_segment[0]: |
# Merge segments of the same speaker that are consecutive |
previous_segment = ( |
previous_segment[0], |
previous_segment[1], |
segment[2], |
previous_segment[3] + segment[3] |
) |
else: |
merged_segments.append(previous_segment) |
previous_segment = segment |
if previous_segment: |
merged_segments.append(previous_segment) |
return merged_segments |
def get_last_segment(self, annotation): |
last_segment = None |
for segment in annotation.itersegments(): |
last_segment = segment |
return last_segment |
잠재적 활용
이렇게 해서 나온 결과물은 LLM에서 활용할 수 있는 형식으로 분석, 색인 및 구조화를 용이하게 하여 오디오 및 비디오 콘텐츠를 효율적으로 활용할 수 있게 합니다.
저의 경우는 현재 고객 서비스 전화 통화를 분석하여 사용자 만족도 및 대응 서비스 품질 그리고 고객 인사이트를 파악하려고 합니다. 이 부분은 감정 분석 영역이며 다음과 같은 이점이 있습니다.
- 키워드와 긍정적 또는 부정적 표현을 식별
- 각 통화에 만족도 점수 부여 가능
- 주의가 필요한 전화는 관리자에게 표시
위 케이스외에도 다양한 분야에 활용이 가능합니다.
- 청각 장애인을 위한 접근성
- 콘텐츠 인덱싱 및 검색
- 자동 번역
- 음성 지원 기능 개선
LLM의 등장으로 음성 정보를 텍스트 형식으로 변환하는 것은 대규모 처리, 분석 및 활용에 필수적인 요소가 되었습니다.
끝으로, 음성 인식과 화자 분류 기술이 발전했음에도 여전히 몇 가지 과제가 남아 있습니다. 어려운 점은 다음과 같습니다.
- 배경 소음 및 소리 장애: 시끄러운 환경에서는 음성 신호가 왜곡되기에 정확도가 떨어질 수 있습니다.
- 액센트와 언어적 변형: ASR 모델은 신뢰도에 영향을 줄 수 있는 광범위한 액센트의 사투리를 이해할 수 있어야 합니다.
- 중복된 말투: 여러 화자가 동시에 말할 때, 음성을 구분하는데 어려움을 겪습니다. 때로는 문장의 일부를 잘못된 화자에게 매칭합니다.
- 긴 대화에서 화자 인식: 긴 대화에서 같은 화자에게 세그먼트를 할당하면 일관성을 잃을 수 있습니다. 특히 억양이 바뀌거나 맥락이 바뀔 때 그렇습니다.
ASR 및 Diarization 기술은 언제가는 지금보다 효율성과 정확성을 향상시킬 것이라고 생각합니다. 아마 아래의 영역이 개선되지 않을까 합니다.
- 액센트와 배경 소음 처리
- 더 나은 맥락적 이해를 위한 ASR + LLM 융합
- 임베디드 및 모바일 시스템에 배포 가능한 최적화
- 더욱 견고한 다국어 모델
- 실시간 상호작용 및 AI의 대화 참여
Whisper와 Pyannote를 함께 사용하면 화자별 분할이 가능하기에 다양한 상황에서 유용하게 활용할 수 있습니다. 로컬 배포 또는 IDC에서 사용하면 데이터 기밀성이 더욱 강화될 것입니다.
포크 받은 코드는 https://github.com/monocleface/EchoInStone에 올려두었습니다. 아직 정확도가 높진 않습니다. 차근차근 해결해야 할 것 같아요.
0 Comments:
댓글 쓰기