TTS系统主要包括三个模块:文本前端、声学模型和声码器。
一个文本前端模块主要包括:
- Text Segmentation
- Text Normalization (TN)
- Word Segmentation (mainly in Chinese)
- Part-of-Speech
- Prosody
- G2P (Grapheme-to-Phoneme, include Polyphone and Tone Sandhi, etc.)
- Linguistic Features/Charactors/Phonemes
• text: 90 后为中华人民共和国成立 70 周年准备了大礼
• Text Normalization: 九零后为中华人民共和国成立七十周年准备了大礼
• Word Segmentation: 九零后/为/中华人民/共和国/成立/七十/周年/准备/了/大礼
• G2P:
jiu3 ling2 hou4 wei4 zhong1 hua2 ren2 min2 gong4 he2 guo2 ...
• Prosody (prosodic words #1, prosodic phrases #2, intonation phrases #3, sentence #4):
九零后#1为中华人民#1共和国#2成立七十周年#3准备了大礼#4
Text Segmentation
Text Segmentation 主要指的是断句。如何从长文本中分段,分句。一般可以通过标点符号简单处理。
Text Normalization
https://github.com/PaddlePaddle/PaddleSpeech/tree/develop/examples/other/tn
Supported NSW (Non-Standard-Word) Normalization
| NSW type | raw | normalized |
|---|---|---|
| serial number | 电影中梁朝伟扮演的陈永仁的编号27149 | 电影中梁朝伟扮演的陈永仁的编号二七一四九 |
| cardinal | 这块黄金重达324.75克我们班的最高总分为583分 | 这块黄金重达三百二十四点七五克我们班的最高总分为五百八十三分 |
| numeric range | 12 | 十二到二十三负一点五到二 |
| date | 她出生于86年8月18日,她弟弟出生于1995年3月1日 | 她出生于八六年八月十八日, 她弟弟出生于一九九五年三月一日 |
| time | 等会请在12:05请通知我 | 等会请在十二点零五分请通知我 |
| temperature | 今天的最低气温达到-10°C | 今天的最低气温达到零下十度 |
| fraction | 现场有7/12的观众投出了赞成票 | 现场有十二分之七的观众投出了赞成票 |
| percentage | 明天有62%的概率降雨 | 明天有百分之六十二的概率降雨 |
| money | 随便来几个价格12块5,34.5元,20.1万 | 随便来几个价格十二块五,三十四点五元,二十点一万 |
| telephone | 这是固话0421-33441122这是手机+86 18544139121 | 这是固话零四二一三三四四一一二二这是手机八六一八五四四一三九一二一 |
Word Segmentation and Part-of-Speech
Word Segmentation
分词一般在中文中常见。
分词之所以重要可以通过这个例子来说明:
广州市长隆马戏欢迎你 -> 广州市 长隆 马戏 欢迎你
如果没有分词错误会导致句意完全不正确:
广州 市长 隆马戏 欢迎你
POS (Part of Speech)
n/名词 np/人名 ns/地名 ni/机构名 nz/其它专名
m/数词 q/量词 mq/数量词 t/时间词 f/方位词 s/处所词
v/动词 a/形容词 d/副词 h/前接成分 k/后接成分
i/习语 j/简称 r/代词 c/连词 p/介词 u/助词 y/语气助词
e/叹词 o/拟声词 g/语素 w/标点 x/其它
Grapheme-to-Phoneme
https://github.com/PaddlePaddle/PaddleSpeech/tree/develop/examples/other/g2p
在汉语中,G2P是一个非常复杂的模块,主要包括 多音字消歧 和 变调 。
我们使用g2pM和pypinyin作为默认的g2p工具。它们可以在一定程度上解决多音字问题。
然而,g2pM和pypinyin在变调中表现不佳,我们使用规则来解决这个问题,这需要相关的语言学知识。
G2P
g2pM
>>> from g2pM import G2pM
>>> model = G2pM()
>>> sentence = "然而,他红了20年以后,他竟退出了大家的视线。"
>>> model(sentence, tone=True, char_split=False)
['ran2', 'er2', ',', 'ta1', 'hong2', 'le5', '20', 'nian2', 'yi3', 'hou4', ',', 'ta1', 'jing4', 'tui4', 'chu1', 'le5', 'da4', 'jia1', 'de5', 'shi4', 'xian4', '。']
>>> model(sentence, tone=False, char_split=False)
['ran', 'er', ',', 'ta', 'hong', 'le', '2', '0', 'nian', 'yi', 'hou', ',', 'ta', 'jing', 'tui', 'chu', 'le', 'da', 'jia', 'de', 'shi', 'xian', '。']
>>> model(sentence, tone=True, char_split=True)
['ran2', 'er2', ',', 'ta1', 'hong2', 'le5', '2', '0', 'nian2', 'yi3', 'hou4', ',', 'ta1', 'jing4', 'tui4', 'chu1', 'le5', 'da4', 'jia1', 'de5', 'shi4', 'xian4', '。']
g2pM 可以正确处理了标点符号和数字。
g2pW
>>> from g2pw import G2PWConverter
>>> conv = G2PWConverter(style='pinyin', enable_non_tradional_chinese=True)
>>> conv('然而,他红了20年以后,他竟退出了大家的视线。')
[['ran2', 'er2', None, 'ta1', 'hong2', 'le5', None, None, 'nian2', 'yi3', 'hou4', None, 'ta1', 'jing4', 'tui4', 'chu1', 'le5', 'da4', 'jia1', 'de5', 'shi4', 'xian4', None]]
g2pW 不能正确处理标点和数字。
Tone Sandhi
在词语和句子中,音节与音节相连发音时,有些音节的调值会发生变化,这种现象叫变调 。
汉语的变调主要包括:
普通话的变调主要分为五种情况:
- 轻声变调
- "一" "不" 变调
- 三声变调
- 儿化音变调
- “啊”等助词变调
带有词性的分词 → 轻声变调→ 一不变调→ 三声变调。
轻声变调
| cases | |
|---|---|
| 语气助词“吧、呢、啊”等 | 吃吧、走吗、去呢、跑啊 |
| 结构助词:“的、地、得” | 我的书、慢慢地走、跑得很快等 |
| 有的轻声音节和非轻声音节构成对比区别意义 | 买卖:一指生意;二指买和卖。 地道:一指纯粹、真正;二指地下通道。 大意:一指没有注意;二指主要的意思。 东西:一指各种事物;二指东面与西面。 言语:一指所说的话;二指开口,招呼。运气:一指一种锻炼的方法。二指幸运。 |
| 名词的后缀:“们、子、头” | 你们、房子、石头 |
| 名词或动词的第二个重叠音节 | 奶奶、姐姐、爸爸、试试、看看、说说、问问 |
| 名词后面表示方位的:“上、下、里” | 桌上、地下、院里 |
| 动态助词:“了、着、过” | 走了、看着、去过 |
| 作宾语的人称代词:“我、你、他” | 找我、请你、麻烦他。 |
| 约定俗成 | 匀称、盘算、枇杷、篱笆、活泼、玄乎。狐狸、学生、拾掇、麻烦、蛤蟆、石榴。玫瑰、凉快、萝卜、朋友、奴才、云彩。脑袋、老爷、老婆、嘴巴、指头、指甲。委屈、喇叭、讲究、打发、打听、喜欢。点心、伙计、打扮、哑巴、女婿、首饰。自在、吓唬、力气、漂亮、队伍、地方。痛快、念叨、笑语、丈夫、志气、钥匙。月亮、正经、位置、秀气、上司、悟性。告示、动静、热闹、屁股、阔气、意思。等 |
"一" "不" 变调
"一" 变调
| 是否变调 | cases | |
|---|---|---|
| 单独念 | 否 | 第一、一楼 |
| 序数 | 否 | |
| 用在语句末尾 | 否 | |
| 去声前变阳平(四声前变二声) | 一栋yí dòng、一段yí duàn、一律yí lǜ、一路yí lù | |
| 非去声前变去声(非四声前变四声) | 阴平(一声)一发yì fā 、一端yì duān、一天yì tiān、一忽yì hū阳平(二声)一叠yì dié 、一同yì tóng 、一头yì tóu 、一条yì tiáo上声(三声)一统yì tǒng、一体yì tǐ、一览yì lǎn、一口yì kǒu | |
| 轻读,当“一”嵌在重叠式的动词之间 | 听一听 tīng yi tīng |
"不" 变调
| 是否变调 | cases | |
|---|---|---|
| 单独念 | 否 | |
| 用在语句末尾 | 否 | 我不 |
| 去声前变阳平(四声前变成二声) | 不怕bú pà、不妙bú miào、不犯bú fàn、不忿bú fèn | |
| 轻读,不”夹在重叠动词或重叠形容词之间、夹在动词和补语之间 | 懂不懂 dǒng bu dǒng 、看不清 kàn bu qīng |
三声变调
| 子类别 | 如何变调 | cases | |
|---|---|---|---|
| 单独念 | 否 | ||
| 句末 | 否 | ||
| 在句中停顿并没被后音节影响 | 否 | ||
| 三声+三声 | 二声+三声 | 保险、保养、党委、尽管、老板、本领、引导、古老、敏感、鼓舞、永远、语法、口语、岛屿、保姆、远景、北海、首长、母语 | |
| 三个三声相连 | 双音节+单音节(“双单格”结构) | 前两个变二声 | 演讲稿、跑马场、展览馆、管理组、水彩笔、蒙古语、选取法、古典舞、虎骨酒、洗脸水、草稿纸 |
| 单音节+双音节(“单双格”结构) | 第二个变二声 | 史小姐、党小组、好小伙、跑百米、纸老虎、李厂长、老保姆、冷处理、很友好、小雨伞 | |
| 单音节+单音节+单音节(“单三格”结构) | 前两个变二声 | 软懒散、稳准狠 | |
| 更多三声音节相连时 | 按语意与若干二字组成三字组,然后按以上变调规律处理 | 岂有 / 此理。请你 / 给我 / 打点儿 / 洗脸水。手表厂 / 有五种 /好产品。 |
儿化音
北京语言里读“儿”的可分为两类: 一类有具体意义,独立构成音节 。如儿童。 另一类没有具体意义,也不能独立构成音节 。如:花儿、遛弯儿、馅儿饼等。我们常说的“儿化”就是指的后面这一种。( r 代表加卷舌动作。er 代表完整的“儿”读音。 )
儿化规律:
1、韵腹或韵尾是a o e u 的不变加r.
如:刀把儿、单个儿、牙刷儿、小褂儿、煤渣儿、灯泡儿、酒窝儿、小偷儿、手套儿、方格儿、台阶儿、腰包儿、绝活儿、眼珠儿等等。
2、尾音是i n 去i 或n 加r:
如:小孩儿、口袋儿、宝贝儿、心眼儿、糖块儿、纳闷儿、刀片儿、顶针儿、手印儿、没劲儿、人缘儿、窍门儿。
3、尾音是ng 去ng元音鼻化加r 如:没空儿、药瓶儿、花样儿、酒盅儿、打呜儿、小葱儿、赶趟儿、胡同儿、小名儿、沙瓤儿、香肠儿。
4、韵母是i u 不变,加er
如:摸底儿、门鼻儿、小曲儿、打气儿、家底儿、猪蹄儿、书皮儿、金鱼儿、毛驴儿。
5、韵母—i 的去—i 加er
如:松子儿、铁丝儿、柜子儿、针刺儿、石子儿、粉丝儿、记事儿、小事儿。
6、韵母是ui in un 去i 或n 加er
如:麦穗儿、土堆儿、干劲儿、树林儿、飞轮儿、合群儿。
语气词“啊”的变调规律
阴平:啊,好好干!阳平:啊,你怎么这样干! 去声:啊,我明白了。
前一音节结尾的是a、o、(ao、iao除外)、e、i、时读ya例:(1)我说的就是他啊。(2)你快说啊。(3)很难打破啊。(4)洗青菜啊。(5)雪在下啊。(6)你写啊!(7)要提高警惕啊!(8)要注意啊!
前一音节收尾是u时包括ao、iao时,读wa例:(1)生活多美好啊!(2)谁在打鼓啊?
前一音节收尾是n时,读na例:(1)军民一家人啊。(2)天好蓝啊。(3)怎么办啊?
前一音节收尾是ng时,读成nga例:(1)大家一起唱啊!(2)下午劳动啊!(3)歌声好听啊!
前一音节收尾是-i(舌尖后特殊元音)、r、er时,读ra例:(1)你先吃啊!(2)今天是节日啊!
前收尾音是-i(舌尖前特殊元音)时,读za例:(1)你教我写字啊?(2)今天星期四啊!
变调代码
class ToneSandhi():
def \_\_init\_\_(self):
self.must_neural_tone_words = {
'麻烦', '麻利', '鸳鸯', '高粱', '骨头', '骆驼', '马虎', '首饰', '馒头', '馄饨', '风筝',
'难为', '队伍', '阔气', '闺女', '门道', '锄头', '铺盖', '铃铛', '铁匠', '钥匙', '里脊',
'里头', '部分', '那么', '道士', '造化', '迷糊', '连累', '这么', '这个', '运气', '过去',
'软和', '转悠', '踏实', '跳蚤', '跟头', '趔趄', '财主', '豆腐', '讲究', '记性', '记号',
'认识', '规矩', '见识', '裁缝', '补丁', '衣裳', '衣服', '衙门', '街坊', '行李', '行当',
'蛤蟆', '蘑菇', '薄荷', '葫芦', '葡萄', '萝卜', '荸荠', '苗条', '苗头', '苍蝇', '芝麻',
'舒服', '舒坦', '舌头', '自在', '膏药', '脾气', '脑袋', '脊梁', '能耐', '胳膊', '胭脂',
'胡萝', '胡琴', '胡同', '聪明', '耽误', '耽搁', '耷拉', '耳朵', '老爷', '老实', '老婆',
'戏弄', '将军', '翻腾', '罗嗦', '罐头', '编辑', '结实', '红火', '累赘', '糨糊', '糊涂',
'精神', '粮食', '簸箕', '篱笆', '算计', '算盘', '答应', '笤帚', '笑语', '笑话', '窟窿',
'窝囊', '窗户', '稳当', '稀罕', '称呼', '秧歌', '秀气', '秀才', '福气', '祖宗', '砚台',
'码头', '石榴', '石头', '石匠', '知识', '眼睛', '眯缝', '眨巴', '眉毛', '相声', '盘算',
'白净', '痢疾', '痛快', '疟疾', '疙瘩', '疏忽', '畜生', '生意', '甘蔗', '琵琶', '琢磨',
'琉璃', '玻璃', '玫瑰', '玄乎', '狐狸', '状元', '特务', '牲口', '牙碜', '牌楼', '爽快',
'爱人', '热闹', '烧饼', '烟筒', '烂糊', '点心', '炊帚', '灯笼', '火候', '漂亮', '滑溜',
'溜达', '温和', '清楚', '消息', '浪头', '活泼', '比方', '正经', '欺负', '模糊', '槟榔',
'棺材', '棒槌', '棉花', '核桃', '栅栏', '柴火', '架势', '枕头', '枇杷', '机灵', '本事',
'木头', '木匠', '朋友', '月饼', '月亮', '暖和', '明白', '时候', '新鲜', '故事', '收拾',
'收成', '提防', '挖苦', '挑剔', '指甲', '指头', '拾掇', '拳头', '拨弄', '招牌', '招呼',
'抬举', '护士', '折腾', '扫帚', '打量', '打算', '打扮', '打听', '打发', '扎实', '扁担',
'戒指', '懒得', '意识', '意思', '悟性', '怪物', '思量', '怎么', '念头', '念叨', '别人',
'快活', '忙活', '志气', '心思', '得罪', '张罗', '弟兄', '开通', '应酬', '庄稼', '干事',
'帮手', '帐篷', '希罕', '师父', '师傅', '巴结', '巴掌', '差事', '工夫', '岁数', '屁股',
'尾巴', '少爷', '小气', '小伙', '将就', '对头', '对付', '寡妇', '家伙', '客气', '实在',
'官司', '学问', '字号', '嫁妆', '媳妇', '媒人', '婆家', '娘家', '委屈', '姑娘', '姐夫',
'妯娌', '妥当', '妖精', '奴才', '女婿', '头发', '太阳', '大爷', '大方', '大意', '大夫',
'多少', '多么', '外甥', '壮实', '地道', '地方', '在乎', '困难', '嘴巴', '嘱咐', '嘟囔',
'嘀咕', '喜欢', '喇嘛', '喇叭', '商量', '唾沫', '哑巴', '哈欠', '哆嗦', '咳嗽', '和尚',
'告诉', '告示', '含糊', '吓唬', '后头', '名字', '名堂', '合同', '吆喝', '叫唤', '口袋',
'厚道', '厉害', '千斤', '包袱', '包涵', '匀称', '勤快', '动静', '动弹', '功夫', '力气',
'前头', '刺猬', '刺激', '别扭', '利落', '利索', '利害', '分析', '出息', '凑合', '凉快',
'冷战', '冤枉', '冒失', '养活', '关系', '先生', '兄弟', '便宜', '使唤', '佩服', '作坊',
'体面', '位置', '似的', '伙计', '休息', '什么', '人家', '亲戚', '亲家', '交情', '云彩',
'事情', '买卖', '主意', '丫头', '丧气', '两口', '东西', '东家', '世故', '不由', '下水',
'下巴', '上头', '上司', '丈夫', '丈人', '一辈', '那个', '菩萨', '父亲', '母亲', '咕噜',
'邋遢', '费用', '冤家', '甜头', '介绍', '荒唐', '大人', '泥鳅', '幸福', '熟悉', '计划',
'扑腾', '蜡烛', '姥爷', '照顾', '喉咙', '吉他', '弄堂', '蚂蚱', '凤凰', '拖沓', '寒碜',
'糟蹋', '倒腾', '报复', '逻辑', '盘缠', '喽啰', '牢骚', '咖喱', '扫把', '惦记'
}
self.must_not_neural_tone_words = {
'男子', '女子', '分子', '原子', '量子', '莲子', '石子', '瓜子', '电子', '人人', '虎虎',
'幺幺', '干嘛', '学子', '哈哈', '数数', '袅袅', '局地', '以下', '娃哈哈', '花花草草', '留得',
'耕地', '想想', '熙熙', '攘攘', '卵子', '死死', '冉冉', '恳恳', '佼佼', '吵吵', '打打',
'考考', '整整', '莘莘', '落地', '算子', '家家户户'
}
self.punc = ":,;。?!“”‘’':,;.?!"
def modified\_tone(self, word: str, pos: str,
finals: List[str]) -> List[str]:
print("sandhi", word, pos, finals)
finals = self._bu_sandhi(word, finals)
finals = self._yi_sandhi(word, finals)
finals = self._neural_sandhi(word, pos, finals)
finals = self._three_sandhi(word, finals)
print("sandhi done:", word, pos, finals)
return finals
# (词, 词性, 韵母)
sandhi 我喜欢 r ['uo3', 'i3', 'uan1']
sandhi done: 我喜欢 r ['uo2', 'i3', 'uan5']
sandhi 你喜欢 r ['i3', 'i3', 'uan1']
sandhi done: 你喜欢 r ['i2', 'i3', 'uan5']
sandhi 我们 r ['uo3', 'en5']
sandhi done: 我们 r ['uo3', 'en5']
sandhi 要 v ['iao4']
sandhi done: 要 v ['iao4']
sandhi 去 v ['v4']
sandhi done: 去 v ['v4']
sandhi 云南 ns ['vn2', 'an2']
sandhi done: 云南 ns ['vn2', 'an2']
sandhi 非常非常 d ['ei1', 'ang2', 'ei1', 'ang2']
sandhi done: 非常非常 d ['ei1', 'ang2', 'ei1', 'ang2']
def \_split\_word(self, word: str) -> List[str]:
word_list = jieba.cut_for_search(word)
word_list = sorted(word_list, key=lambda i: len(i), reverse=False)
first_subword = word_list[0]
first_begin_idx = word.find(first_subword)
if first_begin_idx == 0:
second_subword = word[len(first_subword):]
new_word_list = [first_subword, second_subword]
else:
second_subword = word[:-len(first_subword)]
new_word_list = [second_subword, first_subword]
return new_word_list
# the meaning of jieba pos tag: https://blog.csdn.net/weixin\_44174352/article/details/113731041
# e.g.
# word: "家里"
# pos: "s"
# finals: ['ia1', 'i3']
def \_neural\_sandhi(self, word: str, pos: str,
finals: List[str]) -> List[str]:
if word in self.must_not_neural_tone_words:
return finals
# reduplication words for n. and v. e.g. 奶奶, 试试, 旺旺
for j, item in enumerate(word):
if j - 1 >= 0 and item == word[j - 1] and pos[0] in {"n", "v", "a"}:
finals[j] = finals[j][:-1] + "5"
ge_idx = word.find("个")
if len(word) >= 1 and word[-1] in "吧呢啊呐噻嘛吖嗨呐哦哒滴哩哟喽啰耶喔诶":
finals[-1] = finals[-1][:-1] + "5"
elif len(word) >= 1 and word[-1] in "的地得":
finals[-1] = finals[-1][:-1] + "5"
# e.g. 走了, 看着, 去过
elif len(word) == 1 and word in "了着过" and pos in {"ul", "uz", "ug"}:
finals[-1] = finals[-1][:-1] + "5"
elif len(word) > 1 and word[-1] in "们子" and pos in {"r", "n"}:
finals[-1] = finals[-1][:-1] + "5"
# e.g. 桌上, 地下
elif len(word) > 1 and word[-1] in "上下" and pos in {"s", "l", "f"}:
finals[-1] = finals[-1][:-1] + "5"
# e.g. 上来, 下去
elif len(word) > 1 and word[-1] in "来去" and word[-2] in "上下进出回过起开":
finals[-1] = finals[-1][:-1] + "5"
# 个做量词
elif (ge_idx >= 1 and
(word[ge_idx - 1].isnumeric() or
word[ge_idx - 1] in "几有两半多各整每做是")) or word == '个':
finals[ge_idx] = finals[ge_idx][:-1] + "5"
else:
if word in self.must_neural_tone_words or word[
-2:] in self.must_neural_tone_words:
finals[-1] = finals[-1][:-1] + "5"
word_list = self._split_word(word)
finals_list = [finals[:len(word_list[0])], finals[len(word_list[0]):]]
for i, word in enumerate(word_list):
# conventional neural in Chinese
if word in self.must_neural_tone_words or word[
-2:] in self.must_neural_tone_words:
finals_list[i][-1] = finals_list[i][-1][:-1] + "5"
finals = sum(finals_list, [])
return finals
def \_bu\_sandhi(self, word: str, finals: List[str]) -> List[str]:
# e.g. 看不懂
if len(word) == 3 and word[1] == "不":
finals[1] = finals[1][:-1] + "5"
else:
for i, char in enumerate(word):
# "不" before tone4 should be bu2, e.g. 不怕
if char == "不" and i + 1 < len(word) and finals[i +
1][-1] == "4":
finals[i] = finals[i][:-1] + "2"
return finals
def \_yi\_sandhi(self, word: str, finals: List[str]) -> List[str]:
# "一" in number sequences, e.g. 一零零, 二一零
if word.find("一") != -1 and all(
[item.isnumeric() for item in word if item != "一"]):
return finals
# "一" between reduplication words shold be yi5, e.g. 看一看
elif len(word) == 3 and word[1] == "一" and word[0] == word[-1]:
finals[1] = finals[1][:-1] + "5"
# when "一" is ordinal word, it should be yi1
elif word.startswith("第一"):
finals[1] = finals[1][:-1] + "1"
else:
for i, char in enumerate(word):
if char == "一" and i + 1 < len(word):
# "一" before tone4 should be yi2, e.g. 一段
if finals[i + 1][-1] in {'4', '5'}:
finals[i] = finals[i][:-1] + "2"
# "一" before non-tone4 should be yi4, e.g. 一天
else:
# "一" 后面如果是标点,还读一声
if word[i + 1] not in self.punc:
finals[i] = finals[i][:-1] + "4"
return finals
def \_all\_tone\_three(self, finals: List[str]) -> bool:
return all(x[-1] == "3" for x in finals)
def \_three\_sandhi(self, word: str, finals: List[str]) -> List[str]:
if len(word) == 2 and self._all_tone_three(finals):
finals[0] = finals[0][:-1] + "2"
elif len(word) == 3:
word_list = self._split_word(word)
if self._all_tone_three(finals):
# disyllabic + monosyllabic, e.g. 蒙古/包
if len(word_list[0]) == 2:
finals[0] = finals[0][:-1] + "2"
finals[1] = finals[1][:-1] + "2"
# monosyllabic + disyllabic, e.g. 纸/老虎
elif len(word_list[0]) == 1:
finals[1] = finals[1][:-1] + "2"
else:
finals_list = [
finals[:len(word_list[0])], finals[len(word_list[0]):]
]
if len(finals_list) == 2:
for i, sub in enumerate(finals_list):
# e.g. 所有/人
if self._all_tone_three(sub) and len(sub) == 2:
finals_list[i][0] = finals_list[i][0][:-1] + "2"
# e.g. 好/喜欢
elif i == 1 and not self._all_tone_three(sub) and finals_list[i][0][-1] == "3" and \
finals_list[0][-1][-1] == "3":
finals_list[0][-1] = finals_list[0][-1][:-1] + "2"
finals = sum(finals_list, [])
# split idiom into two words who's length is 2
elif len(word) == 4:
finals_list = [finals[:2], finals[2:]]
finals = []
for sub in finals_list:
if self._all_tone_three(sub):
sub[0] = sub[0][:-1] + "2"
finals += sub
return finals
# merge "不" and the word behind it
# if don't merge, "不" sometimes appears alone according to jieba, which may occur sandhi error
def \_merge\_bu(self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
last_word = ""
for word, pos in seg:
if last_word == "不":
word = last_word + word
if word != "不":
new_seg.append((word, pos))
last_word = word[:]
if last_word == "不":
new_seg.append((last_word, 'd'))
last_word = ""
return new_seg
# function 1: merge "一" and reduplication words in it's left and right, e.g. "听","一","听" ->"听一听"
# function 2: merge single "一" and the word behind it
# if don't merge, "一" sometimes appears alone according to jieba, which may occur sandhi error
# e.g.
# input seg: [('听', 'v'), ('一', 'm'), ('听', 'v')]
# output seg: [['听一听', 'v']]
def \_merge\_yi(self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
# function 1
for i, (word, pos) in enumerate(seg):
if i - 1 >= 0 and word == "一" and i + 1 < len(seg) and seg[i - 1][
0] == seg[i + 1][0] and seg[i - 1][1] == "v":
if i - 1 < len(new_seg):
new_seg[i -
1][0] = new_seg[i - 1][0] + "一" + new_seg[i - 1][0]
else:
new_seg.append([word, pos])
new_seg.append([seg[i + 1][0], pos])
else:
if i - 2 >= 0 and seg[i - 1][0] == "一" and seg[i - 2][
0] == word and pos == "v":
continue
else:
new_seg.append([word, pos])
seg = new_seg
new_seg = []
# function 2
for i, (word, pos) in enumerate(seg):
if new_seg and new_seg[-1][0] == "一":
new_seg[-1][0] = new_seg[-1][0] + word
else:
new_seg.append([word, pos])
return new_seg
# the first and the second words are all\_tone\_three
def \_merge\_continuous\_three\_tones(
self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
sub_finals_list = [
lazy_pinyin(
word, neutral_tone_with_five=True, style=Style.FINALS_TONE3)
for (word, pos) in seg
]
assert len(sub_finals_list) == len(seg)
merge_last = [False] * len(seg)
for i, (word, pos) in enumerate(seg):
if i - 1 >= 0 and self._all_tone_three(
sub_finals_list[i - 1]) and self._all_tone_three(
sub_finals_list[i]) and not merge_last[i - 1]:
# if the last word is reduplication, not merge, because reduplication need to be \_neural\_sandhi
if not self._is_reduplication(seg[i - 1][0]) and len(
seg[i - 1][0]) + len(seg[i][0]) <= 3:
new_seg[-1][0] = new_seg[-1][0] + seg[i][0]
merge_last[i] = True
else:
new_seg.append([word, pos])
else:
new_seg.append([word, pos])
return new_seg
def \_is\_reduplication(self, word: str) -> bool:
return len(word) == 2 and word[0] == word[1]
# the last char of first word and the first char of second word is tone\_three
def \_merge\_continuous\_three\_tones\_2(
self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
sub_finals_list = [
lazy_pinyin(
word, neutral_tone_with_five=True, style=Style.FINALS_TONE3)
for (word, pos) in seg
]
assert len(sub_finals_list) == len(seg)
merge_last = [False] * len(seg)
for i, (word, pos) in enumerate(seg):
if i - 1 >= 0 and sub_finals_list[i - 1][-1][-1] == "3" and sub_finals_list[i][0][-1] == "3" and not \
merge_last[i - 1]:
# if the last word is reduplication, not merge, because reduplication need to be \_neural\_sandhi
if not self._is_reduplication(seg[i - 1][0]) and len(
seg[i - 1][0]) + len(seg[i][0]) <= 3:
new_seg[-1][0] = new_seg[-1][0] + seg[i][0]
merge_last[i] = True
else:
new_seg.append([word, pos])
else:
new_seg.append([word, pos])
return new_seg
def \_merge\_er(self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
for i, (word, pos) in enumerate(seg):
if i - 1 >= 0 and word == "儿":
new_seg[-1][0] = new_seg[-1][0] + seg[i][0]
else:
new_seg.append([word, pos])
return new_seg
def \_merge\_reduplication(
self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
new_seg = []
for i, (word, pos) in enumerate(seg):
if new_seg and word == new_seg[-1][0]:
new_seg[-1][0] = new_seg[-1][0] + seg[i][0]
else:
new_seg.append([word, pos])
return new_seg
def pre\_merge\_for\_modify(
self, seg: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
seg = self._merge_bu(seg)
seg = self._merge_yi(seg)
seg = self._merge_reduplication(seg)
seg = self._merge_continuous_three_tones(seg)
seg = self._merge_continuous_three_tones_2(seg)
seg = self._merge_er(seg)
return seg
Prosody Prediction
ToBI (an abbreviation of tones and break indices) 是一套转录和注释语音韵律的惯例. 中文主要关注break 。
韵律等级结构:
音素 -> 音节 -> 韵律词(Prosody Word, PW) -> 韵律短语(prosody phrase, PPH) -> 语调短句(intonational phrase, IPH) -> 子句子 -> 主句子 -> 段落 -> 篇章
LP -> LO -> L1(#1) -> L2(#2) -> L3(#3) -> L4(#4) -> L5 -> L6 -> L7
主要关注 PW, PPH, IPH 。
| 停顿时长 | 前后音高特征 | |
|---|---|---|
| 韵律词边界 | 不停顿或从听感上察觉不到停顿 | 无 |
| 韵律短语边界 | 可以感知停顿,但无明显的静音段 | 音高不下倾或稍下倾,韵末不可做句末 |
| 语调短语边界 | 有较长停顿 | 音高下倾比较完全,韵末可以作为句末 |
常用方法使用的是级联CRF,首先预测如果是PW,再继续预测是否是PPH,再预测是否是IPH
参考文献
-
https://slyne.github.io/%E5%85%AC%E5%BC%80%E8%AF%BE/2020/10/03/TTS1/
-
https://github.com/PaddlePaddle/PaddleSpeech/edit/develop/docs/source/tts/zh\_text\_frontend.md
-
chinese_text_normalization
-
声调篇|这些“一、不”变调规律,你不得不知
-
TTS前端模块中的普通话变调规则
-
轻声和变调
-
必读轻声词语表546条
