精华内容
下载资源
问答
  • 命题和联结 ( 命题 | 命题符号化 | 真值联结 | 否 | 合取 | 析取 | 非真值联结 | 蕴涵 | 等价 )





    一. 命题 概念




    1. 命题 概念



    ( 1 ) 命题逻辑的主要内容 ( 逻辑 推理 命题 | 最小单位 | 最简单最基本部分 )


    命题逻辑的主要内容 :

    • 1.逻辑, 推理 与 命题 关系 : 逻辑 主要研究 推理过程 , 推理过程 必须 依靠 命题 来表达 ;
    • 2.最小单位 : 命题逻辑中 , 命题 是 最小单位 ;
    • 3.最简单部分 : 命题 是 数理逻辑中 最基本 , 最简单的部分 ;



    ( 2 ) 什么是命题 ( 陈述句 | 真假 必居 且 只居 其一 )


    什么是命题 :

    • 1.命题概念 : 命题 是 陈述客观 外界发生事情的陈述句 ;
    • 2.真假其一 : 命题是 或为真 或为假陈述句 ;
    • 3.命题特征 : ① 陈述句 ; ② 真假必居其一 , 只居其一 ;
    • 4.命题判定的说明 : 以下两种情况是命题 ;
      • ① 针对将来发生的事 : 只要是 真假只居其一 , 并且是陈述句 , 那么这就是命题 , 虽然现在不知道是真是假 , 但是必定是 非真既假 ;
      • ② 未证明的定理 :哥德巴赫猜想 , 我们 不知道其真假 , 但是其 如果证明出来 必定是非真既假陈述句 , 因此也是命题 ;



    2. 命题 举例



    ( 1 ) 命题举例 ( 非真即假 | 将来会知道 必是真假 | 将来会证明 必是真或假 )


    下面句子都是命题 :

    • 1.( 8小于10 ; ) : 陈述 8 和 10 之间的关系 , 是 真命题 ; 这件事已经发生了 ;
    • 2.( 8大于10 ; ) : 陈述 8 和 10 之间的关系 , 陈述错了 , 是个假命题 ; 这件事是不可能发生的 ; 但其是 陈述句 并且 非真既假 ;
    • 3.( 二十一世纪末 , 人类将住在太空 ; ) : 是陈述句 , 还没有发生 , 但肯定是非真即假 , 将来是否发生 不确定 , 但是 我们不知道 不代表不存在 , 在某个时间就会知道 , 如 二十一世纪末最后 1 秒 ;
    • 4.( 任一个 > 5 的偶数可表成两个素数的和 - 哥德巴赫猜想 ) : 皇冠上的明珠 , 是一个命题 , 是陈述句 , 但现在不知道真假 ; 但是终究会证明这个猜想 ;
    • 5.( 2\sqrt{2} 的小数展开式中 12345 出现偶数多次 ; ) : 有真假 , 但是真假不知道什么时候知道 ;



    ( 2 ) 不是命题举例 ( 不是陈述句 | 没有做出判断 | 真假不确定 | 悖论 )


    不是命题 :

    • 1.( 8 大于 10 吗 ? ) : 不是陈述句 , 是 疑问句 ;
    • 2.( 请勿吸烟 ! ) : 不是陈述句 , 是 祈使句 , 没有做出判断 , 真假不确定 ;
    • 3.( X 大于 Y . ) : 是陈述句 , 但是 真假 不确定 ;
    • 4.( 我正在撒谎 . - 悖论 ) : 是陈述句 , 但属于悖论 ;
      • ① 外层含义 : 如果 我在撒谎 , 这个命题为假 ; 如果 我没撒谎 , 这个命题为真 ;
      • ② 如果命题为真 : 说明我在撒谎 , 含义是 这个命题是假 , 出现了矛盾 ;
      • ③ 如果命题为假 : 说明我没有撒谎 , 含义是 这个命题是真的 , 出现了矛盾 ;





    二. 复合命题 与 命题符号化




    1. 联结词 和 复合命题



    ( 1 ) 复杂命题 引入 ( 复合命题真假由其组成的小命题的真假进行判断 )


    复杂命题 :简单命题 能 构造 更加 复杂的命题 ;

    • 1.期中考试 , 张三 没有 考及格 ;
    • 2.其中考试 , 张三和李四 考及格了 ;
    • 3.其中考试 , 张三和李四中 有人 考了90分 ;
    • 4.如果 张三能考 90 分 , 那么李四 能考 90 分 ;
    • 5.张三能考 90 分 当且仅当 李四 也能考 90 分 ;



    ( 2 ) 联结词 和 复合命题


    联结词 和 复合命题 :

    • 1.联结词 : 上述 没有 , 如果 那么 , 等连词 成为 联结词 ;
    • 2.复合命题 : 由联结词 和 命题 连接而成的 更加复杂命题 成为 复合命题 ;
    • 3.简单命题 : 相对地 , 不能分解成 更简单 的命题 成为简单命题 ;
    • 4.复合命题真假 : 复合命题 的 真假 完全 由 构成它 的简单命题 的 真假决定 ;
    • 5.简单命题 和 复合命题 的划分 是 相对的 ;




    2. 命题符号化



    ( 1 ) 命题符号化


    命题符号化 :

    • 1.命题符号化 : 将 命题 符号化 , 记为 p,q,r,p , q , r , \cdots , 类似于 代数 中 使用 aa 代表 1 数字一样 ;

    • 2.符号是变量 :

      • ① 代表数字 : 在代数中 , 使用字母 aa 代替 数字 , 具体代表哪个数字 并不确定 , 只知道这是个数字即可 ;
      • ② 代表命题 : 同理 , 命题符号 p,q,rp, q, r 代替 命题 , 具体代表哪些命题 也不确定 , 只知道这是个命题即可 ;
    • 3.常元 和 变元 :

      • ① 常元 : 代数中 字母 a 确定的表示某个数字时 , 称为 常元 ;
      • ② 变元 : 代数中 字母 a 表示不确定的数字时 , 称为 变元 ;
    • 4.命题常元 和 命题变元 :

      • ①命题常元 : 命题 pp 代表 确定 的命题时 , 称为 命题常元 ;
      • ②命题变元 : 命题 pp 代表 不确定 的命题时 , 称为 命题常元 ;



    ( 2 ) 命题符 取值 号化


    命题 真假值 符号化 :

    • 1.真 ( True ) : 记为 11TT ;
    • 2.假 ( False ) : 记为 00FF ;
    • 3.命题取值 : 命题变元 p 取值取值 0011 , 取值 00 表示 pp 是真命题 , 取值 11 表示 pp 是假命题 ;





    三. 联结词



    ( 1 ) 否定联结词


    否定联结词 :

    • 1.定义 : 设 p 为 一个命题 , 复合命题 非p 称为 p 的否定式 , 记为 ¬p\lnot p ; ¬\lnot 成为否定联结词 ;
    • 2.真值表 : ¬p\lnot p 为真 此时 p 为假 ;
    p ¬p\lnot p
    0 1
    1 0


    ( 3 ) 合取联结词


    合取联结词 :

    • 1.定义 : 设 p , q 为 两个命题 , 复合命题 " p 而且 q " 称为 p , q 的合取式 , 记为 pqp \land q , \land 称为 合取联结词 ;
    • 2.真值表 : pqp \land q 真 当且仅当 p 与 q 同时真 ;
    p q pqp \land q
    0 0 0
    0 1 0
    1 0 0
    1 1 1


    ( 3 ) 析取联结词


    析取联结词 :

    • 1.定义 : 设 p , q 为 两个命题 , 复合命题 " p 或者 q " 称为 p , q 的析取式 , 记为 pqp \lor q ; \lor 称作 析取联结词 ;
    • 2.真值表 : pqp \lor q 为真 , 当且仅当 p 与 q 至少有一个为真 ;
    p q pqp \lor q
    0 0 0
    0 1 1
    1 0 1
    1 1 1
    • 3.举例 : 其中考试 , 张三和李四中有人考了90分 ;
      • ① p 代表 张三 考了 90分 ;
      • ② q 代表 李四考了 90 分 ;
      • pqp \lor q 代表 : 张三 和 李四中有人考了90分 ;



    ( 4 ) 蕴含联结词


    蕴含联结词 :

    • 1.定义 : 设 p , q 为 命题 , 复合命题 " 如果 p , 则 q " 称为 p 对 q 的 蕴涵式 , 记做 pqp \to q , 其中 又称 p 为 此蕴涵式 的 前件 , 成 q 为 此蕴涵式 的 后件 ; \to 为 蕴涵联结词 ;
    • 2.真值表 : pqp \to q 假 当且仅当 p 真 而 q 假 ;
    p q pqp \to q
    0 0 1
    0 1 1
    1 0 0
    1 1 1
    • 3.举例 : 如果张三能考 90 分 , 那么李四也能考 90 分 ;


    ( 5 ) 等价联结词


    等价联结词 :

    • 1.定义 : 设 p , q 为 命题 , 复合命题 " p 当且仅当 q " 称作 p , q 的等价式 , 记做 pqp \leftrightarrow q , \leftrightarrow 记做等价联结词 ;
    • 2.真值表 : pqp \leftrightarrow q 真 当且仅当 p , q 同时为真 或 同时为假 ;
    p q pqp \leftrightarrow q
    0 0 1
    0 1 0
    1 0 0
    1 1 1
    • 3.举例 : 张三能考 90 分 当且仅当 李四 也能考 90 分





    三. 命题符号化示例



    ( 1 ) 命题符号化 ( 仔细看三个例子 )


    命题符号化 :

    • 1.铁 和 氧 化合 , 但 铁 和 氮 不化合 ;

      • ① 命题 p : 铁和氧化合 ;
      • ② 命题 q : 铁和氮化合 ;
      • ③复合命题 : p(¬q)p \land ( \lnot q ) ;
    • 2.如果我下班早 , 就去商店看看 , 除非我很累 ;

      • ① 命题 p : 我下班早 ;
      • ② 命题 q : 去商店看看 ;
      • ③ 命题 r : 我很累 ;
      • ④ 复合命题 : ((¬r)p)q( ( \lnot r ) \land p ) \to q : 去商店的前提 是 不累 并且 下班早 ;
    • 3.李四是计算机系的学生 , 他住在312室 或 313 室 ;

      • ① 命题 p : 李四是计算机系学生 ;
      • ② 命题 q : 李四住在 312 室;
      • ③ 命题 r : 李四住在 313 室 ;
      • ④ 复合命题 : p((qr)(¬(qr)))p \land ( ( q \lor r ) \land ( \lnot ( q \land r ) ) ) ;注意 这里 李四 只能住在 312 或者 313 之间的一个, 不能都住进入, 因此需要将 qrq \land r 的情况排除 , ¬(qr)\lnot ( q \land r ) ;
      • ⑤ 复合命题 : p((q(¬r))((¬q)r))p \land ( ( q \land ( \lnot r ) ) \lor ( ( \lnot q ) \land r ) ) ; 这里 李四 住在 312 不住在 313, 李四住在 313 不住在 312 只能取其中一种情况 ;


    ( 2 ) 命题符号化 注意点 ( ① 联结词 与 日常词汇不一致 | ② 命题真假根据定义理解 | ③ 不能对号入座 | ④ 有些词也可以表示为五个联结词 )


    命题符号化注意点 :

    • 1.联结词与日常词汇不完全一致 : 上述 五个联结词 非 , 析取 , 合取 , 蕴涵 , 等价 , 来源于 日常使用的 相应词汇 , 但是不完全一致 ;
    • 2.命题真假根据定义理解 : 联结词组成的复合命题的真假值 要根据 它们 的 定义 去理解 , 不能根据日常语言的含义去理解 , 如 肉夹馍之类的 日常含义 ;
    • 3.不能对号入座 : 不要 见到 或 就表示成 \lor 析取 , 如上面的住在 312 或 313 的情况 , 要考虑 只住在 312 , 只住在 313 , 同时住在 312 和 313 的情况 ;
    • 4.有些词也可以表示为这五个联结词 : 如 “但是” 可以表示成 “\land ;

    展开全文
  • 最近要实现的一些功能需要让ES的同义、扩展、停止能够热更新,达到让搜索更精确的目的。在网上看了很多相关的博客,现在热更新的方案已经实施成功,现在来总结一下。 ES版本:5.5.2 IK分词器版本:5.5.2 ...

    最近要实现的一些功能需要让ES的同义词、扩展词、停止词能够热更新,达到让搜索更精确的目的。在网上看了很多相关的博客,现在热更新的方案已经实施成功,现在来总结一下。

    ES版本:5.5.2

    IK分词器版本:5.5.2

    扩展词、停止词 

    我的ES使用的中文分词器是IK分词器,IK分词器支持一种热更新的方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新。

    同义词

    同义词的配置,Elasticsearch自带了一个synonym同义词插件,但是该插件只能使用文件或在分析器中静态地配置同义词,如果需要添加或修改,需要修改配置文件和重启,使用方式不够友好,我需要的是热更新。

     

    基于以上的现有的方案,再加上我参考了两篇博客,决定采用这样的方案:

    (1)修改ik分词器源码,然后手动支持从mysql中每隔一定时间,自动加载新的词库

    (2)修改一款别人自研的一个可动态维护同义词的插件,也是同样的从mysql中每隔一定时间,自动加载新的词库

    (3)在项目中对相应的文档用定时任务进行重建文档操作,因为热更新的词对旧文档无效。

    附上两位的博客地址:
    Elasticsearch之IK分词器热更新
    一个简易的Elasticsearch动态同义词插件
    万分感谢两位~

    好了,下面进入正题

    一、扩展词、停止词

    1.下载IK分词器的源码

    进入github,找到对应版本的ik分词器,下载源码,我这里是5.5.2版本的ES,所以我下载5.5.2版本的IK分词器

    https://github.com/medcl/elasticsearch-analysis-ik/tree/v5.5.2
    这是一个maven工程,下载下来后直接导入到eclipse中进行改造

    2.修改源码

    先看一下下载下来的源码的文件目录

    这个-root后缀的项目就是我刚下载下来的ik的源码,这个源码上面的那个项目是我修改过后的源码。

    由于我们是要动词库,所以我们直奔dic目录,找到Dictionary类,先看看它这个是怎么加载词典的。

    看看他的构造函数:

    initial方法:

    根据上面原博主的思路,是在这个Dictionnary类中,写两个方法,用jdbc去mysql中分别查询扩展词和停止词,然后放到对应的词典中。然后再这个初始化的方法中,像原生的代码那样也调用一下这两个方法。再建立一个监控线程,定时去加载扩展词和停止词。

    我在原博主的的基础上,把配置的数据库的属性初始化的时候封装到Dictionnary类中的一个属性中,包括加载的sql和监控线程隔多久进行一次扫描。这样方便进行配置。

    (1)、先在Dictionnary类定义两个自己要的属性

    DB_PROPERTIES属性是我们自己建的属性文件的文件名字
    myProperties属性是用来把读取到的属性文件里的属性装起来

    (2)、在config目录下创建db.properties文件

    (3)、在构造方法中读取db.properties,并提供几个获取属性的方法

    	private Dictionary(Configuration cfg) {
    		this.configuration = cfg;
    		this.props = new Properties();
    		this.myProperties = new Properties();
    		this.conf_dir = cfg.getEnvironment().configFile().resolve(AnalysisIkPlugin.PLUGIN_NAME);
    		Path configFile = conf_dir.resolve(FILE_NAME);
    		Path myFile = cfg.getConfigInPluginDir().resolve(DB_PROPERTIES);
    		logger.info("加载属性文件db.properties的路径:" + myFile);
    		InputStream input = null;
    		InputStream myInput = null;
    		File file = myFile.toFile();
    		logger.info("file文件:" + file);
    		try {
    			myInput = new FileInputStream(file);
    		} catch (FileNotFoundException e1) {
    			logger.error("db.properties未找到", e1);
    		}
    		try {
    			logger.info("try load config from {}", configFile);
    			input = new FileInputStream(configFile.toFile());
    		} catch (FileNotFoundException e) {
    			conf_dir = cfg.getConfigInPluginDir();
    			configFile = conf_dir.resolve(FILE_NAME);
    			try {
    				logger.info("try load config from {}", configFile);
    				input = new FileInputStream(configFile.toFile());
    			} catch (FileNotFoundException ex) {
    				// We should report origin exception
    				logger.error("ik-analyzer", e);
    			}
    		}
    		if (input != null) {
    			try {
    				props.loadFromXML(input);
    			} catch (InvalidPropertiesFormatException e) {
    				logger.error("ik-analyzer", e);
    			} catch (IOException e) {
    				logger.error("ik-analyzer", e);
    			}
    		}
    		try {
    			myProperties.load(myInput);
    		} catch (IOException e) {
    			logger.error("加载db.properties文件失败!", e);
    		}
    	}

    获取几个属性的方法

    	private String getUrl() {
    		String url = myProperties.getProperty("url");
    		return url;
    	}
    
    	private String getUser() {
    		String user = myProperties.getProperty("user");
    		return user;
    	}
    
    	private String getPassword() {
    		String password = myProperties.getProperty("password");
    		return password;
    	}
    
    	private int getInterval() {
    		Integer interval = Integer.valueOf(myProperties.getProperty("interval"));
    		return interval;
    	}
    
    	private String getExtWordSql() {
    		String extWordSql = myProperties.getProperty("extWordSql");
    		return extWordSql;
    	}
    
    	private String getStopWordSql() {
    		String stopWordSql = myProperties.getProperty("stopWordSql");
    		return stopWordSql;
    	}

    (3)、写两个从数据库中读取扩展词和停止词的方法,再写一个重新加载扩展词和停止词的方法给监控线程去调用

    	private void loadMySQLExtDict() {
    		Connection conn = null;
    		Statement stmt = null;
    		ResultSet rs = null;
    		try {
    			logger.info("query ext dict from mysql, " + getUrl());
    			Class.forName("com.mysql.jdbc.Driver");
    			conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
    			stmt = conn.createStatement();
    			String extWordSql = getExtWordSql();
    			if(!StringUtils.isNullOrEmpty(extWordSql)){
    				rs = stmt.executeQuery(extWordSql);
    				while (rs.next()) {
    					String theWord = rs.getString("main_keyword");
    					logger.info("main_keyword ext word from mysql: " + theWord);
    					_MainDict.fillSegment(theWord.trim().toCharArray());
    				}
    			}
    
    		} catch (Exception e) {
    			logger.error("erorr", e);
    		} finally {
    			if (rs != null) {
    				try {
    					rs.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (stmt != null) {
    				try {
    					stmt.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (conn != null) {
    				try {
    					conn.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    		}
    	}
    
    	private void loadMySQLStopDict() {
    		Connection conn = null;
    		Statement stmt = null;
    		ResultSet rs = null;
    		try {
    			logger.info("query stop dict from mysql, " + getUrl());
    			Class.forName("com.mysql.jdbc.Driver");
    			conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
    			stmt = conn.createStatement();
    			String stopWordSql = getStopWordSql();
    			if(!StringUtils.isNullOrEmpty(stopWordSql)){
    			rs = stmt.executeQuery(stopWordSql);
    				while (rs.next()) {
    					String theWord = rs.getString("main_keyword");
    					logger.info("main_keyword stop word from mysql: " + theWord);
    					_StopWords.fillSegment(theWord.trim().toCharArray());
    				}
    			}
    		} catch (Exception e) {
    			logger.error("erorr", e);
    		} finally {
    			if (rs != null) {
    				try {
    					rs.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (stmt != null) {
    				try {
    					stmt.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (conn != null) {
    				try {
    					conn.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    		}
    	}
    
    	public void reLoadMySqlDict() {
    		logger.info("重新加载远程词典...");
    		// 新开一个实例加载词典,减少加载过程对当前词典使用的影响
    		Dictionary tmpDict = new Dictionary(configuration);
    		tmpDict.configuration = getSingleton().configuration;
    		tmpDict.loadMainDict();
    		tmpDict.loadStopWordDict();
    		tmpDict.loadMySQLExtDict();
    		tmpDict.loadMySQLStopDict();
    		_MainDict = tmpDict._MainDict;
    		_StopWords = tmpDict._StopWords;
    		logger.info("重新加载远程词典完毕...");
    	}

    (4)、代码到这里差不多就改完了,但是由于我的ES原来本来已经有了一个IK分词器插件,我并不想把之前的IK分词器插件替换掉,怕影响到以前的业务,所以我还需要把这个修改了源码过后的IK分词器取个别的名字。

     进入pom.xml中修改插件名字

    修改org.elasticsearch.plugin.analysis.ik.AnalysisIkPlugin类

    (5)、由于我们使用了mysql来做这个词典加载,项目中使用到了mysql连接,所以要把mysql驱动包配置到plugin.xml文件中,否则打出来的包中没有mysql的驱动包,到时候会报错。加一行配置  <include>mysql:mysql-connector-java</include>

    (6)、接下来,mvn clean package打包代码,然后找到 target\releases\elasticsearch-analysis-ik-5.5.2.zip

    (7)、解压缩该文件到es的plugin目录下,为了避免跟之前的ik文件夹重复,最好先在别的地方解压,然后给文件夹重新命名,再复制到plugin目录下。

    二、同义词

    1.下载原博主上传到github的源码

    https://github.com/ginobefun/elasticsearch-dynamic-synonym


    1.1目录结构

    1.2实现方式

    引用原博主自己写的:

    DynamicSynonymTokenFilter

    • DynamicSynonymTokenFilter参考了SynonymTokenFilter的方式,但又予以简化,使用一个HashMap来保存同义词之间的转换关系;
    • DynamicSynonymTokenFilter只支持Solr synonyms,同时也支持expand和ignore_case参数的配置;
    • DynamicSynonymTokenFilter通过数据库来管理同义词的配置,并轮询数据库(通过version字段判断是否存在规则变化)实现同义词的动态管理;

    1.3源码

    1.3.1  DynamicSynonymTokenFilterFactory

    1.3.2  SynonymRuleManager初始化方法

    loadSynonymRule()方法就是去数据库查询相应的数据,原博主提供的有一个表结构来存同义词,每一组同义词按照相应的格式就好,ES支持两种格式,一种xxxx,xxxx,xxxx,英文逗号分隔,一种xxxx,xxxx,xxxx => xxxx格式,选择其中一种就好,源码都有做判断的。

    监控线程就是重新加载数据,创建一个临时的SynonymRuleManager,加载完了数据,然后再把map赋值给原本的那个synonymMap,防止加载同义词的时候影响插件工作。跟上面IK加载扩展词什么的是一个意思。

    2.修改源码

    2.1需要改动的地方

    • 由于我们本身的业务已经有了自己的词库表,所以不能用源码提供的表以及sql,我需要按照自己的逻辑查出数据,然后按照格式放入synonymMap中。
    • 源码用到的db_url是通过ES的settings配置进行加载的,我为了和ik那样统一一下,所以我需要改变加载方式,同样的创建一个db.properties文件来存放要加载的属性,顺便把sql也提取出来,源码是在代码里把sql写死的。

    2.2修改Configuration,添加几个我自己需要的属性

    public class Configuration {
    
        private final boolean ignoreCase;
    
        private final boolean expand;
        
        private final int interval;
    
        private final String dbUrl;
        
        private final String user;
        
        private final String password;
        
        private final String sql;
    
        private final Analyzer analyzer;
        
        public final static String DB_PROPERTIES = "db.properties";
    
        public Configuration(boolean ignoreCase, boolean expand,int interval, Analyzer analyzer, String dbUrl,String user,String password,String sql) {
            this.ignoreCase = ignoreCase;
            this.expand = expand;
            this.interval = interval;
            this.analyzer = analyzer;
            this.dbUrl = dbUrl;
            this.user = user;
            this.password = password;
            this.sql = sql;
        }
    
        public Analyzer getAnalyzer() {
            return analyzer;
        }
    
        public boolean isIgnoreCase() {
            return ignoreCase;
        }
    
        public boolean isExpand() {
            return expand;
        }
    
        public String getDBUrl() {
            return dbUrl;
        }
        
        public int getInterval(){
        	return interval;
        }
        
        public String getUser(){
        	return user;
        }
        
        public String getPassword(){
        	return password;
        }
    
    	public String getSql() {
    		return sql;
    	}
    }

    2.3  在项目目录下新建config文件夹,创建db.properties文件,修改 DynamicSynonymTokenFilterFactory,加载db.properties文件

    public class DynamicSynonymTokenFilterFactory extends AbstractTokenFilterFactory {
    	public DynamicSynonymTokenFilterFactory(IndexSettings indexSettings, Environment env, String name,
    			Settings settings) throws IOException {
    		super(indexSettings, name, settings);
    
    		// get the filter setting params
    		final boolean ignoreCase = settings.getAsBoolean("ignore_case", false);
    		final boolean expand = settings.getAsBoolean("expand", true);
    		File file = PathUtils
    				.get(new File(DynamicSynonymPlugin.class.getProtectionDomain().getCodeSource().getLocation().getPath())
    						.getParent(), "config")
    				.toAbsolutePath().resolve(Configuration.DB_PROPERTIES).toFile();
    		final Properties dbProperties = new Properties();
    		logger.info("加载DynamicSynonym sql properties file" + file);
    		try {
    			dbProperties.load(new FileReader(file));
    		} catch (IOException e) {
    			logger.info("加载数据库属性文件" + Configuration.DB_PROPERTIES + "失败!");
    			e.printStackTrace();
    		}
    		final String tokenizerName = settings.get("tokenizer", "whitespace");
    
    		Analyzer analyzer;
    		if ("standand".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new StandardAnalyzer();
    		} else if ("keyword".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new KeywordAnalyzer();
    		} else if ("simple".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new SimpleAnalyzer();
    		} else {
    			analyzer = new WhitespaceAnalyzer();
    		}
    
    		// NOTE: the manager will only init once
    		SynonymRuleManager
    				.initial(new Configuration(ignoreCase, expand, Integer.valueOf(dbProperties.getProperty("interval")),
    						analyzer, dbProperties.getProperty("url"), dbProperties.getProperty("user"),
    						dbProperties.getProperty("password"), dbProperties.getProperty("sql")));
    	}
    
    	@Override
    	public TokenStream create(TokenStream tokenStream) {
    		return new DynamicSynonymTokenFilter(tokenStream);
    	}
    }
    

    2.4 JDBCUtils新建一个我自己的查询数据库的方法querySynonymAiLikenessKeywords()

        public static List<String> querySynonymAiLikenessKeywords(String dbUrl,String user,String password,String sql) throws Exception {
            List<String> list = new ArrayList<String>();
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection(dbUrl,user,password);
                stmt = conn.createStatement();
    //            String sql = "SELECT main_keyword,likeness_keywords FROM ai_keyword WHERE keyword_type = 1 AND is_delete = 1";
                if(!StringUtils.isNullOrEmpty(sql)){
                	rs = stmt.executeQuery(sql);
                	while (rs.next()) {
                		String likenessKeywords = rs.getString("likeness_keywords");
                		if(!StringUtils.isNullOrEmpty(likenessKeywords)){
                			String keywordAndlikenessKeywords = likenessKeywords + rs.getString("main_keyword");
                			list.add(keywordAndlikenessKeywords);
                		}
                	}
                }
            } finally {
                closeQuietly(conn, stmt, rs);
            }
            return list;
        }

    2.5 修改SynonymRuleManager.loadSynonymRule()方法和reloadSynonymRule()方法

        private void loadSynonymRule() {
        	// 回头修改一下,不需要返回值,只需要把数据查询出来就可以了
            try {
                List<String> synonymRuleList = JDBCUtils.querySynonymAiLikenessKeywords(configuration.getDBUrl(),configuration.getUser(),configuration.getPassword(),configuration.getSql());
                this.synonymMap = new SimpleSynonymMap(this.configuration);
                for (String rule : synonymRuleList) {
                    this.synonymMap.addRule(rule);
                }
    
                LOGGER.info("Load {} synonym rule succeed!", synonymRuleList.size());
            } catch (Exception e) {
                LOGGER.error("Load synonym rule failed!", e);
                //throw new RuntimeException(e);
            }
        }
    
        public void reloadSynonymRule() {
        	// 也需要修改,把数据放到单例的manager中
            LOGGER.info("Start to reload synonym rule...");
            try {
                SynonymRuleManager tmpManager = new SynonymRuleManager();
                tmpManager.configuration = getSingleton().configuration;
                List<String> synonymRuleList = JDBCUtils.querySynonymAiLikenessKeywords(configuration.getDBUrl(),configuration.getUser(),configuration.getPassword(),configuration.getSql());
                SimpleSynonymMap tempSynonymMap = new SimpleSynonymMap(tmpManager.configuration);
                for (String rule : synonymRuleList) {
                    tempSynonymMap.addRule(rule);
                }
    
                this.synonymMap = tempSynonymMap;
                LOGGER.info("Succeed to reload {} synonym rule!", synonymRuleList.size());
            } catch (Throwable t) {
                LOGGER.error("Failed to reload synonym rule!", t);
            }
    
        }

     2.6 修改Monitor.run()方法,直接调用SynonymRuleManager.reloadSynonymRule()就好了

    public class Monitor implements Runnable {
    
        @Override
        public void run() {
        	// 线程任务,直接调用reloadSynonymRule
        	SynonymRuleManager.getSingleton().reloadSynonymRule();
        }
    
    }

    这样就基本上可以了,接下来打包mvn clean package,打包之前别忘了,这个插件也用了mysql,跟上文一样需要到plugin.xml中去修改配置,这里我就不贴图了。打包完成后找到target/releases/下的.zip包,在ES的plugin包下新建一个文件夹,把zip包解压到该目录。到此为止两个插件修改完毕,看看plugin目录:

     

     

    三、配置

    由于同义词过滤器需要配置,Elasticsearch自带的同义词过滤器在分析器配置的话配置如下:

    {
        "index" : {
            "analysis" : {
                "analyzer" : {
                    "synonym_analyzer" : {
                        "tokenizer" : "whitespace",
                        "filter" : ["my_synonym"]
                    }
                },
                "filter" : {
                    "my_synonym" : {
                        "type" : "synonym",
                        "expand": true,
                        "ignore_case": true, 
                        "synonyms_path" : "analysis/synonym.txt"
                        "synonyms" : ["阿迪, 阿迪达斯, adidasi => Adidas","Nike, 耐克, naike"]
                    }
                }
            }
        }
    }

    所以,想要使用上两个插件就需要对文档进行设置,设置它的analyzer和filter。

    由于使用的ES版本问题,5.5.2版本已经不支持在elasticsearch.yml中进行,所以只能通过restfulAPI进行配置

    在ES中新建一个文档,通过URL进行设置

    PUT ES访问地址:9200/新建的文档/
    {
        "settings": {
            "analysis": {
                "analyzer": {
                    // 自定义一个analyzer
                    "ik_max_word_synonym": {
                        // filter用我们自定义的filter
                        "filter": [
                            "my_synonym"
                        ],
                        "tokenizer": "ik_max_word_custom",// 我们修改后的ik分词器的名字
                        "type": "custom"
                      }
                    },
                    "filter": {
                        // 自定义一个filter
                        "my_synonym": {
                        "expand": true,
                        "ignore_case": true,
                        "tokenizer": "ik_max_word_custom",// 这个filter的tokenizer也用修改后的ik
                         "type": "dynamic-synonym"// 类型是我们修改后的插件
                   }
                }
            }
        }
    }

    设置成功后

    这时候就可以使用了。

    接下来还有几个问题:

    (1)如果每新建一个文档,要用到这样配置的时候,都这么手动配置太麻烦了,而且几套环境,每一个都去配置,这样不太友好。

    (2)重建文档问题,新加入词库的词语对于旧的文档不生效,所以需要有一个定时任务去定时重建文档。

    所以接下来我就在项目中把这些实现了一下

    项目是一个springboot项目,最开始使用ES的时候,为了图方便,使用的是elasticsearch spring data jpa ,用repository来操作的ES。现在要对settings进行设置,所以我想使用elasticsearchTemplate来进行设置,但是当用elasticsearchTemplate的时候就导致实体类上的注解@Field中配置的映射关系什么的都不生效了。之后网上找了一下才发现,当使用TransportClient客户端的时候会导致那个映射关系失效的。那就索性就不让jpa帮我生成文档了,生成文档的这一步由我自己来操作。

    就直接设置@Document注解中的属性createIndex的值为false。

    顺便提一下,由于几套环境用的是同一个ES,所以我做了一下动态indexName的操作,通过配置文件来加载索引名字。

    首先在配置文件application.properties中设置一个属性

    然后建立一个配置类

    EsIndexNameConfig
     

    @Component("esIndexNameConfig")
    public class EsIndexNameConfig {
    
    	@Value("${ai_robot}")
    	private String aiRobotindexName;
    
    	public String getAiRobotindexName() {
    		return aiRobotindexName;
    	}
    
    	public void setAiRobotindexName(String aiRobotindexName) {
    		this.aiRobotindexName = aiRobotindexName;
    	}
    
    }

    设置一个成员变量,让这个成员变量加载application.properties中的属性

    之后在@Document中用 #{esIndexNameConfig.aiRobotindexName}就能取到indexName了。

    好的,接下来继续手动生成文档settings和mapping

    1.建立接口类EsSettingsMap

    由于考虑到以后可能还有其他文档需要自定义settings的Map,所以弄一个通用的接口类,以后再有新增的文档,就只需要实现这个接口就可以加载了。

    /**
     * 如果需要把一个映射到es的实体类实现动态加载setting和mapping需要实现此接口,需要把实体类的@Document注解中的属性createIndex = false
     * EsIndexSettingAndMappingInit类会加载实现类getSettingsMap()方法返回的settingsMap和实体类上@Field注解配置的mapping
     * @author zhangch 2019-05-05
     */
    public interface EsSettingsMap {
    
    	/**
    	 * 获取一段settings配置,例如:
    	 * {
    			    "settings": {
    			        "analysis": {
    			            "analyzer": {
    			                "ik_max_word_synonym": {
    			                    "filter": [
    			                        "my_synonym"
    			                    ],
    			                    "tokenizer": "ik_max_word",
    			                    "type": "custom"
    			                }
    			            },
    			            "filter": {
    			                "my_synonym": {
    			                    "expand": true,
    			                    "ignore_case": true,
    			                    "tokenizer": "ik_max_word",
    			                    "type": "dynamic-synonym"
    			                }
    			            }
    			        }
    			    }
    			}
    		这段配置需要的map:
    		
    		Map<String, Object> ik_max_word_synonym  = new HashMap<>(3);
    		ik_max_word_synonym.put("filter", new String[]{"my_synonym"});
    		ik_max_word_synonym.put("tokenizer", "ik_max_word_custorm");
    		ik_max_word_synonym.put("type", "custom");
    		Map<String, Object> analyzer = new HashMap<>(1);
    		analyzer.put("ik_max_word_synonym", ik_max_word_synonym);
    		Map<String, Object> my_synonym  = new HashMap<>(4);
    		my_synonym.put("expand", true);
    		my_synonym.put("ignore_case", true);
    		my_synonym.put("tokenizer", "ik_max_word_custorm");
    		my_synonym.put("type", "dynamic-synonym");
    		Map<String, Object> filter = new HashMap<>(1);
    		filter.put("my_synonym", my_synonym);
    		Map<String, Object> analysis = new HashMap<>(2);
    		analysis.put("analyzer", analyzer);
    		analysis.put("filter", filter);
    		Map<String, Object> settings = new HashMap<>(1);
    		settings.put("analysis", analysis);
    	 * @return settings;
    	 */
    	Map<String, Object> getSettingsMap();
    	
    	
    	Class<?> getOperatorClass();
    	
    }
    

    2.实现接口

    /**
     * @ClassName:  AiRobotEsSettingsMap   
     * @Description: 实体类AiRobotPo映射的es文档自定义的settings
     * @author: zhangch
     * @date:   2019年5月5日 下午5:55:37
     */
    public class AiRobotEsSettingsMap implements EsSettingsMap {
    
    	@Override
    	public Map<String, Object> getSettingsMap() {
    		/**
    		 * {
    			    "settings": {
    			        "analysis": {
    			            "analyzer": {
    			                "ik_max_word_synonym": {
    			                    "filter": [
    			                        "my_synonym"
    			                    ],
    			                    "tokenizer": "ik_max_word_custorm",
    			                    "type": "custom"
    			                }
    			            },
    			            "filter": {
    			                "my_synonym": {
    			                    "expand": true,
    			                    "ignore_case": true,
    			                    "tokenizer": "ik_max_word_custorm",
    			                    "type": "dynamic-synonym"
    			                }
    			            }
    			        }
    			    }
    			}
    		 */
    		Map<String, Object> ik_max_word_synonym  = new HashMap<>(3);
    		ik_max_word_synonym.put("filter", new String[]{"my_synonym"});
    		ik_max_word_synonym.put("tokenizer", "ik_max_word_custorm");
    		ik_max_word_synonym.put("type", "custom");
    		Map<String, Object> analyzer = new HashMap<>(1);
    		analyzer.put("ik_max_word_synonym", ik_max_word_synonym);
    		Map<String, Object> my_synonym  = new HashMap<>(4);
    		my_synonym.put("expand", true);
    		my_synonym.put("ignore_case", true);
    		my_synonym.put("tokenizer", "ik_max_word_custorm");
    		my_synonym.put("type", "dynamic-synonym");
    		Map<String, Object> filter = new HashMap<>(1);
    		filter.put("my_synonym", my_synonym);
    		Map<String, Object> analysis = new HashMap<>(2);
    		analysis.put("analyzer", analyzer);
    		analysis.put("filter", filter);
    		Map<String, Object> settings = new HashMap<>(1);
    		settings.put("analysis", analysis);
    		return settings;
    	}
    
    	@Override
    	public Class<?> getOperatorClass() {
    		return QuestionPo.class;
    	}
    
    }
    

    3.建立init类,实现ApplicationRunner接口,让程序在启动的时候就初始化这个设置。逻辑是先获取到EsSettingsMap下的所有实现类,我这里实现的比较简单,默认只找跟这个接口在一个包下所有实现类,所以回头建立实现类的时候只能跟这个接口放在同一目录下。获取到所有实现类后,遍历实现类,先判断这个index在ES是否存在,如果存在就不用管了(之前已经生成过了)。如果不存在,就创建索引,把settingsMap传进去。并且putMapping()一下,这个操作是使实体类上的注解@field中配置的mapping信息生效。

    /**
     * 初始化es的index的setting和mapping,配置自定义filter和analyzer,并且使po上的mapping配置信息生效
     * 
     * @author zhangch 2019-04-29
     *
     */
    @Component
    public class EsIndexSettingAndMappingInit implements ApplicationRunner {
    
    	private Logger logger = LoggerFactory.getLogger(EsIndexSettingAndMappingInit.class);
    
    	@Autowired
    	private ElasticsearchTemplate elasticsearchTemplate;
    
    	@Override
    	public void run(ApplicationArguments args) throws Exception {
    		// 获取EsSettingsMap的所有实现类,加载他们的settings和mapping
    		logger.info("---------------------------初始化es的setting和mapping--------------------------------");
    		try {
    			List<Class<?>> allAssignedClass = ClassUtil.getAllAssignedClass(EsSettingsMap.class);
    			logger.info("需要加载{}个实现类", allAssignedClass.size());
    			for (Class<?> c : allAssignedClass) {
    				try {
    					EsSettingsMap map = (EsSettingsMap) c.newInstance();
    					Map<String, Object> settingsMap = map.getSettingsMap();
    					boolean indexExists = elasticsearchTemplate.indexExists(map.getOperatorClass());
    					logger.info("索引{}-{}存在",
    							elasticsearchTemplate.getPersistentEntityFor(map.getOperatorClass()).getIndexName(),
    							indexExists ? "" : "不");
    					if (!indexExists) {
    						logger.info("加载settingMap:{},加载settings:{}", c.getName(), settingsMap);
    						elasticsearchTemplate.createIndex(map.getOperatorClass(), settingsMap);
    						elasticsearchTemplate.putMapping(map.getOperatorClass());
    					}
    				} catch (InstantiationException | IllegalAccessException e) {
    					logger.info("加载settings失败!");
    					e.printStackTrace();
    				}
    			}
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    ClassUtils的getAllAssignedClass()方法

    	/**
    	 * 获取同一路径下所有子类或接口实现类
    	 * 
    	 * @param intf
    	 * @return
    	 * @throws IOException
    	 * @throws ClassNotFoundException
    	 */
    	public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws IOException, ClassNotFoundException {
    		List<Class<?>> classes = new ArrayList<Class<?>>();
    		for (Class<?> c : getClasses(cls)) {
    			if (cls.isAssignableFrom(c) && !cls.equals(c)) {
    				classes.add(c);
    			}
    		}
    		return classes;
    	}

    这样就实现了在项目中自己配置settings了。

    接下来是 定时任务重建文档

    springboot的定时任务比较简单,直接用注解就可以了

    在启动类上,加上@EnableAsync和@EnableScheduling注解

    第二个注解是开启定时任务开关,第一个注解是开启多线程定时任务,避免相同时间的定时任务有冲突。

    建立定时任务类EsScheduled
     

    @Component
    public class EsScheduled {
    
    	@Autowired
    	private ElasticsearchTemplate elasticsearchTemplate;
    
    	private Logger logger = LoggerFactory.getLogger(EsScheduled.class);
    
    	/**
    	 * 重建项目中动态索引的po在es中的文档
    	 */
    	@Scheduled(cron = "0 5 0 * * *")
    	public void reBuildDocument() {
    		List<Class<?>> allAssignedClass = null;
    		try {
    			allAssignedClass = ClassUtil.getAllAssignedClass(EsSettingsMap.class);
    		} catch (ClassNotFoundException | IOException e) {
    			e.printStackTrace();
    		}
    		if (allAssignedClass != null && allAssignedClass.size() > 0) {
    			for (Class<?> clazz : allAssignedClass) {
    				EsSettingsMap map;
    				try {
    					map = (EsSettingsMap) clazz.newInstance();
    					boolean indexExists = elasticsearchTemplate.indexExists(map.getOperatorClass());
    					String indexName = elasticsearchTemplate.getPersistentEntityFor(map.getOperatorClass())
    							.getIndexName();
    					logger.info("索引 {} - {} 存在", indexName, indexExists ? "" : "不");
    					if (indexExists) {
    						UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE
    								.newRequestBuilder(elasticsearchTemplate.getClient());
    						updateByQuery.source(indexName).abortOnVersionConflict(false);
    						BulkByScrollResponse response = updateByQuery.get();
    						long updated = response.getUpdated();
    						logger.info("更新 {} 中的文档 {} 条", indexName, updated);
    					}
    				} catch (InstantiationException | IllegalAccessException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    

    逻辑也不复杂,也是遍历实现类,看看索引是否存在,如果存在就更新index中的所有记录。我设置的是每天零点过5分扫描。

     

    好了,这样的话整体的方案已经实现了,通过修改ik分词器的源码和以为博主自己写的插件的源码,然后跟项目搭配,实现同义词、扩展词、停止词的热更新并尽量保证词库更新后的效果。

     

    看看测试效果吧

    我数据库中的表结构是这样的

    CREATE TABLE `ai_keyword` (
      `keyword_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关键词id',
      `mid` int(11) DEFAULT NULL COMMENT '企业id',
      `eid` int(11) DEFAULT NULL COMMENT '业务员id',
      `keyword_type` int(2) NOT NULL COMMENT '关键词类型  1--标准词  2--专有名词  3--停止词',
      `main_keyword` varchar(20) NOT NULL COMMENT '标准词',
      `likeness_keywords` varchar(255) DEFAULT NULL COMMENT '相似词数组(专有名词和停止词无此项)',
      `is_delete` int(2) DEFAULT '1' COMMENT '是否删除  0--已删除  1--未删除',
      `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
      `delete_time` timestamp NULL DEFAULT NULL COMMENT '删除时间',
      `standby1` varchar(255) DEFAULT NULL COMMENT '备用字段1',
      `standby2` varchar(255) DEFAULT NULL COMMENT '备用字段2',
      PRIMARY KEY (`keyword_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='关键词表';

    添加几条数据

    测试

    返回数据

    {
        "tokens": [
            {
                "token": "lol",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 0
            },
            {
                "token": "英雄联盟",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 1
            },
            {
                "token": "毒害小学生",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 2
            },
            {
                "token": "王者荣耀",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 3
            },
            {
                "token": "王者",
                "start_offset": 0,
                "end_offset": 2,
                "type": "CN_WORD",
                "position": 4
            },
            {
                "token": "荣耀",
                "start_offset": 2,
                "end_offset": 4,
                "type": "CN_WORD",
                "position": 5
            }
        ]
    }

     可以看到,扩展词和同义词都已经生效了。

    再添加一条停止词试试

    先分词“你要这个干什么”

    结果

    {
        "tokens": [
            {
                "token": "你",
                "start_offset": 0,
                "end_offset": 1,
                "type": "CN_CHAR",
                "position": 0
            },
            {
                "token": "要",
                "start_offset": 1,
                "end_offset": 2,
                "type": "CN_CHAR",
                "position": 1
            },
            {
                "token": "这个",
                "start_offset": 2,
                "end_offset": 4,
                "type": "CN_WORD",
                "position": 2
            },
            {
                "token": "干什么",
                "start_offset": 4,
                "end_offset": 7,
                "type": "CN_WORD",
                "position": 3
            },
            {
                "token": "干什",
                "start_offset": 4,
                "end_offset": 6,
                "type": "CN_WORD",
                "position": 4
            },
            {
                "token": "什么",
                "start_offset": 5,
                "end_offset": 7,
                "type": "CN_WORD",
                "position": 5
            }
        ]
    }

    添加停止词“什么”

    测试效果

    结果中没有把“什么”这个词给分出来,证明已经被添加到停止词词库了。

     

    好了,这就是我这些天研究的一些乱七八糟的东西的总结,希望能对看到的你有所帮助~~~

     

     

    展开全文
  • 基于情感词典的文本情感分类

    千次阅读 2017-08-29 09:36:50
    基于情感词典的文本情感分类 传统的基于情感词典的文本...然后,我们再输入的句子进行最直接的拆分,看看我们所记忆的词汇表中是否存在相应的词语,然后根据这个词语的类别来判断情感,比如“我喜欢数学”,“喜

    基于情感词典的文本情感分类
    人的最简单的判断思维.png

    传统的基于情感词典的文本情感分类,是对人的记忆和判断思维的最简单的模拟,如上图。我们首先通过学习来记忆一些基本词汇,如否定词语有“不”,积极词语有“喜欢”、“爱”,消极词语有“讨厌”、“恨”等,从而在大脑中形成一个基本的语料库。然后,我们再对输入的句子进行最直接的拆分,看看我们所记忆的词汇表中是否存在相应的词语,然后根据这个词语的类别来判断情感,比如“我喜欢数学”,“喜欢”这个词在我们所记忆的积极词汇表中,所以我们判断它具有积极的情感。

    基于上述思路,我们可以通过以下几个步骤实现基于情感词典的文本情感分类:预处理、分词、训练情感词典、判断,整个过程可以如下图所示。而检验模型用到的原材料,包括薛云老师提供的蒙牛牛奶的评论,以及从网络购买的某款手机的评论数据(见附件)。

    这里写图片描述

    文本的预处理

    由网络爬虫等工具爬取到的原始语料,通常都会带有我们不需要的信息,比如额外的Html标签,所以需要对语料进行预处理。由薛云老师提供的蒙牛牛奶评论也不例外。我们队伍使用Python作为我们的预处理工具,其中的用到的库有Numpy和Pandas,而主要的文本工具为正则表达式。经过预处理,原始语料规范为如下表,其中我们用-1标注消极情感评论,1标记积极情感评论。

    句子自动分词

    为了判断句子中是否存在情感词典中相应的词语,我们需要把句子准确切割为一个个词语,即句子的自动分词。我们对比了现有的分词工具,综合考虑了分词的准确性和在Python平台的易用性,最终选择了“结巴中文分词”作为我们的分词工具。

    下表仅展示各常见的分词工具对其中一个典型的测试句子的分词效果:

    测试句子:工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作

    载入情感词典

    一般来说,词典是文本挖掘最核心的部分,对于文本感情分类也不例外。情感词典分为四个部分:积极情感词典、消极情感词典、否定词典以及程度副词词典。为了得到更加完整的情感词典,我们从网络上收集了若干个情感词典,并且对它们进行了整合去重,同时对部分词语进行了调整,以达到尽可能高的准确率。

    构建情感词典.png

    我们队伍并非单纯对网络收集而来的词典进行整合,而且还有针对性和目的性地对词典进行了去杂、更新。特别地,我们加入了某些行业词汇,以增加分类中的命中率。不同行业某些词语的词频会有比较大的差别,而这些词有可能是情感分类的关键词之一。比如,薛云老师提供的评论数据是有关蒙牛牛奶的,也就是饮食行业的;而在饮食行业中,“吃”和“喝”这两个词出现的频率会相当高,而且通常是对饮食的正面评价,而“不吃”或者“不喝”通常意味着对饮食的否定评价,而在其他行业或领域中,这几个词语则没有明显情感倾向。另外一个例子是手机行业的,比如“这手机很耐摔啊,还防水”,“耐摔”、“防水”就是在手机这个领域有积极情绪的词。因此,有必要将这些因素考虑进模型之中。

    文本情感分类

    基于情感词典的文本情感分类规则比较机械化。简单起见,我们将每个积极情感词语赋予权重1,将每个消极情感词语赋予权重-1,并且假设情感值满足线性叠加原理;然后我们将句子进行分词,如果句子分词后的词语向量包含相应的词语,就加上向前的权值,其中,否定词和程度副词会有特殊的判别规则,否定词会导致权值反号,而程度副词则让权值加倍。最后,根据总权值的正负性来判断句子的情感。基本的算法如图。

    基于情感词典的文本分类-程序框图.png

    要说明的是,为了编程和测试的可行性,我们作了几个假设(简化)。假设一:我们假设了所有积极词语、消极词语的权重都是相等的,这只是在简单的判断情况下成立,更精准的分类显然不成立的,比如“恨”要比“讨厌”来得严重;修正这个缺陷的方法是给每个词语赋予不同的权值,我们将在本文的第二部分探讨权值的赋予思路。假设二:我们假设了权值是线性叠加的,这在多数情况下都会成立,而在本文的第二部分中,我们会探讨非线性的引入,以增强准确性。假设三:对于否定词和程度副词的处理,我们仅仅是作了简单的取反和加倍,而事实上,各个否定词和程度副词的权值也是不一样的,比如“非常喜欢”显然比“挺喜欢”程度深,但我们对此并没有区分。

    在算法的实现上,我们则选用了Python作为实现平台。可以看到,借助于Python丰富的扩展支持,我们仅用了一百行不到的代码,就实现了以上所有步骤,得到了一个有效的情感分类算法,这充分体现了Python的简洁。下面将检验我们算法的有效性。

    这里写图片描述

    困难所在
    经过两次测试,可以初步认为我们的模型正确率基本达到了80%以上。另外,一些比较成熟的商业化程序,它的正确率也只有85%到90%左右(如BosonNLP)。这说明我们这个简单的模型确实已经达到了让人满意的效果,另一方面,该事实也表明,传统的“基于情感词典的文本情感分类”模型的性能可提升幅度相当有限。这是由于文本情感分类的本质复杂性所致的。经过初步的讨论,我们认为文本情感分类的困难在以下几个方面。

    语言系统是相当复杂的

    归根结底,这是因为我们大脑中的语言系统是相当复杂的。(1)我们现在做的是文本情感分类,文本和文本情感都是人类文化的产物,换言之,人是唯一准确的判别标准。(2)人的语言是一个相当复杂的文化产物,一个句子并不是词语的简单线性组合,它有相当复杂的非线性在里面。(3)我们在描述一个句子时,都是将句子作为一个整体而不是词语的集合看待的,词语的不同组合、不同顺序、不同数目都能够带来不同的含义和情感,这导致了文本情感分类工作的困难。

    因此,文本情感分类工作实际上是对人脑思维的模拟。我们前面的模型,实际上已经对此进行了最简单的模拟。然而,我们模拟的不过是一些简单的思维定式,真正的情感判断并不是一些简单的规则,而是一个复杂的网络。

    大脑不仅仅在情感分类

    事实上,我们在判断一个句子的情感时,我们不仅仅在想这个句子是什么情感,而且还会判断这个句子的类型(祈使句、疑问句还是陈述句?);当我们在考虑句子中的每个词语时,我们不仅仅关注其中的积极词语、消极词语、否定词或者程度副词,我们会关注每一个词语(主语、谓语、宾语等等),从而形成对整个句子整体的认识;我们甚至还会联系上下文对句子进行判断。这些判断我们可能是无意识的,但我们大脑确实做了这个事情,以形成对句子的完整认识,才能对句子的感情做了准确的判断。也就是说,我们的大脑实际上是一个非常高速而复杂的处理器,我们要做情感分类,却同时还做了很多事情。

    活水:学习预测

    人类区别于机器、甚至人类区别于其他动物的显著特征,是人类具有学习意识和学习能力。我们获得新知识的途径,除了其他人的传授外,还包括自己的学习、总结和猜测。对于文本情感分类也不例外,我们不仅仅可以记忆住大量的情感词语,同时我们还可以总结或推测出新的情感词语。比如,我们只知道“喜欢”和“爱”都具有积极情感倾向,那么我们会猜测“喜爱”也具有积极的情感色彩。这种学习能力是我们扩充我们的词语的重要方式,也是记忆模式的优化(即我们不需要专门往大脑的语料库中塞进“喜爱”这个词语,我们仅需要记得“喜欢”和“爱”,并赋予它们某种联系,以获得“喜爱”这个词语,这是一种优化的记忆模式)。

    优化思路
    经过上述分析,我们看到了文本情感分类的本质复杂性以及人脑进行分类的几个特征。而针对上述分析,我们提出如下几个改进措施。

    非线性特征的引入

    前面已经提及过,真实的人脑情感分类实际上是严重非线性的,基于简单线性组合的模型性能是有限的。所以为了提高模型的准确率,有必要在模型中引入非线性。

    所谓非线性,指的是词语之间的相互组合形成新的语义。事实上,我们的初步模型中已经简单地引入了非线性——在前面的模型中,我们将积极词语和消极词语相邻的情况,视为一个组合的消极语块,赋予它负的权值。更精细的组合权值可以通过“词典矩阵”来实现,即我们将已知的积极词语和消极词语都放到同一个集合来,然后逐一编号,通过如下的“词典矩阵”,来记录词组的权值。


    并不是每一个词语的组合都是成立的,但我们依然可以计算它们之间的组合权值,情感权值的计算可以阅读参考文献。然而,情感词语的数目相当大,而词典矩阵的元素个数则是其平方,其数据量是相当可观的,因此,这已经初步进入大数据的范畴。为了更加高效地实现非线性,我们需要探索组合词语的优化方案,包括构造方案和储存、索引方案。

    情感词典的自动扩充

    在如今的网络信息时代,新词的出现如雨后春笋,其中包括“新构造网络词语”以及“将已有词语赋予新的含义”;另一方面,我们整理的情感词典中,也不可能完全包含已有的情感词语。因此,自动扩充情感词典是保证情感分类模型时效性的必要条件。目前,通过网络爬虫等手段,我们可以从微博、社区中收集到大量的评论数据,为了从这大批量的数据中找到新的具有情感倾向的词语,我们的思路是无监督学习式的词频统计。

    我们的目标是“自动扩充”,因此我们要达到的目的是基于现有的初步模型来进行无监督学习,完成词典扩充,从而增强模型自身的性能,然后再以同样的方式进行迭代,这是一个正反馈的调节过程。虽然我们可以从网络中大量抓取评论数据,但是这些数据是无标注的,我们要通过已有的模型对评论数据进行情感分类,然后在同一类情感(积极或消极)的评论集合中统计各个词语的出现频率,最后将积极、消极评论集的各个词语的词频进行对比。某个词语在积极评论集中的词频相当高,在消极评论集中的词频相当低,那么我们就有把握将该词语添加到消极情感词典中,或者说,赋予该词语负的权值。

    举例来说,假设我们的消极情感词典中并没有“黑心”这个词语,但是“可恶”、“讨厌”、“反感”、“喜欢”等基本的情感词语在情感词典中已经存在,那么我们就会能够将下述句子正确地进行情感分类:

    这里写图片描述

    本文结论

    综合上述研究,我们得出如下结论:

    基于情感词典的文本情感分类是容易实现的,其核心之处在于情感词典的训练。

    语言系统是相当复杂的,基于情感词典的文本情感分类只是一个线性的模型,其性能是有限的。

    在文本情感分类中适当地引入非线性特征,能够有效地提高模型的准确率。

    引入扩充词典的无监督学习机制,可以有效地发现新的情感词,保证模型的强健性和时效性。

    关注我的技术公众号《漫谈人工智能》,每天推送优质文章

    20190930002642724.jpg

    展开全文
  • pyhanlp 停用与用户自定义词典

    千次阅读 2018-09-20 21:35:02
    之前我们看了hanlp的词性标注,现在我们就要使用自定义词典与停用功能了,首先关于HanLP的词性标注方式具体请看HanLP词性标注集。 其核心词典形式如下: 自定义词典 自定义词典有多种添加模式,首先是展示的一个...

    hanlp的词典模式

    之前我们看了hanlp的词性标注,现在我们就要使用自定义词典与停用词功能了,首先关于HanLP的词性标注方式具体请看HanLP词性标注集

    其核心词典形式如下:
    hanlp的核心词典

    自定义词典

    自定义词典有多种添加模式,首先是展示的一个小例子,展示了词汇的动态增加与强行插入,删除等。更复杂的内容请参考后边的第二段代码。

    简单的例子

    from pyhanlp import *
    
    text = "攻城狮逆袭单身狗,迎娶白富美,走上人生巅峰"  # 怎么可能噗哈哈!
    
    print(HanLP.segment(text))
    
    CustomDictionary = JClass("com.hankcs.hanlp.dictionary.CustomDictionary")
    CustomDictionary.add("攻城狮")  # 动态增加
    CustomDictionary.insert("白富美", "nz 1024")  # 强行插入
    #CustomDictionary.remove("攻城狮"); # 删除词语(注释掉试试)
    CustomDictionary.add("单身狗", "nz 1024 n 1")
    
    
    # 展示该单词词典中的词频统计 展示分词
    print(CustomDictionary.get("单身狗"))
    print(HanLP.segment(text))
    
    
    # 增加用户词典,对其他分词器同样有效
    # 注意此处,CRF分词器将单身狗分为了n 即使单身狗:"nz 1024 n 1"
    CRFnewSegment = HanLP.newSegment("crf")
    print(CRFnewSegment.seg(text))
    
    [攻城狮, 逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走上, 人生, 巅峰]
    nz 1024 n 1 
    [攻城狮, 逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走上, 人生, 巅峰]
    [攻城, 狮逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走, 上, 人生, 巅峰]
    

    复杂的例子

    """ 演示自定义词性,以及往词典中插入自定义词性的词语
        !!!由于采用了反射技术,用户需对本地环境的兼容性和稳定性负责!!!
    
    TO-DO
    如果使用了动态词性之后任何类使用了switch(nature)语句,必须注册每个类
    """
    # 对于系统中已有的词性,可以直接获取
    Nature = JClass("com.hankcs.hanlp.corpus.tag.Nature")
    pc_nature = Nature.fromString("n")
    print(pc_nature)
    # 此时系统中没有"电脑品牌"这个词性
    pc_nature = Nature.fromString("电脑品牌")
    print(pc_nature)
    # 我们可以动态添加一个
    pc_nature = Nature.create("电脑品牌");
    print(pc_nature)
    # 可以将它赋予到某个词语
    LexiconUtility = JClass("com.hankcs.hanlp.utility.LexiconUtility")
    LexiconUtility.setAttribute("苹果电脑", pc_nature)
    # 或者
    LexiconUtility.setAttribute("苹果电脑", "电脑品牌 1000")
    # 它们将在分词结果中生效
    term_list = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗")
    print(term_list)
    for term in term_list:
        if term.nature == pc_nature:
            print("找到了 [{}] : {}\n".format(pc_nature, term.word))
    
    # 还可以直接插入到用户词典
    CustomDictionary = JClass("com.hankcs.hanlp.dictionary.CustomDictionary")
    CustomDictionary.insert("阿尔法狗", "科技名词 1024")
    StandardTokenizer = JClass("com.hankcs.hanlp.tokenizer.StandardTokenizer")
    StandardTokenizer.SEGMENT.enablePartOfSpeechTagging(True)  # 依然支持隐马词性标注
    term_list = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗")
    print(term_list)
    
    n
    None
    电脑品牌
    [苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/v, 阿尔法/nrf, 狗/n, 代码/n, 吗/y]
    找到了 [电脑品牌] : 苹果电脑
    
    [苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/v, 阿尔法狗/科技名词, 代码/n, 吗/y]
    

    关于自定义词典的说明(原作者的原文)

    • 说明
      • CustomDictionary是一份全局的用户自定义词典,可以随时增删,影响全部分词器。另外可以在任何分词器中关闭它。通过代码动态增删不会保存到词典文件。
      • 中文分词≠词典,词典无法解决中文分词,Segment提供高低优先级应对不同场景,请参考FAQ
    • 追加词典
      • CustomDictionary主词典文本路径是data/dictionary/custom/CustomDictionary.txt,用户可以在此增加自己的词语(不推荐);也可以单独新建一个文本文件,通过配置文件CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 我的词典.txt;来追加词典(推荐)。
      • 始终建议将相同词性的词语放到同一个词典文件里,便于维护和分享。
    • 词典格式
      • 每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ... 如果不填词性则表示采用词典的默认词性。
      • 词典的默认词性默认是名词n,可以通过配置文件修改:全国地名大全.txt ns;如果词典路径后面空格紧接着词性,则该词典默认是该词性。
      • 在统计分词中,并不保证自定义词典中的词一定被切分出来。用户可在理解后果的情况下通过Segment#enableCustomDictionaryForcing强制生效。
      • 关于用户词典的更多信息请参考词典说明一章(请看本文最后)。

    停用词

    关于停用词,我同样先给出了一个简单的例子,你可以使用这个例子来完成你所需要的功能。要注意的一点是,因为java中的类所返回的数据类型与Python不统一,所以当你使用不同的函数的时候,一定要先检查输出结果在Python中的类型,不然可能会出现意想不到的问题。

    假如你想了解更多,可以看第二个更复杂的例子。

    简单的例子

    # 使用停用词的简单例子
    
    text = "小区居民有的反对喂养流浪猫"
    CRFnewSegment = HanLP.newSegment("crf")
    term_list = CRFnewSegment.seg(text)
    # BasicTokenizer = SafeJClass("com.hankcs.hanlp.tokenizer.BasicTokenizer")
    # term_list = BasicTokenizer.segment(text)
    
    CoreStopWordDictionary = JClass("com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary")
    CoreStopWordDictionary.apply(term_list)
    HanLP.Config.ShowTermNature = False
    
    print(term_list)
    print([i.word for i in term_list])
    
    [小区, 居民, 反对, 养, 流, 浪, 猫]
    ['小区', '居民', '反对', '养', '流', '浪', '猫']
    

    复杂的例子

    # 停用词
    
    # 在import pyhanlp之前编译自己的Java class,并放入pyhanlp/static中
    import os
    
    from pyhanlp.static import STATIC_ROOT, HANLP_JAR_PATH
    
    java_code_path = os.path.join(STATIC_ROOT, 'MyFilter.java')
    with open(java_code_path, 'w') as out:
        java_code = """
    import com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary;
    import com.hankcs.hanlp.dictionary.stopword.Filter;
    import com.hankcs.hanlp.seg.common.Term;
    
    public class MyFilter implements Filter
    {
        public boolean shouldInclude(Term term)
        {
            if (term.nature.startsWith('m')) return true; // 数词保留
            return !CoreStopWordDictionary.contains(term.word); // 停用词过滤
        }
    }
    """
        out.write(java_code)
    os.system('javac -cp {} {} -d {}'.format(HANLP_JAR_PATH, java_code_path, STATIC_ROOT))
    # 编译结束才可以启动hanlp
    
    CoreStopWordDictionary = JClass("com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary")
    Filter = JClass("com.hankcs.hanlp.dictionary.stopword.Filter")
    Term = JClass("com.hankcs.hanlp.seg.common.Term")
    BasicTokenizer = JClass("com.hankcs.hanlp.tokenizer.BasicTokenizer")
    NotionalTokenizer = JClass("com.hankcs.hanlp.tokenizer.NotionalTokenizer")
    
    text = "小区居民有的反对喂养流浪猫,而有的居民却赞成喂养这些小宝贝"
    # 可以动态修改停用词词典
    CoreStopWordDictionary.add("居民")
    print(NotionalTokenizer.segment(text))
    CoreStopWordDictionary.remove("居民")
    print(NotionalTokenizer.segment(text))
    
    # 可以对任意分词器的结果执行过滤
    term_list = BasicTokenizer.segment(text)
    print(term_list)
    CoreStopWordDictionary.apply(term_list)
    print(term_list)
    
    # 还可以自定义过滤逻辑
    MyFilter = JClass('MyFilter')
    CoreStopWordDictionary.FILTER = MyFilter()
    print(NotionalTokenizer.segment("数字123的保留"))  # “的”位于stopwords.txt所以被过滤,数字得到保留
    
    [小区/n, 反对/v, 喂养/v, 流浪猫/nz, 赞成/v, 喂养/v, 小宝贝/nz]
    [小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
    [小区/n, 居民/n, 有/vyou, 的/ude1, 反对/v, 喂养/v, 流浪猫/nz, ,/w, 而/cc, 有的/rz, 居民/n, 却/d, 赞成/v, 喂养/v, 这些/rz, 小宝贝/nz]
    [小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
    [数字/n, 123/m, 保留/v]
    

    词典说明(原作者原文)

    本章详细介绍HanLP中的词典格式,满足用户自定义的需要。HanLP中有许多词典,它们的格式都是相似的,形式都是文本文档,随时可以修改。

    基本格式

    词典分为词频词性词典和词频词典。

    • 词频词性词典(如CoreNatureDictionary.txt
      • 每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ...
      • 支持省略词性和频次,直接一行一个单词。
      • .txt词典文件的分隔符为空格或制表符,所以不支持含有空格的词语。如果需要支持空格,请使用英文逗号,分割的纯文本.csv文件。在使用Excel等富文本编辑器时,则请注意保存为纯文本形式。
    • 词频词典(如CoreNatureDictionary.ngram.txt
      • 每一行代表一个单词或条目,格式遵从[单词] [单词的频次]
      • 每一行的分隔符为空格或制表符。

    少数词典有自己的专用格式,比如同义词词典兼容《同义词词林扩展版》的文本格式,而转移矩阵词典则是一个csv表格。

    下文主要介绍通用词典,如不注明,词典特指通用词典。

    数据结构

    Trie树(字典树)是HanLP中使用最多的数据结构,为此,我实现了通用的Trie树,支持泛型、遍历、储存、载入。

    用户自定义词典采用AhoCorasickDoubleArrayTrie和二分Trie树储存,其他词典采用基于双数组Trie树(DoubleArrayTrie)实现的AC自动机AhoCorasickDoubleArrayTrie。关于一些常用数据结构的性能评估,请参考wiki

    储存形式

    词典有两个形态:文本文件(filename.txt)和缓存文件(filename.txt.bin或filename.txt.trie.dat和filename.txt.trie.value)。

    • 文本文件
      • 采用明文储存,UTF-8编码,CRLF换行符。
    • 缓存文件
      • 就是一些二进制文件,通常在文本文件的文件名后面加上.bin表示。有时候是.trie.dat和.trie.value。后者是历史遗留产物,分别代表trie树的数组和值。
      • 如果你修改了任何词典,只有删除缓存才能生效。

    修改方法

    HanLP的核心词典训练自人民日报2014语料,语料不是完美的,总会存在一些错误。这些错误可能会导致分词出现奇怪的结果,这时请打开调试模式排查问题:(本文作者FontTian注:在本文动笔前,原词典一进变为了9970万版本的最大中文语料。但是词典说明中原作者没改)

    HanLP.Config.enableDebug();
    
    • 核心词性词频词典
      • 比如你在data/dictionary/CoreNatureDictionary.txt中发现了一个不是词的词,或者词性标注得明显不对,那么你可以修改它,然后删除缓存文件使其生效。
      • 目前CoreNatureDictionary.ngram.txt的缓存依赖于CoreNatureDictionary.txt的缓存,修改了后者之后必须同步删除前者的缓存,否则可能出错
    • 核心二元文法词典
      • 二元文法词典data/dictionary/CoreNatureDictionary.ngram.txt储存的是两个词的接续,如果你发现不可能存在这种接续时,删掉即可。
      • 你也可以添加你认为合理的接续,但是这两个词必须同时在核心词典中才会生效。
    • 命名实体识别词典
      • 基于角色标注的命名实体识别比较依赖词典,所以词典的质量大幅影响识别质量。
      • 这些词典的格式与原理都是类似的,请阅读相应的文章或代码修改它。
    展开全文
  • 自然语言是一套用来表达含义的复杂系统。在这套系统中,是表义的基本单元。在机器学习中,如何使用向量表示? 顾名思义,向量是用来表示的向量,通常也被认为是的特征向量。近年来,向量已逐渐成为自然...
  • 复杂性研究面临的难题

    千次阅读 2018-01-02 00:00:00
    一、什么复杂性目前无法表述清楚 在汉语中“复杂”一的意思为“事物的种类、头绪等多而杂”。在《朗文当代英语词典》中,形容complex被解释为:(1)难于理解、解释或处理,不清楚或不简单; (2)由许多密切...
  • 向量算法

    千次阅读 2019-06-05 22:31:07
    https://www.cnblogs.com/the-wolf-sky/articles/10192363.html...基于神经网络的表示一般称为向量、嵌入(word embdding)或分布式表示。 神经网络的向量和其他分布式类似,都基于分布式表达方式,核心依然是上...
  • 题目Write a method to decide if two strings are anagrams or not.写一个函数判断两个字符串是否是变位。解答变位(anagrams)指的是组成两个单词的字符相同,但位置不同的单词。...该题目有两种做法:时间复杂
  • 嵌入算法

    千次阅读 2018-08-27 01:06:42
    主要通过神经网络上下文,以及上下文和目标之间的关系进行建模,之所以神经网络可以进行建模,主要是由于神经网络的空间非常大,所以这种方法可以表达复杂的上下文关系。 1. 向量 nlp中最常见的第...
  • 这个观察提出了一个自然的问题:为什么许多自然系统具有临界系统的特性?一个可能的答案是自组织临界性(SOC),这是一些系统向临界状态演化并保持它的趋势。 在本章中,我将介绍沙堆模型,这是第一个展示 SOC 的...
  • 向量

    千次阅读 2018-07-31 20:18:25
    向量的来历 Distributed representation 最早是 Hinton 在 1986 年的论文《Learning distributed representations of concepts》中提出的。Distributed representation的意思是分布式表示,通常被称为“Word ...
  • 嵌入来龙去脉 word embedding、word2vec

    万次阅读 多人点赞 2017-08-14 21:57:45
    0嵌入来龙去脉 NLP的核心关键语言表示Representation NLP的表示方法类型 1的独热表示one-hot representation 2的分布式表示distributed representation NLP语言模型 的分布式表示 1基于矩阵的分布表示 2...
  • 中文停用列表

    千次阅读 2018-09-06 12:50:27
    中文停用分享 ! ” # $ % &amp; ’ ( ) * + , – . .. … …… ………………. ./ .一 .数 .日 / // 0 1 2 3 4 5 6 7 8 9 : :// :: ; &lt; &gt; &...
  • 中文分词词典构造简述

    千次阅读 2014-10-05 21:49:58
    在分词系统中常用的分词词典机制有:(1)基于整二分;(2)基于TRIE索引树;(3)基于逐字二分. 一、基于整二分的分词词典机制 这是一种广为使用的分词词典机制.其结构通常分为三级,前两级为索引,如图3.1听示。 ...
  • 原代码: def natural_language_processing(self,response): #所抓取的预料进行自然语言处理 title = response.meta['title'] #print title content = response.meta['content'] #print cont...
  • lucene构建同义分词器

    千次阅读 2014-11-19 16:18:16
    lucene4.0版本以后 已经用TokenStreamComponents 取代了... lucene的倒排列表中,不同的分词单元有不同的PositionIncrementAttribute,如果两个有着相同位置属性,比如:我定义美国和中国这两个在倒排列表中是
  • 复杂SQL的编写要领

    千次阅读 2015-01-10 10:28:50
    SQL的编码显得越来越重要,原因是很多逻辑处理,都可以跟sql挂钩。 个人排斥将逻辑层代码复杂化, 因此sql在未来的项目架构中,举足轻重, 特地开一章节, 来总结一下以往项目中sql的精髓部分。【待完成】
  • 在处理后数据当中,包含相应嵌入模型。 原始数据下载地址:BeerAdvocate–Source 处理后数据下载地址:BeerAdvocate–Preprocess 1.5 NLPCC2014评估任务2_基于深度学习的情感分类 该语料共包含中文和英文两...
  • 关于向量工作原理的理解

    万次阅读 2013-10-16 17:14:24
    在知乎网站上看到一个关于向量的问题:向量( Distributed Representation)工作原理是什么,哪位大咖能否举个通俗的例子说明一下? 恰好最近在学习 word2vec, 尝试着根据所读文献的理解写了个回答。  要...
  • 中文停用词表

    万次阅读 多人点赞 2016-05-20 00:36:27
    停用词表
  • PS一句:最终还是选择CSDN来整理发表这几年的知识...以上第一部分就是JNI开发常见的基本结构模板,实际开发代码量和文件和目录结构都会比这复杂,这只是一个雏形用来领悟重点。 第二部分 概述 如果你已经...
  • 基于word2vec的中文向量训练

    万次阅读 2017-11-24 11:16:52
    基于word2vec的中文向量训练 使用katex解析的数学公式,csdn好像不支持 word2vec来源 Google开源 可以在百万数量级的词典和上亿的数据集上进行高效地训练 该工具得到的训练结果– 向量(word embedding),可以...
  • 向量—Word2Vec入门及Gensim实践

    千次阅读 2018-08-08 18:10:57
    在机器学习领域,语音识别和图像识别都比较容易做到。语音识别的输入数据可以是音频频谱...Word2vec是google在2013年推出的一个向量实现工具(注意,不是向量模型),它的特点是将所有的向量化,这样之...
  • 注意,辅助任务(基于什么类型的上下文,预测什么)的选择相对用来训练的学习方法,结果向量的影响大得多。这方面在嵌入算法后面介绍。 2. 向量训练算法 将视作连续空间中的一个点,即的连续表示...
  • ... 在网上总能发现一些感兴趣的东西,从前是直接转载过来,现在...0. 向量是什么 1. 向量的来历 2. 向量的训练   2.0 语言模型简介   2.1 Bengio 的经典之作   2.2 C&W 的 SENNA   2.3 M...
  • 【NLP】向量的惊人力量

    千次阅读 2018-08-27 16:03:36
    在谷歌有效的矢量表示的单词(以及你可以用它们做什么)。论文是: 向量空间中表示的有效估计 - Mikolov等。2013 单词和短语的分布式表示及其组合性 - Mikolov等。2013 连续空间表示中的语言规律 - ...
  • 深度学习:嵌入之word2vec

    千次阅读 2017-07-26 15:24:57
    word2vec简介 深度学习在自然语言处理中第一个应用:训练嵌入。Google 的 Tomas Mikolov 在《Efficient Estimation of Word Representation in Vector Space》和《Distributed ...
  • 关于向量的一篇好的入门文章

    万次阅读 2017-07-13 15:46:42
    作者:Scofield ... 来源:知乎 著作权归作者所有。...很好,正好可借此机会介绍向量、word2vec以及DeepNLP整套相关的东西: 文章很长,是从CSDN上写好复制过来的,亦可直接跳到博客观看: 深度学

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,040
精华内容 35,616
关键字:

复杂对什么相应的词