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

471 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 "tone_adjuster.hpp"
ToneAdjuster::ToneAdjuster(const std::string &local_resource_root) : word_spliter_(WordSpliter::GetInstance(local_resource_root))
{
auto t0 = clk::now();
PLOG(INFO, "ToneAdjuster 开始初始化...");
resource_root_ = local_resource_root;
yuqici_pattern_ = SplitUtf8String("吧呢啊呐噻嘛吖嗨呐哦哒额滴哩哟喽啰耶喔诶");
de_pattern_ = SplitUtf8String("的地得");
men_pattern_ = SplitUtf8String("们子");
shang_pattern_ = SplitUtf8String("上下里");
lai_pattern_ = SplitUtf8String("来去");
lai_aux_pattern_ = SplitUtf8String("上下进出回过起开");
ge_aux_pattern_ = SplitUtf8String("几有两半多各整每做是");
digital_pattern_ = SplitUtf8String("一二三四五六七八九壹贰叁肆伍陆柒捌玖");
punc_pattern_ = SplitUtf8String(":,;。?!“”‘’':,;.?!");
// spliter_ = WordSpliter::GetInstance(local_resource_root);
auto json_path = resource_root_ + "common/text_processing_jsons/default_tone_words.json";
ParseDefaultToneWordJson(json_path);
auto t1 = clk::now();
auto d1 = std::chrono::duration_cast<ms>(t1 - t0);
PLOG(INFO, " ToneAdjuster 初始化完成, timecost: " + std::to_string(d1.count()) + "ms");
}
void ToneAdjuster::ParseDefaultToneWordJson(const std::string &json_path)
{
auto json_obj = LoadJson(json_path);
must_neural_words_ = json_obj["must_neural_tone_words"].get<std::vector<std::string>>();
not_neural_words_ = json_obj["must_not_neural_tone_words"].get<std::vector<std::string>>();
PLOG(PDEBUG, "tone_adjuster 必须轻声的词语: " + ConcatStrList(must_neural_words_, "|"));
PLOG(PDEBUG, "tone_adjuster 不能轻声的词语: " + ConcatStrList(not_neural_words_, "|"));
}
// 判断一个字符串是否以指定前缀开头
bool ToneAdjuster::StartsWith(const std::string &str, const std::string &prefix)
{
// 如果字符串长度小于前缀长度,直接返回 false
if (str.size() < prefix.size())
{
return false;
}
// 使用 std::string::compare 检查前缀
return str.compare(0, prefix.size(), prefix) == 0;
}
// 判断某个汉字是否为中文的数字,包含大写和小写形式
bool ToneAdjuster::IsChineseDigital(const std::string &hanzi)
{
return HanziIn(hanzi, digital_pattern_);
}
// 判断某个词语中的对应索引的汉字是否为中文的数字,包含大写和小写形式
bool ToneAdjuster::IsChineseDigital(const std::string &word, int index)
{
return PhrasePartIn(word, index, 1, digital_pattern_);
}
// 判断一个汉字是否在所给的模式中,如 HanziIn("我", {"我" “你", "他"}) = true
bool ToneAdjuster::HanziIn(const std::string &hanzi, const std::vector<std::string> &pattern)
{
return std::find(pattern.begin(), pattern.end(), hanzi) != pattern.end();
}
// 判断一个词语是否在所给的模式中,如 PhraseIn("阿里巴巴", {"北京", “阿里巴巴", "他"}) = true
bool ToneAdjuster::PhraseIn(const std::string &hanzi, const std::vector<std::string> &pattern)
{
return std::find(pattern.begin(), pattern.end(), hanzi) != pattern.end();
}
// 判断一个词语的某一部分是否在所给的模式中,如 PhrasePartIn("阿里巴巴", 2, 2, {"北京", “巴巴", "他"}) = true
bool ToneAdjuster::PhrasePartIn(const std::string &word, int start_index, int size, const std::vector<std::string> &pattern)
{
// 将词语拆分为单个的汉字组成的vector
auto hanzi_list = SplitUtf8String(word);
// 为了支持类似python中的负数索引,例如word[-1]表示最后一个汉字
if (start_index < 0)
{
start_index += hanzi_list.size();
}
std::string target_phrase = "";
for (int i = 0; i < size; i++)
{
target_phrase += hanzi_list[start_index + i];
}
return std::find(pattern.begin(), pattern.end(), target_phrase) != pattern.end();
}
// 判断词性是否在所在的模式中,如 PosIn("n", {"a", "n", "v"}) = true
// 注意词性可能是多个字母,因此采用char类型表示不合适,应该采用std::string
bool ToneAdjuster::PosIn(const std::string &pos, const std::vector<std::string> &pattern)
{
return std::find(pattern.begin(), pattern.end(), pos) != pattern.end();
}
// 寻找词语中的第一个字在原始句子中的位置索引
int ToneAdjuster::FindSubstring(const std::vector<std::string> &str_list, const std::vector<std::string> &sub_str_list)
{
int pos = -1;
for (int i = 0; i < str_list.size(); i++)
{
if (str_list[i] == sub_str_list[0])
{
pos = i;
break;
}
}
return static_cast<int>(pos); // 将 size_t 类型的 pos 转换为 int 类型并返回
}
int ToneAdjuster::FindSubstring(const std::string &str, const std::string &sub_str)
{
auto str_list = SplitUtf8String(str);
auto sub_str_list = SplitUtf8String(sub_str);
return FindSubstring(str_list, sub_str_list);
}
std::vector<std::string> ToneAdjuster::SplitWord(const std::string &word)
{
auto utf8_word = SplitUtf8String(word);
auto word_list = word_spliter_.ProcessWoPos(word, true);
// 输入的word里面没有汉字,则直接返回空字符串数组
if (word_list.size() < 1)
{
return {};
}
std::sort(word_list.begin(), word_list.end(), [](const std::string &a, const std::string &b)
{ return a.size() < b.size(); });
auto first_subword = word_list[0];
auto first_subword_utf8 = SplitUtf8String(first_subword);
auto first_begin_idx = FindSubstring(utf8_word, first_subword_utf8);
std::vector<std::string> new_word_list;
if (first_begin_idx == 0)
{
std::string sencond_subword = "";
for (int i = first_subword_utf8.size(); i < utf8_word.size(); i++)
{
sencond_subword += utf8_word[i];
}
// 不添加空的字符串
if (!first_subword.empty())
{
new_word_list.push_back(first_subword);
}
if (!sencond_subword.empty())
{
new_word_list.push_back(sencond_subword);
}
}
else
{
std::string sencond_subword = "";
for (int i = 0; i < utf8_word.size() - first_subword_utf8.size(); i++)
{
sencond_subword += utf8_word[i];
}
// 不添加空的字符串
if (!sencond_subword.empty())
{
new_word_list.push_back(sencond_subword);
}
if (!first_subword.empty())
{
new_word_list.push_back(first_subword);
}
}
return new_word_list;
}
// 判断音调中是否都是3声
bool ToneAdjuster::AllToneThree(const std::vector<std::string> &finals)
{
bool is_all_tone_three = true;
for (int i = 0; i < finals.size(); i++)
{
if (finals[i].back() != '3')
{
is_all_tone_three = false;
break;
}
}
return is_all_tone_three;
}
// 对轻声进行处理
std::vector<std::string> ToneAdjuster::NeuralSandhi(const string &raw_word, const string &pos, std::vector<std::string> &finals)
{
auto word = SplitUtf8String(raw_word);
// 输入文本中没有汉字,直接返回
if (word.size() < 1)
{
PLOG(WARNING, "tone_adjuster NeuralSandhi 没有中文: " + raw_word);
return finals;
}
// 处理叠词,例如奶奶, 试试, 旺旺,爸爸等,将第二个字转换为轻声
for (size_t j = 0; j < word.size(); ++j)
{
if (j >= 1 && word[j] == word[j - 1] && (PosIn(pos, {"n", "a", "v"})) && PhraseIn(raw_word, not_neural_words_))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 叠词: " + raw_word);
finals[j].back() = '5';
}
}
int ge_idx = FindSubstring(raw_word, "个");
// 对语气词进行处理
if (word.size() >= 1 && PhrasePartIn(raw_word, -1, 1, yuqici_pattern_))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 语气词: " + raw_word);
finals.back().back() = '5';
}
// 对"的地得"进行处理
else if (word.size() >= 1 && PhrasePartIn(raw_word, -1, 1, de_pattern_))
{
// PLOG(TRACE, "tone_adjuster NeuralSandhi 的地得: " + raw_word);
// TODO 这里会有问题,例如 ‘机会难得’,
// finals.back().back() = '5';
}
else if (word.size() > 1 && PhrasePartIn(raw_word, -1, 1, men_pattern_) && PosIn(pos, {"r", "n"}) && PhraseIn(raw_word, not_neural_words_))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 子们: " + raw_word);
finals.back().back() = '5';
}
else if (word.size() > 1 && PhrasePartIn(raw_word, -1, 1, shang_pattern_) && PosIn(pos, {"s", "l", "f"}))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 上下里: " + raw_word);
finals.back().back() = '5';
}
else if (word.size() > 1 && PhrasePartIn(raw_word, -1, 1, lai_pattern_) and PhrasePartIn(raw_word, -2, 1, lai_aux_pattern_))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 来去: " + raw_word);
finals.back().back() = '5';
}
else if ((ge_idx >= 1 && (IsChineseDigital(raw_word, ge_idx - 1) || PhrasePartIn(raw_word, ge_idx - 1, 1, ge_aux_pattern_))) || raw_word == "个")
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 个: " + raw_word);
finals[ge_idx].back() = '5';
}
else
{
if (PhraseIn(raw_word, must_neural_words_) || (word.size() > 2 && PhrasePartIn(raw_word, word.size() - 2, 2, must_neural_words_)))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 必须轻声的词: " + raw_word);
finals.back().back() = '5';
}
}
auto word_list = SplitWord(raw_word);
if (word_list.size() < 1)
{
PLOG(WARNING, "tone_adjuster NeuralSandhi 没有汉字: " + raw_word);
return finals;
}
auto first_subword_length = SplitUtf8String(word_list[0]).size();
std::vector<std::vector<std::string>> finals_list;
finals_list.push_back(std::vector<std::string>(finals.begin(), finals.begin() + first_subword_length));
finals_list.push_back(std::vector<std::string>(finals.begin() + first_subword_length, finals.end()));
for (size_t i = 0; i < word_list.size(); ++i)
{
auto cur_word_utf8 = SplitUtf8String(word_list[i]);
if (PhraseIn(word_list[i], must_neural_words_) || (cur_word_utf8.size() >= 2 && PhrasePartIn(word_list[i], cur_word_utf8.size() - 2, 2, must_neural_words_)))
{
PLOG(TRACE, "tone_adjuster NeuralSandhi 细分后必须轻声的词: " + word_list[i]);
finals_list[i].back().back() = '5';
}
}
std::vector<std::string> merged_finals;
for (const auto &part : finals_list)
{
merged_finals.insert(merged_finals.end(), part.begin(), part.end());
}
return merged_finals;
}
// 对 “不”进行处理,如看不懂
std::vector<std::string> ToneAdjuster::BuSandhi(const string &word, const string &pos, std::vector<std::string> &finals)
{
auto utf8_word = SplitUtf8String(word);
// 看不懂
if (utf8_word.size() == 3 && utf8_word[1] == "不")
{
finals[1].back() = '5';
}
else
{
// "不" before tone4 should be bu2, e.g. 不怕
for (int i = 0; i < utf8_word.size(); i++)
{
if (utf8_word[i] == "不" && i + 1 < utf8_word.size() && finals[i + 1].back() == '4')
{
finals[i].back() = '2';
}
}
}
return finals;
}
// 对 “一“ 在词语中的音调进行调整
std::vector<std::string> ToneAdjuster::YiSandhi(const string &word, const string &pos, std::vector<std::string> &finals)
{
auto utf8_word = SplitUtf8String(word);
// 包含一且全部都是数字,一照样发一声。如一百五十,五三一七,
if (HanziIn("一", utf8_word))
{
bool has_no_digital = false;
for (int i = 0; i < utf8_word.size(); i++)
{
if (!IsChineseDigital(utf8_word[i]))
{
has_no_digital = true;
break;
}
}
if (has_no_digital)
{
return finals;
}
}
// "一" between reduplication words should be yi5, e.g. 看一看
if (utf8_word.size() == 3 && utf8_word[1] == "一" && utf8_word[0] == utf8_word[2])
{
finals[1].back() = '5';
}
else if (StartsWith(word, "第一"))
{
finals[1].back() = '1';
}
else
{
for (int i = 0; i < utf8_word.size(); i++)
{
if (utf8_word[i] == "一" && i + 1 < utf8_word.size())
{
// "一" before tone4 should be yi2, e.g.一段
if (finals[i + 1].back() == '4')
{
finals[i].back() = '2';
}
// "一" before non - tone4 should be yi4, e.g.一天
// "一" 后面如果是标点,还读一声
else if (!HanziIn(utf8_word[i + 1], punc_pattern_))
{
finals[i].back() = '4';
}
}
}
}
return finals;
}
// 对词语中的三声进行特殊处理
std::vector<std::string> ToneAdjuster::ThreeToneSandhi(const string &word, const string &pos, std::vector<std::string> &finals)
{
auto utf8_word = SplitUtf8String(word);
// 如果两个字都是三声,第一个三声要变成二声,如你好
if (utf8_word.size() == 2 && AllToneThree(finals))
{
finals[0].back() = '2';
}
else if (utf8_word.size() == 3)
{
auto word_list = SplitWord(word);
if (AllToneThree(finals))
{
// disyllabic + monosyllabic, e.g. 蒙古/包
if (word_list[0].size() == 2)
{
finals[0].back() = '2';
finals[1].back() = '2';
}
// monosyllabic + disyllabic, e.g. 纸/老虎
else if (word_list[0].size() == 1)
{
finals[1].back() = '2';
}
}
else
{
std::vector<std::vector<std::string>> finals_list;
int first_word_size = SplitUtf8String(word_list[0]).size();
finals_list.push_back(std::vector<std::string>(finals.begin(), finals.begin() + first_word_size));
finals_list.push_back(std::vector<std::string>(finals.begin() + first_word_size, finals.end()));
if (finals_list.size() == 2)
{
std::vector<std::string> updated_finals;
for (int i = 0; i < finals_list.size(); i++)
{
auto sub = finals_list[i];
// e.g. 所有/人
if (sub.size() == 2 && AllToneThree(sub))
{
sub[0].back() = '2';
}
else if (i == 1 && !AllToneThree(sub) && finals_list[i][0].back() == '3' && finals_list[0][finals_list[0].size() - 1].back() == '3')
{
// TODO
// finals_list[0] = finals_list[0][finals_list[0].size() - 1];
finals_list[0].back() = '2';
}
updated_finals.insert(updated_finals.end(), finals_list[i].begin(), finals_list[i].end());
}
finals = updated_finals;
}
}
}
// split idiom into two words who's length is 2
else if (utf8_word.size() == 4)
{
std::vector<std::vector<std::string>> finals_list;
finals_list.push_back(std::vector<std::string>(finals.begin(), finals.begin() + 2));
finals_list.push_back(std::vector<std::string>(finals.begin() + 2, finals.end()));
std::vector<std::string> updated_finals;
for (int i = 0; i < finals_list.size(); i++)
{
if (AllToneThree(finals_list[i]))
{
finals_list[i][0].back() = '2';
}
updated_finals.insert(updated_finals.end(), finals_list[i].begin(), finals_list[i].end());
}
finals = updated_finals;
}
return finals;
}
std::vector<std::string> ToneAdjuster::Process(const string &word, const string &pos, std::vector<std::string> &finals)
{
PLOG(PDEBUG, "tone_adjuster 输入韵母: " + ConcatStrList(finals, "|"));
finals = BuSandhi(word, pos, finals);
PLOG(PDEBUG, "tone_adjuster 处理'不'后: " + ConcatStrList(finals, "|"));
finals = YiSandhi(word, pos, finals);
PLOG(PDEBUG, "tone_adjuster 处理'一'后: " + ConcatStrList(finals, "|"));
finals = NeuralSandhi(word, pos, finals);
PLOG(PDEBUG, "tone_adjuster 处理轻声后: " + ConcatStrList(finals, "|"));
finals = ThreeToneSandhi(word, pos, finals);
PLOG(PDEBUG, "tone_adjuster 处理三声后: " + ConcatStrList(finals, "|"));
return finals;
}