C# 自然语言处理-人工智能 -人工智能应用软件开发-创意法人工智能

2022-07-27 21:47
13
0
添加收藏

最近在进行语音项目,涉及到了时间提取,城市提取等,之前也学过python,对于实体识别这方面也有涉及,于是把python代码方面转化为c#代码,方便自己理解。

时间提取
时间提取是比较复杂的一项提取,因为涉及到了中文日期转数字,年份转数字,时间格式化为字符串,检查时间合法性等等。

(1)中文日期转数字
起初我的想法是,不是只有把传进来的中文直接转为数字即可吗,例如十–>10,但仔细想了一下,发现不对,因为当转化到十位数,百位数,千位数的时候,直接转化是错误的,例如一千二百–>1/1000/2/100
,完全脱离了我们提取的数字。因此不能直接转化。
首先了解一下,我们传入的字符串的正则表达式:
([0-9零一二两三四五六七八九十]+年)?([0-9一二两三四五六七八九十]+月)?([0-9一二两三四五六七八九十]+[号日])?([上中下午晚早]+)?([0-9零一二两三四五六七八九十百]+[点:\.时])?([0-9零一二两三四五六七八九十百]+分?)?([0-9零一二三四五六七八九十百]+秒)?
可以看到,匹配格式为xxxx年xx月xx日xx日这种,因此处理时要注意这些。

接下来开始处理:

定义普通中文数字和十百千万的字典。
private readonly static Dictionary<char, int> UTIL_CN_UNIT = new Dictionary<char, int>
       {
           { '十', 10 }, {'百', 100 }, {'千', 1000 }, {'万', 10000 }
       };
private readonly static Dictionary<char, int> UTIL_CN_NUM = new Dictionary<char, int>
       {
           { '零', 0 },{ '一', 1 }, {'二', 2 },{ '两', 2 }, {'三', 3 },
           { '四', 4 },{ '五', 5 }, { '六', 6 },{ '七', 7 }, {'八', 8 },
           { '九', 9 },{ '0', 0 }, {'1', 1 }, {'2', 2 }, {'3', 3 }, {'4', 4 },
           { '5', 5 }, {'6', 6 },{ '7', 7 }, {'8', 8 }, {'9', 9 }
       };
c#的Regex类是处理正则表达式的。

