SaaS 应用程序,需要满足以下 7 个条件:

1. 应用程序必须支持多租户

多租户是决定 SaaS 效率的关键因素。通常,应用程序支持多个用户,但是前提是它认为所有用户都来自同一个组织。这种模型适用于未出现 SaaS 的时代,组织会购买一个软件应用程序供自己的成员使用。但是在 SaaS 和云的世界中,许多组织都将使用同一个应用程序;他们必须能够允许自己的用户访问应用程序,但是应用程序必须只允许每个组织自己的成员访问其组织的数据。

能够让多个组织(SaaS 中的术语称为租户)共存于相同的应用程序,同时不会破坏这些组织的数据的安全性,具备这种能力的应用程序就可以称之为多租户应用程序。

2. 应用程序必须具备某种程度的自助注册功能

应用程序必须具备某种程度的自助注册功能,即便仅仅是一种请求机制,即产生一种向应用程序添加租户的业务流程。

3. 必须具备订阅/记账机制

必须提供订阅和记账机制。因为 SaaS 应用程序被设计为根据各种因素进行支付,如每个租户的用户数、应用程序选择,还可能包括使用时间等,必须通过某种方式来跟踪和管理应用程序的使用,然后生成可由租户管理人员访问的记账信息。

4. 应用程序必须能够有效地扩展

必须能够随着订阅的增长进行扩展。云基础架构是实现这一目的的逻辑方式,因为它嵌入了许多功能,可以支持您实现有效的、高效的扩展。

同样,必须提供治理和应用程序管理功能,以监视、配置和管理应用程序及所有租户。

5. 必须能够监视、配置和管理应用程序和租户

必须提供一种机制,可以对所有的用户进行监视和配置,并且可以管理租户使用的应用程序模块。SaaS运营商那,必须具备管理平台能够统一管理这些信息。

6. 必须有一种机制能够支持惟一的用户标识和身份验证

需要提供一种机制以支持用户标识和身份验证,允许用户拥有惟一的身份标识。由于多租户要求识别注册到系统中的所有用户,从而确定他们所属的租户,因此必须有一种明确的关系来将用户识别为属于某个特定租户。这种用户与租户的关系是非常重要的信息,用于限制用户可以访问的数据。

电子邮件地址是实现这一目的的一种典型方法,通过这种方式可以确保惟一性,每个用户可以被识别和标识为属于某个特定租户。

有多种身份验证机制和集成方法,因此必须具备一种灵活的机制来识别用户。通常,某个特定的租户需要能够利用其现有的 LDAP 或其他目录服务或身份验证机制来支持对 SaaS 应用程序的单点登录。尽管这种外部的用户身份验证非常重要,但是,需要由 SaaS 应用程序确定已识别的用户是属于它们所宣称的租户的成员。

7. 必须有一种机制能够支持对每个租户进行某种程度的自定义

必须提供一种机制来支持对每个租户进行一定程度的基本自定义,从而使它们具有惟一的 URL、登入页面、标识、配色方案、字体,甚至包括语言。

对每个租户的基本配置是预期的功能,但是要真正地满足多租户的需求,必须在基本配置的基础上对每个租户实现一定程度的自定义。

所需的典型定制类似于租户对内部应用程序版本所作出的定制。包括添加字段甚至是表格,设置特殊的业务逻辑,或集成另一种应用程序。在每个租户的基础上能够进行这类自定义,同时无需建立单独的实例(否则会降低多租户设计的效率),这就是高性能 SaaS 架构的典型特征。

安装Zeppelin

