رفتن به محتوای اصلی
هوش مصنوعی صوتی

تبدیل متن به گفتار طبیعی بدون دست زدن به مدل

چطور بدون تغییر مدل، تبدیل متن به گفتار طبیعی بسازیم؟ نرمال‌سازی متن فارسی، نیم‌فاصله، اعداد، تاریخ شمسی، تلفظ و SSML برای TTS انسانی‌تر.

Diagram contrasting acoustic voice quality with linguistic naturalness in text-to-speech

اگر دستیار صوتی یا ربات تلفنی شما «رباتیک» به نظر می‌رسد، احتمالاً مشکل از صدای مدل نیست. مدل‌های عصبی امروزی صدایی تمیز و باورپذیر تولید می‌کنند. چیزی که شنونده را متوجه ماشینی بودن صدا می‌کند معمولاً متنی است که به موتور داده‌ایم: عددی که رقم‌به‌رقم خوانده می‌شود، تاریخ شمسی که شبیه کد پستی تلفظ می‌شود، جمله‌ی پرسشی که با لحن خبری ادا می‌شود، یا نام یک محصول که هر بار به شکلی عجیب خوانده می‌شود.

در این راهنما سراغ لایه‌ای می‌رویم که بیشتر تیم‌ها نادیده‌اش می‌گیرند: لایه‌ی بین برنامه و موتور TTS. تبدیل متن به گفتار طبیعی در بیشتر پروژه‌ها از همین لایه شروع می‌شود، نه از بازآموزی مدل. تمرکز ما روی فارسی است، با همه‌ی دردسرهای آشنایش: نیم‌فاصله، اعداد فارسی و انگلیسی، تاریخ شمسی، واژه‌های انگلیسی وسط جمله و فاصله‌ی زیاد زبان نوشتار با زبان گفتار.

چرا صدای خوب هم می‌تواند رباتیک به نظر برسد؟

مدل TTS از روی هزاران ساعت گفتار ضبط‌شده یاد گرفته که متن را به صدا تبدیل کند. طبیعی است که در چیزهایی که زیاد دیده قوی باشد و در موارد کم‌تکرار یا مبهم ضعیف. در فارسی، این موارد مبهم زیادند:

  • مصوت‌های کوتاه نوشته نمی‌شوند. «کرد» می‌تواند کَرد باشد یا کُرد؛ «شیر» می‌تواند خوراکی باشد، حیوان یا شیرِ آب.
  • کسره‌ی اضافه دیده نمی‌شود. «مدیر فروش شرکت» باید مدیرِ فروشِ شرکت خوانده شود، اما هیچ نشانه‌ای در متن نیست.
  • اعداد، نمادها و تاریخ‌ها چند خوانش ممکن دارند: «۱۴۰۵» سال است یا مبلغ؟
  • واژه‌ها و مخفف‌های انگلیسی وسط جمله‌ی فارسی، که موتور فارسی با قواعد فارسی می‌خواندشان.
  • قالب‌بندی متن: ستاره‌های مارک‌داون، فهرست‌های گلوله‌ای، لینک و ایموجی که در خروجی مدل‌های زبانی فراوان‌اند.

هر کدام از این‌ها کافی است تا شنونده حس کند با ماشین حرف می‌زند، حتی اگر کیفیت خود صدا بی‌نقص باشد.

کیفیت صدا با طبیعی بودن گفتار فرق دارد

بهتر است دو مفهوم را از هم جدا کنیم:

کیفیت صدا (جنبه‌ی آکوستیک): رنگ صدا، وضوح، نبودِ نویز و خش، یکنواختی بین جمله‌ها. این بخش عمدتاً به مدل و صدای انتخاب‌شده برمی‌گردد.

طبیعی بودن گفتار (جنبه‌ی زبانی): آیا واژه‌ها درست تلفظ می‌شوند؟ مکث‌ها سر جای خودشان‌اند؟ تأکید روی کلمه‌ی درست است؟ سؤال شبیه سؤال شنیده می‌شود؟ این بخش به‌شدت به ورودی وابسته است.

مقاله‌ی Voice AI Mastery درباره‌ی انسانی‌تر کردن صدای TTS هم روی همین تمایز تأکید می‌کند: فاصله‌ی بین کیفیت آکوستیک یک صدا و کیفیت زبانی چیزی که آن صدا می‌گوید (How to Make Your TTS Sound Human — Without Touching the Model File). وقتی کیفیت آکوستیک به حد قابل‌قبول رسید، بیشتر ایرادهای باقی‌مانده زبانی‌اند و آن‌ها را می‌شود در کد خودمان حل کرد.

پیش‌پردازش متن؛ مهم‌ترین لایه‌ای که کمتر دیده می‌شود

