上篇文章我们讲解了微信机器人的环境搭建及演示,这期我们来说一下其中在模糊匹配搜索时用到的Segement子项目,也就是其中的中文分词匹配器。
一、原理介绍:
其实这个子项目中的分词插件和solr的IK分词器类似,都是可以支持将一句完整的中文拆成多个单词。例如:我是中国人。 分词器会拆分成:我,是,中国,中国人。然后将拆分的词组和你要匹配的内容进行一个相似度匹配。如果超过预设的匹配值。则匹配成功。
二、分词组件的引用:
首先我们需要再创建一个子项目Segements,然后在项目中引用jieba这个组件,这里需要使用nuget(你可以理解是c#的maven),在nuget中搜索jieba,然后选择安装。
安装后在项目中查看是否引用到,如下图所示
三、使用代码详解
using JiebaNet.Segmenter.PosSeg;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Segments
{
public class Segments
{
public static double CalculateCosineSimilarity(List<String> array1, List<String> array2)
{
Dictionary<string, int> wordCount1 = new Dictionary<string, int>();
Dictionary<string, int> wordCount2 = new Dictionary<string, int>();
foreach (string word in array1)
{
if (wordCount1.ContainsKey(word))
wordCount1[word]++;
else
wordCount1[word] = 1;
}
foreach (string word in array2)
{
if (wordCount2.ContainsKey(word))
wordCount2[word]++;
else
wordCount2[word] = 1;
}
double dotProduct = 0;
double magnitude1 = 0;
double magnitude2 = 0;
foreach (KeyValuePair<string, int> entry in wordCount1)
{
int count1 = entry.Value;
int count2 = wordCount2.ContainsKey(entry.Key) ? wordCount2[entry.Key] : 0;
dotProduct += count1 * count2;
magnitude1 += count1 * count1;
}
foreach (KeyValuePair<string, int> entry in wordCount2)
{
int count2 = entry.Value;
magnitude2 += count2 * count2;
}
magnitude1 = Math.Sqrt(magnitude1);
magnitude2 = Math.Sqrt(magnitude2);
if (magnitude1 == 0 || magnitude2 == 0)
return 0;
return dotProduct / (magnitude1 * magnitude2);
}
private static string getErrorCode(string question)
{
string pattern = @"\d+";
Match match = Regex.Match(question, pattern);
if (match.Success)
{
return match.Value;
}
else
{
return "";
}
}
public static List<JObject> selectQuestion(string userInput, List<JObject> param2)
{
//问题答案表
var questionAnswerTable = new Dictionary<string, JObject> { };
String temp = getErrorCode(userInput);
if (!temp.Equals(""))
{
userInput = temp;
}
for (int i = 0; i < param2.Count; i++)
{
String matchStr = "";
matchStr = ""+param2[i]["errorCode"] + param2[i]["errorDetail"];
if (!questionAnswerTable.ContainsKey(matchStr)) {
questionAnswerTable.Add(matchStr, param2[i]);
}
}
// 用户输入问题
// string userInput = "好像是什么检查好像失败不正确?";
//使用PosSegmenter对用户输入进行分词和词性标注
PosSegmenter segmenter = new PosSegmenter();
var segments = segmenter.Cut(userInput);
//构造分词列表
var queryTokens = segments.Select(segment => segment.Word).ToList();
//在问题答案表中进行匹配
string bestMatchAnswer = "";
String mMostMatch_Sentence = "";
//*******************************【相似匹配度设置】**************************************
double mMostMatch_Sentence_double = 0.1;
List<JObject> answerList = new List<JObject>();
foreach (var kvp in questionAnswerTable)
{
var question = kvp.Key;
var answer = kvp.Value;
//使用PosSegmenter对问题进行分词和词性标注
var questionSegments = segmenter.Cut(question);
//构造问题的分词列表
var questionTokens = questionSegments.Select(segment => segment.Word).ToList();
//进行分词匹配,这里可以使用自定义的相似度算法
//if (queryTokens.SequenceEqual(questionTokens))
{
if (mMostMatch_Sentence_double < CalculateCosineSimilarity(queryTokens, questionTokens))
{
mMostMatch_Sentence_double = CalculateCosineSimilarity(queryTokens, questionTokens);
mMostMatch_Sentence = kvp.Key;
if (answerList.Count >= 10)
{
break;
}
JObject tempObj = kvp.Value;
tempObj.Add("similar",mMostMatch_Sentence_double);
answerList.Add(kvp.Value);
}
else
{
}
//bestMatchAnswer = answer;
// break;
}
}
//string[] array1 = { "我", "爱", "自然语言处理技术" };
//string[] array2 = { "我", "喜欢", "自然语言处理技术" };
//double similarity = CalculateCosineSimilarity(array1, array2);
//Console.WriteLine("相似度: " + similarity);
answerList = answerList.OrderByDescending(obj => (int)obj["similar"]).ToList();
return answerList;
}
}
}
主要的使用逻辑在这个方法中:
List <JObject> selectQuestion(string userInput, List<JObject> param2)
传入参数有两个:用户提出的问题,和待匹配语料集合。
返回参数:匹配到的语料集合。
var questionAnswerTable = new Dictionary<string, JObject> { };
这里的questionAnswerTable的key为待匹配的问题,value为整个问题实体。下面会先填充这个集合。然后遍历它与userInput进行匹配。
//*******************************【相似匹配度设置】**************************************
double mMostMatch_Sentence_double = 0.1;
注意这里是配置相似度的地方。大家在调试的时候可以通过修改它来控制匹配的精确程度。
差不多就是这些。剩下的代码基本都有注释。大家可以慢慢学习。