Chinese Rule-Based Text Frontend

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

picture.image

  
 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 typerawnormalized
serial number电影中梁朝伟扮演的陈永仁的编号27149电影中梁朝伟扮演的陈永仁的编号二七一四九
cardinal这块黄金重达324.75克我们班的最高总分为583分这块黄金重达三百二十四点七五克我们班的最高总分为五百八十三分
numeric range1223-1.52十二到二十三负一点五到二
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

https://github.com/kakaobrain/g2pm/blob/master/README.md

  
>>> 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

https://github.com/GitYCC/g2pW/blob/master/README.md

  
>>> 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']  
  
sandhiv ['iao4']  
sandhi done: 要 v ['iao4']  
  
sandhiv ['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

picture.image

参考文献

0
0
0
0
评论
未登录
暂无评论