بیشتر موتورهای TTS یک «فرانت‌اند متنی» دارند که مقداری نرمال‌سازی انجام می‌دهد. تکیه‌ی کامل به آن سه مشکل دارد:

  1. دامنه‌ی کار شما را نمی‌شناسد. نمی‌داند «گالاتیا» نام محصول شماست یا اینکه «MinIO» را چطور می‌گویید.
  2. رفتارش مستند و ثابت نیست. با عوض کردن سرویس‌دهنده یا حتی به‌روزرسانی نسخه، خوانش‌ها بی‌صدا تغییر می‌کنند.
  3. برای فارسی معمولاً ضعیف‌تر است. پشتیبانی از تاریخ شمسی، تومان و نیم‌فاصله در همه‌ی موتورها یکسان نیست.

راه‌حل، یک لایه‌ی پیش‌پردازش است که مالکش خودتان هستید. کار این لایه ساده است: متنِ نوشتاری را به متنِ گفتاری تبدیل کند؛ متنی که فقط یک خوانش معقول داشته باشد.

مشکل علت راه‌حل مثال
عدد رقم‌به‌رقم خوانده می‌شود موتور عدد را شناسه فرض کرده تبدیل عدد به حروف بر اساس نقش «۱۲۰٬۰۰۰ تومان» ← «صد و بیست هزار تومان»
تاریخ شمسی عجیب خوانده می‌شود الگوی تاریخ برای موتور ناشناخته است تبدیل تاریخ به شکل گفتاری «۱۴۰۵/۰۷/۰۹» ← «نهم مهر هزار و چهارصد و پنج»
«می روم» بد خوانده می‌شود نیم‌فاصله رعایت نشده اصلاح نیم‌فاصله «می روم» ← «می‌روم»
«ي» و «ك» عربی متن از منبع عربی کپی شده یکسان‌سازی حروف «كتاب» ← «کتاب»
نام محصول اشتباه تلفظ می‌شود واژه خارج از واژگان مدل است واژه‌نامه‌ی تلفظ «Galatea» ← «گالاتیا»
سؤال با لحن خبری علامت سؤال حذف شده بازگرداندن «؟» «ادامه بدم» ← «ادامه بدم؟»

علائم نگارشی و کنترل مکث

در فارسیِ نوشتاری، علائم نگارشی اغلب با سهل‌انگاری به کار می‌روند. برای TTS اما همین علائم اصلی‌ترین ابزار کنترل آهنگ و مکث‌اند. نقطه یعنی پایان یک واحد آهنگی و افت لحن، ویرگول یعنی مکث کوتاه، و علامت سؤال یعنی الگوی لحن پرسشی.

چند نکته‌ی مخصوص فارسی:

  • ویرگول فارسی (،) و نقطه‌ویرگول (؛) را یکدست کنید. بعضی فرانت‌اندها «،» را می‌شناسند و «,» را نه، یا برعکس. رفتار موتور خودتان را با آزمایش پیدا کنید و همه‌ی ورودی‌ها را به همان شکل تبدیل کنید.
  • علامت سؤال فارسی (؟) و لاتین (?) همین وضعیت را دارند. یکی را انتخاب و همه‌جا یکسان کنید.
  • جمله‌های طولانی با «که» و «و»های پشت سر هم در فارسی بسیار رایج‌اند. این جمله‌ها در TTS یکنواخت و نفس‌گیر خوانده می‌شوند. شکستنشان به دو یا سه جمله‌ی کوتاه‌تر معمولاً بیشترین اثر را روی طبیعی بودن گفتار دارد.
  • گیومه (« ») معمولاً مکث خاصی ایجاد نمی‌کند، اما مانع خوانده شدن نمادهای عجیب می‌شود. نمادهایی مثل «/» یا «|» را قبل از ارسال حذف یا به واژه تبدیل کنید.

اگر موتور شما SSML را پشتیبانی می‌کند، برای جاهایی که شنونده باید چیزی را بنویسد یا به خاطر بسپارد، مکث صریح بگذارید:

<speak xml:lang="fa-IR">
  کد تأیید شما: پنج، هشت، دو
  <break time="300ms"/>
  نه، یک، هفت.
</speak>

قاعده‌ی عملی: ریتم عادی گفتار را با علائم نگارشی بسازید و <break> را برای کدها، شماره‌تلفن‌ها و مرز مراحل یک دستورالعمل نگه دارید.

آهنگ گفتار، تأکید و ریتم

نوای گفتار (prosody) مجموعه‌ی زیروبمی، بلندی، کشش و مکث است. شنونده با آن تشخیص می‌دهد کدام کلمه مهم است و جمله خبری است یا پرسشی.

تأکید را با ساختار جمله بسازید

در فارسی، که فعل معمولاً آخر جمله می‌آید، تکیه‌ی اصلی جمله اغلب روی سازه‌ی پیش از فعل قرار می‌گیرد. می‌شود از همین الگو استفاده کرد، به‌جای اینکه با برچسب‌های تأکید با آن جنگید:

  • ضعیف: «جلسه‌ای که پرسیده بودید و جابه‌جا شده بود، حالا پنجشنبه است، نه جمعه.»
  • بهتر: «جلسه‌تان جابه‌جا شده. حالا پنجشنبه برگزار می‌شود.»

