오디오-텍스트-텍스트

오디오-텍스트-텍스트 (Audio-text-to-text)

Audio-text-to-text 모델은 오디오와 텍스트를 모두 입력으로 받아들이고 텍스트를 출력으로 생성합니다. 오디오 이해와 언어 생성을 결합해 오디오 질의응답(예: "이 클립에서 무엇이 말해지고 있나요?"), 오디오 추론(예: "화자가 어떤 감정을 전달하나요?"), 음성 대화 이해 같은 작업을 가능하게 합니다.

출처: 문서

본문

음성을 텍스트로만 전사하는 기존의 자동 음성 인식(ASR) 모델과 달리, audio-text-to-text 모델은 오디오 내용에 대해 추론하고, 복잡한 지시를 따르며, 들은 내용을 바탕으로 맥락에 맞는 응답을 생성할 수 있습니다.

아래 예시는 모델과 프로세서를 로드하고, 텍스트 프롬프트와 함께 오디오 파일을 전달해 응답을 생성하는 방법을 보여줍니다. 이 경우 모델에게 음성 녹음을 전사하라고 요청합니다.

from transformers import AudioFlamingo3ForConditionalGeneration, AutoProcessor

model_id = "nvidia/audio-flamingo-3-hf"
processor = AutoProcessor.from_pretrained(model_id)
model = AudioFlamingo3ForConditionalGeneration.from_pretrained(model_id, device_map="auto")

conversation = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Transcribe the input speech."},
            {"type": "audio", "path": "https://huggingface.co/datasets/nvidia/AudioSkills/resolve/main/assets/WhDJDIviAOg_120_10.mp3"},
        ],
    }
]

inputs = processor.apply_chat_template(
    conversation,
    tokenize=True,
    add_generation_prompt=True,
    return_dict=True,
).to(model.device)

outputs = model.generate(**inputs, max_new_tokens=500)

decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True)
print(decoded_outputs)
## ["The transcription of the audio is 'summer follows spring the days grow longer and the nights are warm'."]

이 가이드에서 다룰 내용은 다음과 같습니다.

  1. AudioCaps 데이터셋에서 LoRA를 사용해 Audio Flamingo 3를 오디오 캡셔닝용으로 파인튜닝합니다.
  2. 파인튜닝된 모델을 추론(inference)에 사용합니다.

[!TIP] 이 작업과 호환되는 모든 아키텍처와 체크포인트를 보려면 task-page를 확인하는 것을 권장합니다.

시작하기 전에 필요한 라이브러리를 모두 설치했는지 확인해 주세요.

pip install transformers datasets peft accelerate

Hugging Face 계정에 로그인해 모델을 커뮤니티에 업로드하고 공유하는 것을 권장합니다. 프롬프트가 나타나면 토큰을 입력해 로그인하세요.

>>> from huggingface_hub import notebook_login
>>> notebook_login()

AudioCaps 데이터셋 로드하기

🤗 Datasets 라이브러리에서 AudioCaps 데이터셋을 스트리밍 모드로 로드하는 것부터 시작하겠습니다. 이 데이터셋은 설명 캡션이 달린 오디오 클립을 포함해 오디오 캡셔닝 작업에 완벽합니다.

>>> from datasets import load_dataset, Audio
>>> dataset = load_dataset("OpenSound/AudioCaps", split="train", streaming=True)

Audio Flamingo의 Whisper 피처 추출기가 요구하는 대로 오디오 컬럼을 16kHz로 캐스트합니다.

>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))

스트리밍 데이터셋에서 .take()와 .skip()을 사용해 데이터셋을 train과 test 세트로 나눕니다.

>>> train_dataset = dataset.take(1000)
>>> eval_dataset = dataset.skip(1000).take(100)

예시를 하나 살펴봅니다.

>>> next(iter(train_dataset))
{'audiocap_id': 91139, 'youtube_id': 'r1nicOVtvkQ', 'start_time': 130, 'caption': 'A woman talks nearby as water pours', 'audio_length': 480000, 'audio': <datasets.features._torchcodec.AudioDecoder object at ...>}

데이터셋이 포함하는 내용은 다음과 같습니다.

  • audio: 오디오 파형
  • caption: 오디오에 대한 설명 텍스트 캡션