到官网下载二进制包(http://zeppelin.apache.org/download.html),当然,你要是愿意,可以下载源码自己编译。

wget http://apache.fayea.com/incubator/zeppelin/0.5.0-incubating/zeppelin-0.5.0-incubating-bin-spark-1.4.0_hadoop-2.3.tgz
tar -zxvf zeppelin-0.5.0-incubating-bin-spark-1.4.0_hadoop-2.3.tgz

至此,安装完毕,是不是非常简单?

运行Zeppelin(启动/停止)

启动Zeppelin

bin/zeppelin-daemon.sh start

停止Zeppelin

bin/zeppelin-daemon.sh stop

是不是超级简单?打开浏览器: http://192.168.1.230:8080,界面就出现了。

1.png

什么是Interpreter?

Zeppelin Interpreter是一门后端语言。例如,要在Zeppelin使用Scala代码,你需要scala解释器(interpreter)。简单说,你要运行MySQL代码,你需要MySQL解释器,这个需要一些小小的开发。

开发MySQL Interpreter

编译代码

去我的github下载源码并且编译,这个很重要!!!

git clone https://github.com/jiekechoo/zeppelin-interpreter-mysql
mvn clean package

部署二进制包

假装你的zeppelin安装在 /opt/zeppelin目录

mkdir /opt/zeppelin/interpreter/mysql
cp target/zeppelin-mysql-0.5.0-incubating.jar /opt/zeppelin/interpreter/mysql
# copy dependencies to mysql directory
cp mysql-connector-java-5.1.6.jar log4j-1.2.17.jar slf4j-api-1.7.10.jar slf4j-log4j12-1.7.10.jar commons-exec-1.1.jar /opt/zeppelin/interpreter/mysql
cp conf/zeppelin-site.xml.template conf/zeppelin-site.xml
vi conf/zeppelin-site.xml

在zeppelin.interpreters 的value里增加一些内容 ,org.apache.zeppelin.mysql.MysqlInterpreter
如下所示

<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.mysql.MysqlInterpreter</value>

重启zeppelin即可

bin/zeppelin-daemon.sh restart

运行MySQL代码

加载mysql interpreter

登录管理界面,Interpreter -> Create, 类似下面的页面,完成点击 Save

2.png

创建 Notebook,完成你的可视化

点击右上角的setting,并且确保mysql被选中,保存Save

3.png

输入你要执行的SQL语句,相信你再熟悉不过了

%mysql

SELECT datetime, count FROM Syslog.PerformanceLog WHERE datetime > DATE_SUB(CURDATE(), INTERVAL 7 DAY);

点击运行按钮,结果出现了,是不是很神奇?

4.png

可以做成报表模式,更好看更爽了

5.png

可以在其他地方引用这张报表

6.png

将链接作为框架引入你的代码中吧

7.png

几张效果图

8.png

9.png

前面有篇文章,介绍Zeppelin MySQL Interpreter 的功能,可以访问MySQL数据库,并实现数据可视化,今天,有更好更方便的方式来访问MySQL,那就是 Zeppelin JDBC 方式,当然,你得有Spark和scala编程知识。虽然是一大挑战,但是值得你付出。不行咱们来看……

理解概念

  • Zeppelin: Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的notebook。Zeppelin提供了数据可视化的框架。Zeppelin提供了数据分析、数据可视化等功能。使用Spark作为数据获取方式,更加方便可靠。
  • 可视化: 可视化(Visualization)是利用计算机图形学和图像处理技术,将数据转换成图形或图像在屏幕上显示出来,并进行交互处理的理论、方法和技术。它涉及到计算机图形学、图像处理、计算机视觉、计算机辅助设计等多个领域,成为研究数据表示、数据处理、决策分析等一系列问题的综合技术。目前正在飞速发展的虚拟现实技术也是以图形图像的可视化技术为依托的。
  • SPARK: Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
  • JDBC: JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序.

需求描述

其实,对于很多时候,大家的可视化需求都差不多,最容易让人体验到的是:WEB展示方式,JDBC连接方便(Oracle, MySQL, MSSQL, PostgreSQL等),SQL语句,图表化,最好可以随时调整。对了,就是这样的需求。

1.png

实现方式

1. 推荐下载zeppelin 0.5.5版本,找一个离你最近的站点,

wget http://www.apache.org/dyn/closer.cgi/incubator/zeppelin/0.5.5-incubating/zeppelin-0.5.5-incubating-bin-all.tgz
tar -zxvf zeppelin-0.5.5-incubating-bin-all.tgz
cd zeppelin-0.5.5-incubating-bin-all

下载MySQL jdbc driver,并复制到 zeppelin-0.5.5-incubating-bin-all/lib 目录下,启动zeppelin:

bin/zeppelin-daemon.sh start

2. MySQL服务器需要开启zeppelin服务器的访问权限,这里不做说明;

3. 在MySQL服务器上把SQL语句跑一遍,保证SQL没有问题。

4. Zeppelin新建一个Notebook,把Spark语句放进去:

输入如下信息,点击运行Run:

// MySQL连接信息
val properties = new java.util.Properties()
properties.setProperty("user", "user")
properties.setProperty("password", "password")

// 获取数据内容
val users = sqlContext.read.jdbc("jdbc:mysql://20.1.1.130:3306/mydb", "mytable", Array("ip='192.168.1.24'"), properties)

// 创建临时查询表
users.registerTempTable("users")

// 检查临时表是否存在,实际应用中可以去除
sqlContext.tableNames().foreach(println)

新建一个操作框,把SQL语句放进去,点击运行Run:

%sql
SELECT * FROM users

实现效果

至于图表展示,根据自己的需求灵活定义,下面来放几张样例参考一下:

最近数据量统计——柱状图

2.png

最近数据量统计——折线图

3.png

最近数据量饼图

4.png

数据库操作响应时间——散点图

5.png

什么是OpenSOC

思科在BroCON大会上亮相了其安全大数据分析架构OpenSOC(由Cisco和Hortonworks共同开发),引起了广泛关注。OpenSOC是一个针对网络包和流的大数据分析框架,它是大数据分析与安全分析技术的结合,
能够实时的检测网络异常情况并且可以扩展很多节点,它的存储使用开源项目Hadoop,实时索引使用开源项目ElasticSearch,在线流分析使用著名的开源项目Storm。

OpenSOC是大数据安全分析的框架设计,对数据中心机排放数据进行消费和监控网络流量。opensoc是可扩展的,目的是在一个大规模的集群上工作。

OpenSOC能做什么?

  • 可扩展的接收器和分析器能够监视任何Telemetry数据源
  • 是一个扩展性很强的框架,且支持各种Telemetry数据流
  • 支持对Telemetry数据流的异常检测和基于规则实时告警
  • 通过预设时间使用Hadoop存储Telemetry的数据流
  • 支持使用ElasticSearch实现自动化实时索引Telemetry数据流
  • 支持使用Hive利用SQL查询存储在Hadoop中的数据
  • 能够兼容ODBC/JDBC和继承已有的分析工具
  • 具有丰富的分析应用,且能够集成已有的分析工具
  • 支持实时的Telemetry搜索和跨Telemetry的匹配
  • 支持自动生成报告、和异常报警
  • 支持原数据包的抓取、存储、重组
  • 支持数据驱动的安全模型

是不是很强大?

OpenSOC运行组件包括哪些?

intro.jpg

  • 两个网卡(建议使用Napatech的NT20E2-CAP网卡)
  • Apache Flume 1.4.0版本及以上
  • Apache Kafka 0.8.1版本及以上
  • Apache Storm 0.9版本及以上
  • Apache Hadoop 2.x系列的任意版本
  • Apache Hive 12版本及以上(建议使用13版本)
  • Apache Hbase 0.94版本及以上
  • ElasticSearch 1.1版本及以上
  • MySQL 5.6版本及以上等。

据说,能把这个框架跑起来的人不多。

OpenSOC组成部分(核心)

OpenSOC-Streaming:这个库包含了拓扑结构的加工、丰富,索引,及相关的信息,PCAP重建服务,以及其他各种数据服务。可以在GitHub下载:https://github.com/opensoc/opensoc-streaming

OpenSOC-UI:操作界面,日志和网络数据包的分析,显示警告和错误。可以在GitHub下载:https://github.com/opensoc/opensoc-ui

OpenSOC深入剖析

OpenSOC框架是大数据分析框架的衍生

先来看看OpenSOC概念性框架,总体来说,就是分为三个部分:左侧为数据输入(采集),中间为数据处理(计算),右侧为数据分析(展现和输出)。

arc1.jpg

再来看看其数据流框架,在概念性框架的基础上,把每一步的功能组件都一一列清楚了。分为六个部分:

  1. Source Systems,数据输入源,分为主动和被动两种方式;
  2. Data Collection,数据收集,主要采用Flume进行数据收集和预处理,PCAP进行抓包收集;
  3. Messaging System,消息系统,主要是Kafka分布式消息系统进行数据缓存,根据数据源不一样来划分不同的topic;
  4. Real Time Processing,实时处理,主要采用Storm实时计算框架进行数据整理,聚合,DPI分析,等,这里,每个kafka topic都需要单独的storm 应用程序来独立处理;
  5. Storage,存储,就是把计算的结果和原始数据写入相应的存储模块,原始数据存入Hive,日志数据存入ElasticSearch便于索引查找(结合kibana),抓包数据存入HBase;
  6. Access,访问层,简单说就是把分析结果数据从存储中取出来,通过各种BI工具渲染到页面,当然,也可以把数据以web service的方式提供给第三方。

arc2.jpg

赛克通公司在大数据处理方面,也有自己的一套计算框架,此框架可调整性相对较大,可以根据用户数据体量,计算要求灵活变换。

sectong_arc.jpg

核心计算模型

OpenSOC核心计算模型采用Storm来负责处理,前面提到过,每个设备或数据流,都需要单独的storm应用程序(topology,拓扑)来进行运算,这个对于部署storm应用的时候特别要注意了。
当然,storm是分布式计算框架,应用程序多的话只需要增加物理计算机就能解决,不是什么大问题。

PCAP分析拓扑

pcap.jpg

由图示可以看出,storm的kafka spout获取到的pcap数据,经过基本解析后即存入相应的存储模块,并没有太多的计算内容。这个相对而言比较容易理解。

DPI分析拓扑

dpi.jpg

由图示看出,DPI拓扑中,计算引擎做的功能比pcap多了许多,中间关联了很多其他模块,比如:whois查询,GEO地址定位等,丰富了分析的内容。
由此可以联想到,在DPI拓扑中,我们可以增加其他的第三方关联,比如:botnet僵尸网络知识库,ip reputation数据库(ossim提供),舆情数据库等,这样这个计算框架就强大了许多,可以做的分析也就更多。

Enrichment高级分析

enrichment.jpg

高级分析模块详解,这里把每个步骤的数据流都做了对照,GEO信息来自MySQL数据库,whois信息和CIF来自HBase数据库。

更多的关于最佳实践和学习,请参照官方发布的PPT内容。

OpenSOC 改进建议

总体框架性改进

improve.jpg

总体框架上,可以让OpenSOC更加灵活一些,处理流程可以更加简化一些。比如:由storm计算完成之后,不一定由storm直接写入相应存储(mysql,hive,hbase,es等),
而是再次丢入kafka(其他应用和计算框架还能重复利用),由另外的程序来进行执行写入操作,这样减轻了storm的负担,增加了灵活性。

处理引擎类改进

spark_replace_storm.jpg

大数据工程师都知道,storm是实时处理引擎,时效性非常好。但是,需要加入机器学习或SQL引擎时,storm就失色了。这时候,Spark来了,有可能替代hadoop的引擎出现了,对于应用开发更加轻松。不多说,上图。

storm_vs_spark.jpg

可视化引擎改进

我们都知道,OpenSOC可视化引擎用的kibana,kibana能做的事情很多很多,当然,kibana对于elasticsearch非常合适,其他的就不一定了。所以,必须要结合相应的BI工具,比如:Tableau,Power Pivot等。
如果由spark代替了storm作为计算引擎,可以在可视化框架中加入zeppelin(一个基于spark的开源可视化notebook工具),直接在zeppelin中将业务逻辑梳理好,这样更加快速的加速spark的开发。
关于zeppelin的详细信息请参见【数据可视化】Zeppelin JDBC 数据可视化(WEB方式)

1、简单概述

1.1 NLP概念

NLP(Natural Language Processing),自然语言处理,又称NLU(Natural Language Understanding)自然语言理解,是语言信息处理的分支,也是人工智能的核心课题,简单来说就是让计算机理解自然语言。

1.2 NLP涉及的内容及技术

自然语言处理研究包含的内容十分广泛,这里只列举出其中的其中的一部分(主要是在移动易系统中涉及到的),包括分词处理(Word-Segment),词性标注(Part-of-Speech tagging),句法分析(Parsing),信息检索(Infomation-Retrieval),文字校对(Text-Rroofing),词向量模型(WordVector-Model),语言模型(Language-Model),问答系统(Question-Answer-System)。如下逐一介绍。

2、前期准备

  1. Lucene使用经验
  2. python使用经验
  3. 相关工具包如下:
工具版本下载地址
哈工大LTPltp4jdownload
berkeleylmberkeleylm 1.1.5download
ElasticSearchelasticsearch-2.4.5download

3、具体实现

3.1 分词(Word-Segment)

3.1.1 这里主要介绍中文分词的实现,实现中文分词的方法有许多种,例如StandfordCore NLP(具体实现参见【NLP】使用 Stanford NLP 进行中文分词 ),jieba分词,这里使用哈工大的语言技术平台LTP(包括后面的词性标注,句法分析)。具体步骤如下:

  • 首先下载LTP4J的jar包(download),
  • 下载完解压缩后的文件包为ltp4j-master,相应的jar包就在output文件夹下的jar文件夹中。
  • 下载编译好的C++动态链接库download,解压后如下所示:

1.png
 

  • 将文件夹中的所有内容复制到jdk的bin目录下,如下所示:

2.png

  • 构建Java项目,将jar包导入到项目中,右键项目buildpath,为添加的jar包添加本来地库依赖,路劲即下载解压后的dll动态库文件路径,如下所示:

3.png

  • 接着便是中文分词的测试了,实现代码如下:

    package ccw.ltpdemo;
    import java.util.ArrayList;
    import java.util.List;
    
    import edu.hit.ir.ltp4j.Segmentor;
    public class ltpSegmentDemo {
        public static void main(String[] args) {
            Segmentor segmentor = new Segmentor();
            if(segmentor.create("D:/NLP/ltp/ltp_data_v3.4.0/ltp_data_v3.4.0/cws.model")<0)
            {
                System.out.println("model load failed");
            }
            else
            {
                String sent = "这是中文分词测试";
                List<String> words = new ArrayList<String>();
                int size = segmentor.segment(sent, words);
                for(String word :words)
                {
                    System.out.print(word+"\t");
                }
                segmentor.release();
            }
        }
    }

3.1.2 效果如下:

4.png

3.2 词性标注(Part-of-Speech tagging)

3.2.1 这里介绍如何通过ltp实现中文的词性标注,具体实现代码如下:


    package ccw.ltpdemo;
    import java.util.ArrayList;
    import java.util.List;
    
    import edu.hit.ir.ltp4j.Postagger;
    public class ltpPostaggerDemo {
        public static void main(String[] args) {
            Postagger postagger = new Postagger();
            if(postagger.create("D:/NLP/ltp/ltp_data_v3.4.0/ltp_data_v3.4.0/pos.model")<0)
            {
                System.out.println("model load failed");
            }
            else
            {
                List<String> words = new ArrayList<String>();
                words.add("我");
                words.add("是");
                words.add("中国");
                words.add("人");
                List<String> values = new ArrayList<String>();
                
                int size = postagger.postag(words, values);
                for(int i = 0;i<words.size();i++)
                {
                    System.out.print(words.get(i)+" "+values.get(i)+"\t");
                }
                postagger.release();
            }
        }
    }

3.2.2 实现效果如下:

5.png

3.3 句法分析(Parsing)

3.3.1 这里介绍如何通过ltp实现对中文句子的句法分析,核心方法int size = Parser.parse(words,tags,heads,deprels),其中,words[]表示待分析的词序列,tags[]表示待分析的词的词性序列,heads[]表示结果依存弧,heads[i]代表第i个节点的父节点编号(其中第0个表示根节点root),deprels[]表示依存弧的关系类型,size表示返回结果中词的个数。实现代码如下:

    
    package ccw.ltpdemo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import edu.hit.ir.ltp4j.Parser;
    
    public class ltpParserDemo {
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            Parser parser = new Parser();
            if(parser.create("D:/NLP/ltp/ltp_data_v3.4.0/ltp_data_v3.4.0/parser.model")<0)
            {
                System.out.println("model load failed");
            }
            else
            {
                 List<String> words = new ArrayList<String>();
                    List<String> tags = new ArrayList<String>();
                    words.add("我");tags.add("r");
                    words.add("非常");tags.add("d");
                    words.add("喜欢");tags.add("v");
                    words.add("音乐");tags.add("n");
                    List<Integer> heads = new ArrayList<Integer>();
                    List<String> deprels = new ArrayList<String>();
                    int size = Parser.parse(words,tags,heads,deprels);
                    for(int i = 0;i<size;i++) {
                      System.out.print(heads.get(i)+":"+deprels.get(i));
                      if(i==size-1) {
                        System.out.println();
                      }
                      else{
                        System.out.print("        ");
                      }
                    }
    
                    parser.release();
            }
        }
    
    }