بازنویسی جمله روی هر موتوری جواب می‌دهد. برچسب <emphasis> در SSML هم ممکن است کمک کند، اما پشتیبانی و شدتش در موتورهای مختلف متفاوت است و بعضی صداهای عصبی آن را نادیده می‌گیرند.

جمله‌های پرسشی

در فارسی گفتاری، سؤال بله/خیر معمولاً بدون «آیا» و فقط با آهنگ پرسشی ساخته می‌شود: «فردا میای؟». یعنی تنها سرنخ موتور، علامت سؤال است. اگر در مسیر تولید متن، مثلاً در خروجی مدل زبانی یا یک قالب پیام، این علامت حذف شود، جمله کاملاً خبری خوانده می‌شود. برای سؤال‌های کلیدی یک بررسی خودکار بگذارید: اگر جمله با الگوهای پرسشی شروع یا تمام می‌شود («آیا»، «چرا»، «چطور»، «کی»، «…ید؟») و علامت سؤال ندارد، هشدار بدهید یا علامت را اضافه کنید.

سؤال‌هایی را هم که وسط یک جمله‌ی خبری گم شده‌اند جدا کنید: «می‌تونم براتون رزرو کنم پس ساعت ده خوبه» ← «می‌تونم براتون رزرو کنم. ساعت ده خوبه؟»

سرعت و زیروبمی

سرعت را بر اساس محتوا تنظیم کنید، نه برای کل سیستم. اطلاعات فشرده، مثل مبلغ و شماره‌تلفن و آدرس، با کمی کاهش سرعت (در حد پنج تا ده درصد) بهتر فهمیده می‌شوند. تغییرهای بزرگ‌تر در بسیاری از صداها باعث افت کیفیت می‌شود. تغییر زیروبمی (pitch) برای طبیعی‌تر شدن گفتار به‌ندرت لازم است و به‌راحتی اغراق‌آمیز می‌شود.

مهندسی تلفظ

غلط تلفظی بیشترین آسیب را به حس طبیعی بودن می‌زند، چون شنونده فوراً می‌فهمد کلمه اشتباه است. ابزار اصلی، واژه‌نامه‌ی تلفظ است: فهرستی نگه‌داری‌شده از شکل نوشتاری به شکل گفتاری.

سه سطح کنترل وجود دارد:

  1. بازنویسی املایی: واژه را به شکلی بنویسید که موتور درست بخواند. مثلاً برای رفع ابهام، «کُرد» را با اِعراب بنویسید. این روش همه‌جا کار می‌کند، ولی نتیجه‌اش به موتور بستگی دارد.
  2. جایگزینی با <sub>: متن اصلی در لاگ‌ها حفظ می‌شود و فقط شکل گفتاری خوانده می‌شود.
  3. واج‌نویسی با <phoneme>: دقیق‌ترین روش است، اما همه‌ی موتورها و صداها پشتیبانی نمی‌کنند.

برای واژه‌های چندخوانشی مثل «کرد»، «شیر» یا «مرد»، اگر در دامنه‌ی کارتان مهم‌اند، قاعده‌ی مبتنی بر بافت بنویسید. «کرد» بعد از مفعول و در جایگاه فعل معمولاً کَرد است. ساده‌ترین راه اما بازنویسی قالب پیام است، طوری که ابهامی باقی نماند.

اسامی خاص (نام مشتری، شهر، برند) را در واژه‌نامه نگه دارید و هر بار که غلطی گزارش شد، یک مدخل اضافه کنید. همان جمله را هم به مجموعه‌ی تست بازگشتی ببرید تا دوباره خراب نشود.

اعداد، تاریخ، واحد پول و مخفف‌ها در فارسی

عددِ نوشته‌شده فشرده است، اما عددِ گفته‌شده به نقش عدد بستگی دارد:

نوشتاری نقش شکل گفتاری
۱۴۰۵ سال هزار و چهارصد و پنج
۱۴۰۵/۰۷/۰۹ تاریخ شمسی نهم مهر هزار و چهارصد و پنج
۱۲۰٬۰۰۰ تومان مبلغ صد و بیست هزار تومان
۲۵٪ درصد بیست و پنج درصد
۰۹۱۲۱۲۳۴۵۶۷ شماره موبایل صفر نهصد و دوازده، صد و بیست و سه، چهل و پنج، شصت و هفت
ساعت ۱۰:۳۰ زمان ساعت ده و سی دقیقه
۳ام ترتیبی سوم
ق.ظ / ب.ظ مخفف «صبح» / «بعدازظهر» (یا «قبل از ظهر» در متن رسمی)