전처리 (Preprocess)

오디오와 텍스트 입력을 모두 처리할 Audio Flamingo 프로세서를 로드합니다.

>>> from transformers import AutoProcessor
>>> processor = AutoProcessor.from_pretrained("nvidia/audio-flamingo-3-hf")

오디오-텍스트 쌍을 Audio Flamingo가 기대하는 형식으로 처리하는 데이터 콜레이터를 만듭니다. 콜레이터는 직접 오디오 배열과 함께 채팅 템플릿 형식을 사용합니다.

>>> class AudioFlamingo3DataCollator:
...     """Data collator for Audio Flamingo 3 audio captioning training."""
...
...     def __init__(self, processor):
...         self.processor = processor
...
...     def __call__(self, features):
...         conversations = []
...
...         for feature in features:
...             # Build conversation format for Audio Flamingo
...             # Audio is passed directly as an array, no base64 encoding needed
...             sample = [
...                 {
...                     "role": "user",
...                     "content": [
...                         {"type": "text", "text": "Describe the audio."},
...                         {"type": "audio", "audio": feature["audio"].get_all_samples().data[0].numpy()},
...                     ],
...                 },
...                 {
...                     "role": "assistant",
...                     "content": [{"type": "text", "text": feature["caption"]}],
...                 }
...             ]
...             conversations.append(sample)
...
...         # Apply chat template and format labels for training
...         return self.processor.apply_chat_template(
...             conversations,
...             tokenize=True,
...             add_generation_prompt=False,
...             return_dict=True,
...             output_labels=True,  # Automatically creates labels for training
...         )

데이터 콜레이터를 인스턴스화합니다.

>>> data_collator = AudioFlamingo3DataCollator(processor)

훈련 (Train)

[!TIP] Trainer로 모델을 파인튜닝하는 방법이 익숙하지 않다면 기본 튜토리얼 여기를 확인해 보세요!

Audio Flamingo 모델을 로드합니다. 메모리를 효율적으로 사용하기 위해 bfloat16 정밀도와 device_map="auto"를 사용합니다.

>>> from transformers import AudioFlamingo3ForConditionalGeneration
>>> import torch
>>> model = AudioFlamingo3ForConditionalGeneration.from_pretrained(
...     "nvidia/audio-flamingo-3-hf",
...     torch_dtype=torch.bfloat16,
...     device_map="auto",
... )

LoRA 구성

LoRA(Low-Rank Adaptation)는 적은 수의 추가 매개변수만 훈련해 효율적인 파인튜닝을 가능하게 합니다. 언어 모델의 어텐션과 피드포워드 레이어를 대상으로 LoRA를 구성합니다.

>>> from peft import LoraConfig, get_peft_model
>>> lora_config = LoraConfig(
...     r=16,  # LoRA rank
...     lora_alpha=32,  # LoRA scaling factor
...     target_modules=[
...         # Language model attention
...         "q_proj",
...         "k_proj",
...         "v_proj",
...         "o_proj",
...         # Feed-forward layers
...         "gate_proj",
...         "up_proj",
...         "down_proj",
...     ],
...     lora_dropout=0.05,
...     bias="none",
...     task_type="CAUSAL_LM",
... )
>>> model = get_peft_model(model, lora_config)
>>> model.print_trainable_parameters()
trainable params: 44,302,336 || all params: 8,311,517,696 || trainable%: 0.5330

[!TIP] LoRA는 전체 모델 대신 적은 수의 어댑터 매개변수만 업데이트해 메모리 사용량과 훈련 시간을 크게 줄여줍니다. 이 구성은 오디오 인코더는 동결한 채 언어 모델의 어텐션과 피드포워드 레이어를 대상으로 하므로, 단일 GPU에서도 파인튜닝이 가능합니다.

훈련 설정

TrainingArguments에서 훈련 하이퍼파라미터를 정의합니다. 스트리밍 데이터셋을 사용하므로 에폭 대신 max_steps를 사용한다는 점에 주의하세요.