3.3.2 实现效果如下:

6.png

3.4 信息检索(Information-Retrieval)

信息检索(Information Retrieval)是用户进行信息查询和获取的主要方式,是查找信息的方法和手段。狭义的信息检索仅指信息查询(Information Search)。即用户根据需要,采用一定的方法,借助检索工具,从信息集合中找出所需要信息的查找过程。实现参见移动易实现全文搜索

3.5 文字校对(Text-Rroofing),语言模型(Language-Model)

3.5.1 N元模型(N-gram)

首先介绍N-gram模型,N-gram模型是自然语言处理中一个非常重要的概念,通常,在NLP中,基于一定的语料库, 可以通过N-gram来预计或者评估一个句子是否合理。对于一个句子T,假设T由词序列w1,w2,w3...wn组成,那么T出现的概率

  • P(T)=P(w1,w2,w3...wn)=P(w1)P(w2|w1)P(w3|w2,w1)...p(wn|wn-1,...w2,w1),
    此概率在参数巨大的情况下显然不容易计算,因此引入了马尔可夫链(即每个词出现的概率仅仅与它的前后几个词相关),这样可以大幅度缩小计算的长度,即
  • P(wi|w1,⋯,wi−1)=P(wi|wi−n+1,⋯,wi−1)
    特别的,当n取值较小时:

