OpenAI Whisper - Tutorial Lengkap Speech-to-Text
Daftar Isi
Pendahuluan
OpenAI Whisper adalah model pengenalan suara serbaguna yang dilatih dengan 680.000 jam data terawasi multibahasa dan multitugas yang dikumpulkan dari web. Model ini mampu melakukan pengenalan suara multibahasa, penerjemahan suara, dan identifikasi bahasa. Tutorial ini akan memandu Anda dari transkripsi dasar hingga deployment tingkat produksi menggunakan FastAPI.
Whisper sangat cocok untuk berbagai skenario penggunaan di Indonesia, mulai dari transkripsi rapat, pembuatan subtitle video YouTube, hingga sistem customer service otomatis yang memerlukan konversi suara ke teks.
Prasyarat
- Python 3.9 atau lebih tinggi
- GPU yang kompatibel dengan CUDA (disarankan untuk model yang lebih besar)
- Pemahaman dasar tentang Python dan konsep machine learning
Pasang dependensi utama:
pip install openai-whisper
pip install faster-whisper
pip install fastapi uvicorn python-multipart
pip install torch torchaudio
pip install datasets transformers
Memahami Ukuran Model Whisper
Whisper hadir dalam beberapa ukuran model, masing-masing menawarkan pertukaran antara kecepatan dan akurasi:
| Model | Parameter | Khusus Inggris | Multibahasa | VRAM Diperlukan | Kecepatan Relatif |
|--------|-----------|----------------|-------------|-----------------|-------------------|
| tiny | 39 M | tiny.en | tiny | ~1 GB | ~32x |
| base | 74 M | base.en | base | ~1 GB | ~16x |
| small | 244 M | small.en | small | ~2 GB | ~6x |
| medium | 769 M | medium.en | medium | ~5 GB | ~2x |
| large | 1550 M | N/A | large-v3 | ~10 GB | 1x |
Pemilihan model yang tepat bergantung pada kebutuhan Anda:
import whisper
Untuk prototyping cepat atau lingkungan dengan sumber daya terbatas
model = whisper.loadmodel("tiny")
Untuk produksi dengan akurasi yang baik
model = whisper.loadmodel("medium")
Untuk akurasi terbaik (memerlukan GPU dengan VRAM 10GB+)
model = whisper.loadmodel("large-v3")
Untuk transkripsi Bahasa Indonesia, gunakan model multibahasa (tanpa akhiran .en) karena model khusus Inggris tidak mendukung bahasa lain.
Transkripsi Audio Dasar
Penggunaan paling sederhana adalah mentranskripsi file audio menjadi teks:
import whisper
model = whisper.loadmodel("medium")
Transkripsi dasar
result = model.transcribe("rekamanaudio.mp3")
print(result["text"])
Akses informasi segmen secara detail
for segment in result["segments"]:
print(f"[{segment['start']:.2f}d -> {segment['end']:.2f}d] {segment['text']}")
Anda dapat menyesuaikan perilaku transkripsi dengan berbagai parameter:
result = model.transcribe(
"rekamanaudio.mp3",
language="id", # Paksa bahasa Indonesia (lewati deteksi)
temperature=0.0, # Lebih rendah = lebih deterministik
wordtimestamps=True, # Dapatkan timestamp tingkat kata
fp16=True, # Gunakan presisi setengah (khusus GPU)
beamsize=5, # Pencarian beam untuk akurasi lebih baik
bestof=5, # Jumlah kandidat saat sampling
nospeechthreshold=0.6, # Ambang batas untuk menyaring segmen sunyi
conditiononprevioustext=True, # Gunakan konteks dari segmen sebelumnya
initialprompt="Rapat teknis tentang model machine learning."
)
Dengan timestamp tingkat kata
for segment in result["segments"]:
for wordinfo in segment.get("words", []):
print(f" {wordinfo['word']} [{wordinfo['start']:.2f}d - {wordinfo['end']:.2f}d]")
Dukungan Multi-Bahasa
Whisper mendukung 99+ bahasa, termasuk Bahasa Indonesia. Anda dapat mendeteksi bahasa secara otomatis atau menentukannya:
import whisper
model = whisper.loadmodel("large-v3")
Deteksi bahasa otomatis
audio = whisper.loadaudio("audioasing.mp3")
audio = whisper.padortrim(audio)
mel = whisper.logmelspectrogram(audio).to(model.device)
, probs = model.detectlanguage(mel)
detectedlang = max(probs, key=probs.get)
print(f"Bahasa terdeteksi: {detectedlang} (kepercayaan: {probs[detectedlang]:.2%})")
Transkripsi dalam bahasa asli
result = model.transcribe("audioasing.mp3", language=detectedlang)
print(f"Transkripsi: {result['text']}")
Terjemahkan ke Bahasa Inggris (penerjemahan suara)
resulttranslated = model.transcribe(
"audioasing.mp3",
task="translate" # Menerjemahkan bahasa apa pun ke Inggris
)
print(f"Terjemahan: {resulttranslated['text']}")
Pemrosesan batch untuk banyak file dengan bahasa berbeda:
import os
from pathlib import Path
def batchtranscribe(audiodir: str, modelname: str = "medium") -> dict:
model = whisper.loadmodel(modelname)
results = {}
for audiofile in Path(audiodir).glob(".mp3"):
result = model.transcribe(str(audiofile))
results[audiofile.name] = {
"bahasa": result["language"],
"teks": result["text"],
"segmen": result["segments"]
}
print(f"Diproses: {audiofile.name} (bahasa: {result['language']})")
return results
semuahasil = batchtranscribe("/data/fileaudio/")
Fine-Tuning Whisper dengan Dataset Kustom
Fine-tuning Whisper pada data spesifik domain dapat meningkatkan akurasi secara drastis untuk kosakata khusus. Ini sangat berguna jika Anda bekerja dengan terminologi teknis, dialek daerah, atau jargon industri tertentu:
from datasets import loaddataset, Audio
from transformers import (
WhisperFeatureExtractor,
WhisperTokenizer,
WhisperProcessor,
WhisperForConditionalGeneration,
Seq2SeqTrainingArguments,
Seq2SeqTrainer,
)
import torch
from dataclasses import dataclass
from typing import Any, Dict, List, Union
Muat komponen prosesor
featureextractor = WhisperFeatureExtractor.frompretrained("openai/whisper-small")
tokenizer = WhisperTokenizer.frompretrained(
"openai/whisper-small", language="Indonesian", task="transcribe"
)
processor = WhisperProcessor.frompretrained(
"openai/whisper-small", language="Indonesian", task="transcribe"
)
Muat dataset kustom Anda (contoh dengan Common Voice Indonesia)
dataset = loaddataset("mozilla-foundation/commonvoice110", "id", split="train[:1000]")
dataset = dataset.castcolumn("audio", Audio(samplingrate=16000))
def persiapkandataset(batch):
audio = batch["audio"]
batch["inputfeatures"] = featureextractor(
audio["array"], samplingrate=audio["samplingrate"]
).inputfeatures[0]
batch["labels"] = tokenizer(batch["sentence"]).inputids
return batch
dataset = dataset.map(persiapkandataset, removecolumns=dataset.columnnames, numproc=4)
Data collator untuk padding dinamis
@dataclass
class DataCollatorSpeechSeq2SeqWithPadding:
processor: Any
decoderstarttokenid: int
def call(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]) -> Dict[str, torch.Tensor]:
inputfeatures = [{"inputfeatures": f["inputfeatures"]} for f in features]
batch = self.processor.featureextractor.pad(inputfeatures, returntensors="pt")
labelfeatures = [{"inputids": f["labels"]} for f in features]
labelsbatch = self.processor.tokenizer.pad(labelfeatures, returntensors="pt")
labels = labelsbatch["inputids"].maskedfill(labelsbatch.attentionmask.ne(1), -100)
if (labels[:, 0] == self.decoderstarttokenid).all().cpu().item():
labels = labels[:, 1:]
batch["labels"] = labels
return batch
Muat model dan konfigurasi pelatihan
model = WhisperForConditionalGeneration.frompretrained("openai/whisper-small")
model.generationconfig.language = "Indonesian"
model.generationconfig.task = "transcribe"
model.generationconfig.forceddecoderids = None
datacollator = DataCollatorSpeechSeq2SeqWithPadding(
processor=processor,
decoderstarttokenid=model.config.decoderstarttokenid,
)
trainingargs = Seq2SeqTrainingArguments(
outputdir="./whisper-finetuned-id",
perdevicetrainbatchsize=16,
gradientaccumulationsteps=2,
learningrate=1e-5,
warmupsteps=500,
maxsteps=4000,
fp16=True,
evaluationstrategy="steps",
evalsteps=500,
savesteps=1000,
loggingsteps=25,
generationmaxlength=225,
predictwithgenerate=True,
reportto=["tensorboard"],
)
trainer = Seq2SeqTrainer(
args=trainingargs,
model=model,
traindataset=dataset,
datacollator=datacollator,
tokenizer=processor.featureextractor,
)
trainer.train()
model.savepretrained("./whisper-finetuned-id-final")
processor.savepretrained("./whisper-finetuned-id-final")
Transkripsi Real-Time
Implementasikan transkripsi real-time menggunakan input mikrofon dengan pendekatan sliding window:
import numpy as np
import sounddevice as sd
import whisper
import queue
import threading
class TranskripsiRealTime:
def init(self, namamodel: str = "base", samplerate: int = 16000, durasiblok: float = 3.0):
self.model = whisper.loadmodel(namamodel)
self.samplerate = samplerate
self.durasiblok = durasiblok
self.antrianaudio = queue.Queue()
self.sedangberjalan = False
self.buffer = np.array([], dtype=np.float32)
def callbackaudio(self, indata, frames, timeinfo, status):
if status:
print(f"Status audio: {status}")
self.antrianaudio.put(indata.copy().flatten())
def prosesaudio(self):
while self.sedangberjalan:
try:
potonganaudio = self.antrianaudio.get(timeout=1.0)
self.buffer = np.concatenate([self.buffer, potonganaudio])
# Proses ketika buffer mencapai durasi yang diinginkan
minsamples = int(self.samplerate self.durasiblok)
if len(self.buffer) >= minsamples:
dataaudio = self.buffer.copy()
self.buffer = np.array([], dtype=np.float32)
# Normalisasi audio
dataaudio = dataaudio.astype(np.float32)
if np.max(np.abs(dataaudio)) > 0:
dataaudio = dataaudio / np.max(np.abs(dataaudio))
result = self.model.transcribe(
dataaudio,
fp16=False,
language="id",
nospeechthreshold=0.5
)
teks = result["text"].strip()
if teks:
print(f">> {teks}")
except queue.Empty:
continue
def mulai(self):
self.sedangberjalan = True
threadproses = threading.Thread(target=self.prosesaudio, daemon=True)
threadproses.start()
print("Mendengarkan... Tekan Ctrl+C untuk berhenti.")
with sd.InputStream(
samplerate=self.samplerate,
channels=1,
dtype="float32",
callback=self.callbackaudio,
blocksize=int(self.samplerate 0.5)
):
try:
while True:
sd.sleep(100)
except KeyboardInterrupt:
self.sedangberjalan = False
print("\nBerhenti.")
Penggunaan
transkriber = TranskripsiRealTime(namamodel="base", durasiblok=3.0)
transkriber.mulai()
Optimasi dengan Faster-Whisper
faster-whisper menggunakan CTranslate2 untuk inferensi hingga 4x lebih cepat dengan penggunaan memori lebih rendah:
from fasterwhisper import WhisperModel
Muat model dengan optimasi CTranslate2
model = WhisperModel(
"large-v3",
device="cuda",
compute
type="float16", # Opsi: float16, int8float16, int8
cpu
threads=4,
numworkers=2
)
Transkripsi dengan faster-whisper
segments, info = model.transcribe(
"rekamanaudio.mp3",
beamsize=5,
language="id",
vadfilter=True, # Filter Deteksi Aktivitas Suara
vadparameters=dict(
minsilencedurationms=500,
speechpadms=400
),
wordtimestamps=True
)
print(f"Bahasa terdeteksi: {info.language} (probabilitas: {info.languageprobability:.2f})")
print(f"Durasi: {info.duration:.2f}d")
for segment in segments:
print(f"[{segment.start:.2f}d -> {segment.end:.2f}d] {segment.text}")
if segment.words:
for word in segment.words:
print(f" {word.word} ({word.start:.2f}d - {word.end:.2f}d) p={word.probability:.2f}")
Perbandingan benchmark:
import time
def benchmarkmodel(jaluraudio: str):
# Whisper Standar
import whisper
modelstd = whisper.loadmodel("large-v3")
mulai = time.time()
resultstd = modelstd.transcribe(jaluraudio)
waktustd = time.time() - mulai
# Faster-Whisper
from fasterwhisper import WhisperModel
modelcepat = WhisperModel("large-v3", device="cuda", computetype="float16")
mulai = time.time()
segments, = modelcepat.transcribe(jaluraudio)
= list(segments) # Konsumsi generator
waktucepat = time.time() - mulai
print(f"Whisper Standar: {waktustd:.2f}d")
print(f"Faster-Whisper: {waktucepat:.2f}d")
print(f"Percepatan: {waktustd / waktucepat:.1f}x")
benchmarkmodel("audiouji.mp3")
Integrasi dengan FastAPI
Bangun API transkripsi siap produksi:
from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
from fasterwhisper import WhisperModel
import tempfile
import os
import uuid
from typing import Optional
from pydantic import BaseModel
app = FastAPI(title="API Transkripsi Whisper", version="1.0.0")
Instance model global
model = WhisperModel("medium", device="cuda", computetype="float16")
Penyimpanan job di memori (gunakan Redis untuk produksi)
jobs: dict = {}
class ResponTranskripsi(BaseModel):
teks: str
bahasa: str
durasi: float
segmen: list
@app.post("/transkripsi", responsemodel=ResponTranskripsi)
async def transkripsiaudio(
file: UploadFile = File(...),
bahasa: Optional[str] = None,
tugas: str = "transcribe"
):
formatdidukung = (".mp3", ".wav", ".m4a", ".flac", ".ogg", ".webm")
if not file.filename.endswith(formatdidukung):
raise HTTPException(statuscode=400, detail="Format audio tidak didukung")
with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp:
konten = await file.read()
tmp.write(konten)
jalurtmp = tmp.name
try:
segmentsgen, info = model.transcribe(
jalurtmp,
language=bahasa,
task=tugas,
beamsize=5,
vadfilter=True
)
daftarsegmen = []
bagianteks = []
for seg in segmentsgen:
daftarsegmen.append({
"mulai": round(seg.start, 2),
"selesai": round(seg.end, 2),
"teks": seg.text.strip()
})
bagianteks.append(seg.text.strip())
return ResponTranskripsi(
teks=" ".join(bagianteks),
bahasa=info.language,
durasi=round(info.duration, 2),
segmen=daftarsegmen
)
finally:
os.unlink(jalurtmp)
def prosesjobtranskripsi(jobid: str, jalurfile: str, bahasa: Optional[str]):
try:
segmentsgen, info = model.transcribe(jalurfile, language=bahasa, vadfilter=True)
daftarsegmen = []
tekslengkap = []
for seg in segmentsgen:
daftarsegmen.append({
"mulai": seg.start, "selesai": seg.end, "teks": seg.text.strip()
})
tekslengkap.append(seg.text.strip())
jobs[jobid] = {
"status": "selesai",
"hasil": {
"teks": " ".join(tekslengkap),
"bahasa": info.language,
"durasi": info.duration,
"segmen": daftarsegmen
}
}
except Exception as e:
jobs[jobid] = {"status": "gagal", "error": str(e)}
finally:
os.unlink(jalurfile)
@app.post("/transkripsi/async")
async def transkripsiasync(
backgroundtasks: BackgroundTasks,
file: UploadFile = File(...),
bahasa: Optional[str] = None
):
jobid = str(uuid.uuid4())
with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp:
konten = await file.read()
tmp.write(konten)
jalurtmp = tmp.name
jobs[jobid] = {"status": "memproses"}
backgroundtasks.addtask(prosesjobtranskripsi, jobid, jalurtmp, bahasa)
return {"jobid": jobid, "status": "memproses"}
@app.get("/transkripsi/status/{jobid}")
async def ambilstatusjob(jobid: str):
if jobid not in jobs:
raise HTTPException(statuscode=404, detail="Job tidak ditemukan")
return jobs[jobid]
Jalankan dengan: uvicorn whisperapi:app --host 0.0.0.0 --port 8000
Pembuatan Subtitle (SRT/VTT)
Buat subtitle profesional dari audio:
from fasterwhisper import WhisperModel
def format
timestampsrt(detik: float) -> str:
jam = int(detik // 3600)
menit = int((detik % 3600) // 60)
dtk = int(detik % 60)
milis = int((detik % 1)
1000)
return f"{jam:02d}:{menit:02d}:{dtk:02d},{milis:03d}"
def formattimestampvtt(detik: float) -> str:
jam = int(detik // 3600)
menit = int((detik % 3600) // 60)
dtk = int(detik % 60)
milis = int((detik % 1) * 1000)
return f"{jam:02d}:{menit:02d}:{dtk:02d}.{milis:03d}"
def buatsrt(jaluraudio: str, jaluroutput: str, makskarakter: int = 42):
model = WhisperModel("medium", device="cuda", computetype="float16")
segments, info = model.transcribe(jaluraudio, wordtimestamps=True, vadfilter=True)
kontensrt = []
indeks = 1
for segment in segments:
teks = segment.text.strip()
# Pecah baris yang terlalu panjang
if len(teks) > makskarakter:
tengah = len(teks) // 2
posspasi = teks.rfind(" ", 0, tengah)
if posspasi != -1:
teks = teks[:posspasi] + "\n" + teks[posspasi + 1:]
mulai = formattimestampsrt(segment.start)
selesai = formattimestampsrt(segment.end)
kontensrt.append(f"{indeks}\n{mulai} --> {selesai}\n{teks}\n")
indeks += 1
with open(jaluroutput, "w", encoding="utf-8") as f:
f.write("\n".join(kontensrt))
print(f"SRT tersimpan: {jaluroutput} ({indeks - 1} subtitle)")
def buatvtt(jaluraudio: str, jaluroutput: str):
model = WhisperModel("medium", device="cuda", computetype="float16")
segments, info = model.transcribe(jaluraudio, wordtimestamps=True, vadfilter=True)
barisvtt = ["WEBVTT", ""]
for segment in segments:
mulai = formattimestampvtt(segment.start)
selesai = formattimestampvtt(segment.end)
barisvtt.append(f"{mulai} --> {selesai}")
barisvtt.append(segment.text.strip())
barisvtt.append("")
with open(jaluroutput, "w", encoding="utf-8") as f:
f.write("\n".join(barisvtt))
print(f"VTT tersimpan: {jaluroutput}")
Buat kedua format
buatsrt("kuliah.mp3", "kuliah.srt")
buatvtt("kuliah.mp3", "kuliah.vtt")
Praktik Terbaik
base atau small untuk pengembangan, gunakan medium atau large-v3 untuk produksi. Untuk Bahasa Indonesia, selalu gunakan model multibahasa.ffmpeg untuk konversi:
ffmpeg -i input.mp4 -ar 16000 -ac 1 -c:a pcms16le output.wav
faster-whisper dengan tipe komputasi int8 pada server tanpa GPU.vadfilter=True di faster-whisper untuk melewati bagian sunyi dan meningkatkan kecepatan serta akurasi.initialprompt untuk meningkatkan pengenalan istilah teknis. Contoh: "Rapat tentang implementasi microservice dan Kubernetes."Kesimpulan
OpenAI Whisper adalah sistem pengenalan suara yang kuat dan serbaguna. Dengan menggabungkannya dengan faster-whisper untuk optimasi, FastAPI untuk penyajian, dan fine-tuning yang tepat untuk adaptasi domain, Anda dapat membangun sistem transkripsi tingkat produksi yang menangani berbagai bahasa dan kondisi audio. Kunci utamanya adalah memilih ukuran model yang tepat untuk kebutuhan latensi dan akurasi Anda, serta memanfaatkan filter VAD dan preprocessing audio yang tepat untuk memaksimalkan kualitas.
Untuk penggunaan di Indonesia, model large-v3 memberikan hasil terbaik untuk Bahasa Indonesia, sementara medium menawarkan keseimbangan yang baik antara akurasi dan kecepatan. Jangan lupa untuk melakukan fine-tuning jika Anda bekerja dengan dialek daerah atau terminologi industri yang spesifik.