>>> from transformers import TrainingArguments, Trainer
>>> training_args = TrainingArguments(
...     output_dir="audio-flamingo-3-hf-lora-finetuned",
...     per_device_train_batch_size=4,
...     per_device_eval_batch_size=4,
...     gradient_accumulation_steps=4,
...     learning_rate=1e-4,
...     max_steps=500,  # Use max_steps with streaming datasets
...     bf16=True,
...     logging_steps=10,
...     eval_steps=100,
...     save_steps=250,
...     save_total_limit=2,  # Keep only the latest 2 checkpoints
...     save_only_model=True,  # Skip saving optimizer state to save disk space
...     eval_strategy="steps",
...     save_strategy="steps",
...     remove_unused_columns=False,
...     dataloader_num_workers=0,  # Must be 0 for streaming datasets
...     gradient_checkpointing=True,
...     report_to="none",
... )

Trainer에 모델, 데이터셋, 데이터 콜레이터와 함께 훈련 인자를 전달합니다.

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=train_dataset,
...     eval_dataset=eval_dataset,
...     data_collator=data_collator,
... )
>>> trainer.train()

LoRA 어댑터와 프로세서를 저장합니다.

>>> trainer.save_model()
>>> processor.save_pretrained("audio-flamingo-3-hf-lora-finetuned")
['audio-flamingo-3-hf-lora-finetuned/processor_config.json']

훈련이 끝나면 모델을 Hub에 공유하세요.

>>> trainer.push_to_hub()

추론 (Inference)

이제 모델을 파인튜닝했으니 오디오 캡셔닝에 사용할 수 있습니다.

파인튜닝된 모델과 프로세서를 로드합니다.

>>> from transformers import AudioFlamingo3ForConditionalGeneration, AutoProcessor
>>> from peft import PeftModel
>>> import torch
>>> base_model = AudioFlamingo3ForConditionalGeneration.from_pretrained(
...     "nvidia/audio-flamingo-3-hf",
...     torch_dtype=torch.bfloat16,
...     device_map="auto",
... )
>>> model = PeftModel.from_pretrained(base_model, "audio-flamingo-3-hf-lora-finetuned")
>>> processor = AutoProcessor.from_pretrained("audio-flamingo-3-hf-lora-finetuned")

추론에 사용할 오디오 샘플을 로드합니다.

>>> from datasets import load_dataset, Audio
>>> dataset = load_dataset("OpenSound/AudioCaps", split="test", streaming=True)
>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))
>>> sample = next(iter(dataset))
>>> audio = sample["audio"]
>>> audio_array = audio.get_all_samples().data[0].numpy() if hasattr(audio, "get_all_samples") else audio["array"]

대화 형식으로 입력을 준비합니다.

>>> messages = [
...     {
...         "role": "user",
...         "content": [
...             {"type": "text", "text": "Describe the audio."},
...             {"type": "audio", "audio": audio_array},
...         ],
...     }
... ]
>>> inputs = processor.apply_chat_template(
...     messages,
...     tokenize=True,
...     add_generation_prompt=True,
...     return_dict=True,
... ).to(device=model.device, dtype=model.dtype)

응답을 생성합니다.

>>> with torch.no_grad():
...     output_ids = model.generate(**inputs, max_new_tokens=100)
>>> # Decode only the generated tokens
>>> input_len = inputs["input_ids"].shape[1]
>>> response = processor.tokenizer.decode(output_ids[0][input_len:], skip_special_tokens=True)
>>> print(response)
## A sewing machine is running while people are talking

Pipeline

빠른 추론을 위해 any-to-any 모델과 함께 Pipeline API를 사용하세요. 아래 예시는 오디오와 텍스트 입력을 받아 텍스트를 생성하는 Voxtral을 사용합니다. 더 많은 예시는 any-to-any task guide를 참고하세요.

from transformers import pipeline

pipe = pipeline("any-to-any", model="mistralai/Voxtral-Mini-3B-2507")

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "audio",
                "url": "https://huggingface.co/datasets/raushan-testing-hf/audio-test/resolve/main/glass-breaking-151256.mp3",
            },
            {"type": "text", "text": "What do you hear in this audio?"},
        ],
    },
]

outputs = pipe(text=messages, max_new_tokens=100, return_full_text=False)
print(outputs[0]["generated_text"])

추가 자료 (Further Reading)

더 알아보기 (Learn more)