چند قاعده که بیشتر خطاها را حذف می‌کند:

  • اول ارقام فارسی، عربی و لاتین را یکی کنید. متن فارسی ممکن است هم «۱۲۳» داشته باشد، هم «١٢٣» عربی و هم «123». برای تحلیل، همه را به یک شکل تبدیل کنید و بعد به حروف ببرید.
  • جداکننده‌ی هزارگان در فارسی ممکن است «٬»، «،» یا «,» باشد. «،» با ویرگول جمله اشتباه گرفته می‌شود، پس الگو را محدود به ارقام کنید.
  • تومان و ریال را جدی بگیرید. اگر داده به ریال است و کاربر به تومان فکر می‌کند، تبدیل را در لایه‌ی داده انجام دهید، نه در TTS، و واحد را صریح بخوانید.
  • شماره‌تلفن را گروه‌بندی کنید. یک عدد یازده‌رقمی یک‌جا خوانده نشود. گروه‌بندی رایج فارسی را رعایت کنید و بین گروه‌ها مکث کوتاه بگذارید.
  • فقط یک بار نرمال‌سازی کنید. متنی که نیمی از عددهایش به حروف تبدیل شده و نیمی نه، فرانت‌اند موتور را هم گیج می‌کند.

طبیعی‌تر کردن TTS فارسی: از متن نوشتاری تا متن گفتاری

یکسان‌سازی حروف و نیم‌فاصله

بخش زیادی از بهبود TTS فارسی با تمیزکاری ساده‌ی متن به دست می‌آید:

  • «ي» و «ى» عربی ← «ی» فارسی؛ «ك» ← «ک»
  • اصلاح نیم‌فاصله در «می»، «نمی» و پسوندهای «ها» و «های»
  • حذف کشیده (ـ) و اِعراب ناخواسته
  • یکسان‌سازی ارقام

این کد پایه‌ی یک نرمال‌ساز فارسی است. برای پروژه‌های جدی، کتابخانه‌هایی مثل hazm نرمال‌ساز کامل‌تری دارند که می‌شود روی آن‌ها ساخت.

import re

ZWNJ = "‌"
DIGITS = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
CHARS = str.maketrans({"ي": "ی", "ى": "ی", "ك": "ک", "ـ": None})

def normalize_fa(text: str) -> str:
    text = text.translate(CHARS).translate(DIGITS)
    # «می روم» / «نمی دانم» -> «می‌روم» / «نمی‌دانم» (heuristic)
    text = re.sub(r"(?<!\w)(ن?می)\s+(?=\w)", r"\1" + ZWNJ, text)
    # «کتاب ها» -> «کتاب‌ها»
    text = re.sub(r"(\w)\s+(ها|های|هایی)(?!\w)", r"\1" + ZWNJ + r"\2", text)
    return re.sub(r"[ \t]+", " ", text).strip()

قاعده‌ی «می» ابتکاری است و در متن‌های خاص، مثلاً جایی که «می» به معنای شراب آمده، ممکن است اشتباه کند. در دامنه‌ی خودتان تستش کنید.

عدد، تاریخ شمسی و مبلغ به حروف