当n=1时,即每一个词出现的概率只由该词的词频决定,称为一元模型(unigram-model):

  • P(w1,w2,⋯,wm)=∏i=1mP(wi)
    设M表示语料库中的总字数,c(wi)表示wi在语料库中出现的次数,那么
  • P(wi)=C(wi)/M
    当n=2时,即每一个词出现的概率只由该词的前一个词以及后一个词决定,称为二元模型(bigram-model):
  • P(w1,w2,⋯,wm)=∏i=1mP(wi|wi−1)
    设M表示语料库中的总字数,c(wi-1WI)表示wi-1wi在语料库中出现的次数,那么
  • P(wi|wi−1)=C(wi−1wi)/C(wi−1)
    当n=3时,称为三元模型(trigram-model):
  • P(w1,w2,⋯,wm)=∏i=1mP(wi|wi−2wi−1)
    那么
  • P(wi|wi−1wi-2)=C(wi-2wi−1wi)/C(wi−2wi-1)

3.5.2 中文拼写纠错

接着介绍如何通过Lucene提供的spellChecker(拼写校正)模块实现中文字词的纠错,首先创建语料词库,如下所示:

7.png

然后在代码中创建索引并测试,具体实现代码如下:

    
      package ccw.spring.ccw.lucencedemo;
      import java.io.BufferedReader;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.util.Iterator;
      import org.apache.lucene.index.IndexReader;
      import org.apache.lucene.index.IndexWriterConfig;
      import org.apache.lucene.search.spell.LuceneDictionary;
      import org.apache.lucene.search.spell.PlainTextDictionary;
      import org.apache.lucene.search.spell.SpellChecker;
      import org.apache.lucene.search.suggest.InputIterator;
      import org.apache.lucene.store.Directory;
      import org.apache.lucene.store.FSDirectory;
      import org.apache.lucene.util.Version;
      public class Spellcheck {
        public static String directorypath;
        public static String origindirectorypath;
        public SpellChecker spellcheck;
        public LuceneDictionary dict;
        
    /**
     * 创建索引
     * a
     * @return
     * @throws IOException
     * boolean
     */
    public static void createIndex(String directorypath,String origindirectorypath) throws IOException
    {
        Directory directory = FSDirectory.open(new File(directorypath));
        
        SpellChecker spellchecker = new SpellChecker(directory);
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_9, null);
        PlainTextDictionary pdic = new PlainTextDictionary(new InputStreamReader(new FileInputStream(new File(origindirectorypath)),"utf-8"));
        spellchecker.indexDictionary(new PlainTextDictionary(new File(origindirectorypath)), config, false);
        directory.close();
        spellchecker.close();
    }
    public Spellcheck(String opath ,String path)
    {
        origindirectorypath = opath;
        directorypath = path;
        Directory directory;
        try {
               directory = FSDirectory.open(new File(directorypath));
               spellcheck = new SpellChecker(directory);
               IndexReader oriIndex = IndexReader.open(directory);
               dict = new LuceneDictionary(oriIndex,"name");
            }
         catch (IOException e) {
                     e.printStackTrace();
                 }
             
    }
    public void setAccuracy(float v)
    {
        spellcheck.setAccuracy(v);
    }
    
    
    public String[]search(String queryString, int suggestionsNumber)
    {
        String[]suggestions = null;
        try {
              if (exist(queryString))
              return null;
              suggestions = spellcheck.suggestSimilar(queryString,suggestionsNumber);
            }
             catch (IOException e) 
            {
              e.printStackTrace();
            }
            return suggestions;
    }
              
    private boolean exist(String queryString) throws IOException {
        InputIterator ite =  dict.getEntryIterator();
            while (ite.hasContexts())
              {
                  if (ite.next().equals(queryString))
                      return true;
              }
                     return false;
        }
    
    
    public static void main(String[] args) throws IOException {
        String opath = "D:\\Lucene\\NLPLucene\\words.txt";
        String ipath = "D:\\Lucene\\NLPLucene\\index";
        Spellcheck.createIndex(ipath, opath);
        Spellcheck spellcheck = new Spellcheck(opath,ipath);
        //spellcheck.createSpellIndex();
        
        spellcheck.setAccuracy((float) 0.5);
        String [] result = spellcheck.search("麻辣糖", 15);
        if(result.length==0||null==result)
        {
            System.out.println("未发现错误");
        }
        else
        {
            System.out.println("你是不是要找:");
            for(String hit:result)
            {
                System.out.println(hit);
            }
        }
    }
    
                 
    }