public static int Cn2Dig(string src)
       {
           if (src == "") return -1;
           Match m = Regex.Match(src, @"\d+");
           if (m != Match.Empty)
           {
               return Convert.ToInt32(m.Groups[0].Value);
           }
           int rsl = 0;
           int unit = 1;

           for (int i = src.Length - 2; i >= 0; i--)//减二是因为最后一个为号,日,时分这种
           {
               if (UTIL_CN_UNIT.ContainsKey(src[i]))//十百千这种数字
               {
                   unit = UTIL_CN_UNIT[src[i]];

               }
               else if (UTIL_CN_NUM.ContainsKey(src[i]))//0~9这种数字
               {
                   int num = UTIL_CN_NUM[src[i]];
                   rsl += num * unit;
               }
               else
               {
                   return -1;
               }
           }
           if (rsl < unit) rsl += unit;//防止单个unit位数时没有结果
           return rsl;
————————————————
为什么从字符串的后面往前匹配?因为需要从个位数开始累加,否则如果从前开始无法确定是十位,百位还是千位。

(2)中文年份转数字
或许看了上面转中文日期后,你有个疑问,就是为什么还需要单独转年份?那是因为我们没有像月份和日一样的叫法。例如对于2012年,我们会说二零一二年及一二年这种,而不是两千零一十二年。所以对年份要做特殊处理。

public static int Year2Dig(string year)
       {
           string res = "";

           foreach (var item in year)//对于年份而言,直接转化字符串即可
           {
               if (UTIL_CN_NUM.ContainsKey(item))
                   res = res + UTIL_CN_NUM[item].ToString();
               else
                   res = res + item;
           }

           Match m = Regex.Match(res, @"\d+");

           if (m != Match.Empty)
           {
               if (m.Groups[0].Value.Length == 2)//如果是01年,12年这种
                   return (DateTime.Now.Year / 100) * 100 + Convert.ToInt32(m.Groups[0].Value);
               else
                   return Convert.ToInt32(m.Groups[0].Value);
           }
           return -1;
       }
————————————————
(3)格式化时间
对于传入的中文日期,我们最终是要转化为我们需要的格式,例如yyyy-MM-dd hh:mm:ss等。
用正则表达式提取出中文时间字符串,接下来对字符串进行年份转数字,日期转数字的操作,然后提取出是否包含晚上,中午,下午关键词,如果有则修改时间,没有则返回最后的格式化后的字符串。

public static string ParseDatetime(string msg)
       {
           if (string.IsNullOrEmpty(msg)) return null;

           try
           {
               Match m = Regex.Match(msg, @"([0-9零一二两三四五六七八九十]+年)?([0-9一二两三四五六七八九十]+月)?([0-9一二两三四五六七八九十]+[号日])?([上中下午晚早]+)?([0-9零一二两三四五六七八九十百]+[点:\.时])?([0-9零一二两三四五六七八九十百]+分?)?([0-9零一二三四五六七八九十百]+秒)?");
               if (!string.IsNullOrEmpty(m.Groups[0].Value))
               {
                   Dictionary<string, string> res = new Dictionary<string, string>
                   {
                       {"year",m.Groups[1].Value},
                       {"month",m.Groups[2].Value},
                       {"day",m.Groups[3].Value},
                       { "hour",m.Groups[5].Value},
                       { "minute",m.Groups[6].Value},
                       { "second",m.Groups[7].Value},
                   };

                   Dictionary<string, int> Params = new Dictionary<string, int>();

                   foreach (var name in res.Keys)
                   {
                       if (res[name] != null)
                       {
                           int temp;
                           if (name == "year") temp = Year2Dig(res[name]);
                           else temp = Cn2Dig(res[name]);
                           if (temp != -1) Params[name] = temp;
                           else Params[name] = -1;
                       }
                   }
                   Dictionary<string, int> dateDic = ReplaceToday(Params);
                   string is_pm = m.Groups[4].Value;
                   if (!string.IsNullOrEmpty(is_pm))
                   {
                       if (is_pm == "下午" || is_pm == "晚上" || is_pm == "中午")
                       {
                           int hour = dateDic["hour"];
                           if (hour < 12)
                               dateDic["hour"] = dateDic["hour"] + 12;
                       }
                   }
                   return Dic2Str(dateDic);
               }
               else return null;
           }
           catch (Exception e)
           {
               Console.WriteLine(e);
           }
           return null;
       }
————————————————
函数中包含ReplaceToday和Dic2Str两个函数,这时候就体现python对数据处理的强大之处,python的自带处理字典转字符串和代替日期字典的日期的函数的。对此,我们只能自己写。

由于我们日常口头语中可能会说明天早上,后天早上,某某天,某月某日,我们一般不会具体到分秒,年等等,所以对于我们没有具体的方面,我们要做处理,用现在的时间代替我们没有具体的时期。例如“1月7号上午“,其中虽然年份没有出现,但我们能看出说的是今年的1月。

private static Dictionary<string, int> ReplaceToday(Dictionary<string, int> Params)
       {
           //有直接说八号早上这种说法,但没有八号早上20分这种说法
           Dictionary<string, int> dateDic = new Dictionary<string, int>();
           if (Params["year"] == -1) dateDic["year"] = DateTime.Now.Year;
           else dateDic["year"] = Params["year"];

           if (Params["month"] == -1) dateDic["month"] = DateTime.Now.Month;
           else dateDic["month"] = Params["month"];

           if (Params["day"] == -1) dateDic["day"] = DateTime.Now.Day;
           else dateDic["day"] = Params["day"];

           if (Params["hour"] == -1) dateDic["hour"] = 0;
           else dateDic["hour"] = Params["hour"];

           if (Params["minute"] == -1) dateDic["minute"] = 0;
           else dateDic["minute"] = Params["minute"];

           if (Params["second"] == -1) dateDic["second"] = 0;
           else dateDic["second"] = Params["second"];

           return dateDic;
       }
private static string Dic2Str(Dictionary<string, int> Params)
       {
           return string.Format("{0}-{1}-{2} {3}:{4}:{5}", Params["year"].ToString(), Params["month"].ToString(),
               Params["day"].ToString(), Params["hour"].ToString(), Params["minute"].ToString(), Params["second"].ToString());
       }
————————————————
(4)提取时间
时间的提取我们要考虑的因素有很多,例如说话的人说的是明天,后天,或者星期一,周日,下周等等,我们需要识别这些。

private readonly static Dictionary<string, int> keyDate = new Dictionary<string, int>
       {
           { "今天",0 }, {"明天", 1 }, {"后天", 2 },{"今天上午" ,3 },{"今天下午",4 }
       };
private readonly static Dictionary<string, int> weekdayDic = new Dictionary<string, int>
       {
           { "星期一", 0 },{"星期二", 1 },{"星期三", 2 },{"星期四", 3 },{"星期五", 4 },{"星期六", 5 },{"星期天", 6 },
           { "星期日", 6 },{"周一", 0 },{"周二", 1 },{"周三", 2 },{"周四", 3 },{"周五", 4 },{"周六", 5 },{"周天", 6 },{"周日",6 }
       };
public static List<string> TimeExtract(string text)
       {
           
           string text1;
           MatchCollection a = Regex.Matches(text, @"[看等]一下(.*)");//一下会被识别为数词
           if (a.Count == 0) text1 = text;
           else text1 = a[0].Groups[1].Value;

           List<string> timeRes = new List<string>();

           //得到的时间字符串
           string word = "";

           DateTime today = DateTime.Now;
           int t = (int)today.DayOfWeek;//C#的周一为1
           t = t - 1;
           if (t == -1) t = 6;

           //暂时无法判断下周三和周六,周三和下周六这两种说法,这里统一为两个下周
           int sub = 0;
           Match m = Regex.Match(text1, @"(?:下星期|下周|下个星期).*$");
           if (m != Match.Empty) sub += 7;
           int subCopy = sub;

           //分词,得出词语和词性
           var posSeg = new PosSegmenter();
           var tokens = posSeg.Cut(text1);

           string[] wordFlag = { "m", "t" };
           int[] dayNum = { 0, 1, 2 };

           foreach (var item in tokens)
           {
               if (keyDate.ContainsKey(item.Word))
               {
                   if (word != "") timeRes.Add(word);
                   int day = DicGet(keyDate, item.Word, 0);

                   if (dayNum.Contains(day))
                   {
                       word = DateTime.Today.AddDays(day).ToString("D");
                   }
                   else if (day == 3)
                   {
                       word = DateTime.Today.ToString("D") + "上午";
                   }
                   else if (day == 4)
                   {
                       word = DateTime.Today.ToString("D") + "下午";
                   }
               }
               else if (weekdayDic.ContainsKey(item.Word))
               {
                   if (word != "") timeRes.Add(word);
                   int day = weekdayDic[item.Word];
                   sub += day - t;
                   if (sub > 0)
                   {
                       word = DateTime.Today.AddDays(sub).ToString("D");
                       sub = subCopy;
                   }
               }
               else if (word != "")
               {
                   if (wordFlag.Contains(item.Flag))
                   {
                       word = word + item.Word;
                   }
                   else
                   {
                       timeRes.Add(word);
                       word = "";
                   }
               }
               else if (wordFlag.Contains(item.Flag))
               {
                   word = item.Word;
               }
           }

           if (word != "")
               timeRes.Add(word);

           List<string> result = new List<string>();
           foreach (var item in timeRes)
           {
               string temp = CheckTimeValid(item);
               if (temp != null) result.Add(temp);
           }

           List<string> finalRes = new List<string>();
           foreach (var item in result)
           {
               string temp = ParseDatetime(item);
               if (temp != null) finalRes.Add(temp);
           }
           return finalRes;
       }
————————————————
此函数用于提取多个时间,我的项目中只涉及到提取一个,因此没有细分。日期检查部分可以省略。

完成日期提取,最后的结果为你要输出的字符串格式日期。

地点提取

先到这,以后有时间了在补充,实际上步骤都差不多。
版权声明:本文为CSDN博主「anpluto」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42672981/article/details/110924407

全部评论