Files
2026-07-13 13:33:03 +08:00

455 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "chinese_g2p.hpp"
extern std::unordered_map<std::string, std::string> rep_map = {
{"", ","}, {"", ","}, {"", ","}, {"。", "."}, {"", "!"}, {"", "?"}, {"\n", "."}, {"·", ","}, {"、", ","}, {"...", "…"}, {"$", "."}, {"“", "'"}, {"”", "'"}, {"", "'"}, {"", "'"}, {"", "'"}, {"", "'"}, {"(", "'"}, {")", "'"}, {"《", "'"}, {"》", "'"}, {"【", "'"}, {"】", "'"}, {"[", "'"}, {"]", "'"}, {"—", "-"}, {"", "-"}, {"~", "-"}, {"「", "\""}, {"」", "\""}};
std::vector<std::string> punctuation = {"!", "?", ",", ".", "\"", "-", "…", "【", "】", "", ":", "「", "」", "'", "“"};
ChineseG2P::ChineseG2P(const std::string &local_resource_root) : pinyin_(local_resource_root), word_spliter_(WordSpliter::GetInstance(local_resource_root)), tone_adjuster_(local_resource_root)
{
auto t0 = clk::now();
PLOG(INFO, "ChineseG2P 开始初始化...");
resource_root_ = local_resource_root;
an2cn_ = An2Cn();
// pinyin_.AddCustomPhrases(custom_pharse);
auto pinyin_to_symbol_bin_path = resource_root_ + "common/text_processing_jsons/pinyin_to_symbol_map.bin";
LoadBinToMap(pinyin_to_symbol_bin_path, pinyin_to_symbol_map_);
auto hotwords_bin_path = resource_root_ + "common/text_processing_jsons/hotwords_cn.bin";
LoadHotwordsFromBin(hotwords_bin_path);
// 创建映射
int id = 0;
for (auto &symbol : symbols)
{
_symbol_to_id[symbol] = id++;
}
auto t1 = clk::now();
auto d1 = std::chrono::duration_cast<ms>(t1 - t0);
PLOG(INFO, "ChineseG2P 初始化成功, timecost: " + std::to_string(d1.count()) + "ms");
}
void ChineseG2P::ParseHotwordsCNFile(const std::string &hotwords_cn_json_path)
{
auto json_obj = LoadJson(hotwords_cn_json_path);
std::vector<std::string> hot_word_list;
// 由于hotwords_cn.json文件中还有别的类型的数据,这里只获取我们想要的pinyin字段
for (auto &item : json_obj)
{
std::string cur_word = item["hotword"];
hot_word_list.push_back(cur_word);
std::vector<std::string> initials = SplitString(item["initials"], ',');
std::vector<std::string> finals = SplitString(item["finals"], ',');
custom_phone_[cur_word] = std::make_tuple(initials, finals);
}
}
void ChineseG2P::SaveHotwordsToBin(const std::string &filename)
{
std::ofstream ofs(filename, std::ios::binary);
if (!ofs)
{
throw std::runtime_error("Unable to open file for writing");
}
// 写入热词数量
size_t size = custom_phone_.size();
ofs.write(reinterpret_cast<const char *>(&size), sizeof(size));
// 写入每个热词及其拼音信息
for (const auto &pair : custom_phone_)
{
const std::string &hotword = pair.first;
const auto &phonemes = pair.second;
const auto &initials = std::get<0>(phonemes);
const auto &finals = std::get<1>(phonemes);
// 写入热词长度和内容
size_t hotword_size = hotword.size();
ofs.write(reinterpret_cast<const char *>(&hotword_size), sizeof(hotword_size));
ofs.write(hotword.c_str(), hotword_size);
// 写入声母
size_t initials_size = initials.size();
ofs.write(reinterpret_cast<const char *>(&initials_size), sizeof(initials_size));
for (const auto &initial : initials)
{
size_t initial_size = initial.size();
ofs.write(reinterpret_cast<const char *>(&initial_size), sizeof(initial_size));
ofs.write(initial.c_str(), initial_size);
}
// 写入韵母
size_t finals_size = finals.size();
ofs.write(reinterpret_cast<const char *>(&finals_size), sizeof(finals_size));
for (const auto &final : finals)
{
size_t final_size = final.size();
ofs.write(reinterpret_cast<const char *>(&final_size), sizeof(final_size));
ofs.write(final.c_str(), final_size);
}
}
ofs.close();
}
void ChineseG2P::LoadHotwordsFromBin(const std::string &filename)
{
std::ifstream ifs(filename, std::ios::binary);
if (!ifs)
{
throw std::runtime_error("Unable to open file for reading");
}
size_t size;
ifs.read(reinterpret_cast<char *>(&size), sizeof(size)); // 读取热词数量
for (size_t i = 0; i < size; ++i)
{
// 读取热词
size_t hotword_size;
ifs.read(reinterpret_cast<char *>(&hotword_size), sizeof(hotword_size));
std::string hotword(hotword_size, '\0');
ifs.read(&hotword[0], hotword_size);
// 读取声母
size_t initials_size;
ifs.read(reinterpret_cast<char *>(&initials_size), sizeof(initials_size));
std::vector<std::string> initials(initials_size);
for (size_t j = 0; j < initials_size; ++j)
{
size_t initial_size;
ifs.read(reinterpret_cast<char *>(&initial_size), sizeof(initial_size));
initials[j].resize(initial_size);
ifs.read(&initials[j][0], initial_size);
}
// 读取韵母
size_t finals_size;
ifs.read(reinterpret_cast<char *>(&finals_size), sizeof(finals_size));
std::vector<std::string> finals(finals_size);
for (size_t j = 0; j < finals_size; ++j)
{
size_t final_size;
ifs.read(reinterpret_cast<char *>(&final_size), sizeof(final_size));
finals[j].resize(final_size);
ifs.read(&finals[j][0], final_size);
}
custom_phone_[hotword] = std::make_tuple(initials, finals);
}
ifs.close();
}
void ChineseG2P::ParsePinyToSymbolJsonFile(const std::string &pinyin_json_path)
{
auto json_obj = LoadJson(pinyin_json_path);
// 将json对象转换为std::map
pinyin_to_symbol_map_ = json_obj.get<pinyin_to_symbol_map>();
SaveJsonToBin("pinyin_to_symbol_map.bin", pinyin_to_symbol_map_);
}
g2p_data ChineseG2P::ConvertInitialFinalToPhoneTone(
const std::vector<std::string> &initials, const std::vector<std::string> &finals)
{
std::vector<std::string> phones_list;
std::vector<int> tones_list;
std::vector<int> word2ph;
for (size_t i = 0; i < initials.size(); ++i)
{
assert(finals.size() > i); // Ensure there are enough finals
const std::string &c = initials[i];
const std::string &v = finals[i];
std::string raw_pinyin = c + v;
// NOTE: post process for pypinyin outputs
// we discriminate i, ii and iii
if (c == v)
{
phones_list.push_back(std::string(1, c[0]));
tones_list.push_back(0);
word2ph.push_back(1);
}
else
{
std::string v_without_tone = v.substr(0, v.size() - 1);
char tone = v.back();
assert(std::string("12345").find(tone) != std::string::npos);
std::string pinyin = c + v_without_tone;
if (!c.empty())
{
// 多音节
std::unordered_map<std::string, std::string> v_rep_map = {{"uei", "ui"}, {"iou", "iu"}, {"uen", "un"}};
auto it = v_rep_map.find(v_without_tone);
if (it != v_rep_map.end())
{
pinyin = c + it->second;
}
}
else
{
// 单音节
std::unordered_map<std::string, std::string> pinyin_rep_map = {
{"ing", "ying"}, {"i", "yi"}, {"in", "yin"}, {"u", "wu"}};
auto it = pinyin_rep_map.find(pinyin);
if (it != pinyin_rep_map.end())
{
pinyin = it->second;
}
else
{
std::unordered_map<char, std::string> single_rep_map = {
{'v', "yu"}, {'e', "e"}, {'i', "y"}, {'u', "w"}};
auto it2 = single_rep_map.find(pinyin[0]);
if (it2 != single_rep_map.end())
{
pinyin = it2->second + pinyin.substr(1);
}
}
}
std::vector<std::string> phone = SplitString(pinyin_to_symbol_map_[pinyin], ' ');
word2ph.push_back(phone.size());
for (const auto &ph : phone)
{
phones_list.push_back(ph);
}
tones_list.insert(tones_list.end(), phone.size(), tone - '0');
}
}
return std::make_tuple(phones_list, tones_list, word2ph);
}
std::string ChineseG2P::ReplacePunctuations(const std::string &text)
{
std::string modified_text = text;
// 将英文的"..."替换为"…"
StrReplaceAll(modified_text, "...", "…");
// // 替换先定义的字符映射
for (const auto &pair : rep_map)
{
auto word_splits = SplitUtf8String(modified_text);
if (HanListContains(word_splits, pair.first))
{
StrReplaceAll(modified_text, pair.first, pair.second);
}
}
return modified_text;
}
std::string ChineseG2P::TextAn2cn(std::string &text)
{
// 使用正则表达式查找匹配的数字
std::regex pattern(R"(\d+(?:\.?\d+)?)");
std::sregex_iterator iter(text.begin(), text.end(), pattern);
std::sregex_iterator end;
std::vector<std::string> numbers;
// 将找到的数字存储到 vector 中
while (iter != end)
{
numbers.push_back(iter->str());
++iter;
}
// 替换每个数字
for (const std::string &number : numbers)
{
text.replace(text.find(number), number.length(), an2cn_.Process(number));
}
return text;
}
// Function to normalize text by converting numbers and replacing punctuation.
std::string ChineseG2P::TextNormalize(const std::string &text)
{
std::string normalized_text = text;
PLOG(PDEBUG, "g2p text_norm 输入: " + normalized_text);
// 进行阿拉伯数字到汉字数字的转换
normalized_text = TextAn2cn(normalized_text);
PLOG(PDEBUG, "g2p text_norm an2cn后: " + normalized_text);
// 替换中文标点符号为英文标点符号
normalized_text = ReplacePunctuations(normalized_text);
PLOG(PDEBUG, "g2p text_norm 替换标点后: " + normalized_text);
return normalized_text;
}
g2p_data ChineseG2P::G2P(std::string &text)
{
PLOG(PDEBUG, "g2p 输入句子: " + text);
std::vector<std::string> phones;
std::vector<int> tones;
std::vector<int> word2ph;
// text是从原始句子中切出来的中文部分,可能包含标点符号,如 '你好啊.你是,我的男朋友?'
// sentences是根据标点符号切分的小的分句,标点符号跟随前面的文本,如 ['你好啊.', '你是,', '我的男朋友?']
auto segments = SplitChineseSentenceToParts(text);
PLOG(PDEBUG, "g2p 标点分句: " + ConcatStrList(segments, "|"));
std::tie(phones, tones, word2ph) = G2PInternal(segments);
// Prepend and append special tokens to the lists
phones.insert(phones.begin(), "_");
phones.push_back("_");
tones.insert(tones.begin(), 0);
tones.push_back(0);
word2ph.insert(word2ph.begin(), 1);
word2ph.push_back(1);
return std::make_tuple(phones, tones, word2ph);
}
g2p_data ChineseG2P::G2PInternal(const std::vector<std::string> &segments)
{
std::vector<std::string> all_initials;
std::vector<std::string> all_finals;
std::vector<std::string> phones;
std::vector<int> tones;
std::vector<int> word2ph;
// 对每个标点符号分割的小分句进行分词,并返回对应的词性,如 ['你好啊'] -> [[(‘你好', 'l) ('啊', 'zg')]]
for (auto &seg : segments)
{
PLOG(TRACE, DELIMITER_LINE);
PLOG(TRACE, "g2p processing seg " + seg);
// 保存当前分句的声母和韵母
std::vector<std::string> initials;
std::vector<std::string> finals;
// 返回值类型: vector<WordPosPair>
auto seg_cut_list = word_spliter_.Process(seg);
PLOG(TRACE, "g2p 分词结果: " + ConcatList(seg_cut_list, "|"));
// 计算拼音
for (auto &[word, pos] : seg_cut_list)
{
PLOG(TRACE, "g2p 开始处理词语: " + word + "/" + pos);
// eng表示英文字母和数字
if (pos == "eng")
{
PLOG(TRACE, "g2p 是英文和数字: " + word);
continue;
}
// 如果词语在自定义词表里面,采用自定义的发音
if (custom_phone_.find(word) != custom_phone_.end())
{
PLOG(TRACE, "g2p 是自定义词语: " + word);
auto [sub_initials, sub_finals] = custom_phone_[word];
initials.insert(initials.end(), sub_initials.begin(), sub_initials.end());
finals.insert(finals.end(), sub_finals.begin(), sub_finals.end());
}
else
{
PLOG(TRACE, "g2p 提取拼音: " + word);
auto [sub_initials, sub_finals] = pinyin_.Process(word);
PLOG(TRACE, "g2p 原始声母: " + ConcatStrList(sub_initials, "|"));
PLOG(TRACE, "g2p 原始韵母: " + ConcatStrList(sub_finals), "|");
sub_finals = tone_adjuster_.Process(word, pos, sub_finals);
PLOG(TRACE, "g2p 修改声调后韵母: " + ConcatStrList(sub_finals));
initials.insert(initials.end(), sub_initials.begin(), sub_initials.end());
finals.insert(finals.end(), sub_finals.begin(), sub_finals.end());
}
}
PLOG(TRACE, "g2p 该seg全部声母: " + ConcatStrList(initials, "|"));
PLOG(TRACE, "g2p 该seg全部韵母: " + ConcatStrList(finals, "|"));
all_initials.insert(all_initials.end(), initials.begin(), initials.end());
all_finals.insert(all_finals.end(), finals.begin(), finals.end());
// 将拼音转换为音素和音调
auto [cur_phones, cur_tones, cur_word2ph] = ConvertInitialFinalToPhoneTone(initials, finals);
phones.insert(phones.end(), cur_phones.begin(), cur_phones.end());
tones.insert(tones.end(), cur_tones.begin(), cur_tones.end());
word2ph.insert(word2ph.end(), cur_word2ph.begin(), cur_word2ph.end());
PLOG(TRACE, DELIMITER_LINE);
}
PLOG(PDEBUG, "g2p 整句内容: " + ConcatStrList(segments, "|"));
PLOG(PDEBUG, "g2p 整句声母: " + ConcatStrList(all_initials, "|"));
PLOG(PDEBUG, "g2p 整句韵母: " + ConcatStrList(all_finals, "|"));
PLOG(PDEBUG, "g2p 整句phones: " + ConcatStrList(phones, "|"));
PLOG(PDEBUG, "g2p 整句tones: " + ConcatIntList(tones, "|"));
PLOG(PDEBUG, "g2p 整句word2ph: " + ConcatIntList(word2ph, "|"));
return make_tuple(phones, tones, word2ph);
}
phone_data ChineseG2P::CleanedTextToSequence(const g2p_data &g2p_data_, const std::string &language)
{
auto phones = std::get<0>(g2p_data_);
auto tones = std::get<1>(g2p_data_);
auto word2ph = std::get<2>(g2p_data_);
std::vector<int> int_phones;
for (auto &symbol : phones)
{
// 假设 symbol 在 _symbol_to_id 中一定能找到。
int_phones.push_back(_symbol_to_id[symbol]);
}
int tone_start = language_tone_start_map[language];
std::vector<int> adjusted_tones;
for (int tone : tones)
{
adjusted_tones.push_back(tone + tone_start);
}
std::vector<int> lang_ids;
for (int i = 0; i < int_phones.size(); i++)
{
lang_ids.push_back(language_id_map[language]);
}
// 增加空白
int_phones = Intersperse<int>(int_phones, 0);
adjusted_tones = Intersperse<int>(adjusted_tones, 0);
lang_ids = Intersperse<int>(lang_ids, 0);
// 下面的循环操作将每个元素值翻倍,然后将第一个元素增加1
for (size_t i = 0; i < word2ph.size(); ++i)
{
word2ph[i] = word2ph[i] * 2;
}
word2ph[0] += 1;
PLOG(PDEBUG, "g2p 调整后phones(size=" + std::to_string(int_phones.size()) + "):" + ConcatIntList(int_phones, "|"));
PLOG(PDEBUG, "g2p 调整后tones(size=" + std::to_string(adjusted_tones.size()) + "):" + ConcatIntList(adjusted_tones, "|"));
PLOG(PDEBUG, "g2p 调整后word2ph(size=" + std::to_string(word2ph.size()) + "):" + ConcatIntList(word2ph, "|"));
PLOG(PDEBUG, "g2p lang_ids(size=" + std::to_string(lang_ids.size()) + "):" + ConcatIntList(lang_ids, "|"));
return std::make_tuple(int_phones, adjusted_tones, lang_ids, word2ph);
}
std::tuple<std::string, phone_data> ChineseG2P::Process(SentLangPair &sent_lang)
{
PLOG(PDEBUG, "g2p 输入文本: " + sent_lang.sent);
auto norm_text = TextNormalize(sent_lang.sent);
PLOG(PDEBUG, "g2p norm后文本: " + norm_text);
auto g2p_res = G2P(norm_text);
auto res = CleanedTextToSequence(g2p_res, sent_lang.lang);
return std::make_tuple(norm_text, res);
}