ONES = ["", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه"]
TEENS = ["ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده"]
TENS = ["", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود"]
HUNDREDS = ["", "صد", "دویست", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد"]
SCALES = ["", "هزار", "میلیون", "میلیارد"]
MONTHS = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور",
          "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"]

def _three(n: int) -> str:
    parts, rest = [HUNDREDS[n // 100]], n % 100
    if 10 <= rest < 20:
        parts.append(TEENS[rest - 10])
    else:
        parts += [TENS[rest // 10], ONES[rest % 10]]
    return " و ".join(p for p in parts if p)

def num_to_words(n: int) -> str:
    if n == 0:
        return "صفر"
    groups, i = [], 0
    while n:
        n, g = divmod(n, 1000)
        if g:
            # «هزار»، نه «یک هزار»
            words = SCALES[i] if (i == 1 and g == 1) else f"{_three(g)} {SCALES[i]}".strip()
            groups.append(words)
        i += 1
    return " و ".join(reversed(groups))

def ordinal(n: int) -> str:
    w = num_to_words(n)
    if w.endswith("سه"):
        return w[:-2] + "سوم"
    return w + (ZWNJ + "ام" if w.endswith("ی") else "م")

def expand_jalali_dates(text: str) -> str:
    def repl(m):
        y, mo, d = map(int, m.groups())
        if not (1 <= mo <= 12 and 1 <= d <= 31):
            return m.group(0)
        return f"{ordinal(d)} {MONTHS[mo - 1]} {num_to_words(y)}"
    return re.sub(r"(?<!\d)(1[34]\d\d)/(\d{1,2})/(\d{1,2})(?!\d)", repl, text)

def expand_toman(text: str) -> str:
    def repl(m):
        return f"{num_to_words(int(re.sub(r'[,٬]', '', m.group(1))))} {m.group(2)}"
    return re.sub(r"(\d[\d,٬]*)\s*(تومان|ریال)", repl, text)

خروجی نمونه:

  • expand_jalali_dates("1405/07/09") ← «نهم مهر هزار و چهارصد و پنج»
  • expand_toman("120,000 تومان") ← «صد و بیست هزار تومان»
  • ordinal(23) ← «بیست و سوم» و ordinal(30) ← «سی‌ام»

ترتیب اجرا مهم است: اول normalize_fa (که ارقام را یکسان می‌کند)، بعد تاریخ‌ها، بعد مبلغ‌ها و در آخر بقیه‌ی عددها. اگر عدد عمومی زودتر به حروف تبدیل شود، الگوی تاریخ دیگر پیدا نمی‌شود.

رسمی یا محاوره‌ای؟

یکی از بزرگ‌ترین تفاوت‌های فارسی با انگلیسی، فاصله‌ی زبان نوشتار و گفتار است. «درخواست شما با موفقیت ثبت گردید» در TTS درست خوانده می‌شود، اما در یک مکالمه‌ی تلفنی شبیه اطلاعیه‌ی اداری است. «درخواستتون ثبت شد» طبیعی‌تر است.

چند توصیه:

  • لحن را بر اساس کاربرد انتخاب کنید. برای دستیار مکالمه‌ای، گفتاری‌ِ مؤدبانه («می‌خواید»، «براتون») معمولاً طبیعی‌تر است. برای خواندن خبر یا متن رسمی، نوشتاری مناسب‌تر است.
  • مدل TTS را با متن محاوره‌ای امتحان کنید. بعضی مدل‌ها با املای شکسته («میخوام»، «برم») خوب کنار می‌آیند و بعضی نه. اگر مدل شما شکل شکسته را بد می‌خواند، شکل نیمه‌رسمی («می‌خواهم بروم» با واژه‌های ساده‌تر) امن‌تر است.
  • فعل‌های مرکب و جمله‌های مجهول («انجام گردید»، «صورت پذیرفت») را به شکل فعال و کوتاه برگردانید.
  • یکدست باشید. مخلوط کردن «شما» و «تو» یا رسمی و شکسته در یک پاسخ، حتی با تلفظ درست، مصنوعی به نظر می‌رسد.

مکث‌های طبیعی در فارسی

فارسی‌زبان‌ها معمولاً بعد از بندهای شرطی («اگر …،»)، بعد از قیدهای آغازین («راستش»، «در ضمن»)، و قبل از «ولی» و «اما» مکث کوتاه می‌کنند. اگر این جاها ویرگول نداشته باشند، TTS معمولاً بی‌مکث از رویشان رد می‌شود. یک قاعده‌ی ساده که قبل از «ولی»، «اما» و «چون» در جمله‌های بلند ویرگول بگذارد، تفاوت محسوسی ایجاد می‌کند.

متن ترکیبی فارسی و انگلیسی

گفتار فنی فارسی پر از واژه‌ی انگلیسی است: نام محصول، مخفف، دستور و ابزار. صدای فارسی این واژه‌ها را با قواعد فارسی می‌خواند و صدای انگلیسی فارسی را بدتر. گزینه‌ها به ترتیب اولویت:

  1. آوانویسی فارسی در واژه‌نامه. واژه را همان‌طور بنویسید که فارسی‌زبان‌ها می‌گویند: «Kubernetes» ← «کوبرنتیز»، «API» ← «اِی‌پی‌آی»، «GPU» ← «جی‌پی‌یو»، «Galatea» ← «گالاتیا». یک صدا و یک زبان حفظ می‌شود و برای مخاطب فارسی‌زبانی که خودش هم همین‌طور حرف می‌زند، طبیعی‌ترین نتیجه را می‌دهد.
  2. برچسب <lang> در SSML، اگر موتور تعویض زبان وسط جمله را پشتیبانی کند. با دقت آزمایش کنید، چون بعضی موتورها برای بخش انگلیسی صدا را عوض می‌کنند و گسست محسوسی ایجاد می‌شود.
  3. ترجمه، اگر معادل فارسی رایج است و مخاطب همان را به کار می‌برد: «هوش مصنوعی» به‌جای «AI».
FA_LEXICON = {
    "Kubernetes": "کوبرنتیز",
    "MinIO": "مین‌آی‌او",
    "Galatea": "گالاتیا",
    "API": "اِی‌پی‌آی",
    "GPU": "جی‌پی‌یو",
    "AI": "اِی‌آی",
}

همان تابع apply_lexicon نسخه‌ی انگلیسی این مقاله اینجا هم کار می‌کند. کلیدها را به ترتیب طول مرتب کنید تا «API» قبل از «AI» تطبیق بخورد. برای مخفف‌های حرف‌به‌حرف، اگر موتور حروف لاتین را بد می‌خواند، شکل فارسی را با نیم‌فاصله بین حروف بنویسید.

SSML و سایر ابزارهای کنترل

SSML استاندارد W3C برای کنترل گفتار است. عناصر مفید آن <break> برای مکث، <prosody> برای سرعت و زیروبمی، <emphasis> برای تأکید، <say-as> برای تفسیر تاریخ و عدد و حروف، <sub> برای جایگزینی، <phoneme> برای واج‌نویسی و <lang> برای تغییر زبان‌اند.

دو نکته از خود فهرست مهم‌ترند:

  • پشتیبانی، به‌ویژه برای فارسی، یکسان نیست. ممکن است موتوری <say-as interpret-as="date"> را برای انگلیسی پشتیبانی کند اما تاریخ شمسی را نشناسد. به همین دلیل پیشنهاد می‌کنیم عدد و تاریخ فارسی را در لایه‌ی خودتان به حروف تبدیل کنید و از SSML فقط برای مکث و سرعت استفاده کنید.
  • SSML جایگزین متن خوب نیست. متنی که پر از برچسب است نگه‌داری سختی دارد و خودش مصنوعی به نظر می‌رسد. اول متن را درست کنید، بعد فقط جایی برچسب بگذارید که متن به‌تنهایی کافی نیست.

بعضی موتورها امکانات غیر SSML هم دارند، مثل بارگذاری واژه‌نامه‌ی اختصاصی یا پارامتر سبک و احساس. این امکانات وابسته به سرویس‌دهنده‌اند. پشت یک رابط داخلی پنهانشان کنید تا بقیه‌ی سیستم به یک موتور خاص وابسته نشود.

معماری یک خط لوله‌ی پیش‌پردازش TTS

متن ورودی (قالب پیام، CMS، خروجی مدل زبانی)
  → پاک‌سازی           حذف مارک‌داون، HTML، ایموجی، لینک
  → یکسان‌سازی فارسی    حروف عربی، ارقام، نیم‌فاصله
  → تشخیص زبان          جدا کردن بخش‌های انگلیسی
  → نرمال‌سازی          تاریخ شمسی، مبلغ، عدد، ساعت، واحد
  → واژه‌نامه‌ی تلفظ     برند، مخفف، اسامی خاص
  → علائم و جمله‌بندی    «؟» گم‌شده، شکستن جمله‌های بلند
  → SSML (اختیاری)      مکث و سرعت
  → قطعه‌بندی           برای پخش جریانی
  → موتور TTS
  → پس‌پردازش           اتصال قطعه‌ها، سکوت، یکسانی بلندی صدا
تکنیک لایه اثر مورد انتظار هزینه و پیچیدگی
یکسان‌سازی حروف و نیم‌فاصله پیش‌پردازش حذف خطاهای توکن‌سازی و تلفظ کم
عدد و تاریخ شمسی به حروف پیش‌پردازش خوانش درست اطلاعات فشرده متوسط
واژه‌نامه‌ی تلفظ پیش‌پردازش رفع غلط نام‌ها و مخفف‌ها کم در شروع، نیازمند نگه‌داری
شکستن جمله‌های بلند پیش‌پردازش یا تولید محتوا مکث و آهنگ طبیعی‌تر کم
بازنویسی محاوره‌ای تولید محتوا / پرامپت حذف حس «متن اداری» متوسط
SSML نشانه‌گذاری کنترل دقیق نقطه‌ای متوسط، وابسته به موتور
قطعه‌بندی جریانی ارکستراسیون کاهش تأخیر اولین صدا متوسط، روی آهنگ اثر دارد

تأخیر در برابر طبیعی بودن

در دستیارهای صوتی جریانی، سنتز قبل از کامل شدن پاسخ شروع می‌شود. قطعه‌ی کوچک‌تر یعنی صدای زودتر، اما آهنگ ناپیوسته‌تر، چون هر قطعه با بافت کمتری سنتز می‌شود. قطعه‌ی بزرگ‌تر یعنی آهنگ بهتر و انتظار بیشتر. مصالحه‌ی رایج این است که اولین جمله یا بند را جداگانه بفرستید تا کاربر زود صدا بشنود، بعد جمله‌های کامل را. هیچ‌وقت وسط یک بند قطع نکنید. در فارسی، «که» و «و» مرزهای خوبی برای قطع کردن نیستند، اما «.» و «؟» و «؛» هستند.

متن را از همان ابتدا برای گوش بنویسید

اگر متن را مدل زبانی تولید می‌کند، ارزان‌ترین اصلاح در پرامپت است: جمله‌های کوتاه، بدون فهرست و مارک‌داون، بدون لینک، عددها همان‌طور که گفته می‌شوند، و در هر نوبت یک سؤال. لایه‌ی پیش‌پردازش را هم به‌عنوان تور ایمنی نگه دارید، چون مدل‌ها همیشه از دستور قالب‌بندی پیروی نمی‌کنند.

نمونه‌های قبل و بعد

جمله‌ی بلند ← گفتار بخش‌بندی‌شده
– قبل: «سفارش شما که روز دوشنبه ثبت شده بود و شامل سه قلم کالا است ارسال شد و تا پنجشنبه به دستتان می‌رسد و اگر نرسید با پشتیبانی تماس بگیرید»
– بعد: «سفارش دوشنبه‌تون ارسال شد. سه قلم کالاست و تا پنجشنبه می‌رسه. اگه نرسید، با پشتیبانی تماس بگیرید.»
– تفاوت: مرز جمله‌ها واضح است، آهنگ در هر جمله از نو شروع می‌شود و اطلاعات کلیدی (پنجشنبه) در جای طبیعی تأکید قرار می‌گیرد.

تاریخ و مبلغ ← شکل گفتاری
– قبل: «قسط بعدی ۱٬۲۵۰٬۰۰۰ تومان در تاریخ ۱۴۰۵/۰۸/۰۱»
– بعد: «قسط بعدی، یک میلیون و دویست و پنجاه هزار تومان، در یکم آبان هزار و چهارصد و پنج.»
– تفاوت: نه رقم‌خوانی، نه «ممیز» و «اسلش»؛ ویرگول‌ها به شنونده فرصت فهم می‌دهند.

نیم‌فاصله و حروف عربی
– قبل: «مي خواهيد گزارش ها را ببينيد»
– بعد: «می‌خواهید گزارش‌ها را ببینید؟»
– تفاوت: توکن‌سازی درست، تلفظ درست «می‌» و آهنگ پرسشی.

فارسی و انگلیسی ترکیبی
– قبل: «برای deploy روی Kubernetes از API جدید استفاده کنید.»
– بعد: «برای دیپلوی روی کوبرنتیز، از اِی‌پی‌آی جدید استفاده کنید.»
– تفاوت: واژه‌های انگلیسی همان‌طور ادا می‌شوند که یک مهندس فارسی‌زبان می‌گوید و صدا یکدست می‌ماند.

مخفف مبهم
– قبل: «جلسه ساعت ۱۰ ق.ظ»
– بعد: «جلسه ساعت ده صبح.»
– تفاوت: «ق.ظ» حرف‌به‌حرف یا با مکث‌های عجیب خوانده نمی‌شود.

چطور طبیعی بودن TTS را ارزیابی کنیم؟

«به نظر من بهتر شد» مقیاس‌پذیر نیست. ارزیابی ذهنی و خودکار را کنار هم بگذارید:

  • آزمون شنیداری با گویشوران فارسی. میانگین امتیاز نظر (MOS، مطابق ITU-T P.800) برای امتیاز مطلق، و آزمون ترجیحی A/B برای مقایسه‌ی دو نسخه از خط لوله. آزمون A/B معمولاً به تغییرهای کوچک حساس‌تر است.
  • مجموعه‌ی جمله‌های دشوار. خطاهای واقعی را جمع کنید: تاریخ شمسی، مبلغ، شماره‌تلفن، نام‌ها، جمله‌های ترکیبی و سؤال‌ها. با هر تغییر، دوباره سنتز و مقایسه‌شان کنید.
  • رفت‌وبرگشت با ASR. خروجی TTS را با یک سامانه‌ی تشخیص گفتار فارسی به متن برگردانید و با متن هدف مقایسه کنید. افزایش نرخ خطای واژه، پسرفت تلفظ را خودکار نشان می‌دهد. این معیار فهم‌پذیری را می‌سنجد، نه طبیعی بودن را، پس فقط تست اولیه است.
  • تست واحد برای لایه‌ی متن. پیش‌پردازش قطعی است: ورودی مشخص، خروجی گفتاری مشخص. مثل هر کد دیگری تستش کنید.
  • سیگنال‌های محیط عملیاتی. تکرار درخواست («ببخشید؟»)، قطع صحبت دستیار توسط کاربر، و ترک مکالمه در یک پیام خاص اغلب نشانه‌ی جمله‌ای است که بد شنیده می‌شود.

اشتباهات رایج

  1. فرستادن مستقیم خروجی مدل زبانی به TTS؛ ستاره و علامت فهرست و لینک خوانده می‌شوند.
  2. نادیده گرفتن نیم‌فاصله و حروف عربی، با این فرض که «موتور خودش درست می‌کند».
  3. ارزیابی فقط با جمله‌های نمایشی؛ خطاهای واقعی در تاریخ، مبلغ و اسم‌ها هستند.
  4. نرمال‌سازی ناقص یا دوباره؛ مخلوط عدد حروفی و رقمی.
  5. تکیه بر <say-as> برای تاریخ شمسی بدون اطمینان از پشتیبانی موتور.
  6. استفاده از زبان اداری در مکالمه؛ «گردید» و «می‌باشد» در گفت‌وگو مصنوعی‌اند.
  7. قطعه‌بندی وسط بند برای کاهش تأخیر؛ چند میلی‌ثانیه صرفه‌جویی، به قیمت آهنگ شکسته.
  8. نداشتن مسئول برای واژه‌نامه‌ی تلفظ؛ خطاها گزارش می‌شوند و هیچ‌وقت درست نمی‌شوند.

چک‌لیست تبدیل متن به گفتار طبیعی پیش از انتشار

  • پاک‌سازی مارک‌داون، HTML، ایموجی و لینک
  • یکسان‌سازی «ی» و «ک»، ارقام و نیم‌فاصله
  • تبدیل تاریخ شمسی، مبلغ (تومان/ریال)، ساعت و درصد به حروف
  • گروه‌بندی شماره‌تلفن و کدها با مکث
  • واژه‌نامه‌ی تلفظ برای برندها، مخفف‌ها و واژه‌های انگلیسی، در کنترل نسخه
  • یکدست بودن علائم نگارشی (، ؛ ؟) مطابق رفتار موتور
  • شکستن جمله‌های بلند و بازگرداندن «؟» گم‌شده
  • لحن یکدست (رسمی یا گفتاری) متناسب با کاربرد
  • قطعه‌بندی جریانی فقط در مرز جمله یا بند
  • مجموعه‌ی تست جمله‌های دشوار و بررسی ASR در CI
  • آزمون شنیداری دوره‌ای با گویشوران فارسی

جمع‌بندی

تبدیل متن به گفتار طبیعی بیشتر از آنکه مسئله‌ی مدل باشد، مسئله‌ی متن است. صدای عصبی امروزی متنِ خوب‌آماده‌شده را طبیعی می‌خواند و در برابر عدد و تاریخ و مخفف و نیم‌فاصله‌ی جاافتاده و سؤالِ بی‌علامت کم می‌آورد. یک لایه‌ی پیش‌پردازش که مالکش خودتان هستید (یکسان‌سازی فارسی، نرمال‌سازی عدد و تاریخ شمسی، واژه‌نامه‌ی تلفظ، جمله‌بندی و SSML هدفمند) بیشتر این مشکلات را حل می‌کند، به موتور خاصی وابسته نیست و مثل هر کد دیگری تست می‌شود. برای فارسی، همین لایه جایی است که فاصله‌ی زبان نوشتار و گفتار پر می‌شود. آن را بسازید، مدام بسنجید و بهبود دهید؛ صدای دستیارتان با همین مدلی که دارید انسانی‌تر خواهد شد.

پرسش‌های متداول

چطور بدون آموزش دوباره‌ی مدل، TTS فارسی را طبیعی‌تر کنیم؟

متن ورودی را بهتر کنید: حروف و نیم‌فاصله را یکسان کنید، عدد و تاریخ شمسی و مبلغ را به حروف تبدیل کنید، جمله‌های بلند را بشکنید، برای نام‌ها و واژه‌های انگلیسی واژه‌نامه‌ی تلفظ بسازید و در صورت پشتیبانی موتور از SSML استفاده کنید.

چرا TTS تاریخ شمسی را اشتباه می‌خواند؟

بسیاری از فرانت‌اندهای متنی الگوی تاریخ شمسی را نمی‌شناسند و آن را مثل عدد یا کسر می‌خوانند. تاریخ را قبل از ارسال به شکل گفتاری تبدیل کنید، مثلاً «نهم مهر هزار و چهارصد و پنج».

نیم‌فاصله چه تأثیری روی تلفظ TTS دارد؟

نیم‌فاصله مرز واژه را مشخص می‌کند. «می روم» با فاصله ممکن است دو واژه‌ی جدا فرض شود و «میروم» بی‌فاصله ممکن است بد تحلیل شود. شکل درست «می‌روم» توکن‌سازی و تلفظ قابل‌پیش‌بینی‌تری می‌دهد.

واژه‌های انگلیسی داخل متن فارسی را چطور درست بخوانیم؟

مطمئن‌ترین روش، آوانویسی فارسی در واژه‌نامه‌ی تلفظ است، مثل «Kubernetes» ← «کوبرنتیز». اگر موتور تعویض زبان با <lang> را خوب پشتیبانی کند، آن هم گزینه‌ی خوبی است.

آیا SSML برای زبان فارسی کار می‌کند؟

عناصر پایه مثل <break> و <prosody> در بیشتر موتورها کار می‌کنند، اما پشتیبانی از تفسیر تاریخ و عدد فارسی یا واج‌نویسی فارسی متفاوت است. همیشه با گوش دادن امتحان کنید.

متن رسمی بهتر است یا محاوره‌ای؟

برای دستیار مکالمه‌ای، گفتاری مؤدبانه طبیعی‌تر است. برای خواندن متن رسمی، نوشتاری مناسب‌تر است. مهم این است که لحن در یک پاسخ یکدست بماند و مدل TTS شما املای انتخاب‌شده را درست بخواند.

منابع

پروژه‌ای در ذهن دارید؟

از ایده تا نمونه اولیه و محصول، در کنار شما هستیم.