实现效果如下:

8.png

3.5.3 中文语言模型训练

这里主要介绍中文语言模型的训练,中文语言模型的训练主要基于N-gram算法,目前开源的语言模型训练的工具主要有SRILM、KenLM、 berkeleylm 等几种,KenLm较SRILM性能上要好一些,用C++编写,支持单机大数据的训练。berkeleylm是用java写。本文主要介绍如何通过berkelylm实现中文语言模型的训练。

  • 首先需要下载berkeleylm的jar包(download),完成后将jar包导入到java项目中。
  • 然后准备训练的语料库,首先通过ltp将每一句文本分词,然后将分完词的语句写入txt文件,如下所示:

9.png

  • 接着就是对语料库的训练,首先要读取分完词的文本,然后就是对每个词计算在给定上下文中出现的概率,这里的概率是对10取对数后计算得到的,最后将结果按照给定的格式存储,可以按照.arpa或者二进制.bin文件存储。文件格式如下:

10.png

实现代码如下:


    package ccw.berkeleylm;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    import edu.berkeley.nlp.lm.ConfigOptions;
    import edu.berkeley.nlp.lm.StringWordIndexer;
    import edu.berkeley.nlp.lm.io.ArpaLmReader;
    import edu.berkeley.nlp.lm.io.LmReaders;
    import edu.berkeley.nlp.lm.util.Logger;
    
    public class demo {
        
        
        private static void usage() {
            System.err.println("Usage: <lmOrder> <ARPA lm output file> <textfiles>*");
            System.exit(1);
        }
        
        public void makelml(String [] argv)
        {
            if (argv.length < 2) {
                usage();
            }
            final int lmOrder = Integer.parseInt(argv[0]);
            final String outputFile = argv[1];
            final List<String> inputFiles = new ArrayList<String>();
            for (int i = 2; i < argv.length; ++i) {
                inputFiles.add(argv[i]);
            }
            if (inputFiles.isEmpty()) inputFiles.add("-");
            Logger.setGlobalLogger(new Logger.SystemLogger(System.out, System.err));
            Logger.startTrack("Reading text files " + inputFiles + " and writing to file " + outputFile);
            final StringWordIndexer wordIndexer = new StringWordIndexer();
            wordIndexer.setStartSymbol(ArpaLmReader.START_SYMBOL);
            wordIndexer.setEndSymbol(ArpaLmReader.END_SYMBOL);
            wordIndexer.setUnkSymbol(ArpaLmReader.UNK_SYMBOL);
            LmReaders.createKneserNeyLmFromTextFiles(inputFiles, wordIndexer, lmOrder, new File(outputFile), new ConfigOptions());
            Logger.endTrack();
        }
        
        public static void main(String[] args) {
            
            demo d = new demo();
            String inputfile = "D:\\NLP\\languagematerial\\quest.txt";
            String outputfile = "D:\\NLP\\languagematerial\\q.arpa";
            String s[]={"8",outputfile,inputfile};
            d.makelml(s);
            
        }
    
    }
  • 最后就是读取模型,然后判断句子的相似性,实现代码如下:

    package ccw.berkeleylm;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    import edu.berkeley.nlp.lm.ArrayEncodedProbBackoffLm;
    import edu.berkeley.nlp.lm.ConfigOptions;
    import edu.berkeley.nlp.lm.StringWordIndexer;
    import edu.berkeley.nlp.lm.io.LmReaders;
    
    public class readdemo {
        
        public static ArrayEncodedProbBackoffLm<String> getLm(boolean compress,String file) {
            final File lmFile = new File(file);
            final ConfigOptions configOptions = new ConfigOptions();
            configOptions.unknownWordLogProb = 0.0f;
            final ArrayEncodedProbBackoffLm<String> lm = LmReaders.readArrayEncodedLmFromArpa(lmFile.getPath(), compress, new StringWordIndexer(), configOptions,
                Integer.MAX_VALUE);
            return lm;
        }
        
        
        public static void main(String[] args) {
            readdemo read = new readdemo();
            LmReaders readers = new LmReaders();
            ArrayEncodedProbBackoffLm<String> model = (ArrayEncodedProbBackoffLm) readdemo.getLm(false, "D:\\NLP\\languagematerial\\q.arpa");
            String sentence = "是";
            String [] words = sentence.split(" ");
            List<String> list = new ArrayList<String>();
            for(String word : words)
            {
                System.out.println(word);
                list.add(word);
            }
            float score = model.getLogProb(list);
            System.out.println(score);
        }
    
    }

