# 四、从文本提取含义 在前几章中,我们已经看到了如何从电子邮件、截图和网络中提取文本。我们已经研究了带有一组特定单词的电子邮件是如何触发自动响应的,试图模拟电子邮件的上下文被某种程度上理解(以一种非常基本和基本的方式)。基本上,给定一组特定的单词,检查电子邮件是否匹配,并发回与找到的匹配相关的响应。 提取文本是一回事,但理解文本在给定上下文中的含义是另一回事。从文本中提取意义是一个广泛的领域,主要属于[自然语言处理](https://en.wikipedia.org/wiki/Natural_language_processing) (NLP),包括情感分析、命名实体识别和关系提取等子类别。 情感分析包括分析文本中的一组词,并根据特定规则确定特定主题的极性。例如,当分析电子邮件中的单词时,情绪分析可用于确定电子邮件的语气,例如,礼貌、粗鲁、适当、冒犯、消极、积极等。提取的单词是根据决定其情感的标准来衡量的。 命名实体识别包括确定单词如何映射到专有名称,即地点、人物、组织等。单词的大写还有助于识别某些语言中的特定实体。在某些语言中,也可能使用两个或多个单词来标识单个实体,例如,Las Ventas(西班牙马德里的公牛竞技场)。 关系抽取包括确定两个或多个实体之间的关系,例如,哪个人是新闻文章的主要主题,或者谁与谁结婚。 NLP 并不局限于这三个领域。它的范围非常广泛。这三个只是 NLP 被广泛使用和应用的几个最常见的类别。在本章中,我们将重点讨论前两个:情感分析和命名实体识别。 对于任何开发人员来说,能够使用自然语言处理来理解文本都是一项巨大的资产,从业务角度来看,这些知识可以帮助公司简化和改进业务流程,例如人员招聘的自动化(简历/简历解析和简档匹配)、分类和归类以及电子邮件垃圾邮件检测。 到本章结束时,您应该能够使用这些技术中的一些,以便使用 C# 对任何给定的文本执行朴素贝叶斯分类和命名实体识别。 为了将项目分类或归类到特定的类别中,我们必须了解特定的单词如何适合特定的上下文。就我们的目的而言,上下文是书面或口头陈述的一部分,位于特定单词或段落之前或之后,通常影响其含义或效果。语境化是情感分析、文本分类和归类的支柱。 文本分类和分类可用于对文档或文本块进行分类。有三种常用的技术:朴素贝叶斯分类、向量机和语义索引。在本章中,我们将只关注第一个。 朴素贝叶斯是一种机器学习技术,可以预测特定数据案例属于哪个类别。它准确、健壮,并且相对容易实现。朴素贝叶斯是由英国数学家托马斯·贝叶斯发明的。本质上,我们要知道 P(A|X),通常读作“给定自变量取值 X 的概率 A”,其中 X 是一个或多个属性。朴素贝叶斯中的“天真”一词表示所有的 X 属性都被假定为数学上独立的。 为了更清楚地形象化这一点,让我们把它看作一个数学方程,其中 P 代表概率。 P(A|X) = (P(X|A) * P(A)) / PP(X) 想象一个场景,其中你有三组人(组 1、组 2 和组 3)参与一个项目,每个组处理同一个项目的一个子集。组 1 正在处理 50%的模块(属性 1),并产生 3%的缺陷率(属性 F)。第 2 组在项目管理上花费了 30%(属性 2),失败的几率为 4%(属性 F)。第 3 组提供 20%的支持(属性 3),并且由于项目大部分是实时的,因此无法找到报告问题的解决方案的概率大约为 5%(暴露于缺陷——属性 F)。我们的问题是:对于所有组(属性 1、2 和 3),遇到缺陷(属性 F)的部分概率是多少? 所有组在严格的数学术语中遇到缺陷(属性 F)的部分概率如下: PP(X)= P(1)* P(1 | F)+P(2)* P(2 | F)+P(3)* P(3 | F) 其中 P(1|F)可以理解为“第 1 组遇到缺陷的概率。” PP(X)=(50%)*(3%)+(30%)*(4%)+(20%)*(5%)= 3.7% PP(X)意味着在整个团队中有 3.7%的机会遇到缺陷。这 3.7%代表了朴素贝叶斯公式中的概率密度。 然而,分类不确定性的力量要求我们计算随机选择的缺陷可能来自任何一组的概率。使用朴素贝叶斯方程,我们将得到第 1 组的以下计算: P(1|F) = ((3%) * (50%)) / 3.7% = 40.5% 第 2 组:P(2|F) = ((4%) * (30%)) / 3.7% = 32.5% 第 3 组:P(3|F) = ((5%) * (20%)) / 3.7% = 27% 这意味着随机选择的缺陷有 40.5%的概率来自第 1 组,这种随机缺陷有 32.5%的概率来自第 2 组,而来自第 3 组的概率为 27%。 现在让我们处理数据库表中的以下数据集。 | 部门 | 性别 | 年龄 | 作用 | | 金融 | 女性的 | Thirty-two | 助理管制员 | | 金融 | 女性的 | Thirty-six | 高级控制员 | | 金融 | 男性的 | Forty-six | 财务经理 | | 信息技术 | 男性的 | Forty | 信息技术经理 | | 金融 | 男性的 | Thirty | 财务主管 | *表 4:样本数据表* 如果我们想确定在给定属性 X(其中 X 指定部门=财务,年龄=小于 40,角色=高级)的情况下找到女性(A)的概率,等式如下: P(A|X) = [P(X|A) * P(A)] / PP(X) 在这种情况下,我们的群体是男性和女性。 将 X 分解成每个属性,我们得到如下结果: P(A|X) = [P(财务| A) * P(<40 | A) * P(高级| A) * P(A)] / PP(X) P(女| X) = [P(金融|女)* P(<40 |女)* P(高级|女)* P(女)] / [PP(女| X) + PP(男| X)] 因为 PP(X)考虑所有组,所以分母变成 PP(女| X) + PP(男| X)。在本例中,因为在“性别”列中只有两次出现,所以仅有的两个可能的组是女性和男性。 根据我们对前面例子的了解,我们应该已经知道了 X(属性 F)的概率。然而,在这种情况下,我们需要确定每一个。因此: p(金融|女性)=计数(金融&女性)/计数(女性) P(<40 |女性)=计数(< 40 &女性)/计数(女性) p(高年级|女生)=人数(高年级&女生)/人数(女生) p(女性)=计数(女性)/计数(性别) PP(女| X) = P(金融|女)* P(<40 |女)* P(高级|女)* P(女) 因为我们需要确定 P(男性| X),所以对于每个属性 X,必须对男性进行同样的女性计算。 p(财务|男性)=计数(财务&男性)/计数(男性) P(<40 |男性)=计数(< 40 &男性)/计数(男性) p(高级|男性)=计数(高级&男性)/计数(男性) p(男性)=计数(男性)/计数(性别) PP(男| X) = P(金融|男)* P(<40 |男)* P(高级|男)* P(男) 总有一种可能,这些计算之一会给出零作为结果。例如: P(>50 |女性)=计数(> 50 &女性)/计数(女性) 因为在我们的数据集中没有 50 岁以上的女性,所以这个等式的结果为零。当联合计数为 0 时,这是不好的。为了避免这种情况,您只需将 1 添加到所有关节计数中。虽然这看起来像是一个狡猾的诡计,但它有坚实的数学基础,被称为加一平滑,这是拉普拉斯平滑的一种特定类型。 通过平滑,前面的等式将如下所示: p(金融|女性)=计数(金融&女性)+ 1 /计数(女性)+ 3 P(>50 |女性)=计数(> 50 &女性)+ 1 /计数(女性)+ 3 p(高年级|女生)=人数(高年级和女生)+ 1 /人数(女生)+ 3 平滑可以按如下方式恢复和应用: P(X|A) =计数(X|A) + 1 /计数(A)+X 属性数 请注意,平滑仅适用于涉及计数的计算。 正如我们已经看到的,从逻辑上组织提取的文本内容(单词)很重要。表 4 中的数据集被提取为文本,并组织成一个逻辑结构(数据库表)。我们这样做是为了成功地应用朴素贝叶斯。 让我们在这里回顾一下。朴素贝叶斯可以计算如下: p(女| X) = [PP(女| X)] / [PP(女| X) + PP(男| X)] PP(女| X) = P(金融|女)* P(<40 |女)* P(高级|女)* P(女) PP(男| X) = P(金融|男)* P(<40 |男)* P(高级|男)* P(男) 其中,女性和男性是组(描述为 A),金融,< 40,高级是属性(描述为 X)。 有了这个理论,让我们用 C# 编写一个简单的朴素贝叶斯引擎,包括加一平滑。引擎将要求输入等式所期望的相同属性(X),并且它将要求对组进行评估。 代码清单 20:一个简单的平滑贝叶斯引擎 ```cs // Bayes Engine with & without smoothing. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TextProcessing { public class BayesEngine : IDisposable {   protected bool disposed; public BayesEngine() { dataset = new List>(); StrictCounting = false; } ~BayesEngine() { this.Dispose(false);         } protected bool ExistsAinXRow(int xRow, string A, string aCol =         "") { bool res = false; if (dataset != null) { foreach (KeyValuePair column in    dataset) { if (aCol == String.Empty || column.Key.ToUpper().Contains(aCol.ToUpper())) { if (StrictCounting)          res = (column.Value[xRow].ToUpper() == A.ToUpper()) ? true : false; else { if (column.Value[xRow].ToUpper().             Contains(" "))                       res = (column.Value[xRow].ToUpper().                             Contains(A.ToUpper()))                             ? true : false;                          else                             res = (column.Value[xRow].ToUpper()                             == A.ToUpper()) ? true : false; } if (res) break;               } } } return res; } public List> dataset = null; public bool StrictCounting { get; set; } // Count(finance & male) public double CountXA(string xAttribute, string A, string xCol = "", string aCol = "") { double res = 0; if (dataset != null) { foreach (KeyValuePair xColumn in dataset) { if (xCol == String.Empty ||                      xColumn.Key.ToUpper(). Contains(xCol.ToUpper())) { int xRow = 0; foreach (string x in xColumn.Value)               { if (StrictCounting) {                             if (x.ToUpper() ==                             xAttribute.ToUpper() &&                             ExistsAinXRow(xRow, A, aCol)) res++; } else {                              if (x.ToUpper().Contains(" ")) {                               if (x.ToUpper().Contains                             (xAttribute.ToUpper()) &&                             ExistsAinXRow(xRow, A, aCol))                             res++;                             } else                             if (x.ToUpper() ==                              xAttribute.ToUpper() && ExistsAinXRow(xRow, A, aCol))                             res++; } xRow++; } } } } return res; } // Count(female, where female is a group) public double CountA(string A, string col = "") { double res = 0; if (dataset != null) { foreach (KeyValuePair column in dataset) { if (col == String.Empty || column.Key.ToUpper(). Contains(col.ToUpper())) { foreach (string wrd in column.Value)            { if (StrictCounting) {                             if (wrd.ToUpper() == A.ToUpper())                             res++; }   else {                             if (wrd.ToUpper().Contains(" "))                             {                             if                             (wrd.ToUpper().                             Contains(A.ToUpper()))                             res++;                                 }                             else                             if (wrd.ToUpper() == A.ToUpper())                             res++; } } } } } return res; } // Count(gender, where gender is a column—i.e. all groups) public double CountCol(string col) { double res = 0; if (dataset != null) { foreach (KeyValuePair column in                  dataset) { if (col != String.Empty && column.Key.ToUpper(). Contains(col.ToUpper())) { res = column.Value.Length; break; } } } return res; } // P(male) = count(male) / count(gender) public double ProbA(string A, string aCol) { double res = 0; res = CountA(A, aCol) / CountCol(aCol); return res;    } // P(finance | male) = count(finance & male) / count(male) public double ProbXA(string xAttribute, string A, string xCol = "", string aCol = "") { double res = 0; res = CountXA(xAttribute, A, xCol, aCol) / CountA(A, aCol); return res; } // P(finance | male) = count(finance & male) + 1 / count(male) + // 3 (Add-one smoothing) public double SmoothingProbXA(string xAttribute, string A, int numAttributes, string xCol = "", string aCol = "") { double res = 0; res = (CountXA(xAttribute, A, xCol, aCol) + 1) /             (CountA(A, aCol) + numAttributes); return res; } // Decides whether to use ProbXA or SmoothingProbXA. public double CalcProbXA(bool smoothing, string xAttribute, string A, int numAttributes = 0, string xCol = "", string aCol = "") { double res = 0; res = ProbXA(xAttribute, A, xCol, aCol); res = (res == 0 || smoothing) ? SmoothingProbXA(xAttribute, A, numAttributes, xCol, aCol) : res; return res; }   // PP(male | X) = P(finance | male) * P(<40 | male) * P(senior | // male) * P(male) public double PProbAX(bool smoothing, string A, string[] xAttributes, string[] xColls, string aCol = "") { double res = 0; if (xAttributes != null && xAttributes.Length > 0) { int i = 0; List rlts = new List(); foreach (string xAtrrib in xAttributes) { string xCol = (xColls != null && xColls.Length > 0 && xColls.Length ==                             xAttributes.Length) ?                             xColls[i] : String.Empty; rlts.Add(CalcProbXA(smoothing, xAtrrib, A, xAttributes.Length, xCol, aCol)); i++; } rlts.Add(ProbA(A, aCol)); double tmp = 0; int cnt = 0;              foreach (double r in rlts)            { tmp = (cnt == 0) ? r : tmp *= r; cnt++; } res = tmp; } return res; } // P(female | X) = [P(finance | female) * P(<40 | female) * // P(senior | female) * P(female)] / // [PP(female | X) + PP(male | X)] public double BayesAX(string A, string[] G, string[] gColls, string[] xAttributes, string[] xColls, string aCol = "", bool smoothing = true) { double res = 0; double nonimator = PProbAX(smoothing, A, xAttributes, xColls, aCol); double denominator = 0;     if (G != null && G.Length > 0 && gColls != null && gColls.Length > 0) { if (G.Length == gColls.Length) { int i = 0; foreach (string group in G)              { denominator += PProbAX(smoothing, group, xAttributes, xColls, gColls[i]); i++; } } } if (denominator > 0) res = nonimator / denominator; return res; } public virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) {                dataset = null; } } this.disposed = true; } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } } } ``` 在代码清单 20 中,贝叶斯引擎的主要方法是 BayesAX,它计算命名者,即 PP(女| X)。然后计算分母,即[PP(女| X) + PP(男| X)],返回最终结果,即 P(女| X) = [PP(女| X)] / [PP(女| X) + PP(男| X)]。 方法 BayesAX 有以下参数:A、G、gColls、xAttributes、xColls、aCol 和平滑。A 代表将计算其概率的组(A)(在给定的例子中,A 是女性)。g 表示可能的组的字符串数组(在给定的示例中,这些组是女性和男性)。参数 gColls 表示在其中找到女性和男性组的列名的字符串数组(在给定的示例中,女性和男性的列名都是性别)。参数 xAttributes 表示用于计算概率的 X 值的字符串数组(在给定的示例中,财务、高级和< 40)。xColls 也是一个字符串数组,表示定义的 X 属性的列名(在给定的示例中,分别是部门、角色和年龄)。最后,aCol 是性别组的列名。 贝叶斯引擎计算每个组的属性,计算每个组的概率,然后确定总体概率。BayesEngine 类有一个非常重要的属性:StrictCounting。当设置为 false(默认值)时,StrictCounting 表示将使用 Contains()对单词匹配进行计数,而当设置为 true 时,将对精确的字符串匹配进行计数(使用==运算符)。在这两种情况下,字符串比较不区分大小写。 BayesEngine 还支持加一平滑,默认情况下,它是假设和应用的(值设置为 true)。如果“平滑”设置为“假”,则仅当结果关节计数为零时,才会自动应用平滑。为了获得最高精度,建议使用平滑。 为了更好地理解这些概念,让我们考虑一个场景,其中有两组:一组 24 名男性和一组 16 名女性。工作、手和身高是 X 属性。“职务”的可能值是“行政”、“警察”、“Edu”和“技术”。“手动”的可能值是“右”和“左”。“高度”的可能值有“矮”、“高”和“中”。 我们可以使用这些信息来创建表 5 中的数据集。 | 男性 | 女性的 | | Admin = 2 | Admin = 7 | | Const = 5 | Const = 0 | | Edu = 2 | Edu = 4 | | Tech = 15 | Tech = 5 | | 左= 7 | 左= 2 | | 右= 17 | 右= 14 | | 短= 1 | 短= 6 | | 中等= 19 | 中等= 8 | | 高= 4 | 高= 2 | 表 5:样本数据集 使用表 5 的数据集,我们可以计算 P(男| X)和 P(女| X)以获得表 6 中的结果。 | 公式 | 平滑的结果 | 没有平滑的结果 | | P(Edu |男) | 0.1111 | 0.0833 | | p(右|男) | 0.6667 | 0.7083 | | p(高|男) | 0.1852 | 0.1667 | | p(男性) | Zero point six | Zero point six | | P(Edu |女) | 0.2632 | 0.2500 | | p(右|女) | 0.7895 | 0.8750 | | p(高|女) | 0.1579 | 0.1250 | | p(女) | Zero point four | Zero point four | | PP(男| X) | 0.008230 | 0.005903 | | PP(女| X) | 0.013121 | 0.010938 | | p(男| X) | 0.3855 | 0.3505 | | p(女| X) | 0.6145 | 0.6495 | 表 6:来自前面样本数据集的结果 为了验证我们的贝叶斯引擎可以为这个数据集产生相同的结果,让我们围绕它创建一个包装类。 代码清单 21:样本数据集周围的贝叶斯引擎包装器 ```cs // BayesExample: A BayesEngine Wrapper using System; using System.Collections.Generic; namespace TextProcessing { public class BayesExample { public static void BayesEx() { using (BayesEngine b = new BayesEngine()) { b.dataset.Add(new KeyValuePair("Gender", new string[] { "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "male", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female", "female" })); b.dataset.Add(new KeyValuePair("Job", new string[] { "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "tech", "const", "const", "const", "const", "const", "admin", "admin", "edu", "edu",              "admin", "admin", "admin", "admin", "admin", "admin", "admin", "edu", "edu", "edu", "edu", "tech", "tech", "tech", "tech", "tech" })); b.dataset.Add(new KeyValuePair("Handed", new string[] { "left", "left", "left", "left", "left", "left", "left", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right", "left", "left", "right", "right", "right", "right", "right", "right", "right", "right", "right", "right",  "right", "right", "right", "right" })); b.dataset.Add(new KeyValuePair("Height", new string[] { "short", "tall", "tall", "tall", "tall", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "short", "short", "short", "short", "short", "short", "tall", "tall", "medium", "medium", "medium", "medium", "medium", "medium", "medium", "medium" })); // P(male|(edu | right | tall)) with smoothing double r1 = b.BayesAX("male", new string[] { "male", "female" }, new string[] { "Gender", "Gender" }, new string[] { "edu", "right", "tall" }, new string[]    { "Prof", "Hand", "Height" }, "Gender"); // P(male|(edu | right | tall)) without smoothing double r2 = b.BayesAX("male", new string[] { "male", "female" }, new string[] { "Gender", "Gender" }, new string[] { "edu", "right", "tall" }, new string[] { "Prof", "Hand", "Height" }, "Gender", false); // P(female|(edu | right | tall)) with smoothing double r3 = b.BayesAX("female", new string[] { "male",                "female" }, new string[] { "Gender", "Gender" }, new string[] { "edu", "right", "tall" }, new string[] { "Prof", "Hand", "Height" }, "Gender"); // P(female|(edu | right | tall)) without smoothing double r4 = b.BayesAX("female", new string[] { "male", "female" }, new string[] { "Gender", "Gender" }, new string[] { "edu", "right", "tall" }, new string[]              { "Prof", "Hand", "Height" }, "Gender" ,false); Console.WriteLine("P(male|(edu | right | tall)) with smoothing: " + r1); Console.WriteLine("P(male|(edu | right | tall)) without smoothing: " + r2); Console.WriteLine("P(female|(edu | right | tall)) with smoothing: " + r3); Console.WriteLine("P(female|(edu | right | tall))            without smoothing: " + r4); } } } } // Main Program that calls BayesExample. using TextProcessing; namespace DataCaptureExtraction { class Program { static void Main(string[] args) {  BayesExample.BayesEx(); } } } ``` 运行这段代码会产生如图 13 所示的结果。 ![](img/00016.jpeg) *图 13:来自围绕样本数据集的贝叶斯引擎包装器的结果* 如您所见,BayesEx 产生的结果与手动计算并在表 5 中描述的结果相同(事实上,结果稍微更精确)。 天真的贝叶斯是一种极好的方法,可以确定跑进具有一个或多个属性(X)的给定组(A)的概率。这种方法可以用作判断一个或多个单词(A)是否属于某些类别(X)的晴雨表,这允许更容易的分类和归类。 为了赋予文本意义,我们必须确切地知道将提取哪些字符串数据类型,并且我们必须知道如何从一组特定的单词中识别和提取它们。 [正则表达式](https://en.wikipedia.org/wiki/Regular_expression) (RegEx)只不过是定义了特定字符串数据类型的搜索模式的字符序列,例如电子邮件地址、邮政编码或任何其他具有特定格式化模式的内容。 最基本的正则表达式由单个文字字符组成,如“o”,正则表达式将匹配字符串中第一个出现的字符。例如,如果字符串是“约翰是飞行员”,正则表达式将匹配“j”后面的“o” 正则表达式也可以匹配第二个“o”。只有当您告诉正则表达式引擎在第一次匹配后搜索字符串时,它才会这样做。 有 12 个字符,称为元字符,在正则表达式中有特殊的含义,了解这些项目很重要: * 反斜杠“\” * 插入符号'^' * 美元符号“$” * 句点或点“.” * 竖线或管道符号“|” * 问号“?” * 星号或星号' * ' * 加号“+” * 左括号“(” * 右括号“)” * 左方括号“[” * 开始的大括号“{ 0 如果这些字符中的任何一个在 RegEx 中用作文字,它们需要用反斜杠“\”字符进行转义。如果我们想匹配 1+1=2,正确的正则表达式是 1\+1=2。否则,'+'符号会有特殊的含义。一个让你开始使用 RegEx 的很好的教程可以在 [RegExOne](http://regexone.com/) 找到。 让我们快速探索如何用 C# 实现 RegEx。 代码清单 22:一个正则表达式 C# 示例 ```cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Text.RegularExpressions; namespace DataCaptureExtraction { class Program { public static void RegExExample() { // First we see the input string. string input = "/server/thisUrl.html"; // Here we call Regex.Match.  Match match = Regex.Match(input, @"server/([A-Za-z0-9\- ]+)\.html$", RegexOptions.IgnoreCase); // Here we check the Match instance. if (match.Success) { // Finally, we get the Group value and display it. string key = match.Groups[1].Value; Console.WriteLine(key); } } static void Main(string[] args)      { RegExExample(); } } } ``` 请注意,运行 RegEx "server/([A-Za-z0-9\-]+)\。html$“此字符串上的代码”/server/thisUrl.html”从 Url 字符串中提取单词“thisUrl”。 以下是一些常见且有用的 RegEx 代码: 用户名:^[a-z0-9_-]{3,16}$ 这个-us3r_n4m3 是匹配的。但是,长度超过 16 个字符的字符串不匹配。 密码:^[a-z0-9_-]{6,18}$ 这个 p4ssw0rd 将是一个匹配。但是,短于 6 个字符的字符串不会。 十六进制值:^#?([a-f0-9]{6}|[a-f0-9]{3})$ #a3c113 将是一个匹配。但是,#h3c113 不会,因为包含字母“h”。 电子邮件:^([a-z0-9_\.-]+)@([\da-z\。-]+)\.([a-z\。]{2,6})$ [vito@vito.me](mailto:vito@vito.me) 会匹配。然而,[维托@维托. some 古怪域](mailto:vito@vito.someweirddomain)不会,因为它太长了。 完整 URL: ^(https?:\/\/)?([\ da-z \-]+)。([a-z \]{ 2.6 })([\/\ w \-]* \/?$中 [http://subdomain.vito.com/about](http://subdomain.vito.com/about)会匹配。然而,[http://vito.com/some/page!.html](http://vito.com/some/page!.html)不会,因为它包含了“!”性格。 IP 地址:^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ 71.48.125.121 会匹配。然而,256.58.125.121 不会。 让我们稍微调整一下代码,适应 256.58.125.121,以便在产生匹配时显示。 代码清单 23:调整后的正则表达式 C# 示例 ```cs public static void RegExExample() { // First we see the input string. string input = " this-us3r_n4m3"; // Here we call Regex.Match. Match match = Regex.Match(input, @"^[a-z0-9_-]{3,16}$", RegexOptions.IgnoreCase); // Here we check the Match instance. if (match.Success) { Console.WriteLine(match.Value); } } ``` 您可以在前面的代码中使用前面的任何示例,以验证所提供的匹配是否如预期的那样工作。 其他需要从文本中提取的常见字符串数据类型有电子邮件、邮政编码、社保号、驾照、财政身份证号、银行账号、电话号码、区号等。网站 [RegExLib](http://www.regexlib.com/) 包含了大量可以在 C# 项目中应用的热门 RegEx。另一个有用的网站是[雷盛](http://www.rexegg.com)。 [命名实体识别](https://en.wikipedia.org/wiki/Named-entity_recognition) (NER)包括将文本元素(命名实体)的子集定位和分类到预定义的类别中,例如人名、组织名、地点名、金额名、货币值、百分比等。 使用概率方法(如朴素贝叶斯)将单词分类当然允许我们基于训练好的数据集将一组单词放入特定的类别,但是 NER 的这种使用仍然是非常基本的。为了获得非常精确的 NER,我们必须使用训练有素的数据集模型。 的流行 NER 实现。NET 和 C# 是(NER)的[斯坦福命名实体识别器](http://sergey-tihon.github.io/Stanford.NLP.NET/StanfordNER.html)。NET,也可以通过 NuGet 获得。 ![](img/00017.jpeg) *图 14:作为 NuGet 包安装的斯坦福 NER* 安装 NuGet 包后,需要下载分类器库定义,可以在这里找到:[http://NLP . Stanford . edu/software/Stanford-ner-2015-04-20 . zip](http://nlp.stanford.edu/software/stanford-ner-2015-04-20.zip)。更多信息也可以在这里找到:[http://stanfordnlp.github.io/CoreNLP/download.html](http://stanfordnlp.github.io/CoreNLP/download.html)。 下载库定义后,将文件解压缩并放入本地硬盘上的文件夹中。您需要在代码中引用这个位置。 让我们看看如何用. NET 的斯坦福 NER 快速实现 NER 代码清单 24:一个 C# 斯坦福 NER 实现程序 ```cs // Stanford NER C# Implementation using System; using System.Collections.Generic; using System.Linq; using edu.stanford.nlp.ie.crf; using edu.stanford.nlp.pipeline; using edu.stanford.nlp.util; namespace TextProcessing { public class NER : IDisposable { protected bool disposed; protected CRFClassifier Classifier = null; protected string[] ParseResult(string txt) { List res = new List(); string[] tmp = txt.Split(' '); if (tmp != null && tmp.Length > 0) { foreach (string t in tmp) { if (t.Count(x => x == '/') == 2) { res.Add(t.Substring( 0, t.LastIndexOf("/") - 1));               } } } return res.ToArray(); } public NER() { string root = @"D:\Temp\NER\classifiers"; Classifier = CRFClassifier.getClassifierNoExceptions         (root + @"\english.all.3class.distsim.crf.ser.gz"); } ~NER() { this.Dispose(false); } public string[] Recognize(string txt) { return ParseResult(Classifier.classifyToString(txt)); } public virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { Classifier = null; } }  this.disposed = true; } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } } } // Wrapper class around the Stanford NER Implementation. using System; namespace TextProcessing { public class NerExample { public static void nerExample() { using (NER n = new NER()) { string[] res = n.Recognize("I went to Stanford,        which is located in California"); if (res != null && res.Length > 0) { foreach (string r in res) { Console.WriteLine(r); }      } } } } } // Main Program using System; using TextProcessing; namespace DataCaptureExtraction { class Program { static void Main(string[] args) { NerExample.nerExample(); } } } ``` 代码最重要的部分是调用 crfcclassifier . getclassifiernoeexceptions,这是分类器定义(english.all.3class.distsim.crf.ser.gz)在磁盘上的物理位置。 在识别中,调用斯坦福 NER 的分类器. class ifythong 方法,并解析结果。这将产生如图 15 所示的输出。 ![](img/00018.jpeg) *图 15:斯坦福 NER C# 实现输出* 使用输入字符串“我去了位于加州的斯坦福”,斯坦福 NET C# 程序可以识别两个命名实体:斯坦福(是一个组织)和加州(是一个地点)。 从文本中提取意义是一个有趣的话题,无论我们是在研究如何提取特定的数据类型、识别实体还是对文本中的单词进行分类。当您能够理解提取的数据时,您就可以使用一个强大的工具来帮助您改进、加速和自动化业务流程。事实上,从垃圾邮件过滤器到文本分类等等,各种流程都有无限的潜力可供组织精简和改进。我们只触及了强大的 C# 代码实现的表面。 请记住,我在本书中介绍的技术是推荐给概念测试的,而不是产品使用的。我们专注于从概念角度快速实现可能实现的目标,这些技术不会与任何商业产品竞争或破坏任何商业产品。我鼓励您也考虑各种各样的商业产品,这些产品具有强大的 API 并得到专业支持。 感谢阅读。我希望这些资料有助于拓宽你对 C# 数据获取和提取的看法。 完整的 Visual Studio 项目源代码可以从以下网址下载: [https://bit bucket . org/syncfusiontech/data-capture-and-extraction-with-c-简洁地](https://bitbucket.org/syncfusiontech/data-capture-and-extraction-with-c-succinctly)