目录
一、贝叶斯定理(基础)
贝叶斯定理:在已知P(A|B)的情况下如何求得P(B|A)。其基本求解公式为:
条件概率:P(A|B)表示事件B已经发生的前提下,事件A发生的概率,叫做事件B发生下事件A的条件概率。
二、拼写检测器原理
给定一个单词 w ,我们的任务是从单词库中选择和它最相似的拼写正确的单词 c
对应的贝叶斯问题就是,给定一个词 w, 在所有正确的拼写词中, 我们想要找一个单词 c, 使得对于 w 的正确条件概率最大, 也就是说:
其中argmaxc表示,用来枚举所有可能的 c ,并且选取最大的概率。
结合贝叶斯理论,上式即为:
因为用户可以输错任何词, 因此对于任何 c 来讲, 出现 w 的概率 P(w) 都是一样的, 从而我们在上式中忽略它, 写成:
argmaxc P(c|w) 正比于 argmaxc P(w|c) P(c)
因此argmaxc P(w|c) P(c)就是编辑距离与P(c)的的乘积
三、编辑距离
编辑距离:两个词之间的编辑距离定义为使用了几次【插入】(在词中插入一个单字母), 【删除】(删除一个单字母), 【交换】(交换相邻两个字母), 【替换】(把一个字母换成另一个)的操作从一个词变到另一个词。
一般情况下,编辑距离为2时已经可以覆盖大部分情况
四、先验概率P(c)
正确单词 c 出现的概率
五、Unity中实现
1、将词典文件和训练文件放在StreamingAssets文件夹下,便于读取
2、 定义按钮操作和文件位置
public Button ID_DicTrain_Button;
public Button ID_Bayes_Button;
public InputField ID_InputField;
static Dictionary<string, int> Dic;//词典
static string trainingFile = "training.txt";//训练文件
static string dicFile = "dic.txt";//根据训练文件生成词典里的单词词频数文件(每个单词出现的次数)
static string dicOrigin = "Dictionary1.dic";//原词典
public string IDData_Path = Application.streamingAssetsPath + "/" + trainingFile;//ID数据文件地址
public string dicData_Path = Application.streamingAssetsPath + "/" + dicFile;//生成词典位置
public string dicOrigin_Path = Application.streamingAssetsPath + "/" + dicOrigin;//原词典位置
3、Start函数执行训练词典操作(已存在的话直接加载)
if (File.Exists(dicData_Path))//文件已存在
{
Debug.Log("加载词典中...");
LoadDic();//读取已经训练完成的词典
Debug.Log("加载词典完成");
}
else
{
//训练词典
Debug.Log("训练词典中...");//词频数文件不存在,根据dic词典训练
Dic = LoadUSDic();//训练词典
TrainDic(IDData_Path, Dic);//加载训练文件
StringBuilder dicBuilder = new StringBuilder();
foreach (var item in Dic)
{
dicBuilder.AppendLine(item.Key + "\t" + item.Value);//单词和其个数
}
File.WriteAllText(dicData_Path, dicBuilder.ToString());//写入词频数文件
var wordCount = Dic.Count;
Debug.Log("训练完成..." + wordCount);
}
4、贝叶斯主函数执行
//输入单词
var inputWord = ID_InputField.text;//输入的字符
if (!string.IsNullOrEmpty(inputWord))
{
if (Dic.Keys.Contains(inputWord))//如果单词在词典中已经存在,正确
{
Debug.Log("你输入的字符 【" + inputWord + "】 是正确的!");
}
else//如果不正确,获取建议单词
{
var suggestWords = GetSuggestWords(inputWord);
Debug.Log("候选字符: ");
foreach (var word in suggestWords)
{
Debug.Log("\t\t\t " + word);
}
}
}
else
{
Debug.Log("没有获取到识别的字符");
}
5、其它子函数
/// <summary>
/// 加载已经训练完成的词典
/// </summary>
public void LoadDic()
{
Dic = new Dictionary<string, int>();
var lines = File.ReadAllLines(dicData_Path);//读取已经训练完成的词典
foreach (var line in lines)
{
if (line != "")
{
var dicItem = line.Split('\t');
if (dicItem.Length == 2)
Dic.Add(dicItem[0], int.Parse(dicItem[1]));
}
}
}
/// <summary>
/// 训练词典
/// </summary>
/// <param name="IDData_Path"></param>
/// <param name="ht"></param>
public void TrainDic(string IDData_Path, Dictionary<string, int> ht)
{
StreamReader reader = new StreamReader(IDData_Path);
string sLine = "";//存放每一个句子
string pattern = @"[a-zA-Z0-9]+";//正则表达式匹配单词,[a-z] 表示所有小写字母,[A-Z] 表示所有大写字母,[0-9] 表示所有数字
Regex regex = new Regex(pattern);
int count = 0;//计算单词的个数
while (sLine != null)
{
sLine = reader.ReadLine();//正则表达式单行匹配模式
if (sLine != null)
{
//sLine = sLine.ToLower().Replace("'", " ");
var matchWords = regex.Matches(sLine);
foreach (Match match in matchWords)
{
var word = match.Value;//还是读取的每一行值
if (!ht.ContainsKey(word))
{
count++;
ht.Add(word, 1);
}
else
{
ht[word]++;
}
}
}
}
reader.Close();
}
/// <summary>
/// 从en-US读取词语【词语开始[Words]】
/// </summary>
/// <returns></returns>
public Dictionary<string, int> LoadUSDic()
{
var dic = new Dictionary<string, int>();
string currentSection = "";
FileStream fs = new FileStream(IDData_Path, FileMode.Open, FileAccess.Read, FileShare.Read);
StreamReader sr = new StreamReader(fs, Encoding.UTF8);
Debug.Log("fuck");
while (sr.Peek() >= 0)//判断读取的文件是否结束,如果结束了会返回int型 -1
{
string tempLine = sr.ReadLine().Trim();//读取每一行字符串,Trim()删除字符串头部及尾部出现的空格
if (tempLine.Length > 0)//当前行的字符长度
{
switch (tempLine)//switch表达式
{
case "[Words]"://switch表达式的值和case的值匹配上了,需要执行的代码;
currentSection = tempLine;
break;
default://switch表达式的值和case的值都匹配不上,需要执行的代码;
switch (currentSection)
{
case "[Words]": // dictionary word list
// splits word into its parts
string[] parts = tempLine.Split('/');//字符分割成一块块并储存在数组中
dic.Add(parts[0], 1);
break;
} // currentSection swith
break;
} //tempLine switch
} // if templine
} // read line
sr.Close();
fs.Close();
return dic;
}
/// <summary>
/// 编辑距离为1的词语
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
public List<string> GetEdits1(string word)
{
var n = word.Length;
var tempWord = "";
var editsWords = new List<string>();
for (int i = 0; i < n; i++)//delete一个字母的情况
{
tempWord = word.Substring(0, i) + word.Substring(i + 1);
if (!editsWords.Contains(tempWord))
editsWords.Add(tempWord);
}
for (int i = 0; i < n - 1; i++)//调换transposition一个字母的情况
{
tempWord = word.Substring(0, i) + word.Substring(i + 1, 1) + word.Substring(i, 1) + word.Substring(i + 2);
if (!editsWords.Contains(tempWord))
tempWord = tempWord.ToUpper();//只要大写字母字符
editsWords.Add(tempWord);
}
for (int i = 0; i < n; i++)//替换replace一个字母的情况
{
string t = word.Substring(i, 1);
for (int ch = 'a'; ch <= 'z'; ch++)
{
if (ch != Convert.ToChar(t))
{
tempWord = word.Substring(0, i) + Convert.ToChar(ch) + word.Substring(i + 1);
if (!editsWords.Contains(tempWord))
tempWord = tempWord.ToUpper();//只要大写字母字符
editsWords.Add(tempWord);
}
}
}
for (int i = 0; i <= n; i++)//insert一个字母的情况
{
//string t = word.Substring(i, 1);
for (int ch = 'a'; ch <= 'z'; ch++)
{
tempWord = word.Substring(0, i) + Convert.ToChar(ch) + word.Substring(i);
if (!editsWords.Contains(tempWord))
editsWords.Add(tempWord);
}
}
return editsWords;
}
/// <summary>
/// 获取编辑距离为2的单词
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
public List<string> GetEdits2(string word)
{
var words = GetEdits1(word);
var result = words.AsReadOnly().ToList();
foreach (var edit in words)
{
GetEdits1(edit).ForEach(w =>
{
if (Dic.ContainsKey(w))
{
result.Add(w);
}
});
}
return result;
}
/// <summary>
/// 获取建议词语
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
public List<string> GetSuggestWords(string word)
{
var result = GetEdits1(word).Where(w => Dic.ContainsKey(w)).ToList();//输出编辑距离为1的候选项
//result.Add(word);
if (result.Count == 0)
{
result = GetEdits2(word);//输出编辑距离为2的候选项
if (result.Count == 0)
{
result.Add(word);
}
}
// 按先验概率排序
result = result.OrderByDescending(w => Dic.ContainsKey(w) ? Dic[w] : 1).ToList();
return result.Take(Math.Min(result.Count, 10)).ToList();//控制输出的个数
}
6、挂在unity场景里
六、结果
加载训练词典(已存在,不存在就先训练)
结果