实现效果如下:

11.png

3.5.4 同义词词林

这里使用哈工大提供的同义词词林,词林提供三层编码,第一级大类用大写英文字母表示,第二级中类用小写字母表示,第三级小类用二位十进制整数表示,第四级词群用大写英文字母表示,第五级原子词群用二位十进制整数表示。编码表如下所示:

12.png   

第八位的标记有三种,分别是“=“、”#“、”@“,=代表相等、同义,#代表不等、同类,@代表自我封闭、独立,它在词典中既没有同义词,也没有相关词。通过同义词词林可以比较两词的相似程度,代码实现如下:


    package cilin;
    import java.io.BufferedReader;
    import java.io.FileInputStream;
    import java.io.InputStreamReader;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Vector;
    public class CiLin {
    public static HashMap<String, List<String>> keyWord_Identifier_HashMap;//<关键词,编号List集合>哈希
    
    public int zero_KeyWord_Depth = 12;
    public static HashMap<String, Integer> first_KeyWord_Depth_HashMap;//<第一层编号,深度>哈希
    public static HashMap<String, Integer> second_KeyWord_Depth_HashMap;//<前二层编号,深度>哈希
    public static HashMap<String, Integer> third_KeyWord_Depth_HashMap;//<前三层编号,深度>哈希
    public static HashMap<String, Integer> fourth_KeyWord_Depth_HashMap;//<前四层编号,深度>哈希
    //public HashMap<String, HashSet<String>> ciLin_Sort_keyWord_HashMap = new HashMap<String, HashSet<String>>();//<(同义词)编号,关键词Set集合>哈希
    
    static{
        keyWord_Identifier_HashMap = new HashMap<String, List<String>>();
        first_KeyWord_Depth_HashMap = new HashMap<String, Integer>();
        second_KeyWord_Depth_HashMap = new HashMap<String, Integer>();
        third_KeyWord_Depth_HashMap = new HashMap<String, Integer>();
        fourth_KeyWord_Depth_HashMap = new HashMap<String, Integer>();
        initCiLin();
    }
    
    //3.初始化词林相关
    public static void initCiLin(){
        int i;
        String str = null;
        String[] strs = null;
        List<String> list = null;
        BufferedReader inFile = null;
        try {
            //初始化<关键词, 编号set>哈希
            inFile = new BufferedReader(new InputStreamReader(new FileInputStream("cilin/keyWord_Identifier_HashMap.txt"), "utf-8"));// 读取文本
            while((str = inFile.readLine()) != null){
                strs = str.split(" ");
                list = new Vector<String>();
                for (i = 1; i < strs.length; i++) 
                    list.add(strs[i]);
                keyWord_Identifier_HashMap.put(strs[0], list);
            }
            
            //初始化<第一层编号,高度>哈希
            inFile.close();
            inFile = new BufferedReader(new InputStreamReader(new FileInputStream("cilin/first_KeyWord_Depth_HashMap.txt"), "utf-8"));// 读取文本
            while ((str = inFile.readLine()) != null){
                strs = str.split(" ");
                first_KeyWord_Depth_HashMap.put(strs[0], Integer.valueOf(strs[1]));
            }
            
            //初始化<前二层编号,高度>哈希
            inFile.close();
            inFile = new BufferedReader(new InputStreamReader(new FileInputStream("cilin/second_KeyWord_Depth_HashMap.txt"), "utf-8"));// 读取文本
            while ((str = inFile.readLine()) != null){
                strs = str.split(" ");
                second_KeyWord_Depth_HashMap.put(strs[0], Integer.valueOf(strs[1]));
            }
            
            //初始化<前三层编号,高度>哈希
            inFile.close();
            inFile = new BufferedReader(new InputStreamReader(new FileInputStream("cilin/third_KeyWord_Depth_HashMap.txt"), "utf-8"));// 读取文本
            while ((str = inFile.readLine()) != null){
                strs = str.split(" ");
                third_KeyWord_Depth_HashMap.put(strs[0], Integer.valueOf(strs[1]));
            }
        
            //初始化<前四层编号,高度>哈希
            inFile.close();
            inFile = new BufferedReader(new InputStreamReader(new FileInputStream("cilin/fourth_KeyWord_Depth_HashMap.txt"), "utf-8"));// 读取文本
            while ((str = inFile.readLine()) != null){
                strs = str.split(" ");
                fourth_KeyWord_Depth_HashMap.put(strs[0], Integer.valueOf(strs[1]));
            }
            inFile.close();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        
    //根据两个关键词计算相似度
    public static double calcWordsSimilarity(String key1, String key2){
        List<String> identifierList1 = null, identifierList2 = null;//词林编号list
        if(key1.equals(key2))
            return 1.0;
        
        if (!keyWord_Identifier_HashMap.containsKey(key1) || !keyWord_Identifier_HashMap.containsKey(key2)) {//其中有一个不在词林中,则返回相似度为0.1
            //System.out.println(key1 + "  " + key2 + "有一个不在同义词词林中!");
            return 0.1;
        }
        identifierList1 = keyWord_Identifier_HashMap.get(key1);//取得第一个词的编号集合
        identifierList2 = keyWord_Identifier_HashMap.get(key2);//取得第二个词的编号集合
        
        return getMaxIdentifierSimilarity(identifierList1, identifierList2);
    }
        
    public static double getMaxIdentifierSimilarity(List<String> identifierList1, List<String> identifierList2){
        int i, j;
        double maxSimilarity = 0, similarity = 0;
        for(i = 0; i < identifierList1.size(); i++){
            j = 0;
            while(j < identifierList2.size()){
                similarity = getIdentifierSimilarity(identifierList1.get(i), identifierList2.get(j));
                System.out.println(identifierList1.get(i) + "  " + identifierList2.get(j) + "  " + similarity);
                if(similarity > maxSimilarity)
                    maxSimilarity = similarity;
                if(maxSimilarity == 1.0)    
                    return maxSimilarity;
                j++;
            }
        }
        return maxSimilarity;
    }
        
    public static double getIdentifierSimilarity(String identifier1, String identifier2){
        int n = 0, k = 0;//n是分支层的节点总数, k是两个分支间的距离.
        //double a = 0.5, b = 0.6, c = 0.7, d = 0.96;
        double a = 0.65, b = 0.8, c = 0.9, d = 0.96;
        if(identifier1.equals(identifier2)){//在第五层相等
            if(identifier1.substring(7).equals("="))
                return 1.0;
            else 
                return 0.5;
        }
        else if(identifier1.substring(0, 5).equals(identifier2.substring(0, 5))){//在第四层相等 Da13A01=
            n = fourth_KeyWord_Depth_HashMap.get(identifier1.substring(0, 5));
            k = Integer.valueOf(identifier1.substring(5, 7)) - Integer.valueOf(identifier2.substring(5, 7));
            if(k < 0) k = -k;
            return Math.cos(n * Math.PI / 180) * ((double)(n - k + 1) / n) * d;
        }
        else if(identifier1.substring(0, 4).equals(identifier2.substring(0, 4))){//在第三层相等 Da13A01=
            n = third_KeyWord_Depth_HashMap.get(identifier1.substring(0, 4));
            k = identifier1.substring(4, 5).charAt(0) - identifier2.substring(4, 5).charAt(0);
            if(k < 0) k = -k;
            return Math.cos(n * Math.PI / 180) * ((double)(n - k + 1) / n) * c;
        }
        else if(identifier1.substring(0, 2).equals(identifier2.substring(0, 2))){//在第二层相等
            n = second_KeyWord_Depth_HashMap.get(identifier1.substring(0, 2));
            k = Integer.valueOf(identifier1.substring(2, 4)) - Integer.valueOf(identifier2.substring(2, 4));
            if(k < 0) k = -k;
            return Math.cos(n * Math.PI / 180) * ((double)(n - k + 1) / n) * b;
        }
        else if(identifier1.substring(0, 1).equals(identifier2.substring(0, 1))){//在第一层相等
            n = first_KeyWord_Depth_HashMap.get(identifier1.substring(0, 1));
            k = identifier1.substring(1, 2).charAt(0) - identifier2.substring(1, 2).charAt(0);
            if(k < 0) k = -k;
            return Math.cos(n * Math.PI / 180) * ((double)(n - k + 1) / n) * a;
        }
        
        return 0.1;
    }
    }
    
    //测试
    public class Test {
        public static void main(String args[]) {
            String word1 = "相似", word2 = "相像";
            double sim = 0;
            sim = CiLin.calcWordsSimilarity(word1, word2);//计算两个词的相似度
            System.out.println(word1 + "  " + word2 + "的相似度为:" + sim);
        }
    }

测试效果如下:

13.png

3.6 词向量模型(WordVector-Model)

3.6.1 词向量

词向量顾名思义,就是用一个向量的形式表示一个词。为什么这么做?自然语言理解问题转化为机器学习问题的第一步都是通过一种方法把这些符号数学化。词向量具有良好的语义特性,是表示词语特征的常用方式。词向量的每一维的值代表一个具有一定的语义和语法上解释的特征。

3.6.2 Word2vec

Word2vec是Google公司在2013年开放的一款用于训练词向量的软件工具。它根据给定的语料库,通过优化后的训练模型快速有效的将一个词语表达成向量形式,其核心架构包括CBOW和Skip-gram。Word2vec包含两种训练模型,分别是CBOW和Skip_gram(输入层、发射层、输出层),如下图所示:

14.png

3.6.3 word2vec 训练词向量

    
    # coding:utf-8
    import sys
    reload(sys)
    sys.setdefaultencoding( "utf-8" )
    from gensim.models import Word2Vec
    import logging,gensim,os
     
    class TextLoader(object):
        def __init__(self):
            pass
     
        def __iter__(self):
            input = open('corpus-seg.txt','r')
            line = str(input.readline())
            counter = 0
            while line!=None and len(line) > 4:
                #print line
                segments = line.split(' ')
                yield  segments
                line = str(input.readline())
     
    sentences = TextLoader()
    model = gensim.models.Word2Vec(sentences, workers=8)
    model.save('word2vector2.model')
    print 'ok'
    
    # coding:utf-8
    import sys
    reload(sys)
    sys.setdefaultencoding( "utf-8" )
    from gensim.models import Word2Vec
    import logging,gensim,os
     
    #模型的加载
    model = Word2Vec.load('word2vector.model')
    #比较两个词语的相似度,越高越好
    print('"唐山" 和 "中国" 的相似度:'+ str(model.similarity('唐山','中国')))
    print('"中国" 和 "祖国" 的相似度:'+ str(model.similarity('祖国','中国')))
    print('"中国" 和 "中国" 的相似度:'+ str(model.similarity('中国','中国')))
    #使用一些词语来限定,分为正向和负向的
    result = model.most_similar(positive=['中国', '城市'], negative=['学生'])
    print('同"中国"与"城市"二词接近,但是与"学生"不接近的词有:')
    for item in result:
        print('   "'+item[0]+'"  相似度:'+str(item[1]))
     
    result = model.most_similar(positive=['男人','权利'], negative=['女人'])
    print('同"男人"和"权利"接近,但是与"女人"不接近的词有:')
    for item in result:
        print('   "'+item[0]+'"  相似度:'+str(item[1]))
     
    result = model.most_similar(positive=['女人','法律'], negative=['男人'])
    print('同"女人"和"法律"接近,但是与"男人"不接近的词有:')
    for item in result:
        print('   "'+item[0]+'"  相似度:'+str(item[1]))
    #从一堆词里面找到不匹配的
    print("老师 学生 上课 校长 , 有哪个是不匹配的? word2vec结果说是:"+model.doesnt_match("老师 学生 上课 校长".split()))
    print("汽车 火车 单车 相机 , 有哪个是不匹配的? word2vec结果说是:"+model.doesnt_match("汽车 火车 单车 相机".split()))
    print("大米 白色 蓝色 绿色 红色 , 有哪个是不匹配的? word2vec结果说是:"+model.doesnt_match("大米 白色 蓝色 绿色 红色 ".split()))
    #直接查看某个词的向量
    print('中国的特征向量是:')
    print(model['中国'])

效果如下:

15.png