更新时间 2018年7月11日 By 带着兔子去旅行html
信息抽取是NLP里的一个实用内容。该工具的目标是打造一个泛用的自动信息抽取工具。使得没有任何基础的用户,能够经过简单的步骤提取文档(PDF,HTML,TXT)中的信息。该工具使用C#(.Net Core)开发,因此能够跨平台运行。(Python在作大的工程的时候有诸多不便,因此没有使用python语言)python
ltp工具:哈工大LTP工具(ltp.ai)提供的ltp工具,最新版为3.3.4.该工具在windows,max,centos上,srl的训练可能没法正常完成。(dp,ner阶段没有问题)因此这里使用了3.3.2版本。ltp工具的SRL结果中包含了DP和NER的内容,可是暂时保留DP和NER中间XML文件。算法
pdfminer:请注意处理中文的时候须要额外的步骤,具体方法再也不赘述。部分PDF可能没法正确转换,缘由CaseByCase。windows
结巴分词:某些地名,例如"大连",会被误判。这里使用地名辅助字典的方式作纠正。ltp工具没有这个问题。ltp工具和结巴分词功能虽然重复,可是暂时还不能移除结巴分词。centos
期待文件夹结构微信
训练结果例:ide
协议书(5.180388%)[56] 协议(11.84089%)[128] 合同(58.55689%)[633] 合同书(2.960222%)[32] 买卖合同(3.792784%)[41] 承包合同(12.0259%)[130] 意向书(0.2775208%)[3] 补充协议(1.110083%)[12] 项目(0.2775208%)[3] 书(0.9250694%)[10] 议案(0.2775208%)[3] )(0.8325624%)[9]
(更多规则持续加入中,同时对于相关度低的规则也会剔除)工具
这里暂时使用频率最高的前5位做为抽取依据。同时为了保证正确率,部分特征的占比必须超过某个阈值。
如下是中文冒号的一个例子,要求前导词占比在40%以上。
(例如前导词A能够正确抽取10个关键字,前导词B能够抽取5个关键字,前导词C能够抽取15个关键字。则前导词A的占比为33%)测试
e.LeadingColonKeyWordList = ContractTraning.ContractNameLeadingDict .Where((x) => { return x.Value >= 40; }) //阈值40%以上 .Select((x) => { return x.Key + ":"; }).ToArray();
对于大量表格中的关键字,工具也提供了表格统计的功能。主要是统计一下该关键字的表头标题信息。
同时因为表格中的原始数据可能须要经过参照表格标题才能进行比对的状况,这里支持变换器。ui
/// <summary> /// 增发对象训练 /// </summary> public static void TrainingIncreaseTarget() { var TargetTool = new TableAnlayzeTool(); var IncreaseNumberTool = new TableAnlayzeTool(); IncreaseNumberTool.Transform = NumberUtility.NormalizerStockNumber; var IncreaseMoneyTool = new TableAnlayzeTool(); IncreaseMoneyTool.Transform = MoneyUtility.Format; TraningDataset.InitIncreaseStock(); var PreviewId = String.Empty; var PreviewRoot = new HTMLEngine.MyRootHtmlNode(); foreach (var increase in TraningDataset.IncreaseStockList) { if (!PreviewId.Equals(increase.id)) { var htmlfile = Program.DocBase + @"\FDDC_announcements_round1_train_20180518\定增\html\" + increase.id + ".html"; PreviewRoot = new HTMLEngine().Anlayze(htmlfile, ""); PreviewId = increase.id; } TargetTool.PutTrainingItem(PreviewRoot, increase.PublishTarget); IncreaseNumberTool.PutTrainingItem(PreviewRoot, increase.IncreaseNumber); IncreaseMoneyTool.PutTrainingItem(PreviewRoot, increase.IncreaseMoney); } TargetTool.WriteTop(10); } 增发对象 投资者名称(7.29849%)[546] 股东名称(10.17244%)[761] 发行对象名称(9.089694%)[680] 认购对象(14.86432%)[1112] 发行对象(20.51865%)[1535] 增发数量 获配股数(股)(17.05559%)[1893] 持股数量(股)(7.793495%)[865] 认购数量(股)(5.586089%)[620] 配售股数(股)(5.53203%)[614] 认购股数(股)(3.585909%)[398] 增发金额 认购金额(元)(4.833%)[314] 获配金额(元)(15.94582%)[1036] 2015年度(4.417423%)[287] 2017年1-3月(4.432815%)[288] 2016年度(5.956595%)[387]
除了统计标题以外,还能够经过某个标题下面出现的内容。
下面的例子是看一下增减持方式有哪些:
/// <summary> /// 增减持训练 /// </summary> /// <param name="TraningCnt">训练条数</param> public static void Traning(int TraningCnt = int.MaxValue) { var ChangeMethodTool = new TableAnlayzeTool(); var PreviewId = String.Empty; var PreviewRoot = new HTMLEngine.MyRootHtmlNode(); int Cnt = 0; foreach (var stockchange in TraningDataset.StockChangeList) { if (!PreviewId.Equals(stockchange.id)) { var htmlfile = Program.DocBase + @"\FDDC_announcements_round1_train_20180518\增减持\html\" + stockchange.id + ".html"; PreviewRoot = new HTMLEngine().Anlayze(htmlfile, ""); PreviewId = stockchange.id; Cnt++; if (Cnt == TraningCnt) break; } ChangeMethodTool.PutValueTrainingItem(PreviewRoot, new string[]{"减持方式","增持方式"}.ToList()); } Program.Training.WriteLine("增减持方式"); ChangeMethodTool.WriteTop(10); } 增减持方式 集中竞价(24.37453%)[6771] 集中竞价交易(33.39573%)[9277] 大宗交易(21.38306%)[5940] 竞价交易(8.884409%)[2468] 合计(0.9287592%)[258] 集中竞价减持(1.670326%)[464] 减持方式(1.313942%)[365] <null>(1.090752%)[303] 二级市场竞价(1.040354%)[289] 竞价减持(0.705569%)[196]
采用各类方法抽取数据,务必使得全部数据都抽取出来。根据训练结果从候选值里面得到置信度最大的数据。抽取手段以下:
代码内置表头规则系的表抽取工具,对于表格能够设定以下抽取规则:
/// <summary> /// 表抽取规则(内容系) /// </summary> public struct TableSearchContentRule { /// <summary> /// 匹配内容 /// </summary> public List<String> Content; /// <summary> /// 是否相等模式 /// </summary> public bool IsContentEq; }
下面是一个表格抽取的例子:
var rule = new TableSearchContentRule(); rule.Content = new string[] { "集中竞价交易", "竞价交易", "大宗交易", "约定式购回" }.ToList(); rule.IsContentEq = true; var result = HTMLTable.GetMultiRowsByContentRule(root,rule);
代码内置表头规则系的表抽取工具,对于表格能够设定以下抽取规则:
/// <summary> /// 表抽取规则 /// </summary> public struct TableSearchTitleRule { public string Name; /// <summary> /// 父标题 /// </summary> public List<String> SuperTitle; /// <summary> /// 是否必须一致 /// </summary> public bool IsSuperTitleEq; /// <summary> /// 标题 /// </summary> public List<String> Title; /// <summary> /// 是否必须一致 /// </summary> public bool IsTitleEq; /// <summary> /// 是否必须 /// </summary> public bool IsRequire; /// <summary> /// 表标题不能包含的文字 /// </summary> public List<String> ExcludeTitle; /// <summary> /// 抽取内容预处理器 /// </summary> public Func<String, String, String> Normalize; }
下面是一个表格抽取的例子:
增持前 | (合并表头) | 增持后 | (合并表头) |
---|
持股数 | 持股比例 | 持股数 | 持股比例 |
---|
这里咱们想抽取持股比例和持股数,可是但愿抽取的是增持后的部分,因此须要使用SuperTitle的规则了。
var HoldList = new List<struHoldAfter>(); var StockHolderRule = new TableSearchRule(); StockHolderRule.Name = "股东全称"; StockHolderRule.Title = new string[] { "股东名称", "名称", "增持主体", "增持人", "减持主体", "减持人" }.ToList(); StockHolderRule.IsTitleEq = true; StockHolderRule.IsRequire = true; var HoldNumberAfterChangeRule = new TableSearchRule(); HoldNumberAfterChangeRule.Name = "变更后持股数"; HoldNumberAfterChangeRule.IsRequire = true; HoldNumberAfterChangeRule.SuperTitle = new string[] { "减持后", "增持后" }.ToList(); HoldNumberAfterChangeRule.IsSuperTitleEq = false; HoldNumberAfterChangeRule.Title = new string[] { "持股股数","持股股数", "持股数量","持股数量", "持股总数","持股总数","股数" }.ToList(); HoldNumberAfterChangeRule.IsTitleEq = false; var HoldPercentAfterChangeRule = new TableSearchRule(); HoldPercentAfterChangeRule.Name = "变更后持股数比例"; HoldPercentAfterChangeRule.IsRequire = true; HoldPercentAfterChangeRule.SuperTitle = HoldNumberAfterChangeRule.SuperTitle; HoldPercentAfterChangeRule.IsSuperTitleEq = false; HoldPercentAfterChangeRule.Title = new string[] { "比例" }.ToList(); HoldPercentAfterChangeRule.IsTitleEq = false; var Rules = new List<TableSearchRule>(); Rules.Add(StockHolderRule); Rules.Add(HoldNumberAfterChangeRule); Rules.Add(HoldPercentAfterChangeRule); var result = HTMLTable.GetMultiInfoByTitleRules(root, Rules, false);
EntityProperty对象属性以下:
/// <summary> /// 得到合同名 /// </summary> /// <returns></returns> string GetContractName() { var e = new EntityProperty(); e.PropertyName = "合同名称"; e.PropertyType = EntityProperty.enmType.Normal; e.MaxLength = ContractTraning.MaxContractNameLength; e.MinLength = 5; e.LeadingColonKeyWordList = new string[] { "合同名称:" }; e.QuotationTrailingWordList = new string[] { "协议书", "合同书", "确认书", "合同", "协议" }; e.QuotationTrailingWordList_IsSkipBracket = true; //暂时只能选True var KeyList = new List<ExtractPropertyByDP.DPKeyWord>(); KeyList.Add(new ExtractPropertyByDP.DPKeyWord() { StartWord = new string[] { "签署", "签定" }, //经过SRL训练得到 StartDPValue = new string[] { LTPTrainingDP.核心关系, LTPTrainingDP.定中关系, LTPTrainingDP.并列关系 }, EndWord = new string[] { "补充协议", "合同书", "合同", "协议书", "协议", }, EndDPValue = new string[] { LTPTrainingDP.核心关系, LTPTrainingDP.定中关系, LTPTrainingDP.并列关系, LTPTrainingDP.动宾关系, LTPTrainingDP.主谓关系 } }); e.DpKeyWordList = KeyList; var StartArray = new string[] { "签署了", "签定了" }; //经过语境训练得到 var EndArray = new string[] { "合同" }; e.ExternalStartEndStringFeature = Utility.GetStartEndStringArray(StartArray, EndArray); e.ExternalStartEndStringFeatureCandidatePreprocess = (x) => { return x + "合同"; }; e.MaxLengthCheckPreprocess = str => { return EntityWordAnlayzeTool.TrimEnglish(str); }; //最高级别的置信度,特殊处理器 e.LeadingColonKeyWordCandidatePreprocess = str => { var c = Normalizer.ClearTrailing(TrimJianCheng(str)); return c; }; e.CandidatePreprocess = str => { var c = Normalizer.ClearTrailing(TrimJianCheng(str)); var RightQMarkIdx = c.IndexOf("”"); if (!(RightQMarkIdx != -1 && RightQMarkIdx != c.Length - 1)) { //对于"XXX"合同,有右边引号,但不是最后的时候,不用作 c = c.TrimStart("“".ToCharArray()); } c = c.TrimStart("《".ToCharArray()); c = c.TrimEnd("》".ToCharArray()).TrimEnd("”".ToCharArray()); return c; }; e.ExcludeContainsWordList = new string[] { "平常经营重大合同" }; //下面这个列表的根据不足 e.ExcludeEqualsWordList = new string[] { "合同", "重大合同", "项目合同", "终止协议", "经营合同", "特别重大合同", "相关项目合同" }; e.Extract(this); //是否全部的候选词里面包括(测试集没法使用) var contractlist = TraningDataset.ContractList.Where((x) => { return x.id == this.Id; }); if (contractlist.Count() > 0) { var contract = contractlist.First(); var contractname = contract.ContractName; if (!String.IsNullOrEmpty(contractname)) { e.CheckIsCandidateContainsTarget(contractname); } } //置信度 e.Confidence = ContractTraning.ContractES.GetStardardCI(); return e.EvaluateCI(); }
感谢阿里巴巴组委会提供标注好的金融数据。
感谢组委会@通联数据_梅洁,@梅童的及时答疑。
感谢微信好友 邓少冬 潘昭鸣 NLP宋老师 的帮助和指导