文本聚类引擎

由于自然语言书写文档的多样性,同样内容的文章往往可以采用不同的词句和语法构成进行书写。人对于文档的理解通常在话题的层面,而并非单一的文档或发贴。这使得我们希望机器自动能够对给定的文本进行话题聚类,将语义上相似的文章归为一类,方便人的浏览查看,可进行话题级的统计分析。比如下面两条微博:

  • #新闻追踪#:【冀中星被移送检察院审查起诉】首都机场公安分局对冀中星爆炸案侦查终结,目前已移送朝阳检察院审查起诉。7月20日18时24分,冀中星在首都机场T3航站楼B口外引爆自制炸药。案发当天除冀中星左手腕因被炸截肢外,无其他人伤亡。7月29日,冀中星因涉嫌爆炸罪被批捕。http://t.cn/zQHjr0S
  • #豫广微新闻#【首都机场爆炸案嫌犯冀中星 移送检方审查起诉】 据报道,首都机场公安分局对冀中星爆炸案侦查终结,目前已移送朝阳检察院审查起诉。7月20日,山东籍男子冀中星在首都机场T3航站楼引爆自制炸药,案发当天除冀中星左手腕因被炸截肢外,无其他人伤亡

报道的同一个事件,虽然书写形式有所区别,文章标题也不一样,但由于语义的相似性,仍然可以被话题聚类引擎识别出来。目前 BosonNLP 采用的话题聚类算法为无监督算法,即不需要人工数据标注。

该引擎的使用需要以下三个步骤:

  1. 上传需要聚类的文本数据;
  2. 调用分析引擎(异步);
  3. 从服务器取得聚类的结果。

以下我们将按照上述的三个步骤来介绍引擎的调用。

上传数据

URL

http://api.bosonnlp.com/cluster/push/TASK_ID

TASK_ID 是任务的ID,用于唯一识别该聚类任务,可由字母和数字组成。

Warning

对于每次聚类任务应使用不同的 TASK_ID ,以免导致混淆。

HTTP Method
POST
HTTP Header
Content-Type
application/json
Accept
application/json
X-Token
YOUR_API_TOKEN (需要替换成您自己的 Token)
HTTP 请求 Body

JSON 格式的列表。每个元素为上传到聚类引擎的文档编号和内容组成的 JSON 对象,必须包含两个字段:

  1. _id

    文档的编号,可以采用整数或字符串

  2. text

    文档的内容,原始的文章信息

对于一个聚类任务,建议可上传50条以上,20万条以下内容进行分析。

Note

注意,在文本数量较大时,请分批上传数据。每批最多上传不超过100条文本。

Java调用代码示例

HttpResponse<JsonNode> clusterPushResponse = Unirest
    .post("http://api.bosonnlp.com/cluster/push/TASK_ID")
    .header("Accept", "application/json")
    .header("Content-Type", "application/json")
    .header("X-Token", "YOUR_API_TOKEN")
    .body(YOUR_JSON_TEXT)
    .asJson();

调用分析

一旦所有待分析的文档都上传到了服务器以后,就可以启动分析模块。聚类任务在后台进行,需要的时间比较长,该接口返回成功时仅表明分析任务已开始。

URL
http://api.bosonnlp.com/cluster/analysis/TASK_ID
HTTP Method
GET
HTTP Header
Accept
application/json
X-Token
YOUR_API_TOKEN (需要替换成您自己的 Token)
HTTP GET 参数
参数名 取值范围 默认值 作用
alpha (0, 1] 0.8 调节聚类最大cluster大小
beta (0, 1) 0.45 调节聚类平均cluster大小

alpha 越大,最大产生的cluster会相应减小; beta 越大,形成cluster越不容易,平均cluster大小会减小。默认参数是在微博文本聚类上得到不错结果的一组经验参数,在其他应用(如新闻文本聚类)可能需要作相应的调整。

Tip

聚类默认参数适用于在同一个关键词(例如“MH370”、“食品安全”等)对应的内容中发现细粒度子话题。对于其他类型的聚类需求,可以通过调节参数的方式调节聚类的粒度。

Java调用代码示例

HttpResponse<String> clusterAnalysisResponse = Unirest
    .get("http://api.bosonnlp.com/cluster/analysis/TASK_ID")
    .header("Accept", "application/json")
    .header("X-Token", "YOUR_API_TOKEN")
    .asString();

查看任务状态

一般文本聚类任务都用于处理大量的文本,需要比较长的处理时间。通过这个接口,可以查看聚类任务当前的状态信息。

URL
http://api.bosonnlp.com/cluster/status/TASK_ID
HTTP Method
GET
HTTP Header
Accept
application/json
X-Token
YOUR_API_TOKEN (需要替换成您自己的 Token)
HTTP 返回 Body

返回 JSON 格式的状态信息。可能的状态列表如下:

名字 说明
NOT FOUND (在调用分析时)未找到任何数据
RECEIVED 成功接收到分析请求
RUNNING 数据分析正在进行中
ERROR 分析遇到错误退出
DONE 分析已完成

Java调用代码示例

HttpResponse<String> clusterStatusResponse = Unirest
    .get("http://api.bosonnlp.com/cluster/status/TASK_ID")
    .header("Accept", "application/json")
    .header("X-Token", "YOUR_API_TOKEN")
    .asString();

获取结果

一旦聚类引擎完成执行,结果将会被保存在服务器的数据库中。这个时候可以通过 HTTP GET 来取得聚类的结果数据。

URL
http://api.bosonnlp.com/cluster/result/TASK_ID
HTTP Method
GET
HTTP Header
Accept
application/json
X-Token
YOUR_API_TOKEN (需要替换成您自己的 Token)
HTTP 返回 Body

返回 JSON 格式的列表,包含以下字段:

字段 类型 说明
_id 与上传_id类型相同 该cluster最具代表性的文档
num int 该cluster包含的文档数目
list 列表 所有属于该cluster的文档 _id

Note

上述的返回结果中忽略了独立的评论,即每个聚类结果至少包含2条评论。

Java调用代码示例

HttpResponse<JsonNode> clusterResultResponse = Unirest
    .get("http://api.bosonnlp.com/cluster/result/TASK_ID")
    .header("Accept", "application/json")
    .header("X-Token", "YOUR_API_TOKEN")
    .asJson();

清除数据

清除 TASK_ID 对应的文本和聚类分析结果。

URL
http://api.bosonnlp.com/cluster/clear/TASK_ID
HTTP Method
GET
HTTP Header
Accept
application/json
X-Token
YOUR_API_TOKEN (需要替换成您自己的 Token)
HTTP 返回 Body
返回 String 格式的状态信息:
Cleared %d documents.

详细的 Python SDK 文本聚类文档请看 这里

Java调用代码示例

HttpResponse<String> clusterClearResponse = Unirest
    .get("http://api.bosonnlp.com/cluster/clear/TASK_ID")
    .header("Accept", "application/json")
    .header("X-Token", "YOUR_API_TOKEN")
    .asString();

Python SDK 完整调用示例

# -*- encoding: utf-8 -*-
from __future__ import print_function, unicode_literals
from bosonnlp import BosonNLP

# 注意:在测试时请更换为您的API Token
nlp = BosonNLP('YOUR_API_TOKEN')


def print_cluster(docs, idx, result):
    print('=' * 50)
    print('第%d个聚类中共有%s份文档,如下:' % (idx + 1, result['num']))
    for doc in result['list']:
        print(docs[doc])
    print('-' * 20)
    print('本聚类的中心文档为:')
    print(docs[result['_id']])


def main():
    with open('text_cluster.txt', 'rb') as f:
        docs = [line.decode('utf-8') for line in f if line]
    clusters = nlp.cluster(docs)
    clusters = sorted(clusters, key=lambda cluster: cluster['num'], reverse=True)
    for idx, cluster in enumerate(clusters):
        print_cluster(docs, idx, cluster)


if __name__ == '__main__':
    main()

详细的 Python SDK 文本聚类文档请看 这里

Java 完整调用示例

下面是 Java 的 Unirest 库 (Github) 调用 BosonNLP 的 Cluster API 的示例。

Tip

完整的示例代码可以从 这里 下载。

package clusterApiExample;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;

public class ClusterApiExample {
    // 请把TASK_ID换成您自己的ID
    public static final String CLUSTER_PUSH = "http://api.bosonnlp.com/cluster/push/TASK_ID";
    public static final String CLUSTER_ANALYSIS = "http://api.bosonnlp.com/cluster/analysis/TASK_ID";
    public static final String CLUSTER_STATUS = "http://api.bosonnlp.com/cluster/status/TASK_ID";
    public static final String CLUSTER_RESULT = "http://api.bosonnlp.com/cluster/result/TASK_ID";
    public static final String CLUSTER_CLEAR = "http://api.bosonnlp.com/cluster/clear/TASK_ID";
    // 请记得把 YOUR_API_TOKEN 换成您申请的 Token
    public static final String API_TOKEN = "YOUR_API_TOKEN";
    public static String filePath = "clusterInfos.txt";
    public static boolean flag;
    public static void main(String[] args) throws JSONException,
                                                  UnirestException, IOException {
        // 读取根目录下的clusterInfos.txt文件里的内容作为聚类上传的数据,请根据自身需求做更改
        flag = readTxtFile(filePath);

        // 聚类分析API调用
        HttpResponse<String> clusterAnalysisResponse = Unirest
            .get(CLUSTER_ANALYSIS)
            .header("Accept", "application/json")
            .header("X-Token", API_TOKEN)
            .asString();
        System.out.println(clusterAnalysisResponse.getCode());

        // 调用查看状态的API来查看任务处理进度,这里每隔一段时间查询一次
        while (flag) {
            // 聚类状态API调用
            HttpResponse<String> clusterStatusResponse = Unirest
                .get(CLUSTER_STATUS)
                .header("Accept", "application/json")
                .header("X-Token", API_TOKEN)
                .asString();
            JSONObject jsonObj = new JSONObject(clusterStatusResponse.getBody());
            String status = (String) jsonObj.get("status");

            try{
                Thread.sleep(2000);
            }catch(InterruptedException ie){
                ie.printStackTrace();
            }

            System.out.println(status);
            if ("DONE".equals(status)) {
                flag = false;
            }
            else
                System.out.println("请稍等,数据处理中...");
        }

        // 聚类结果API调用
        HttpResponse<JsonNode> clusterResultResponse = Unirest
            .get(CLUSTER_RESULT)
            .header("Accept", "application/json")
            .header("X-Token", API_TOKEN)
            .asJson();
        System.out.println("以下是返回的结果:");
        System.out.println(clusterResultResponse.getBody());

        // 聚类清除API调用
        HttpResponse<String> clusterClearResponse = Unirest
            .get(CLUSTER_CLEAR)
            .header("Accept", "application/json")
            .header("X-Token", API_TOKEN)
            .asString();
        // 返回的结果是一段JSON,num 指该 cluster 包含的文档数目,_id 指该 cluster最具代表性的文档
	// list 指所有属于该 cluster 的文档 _id
        System.out.println(clusterClearResponse.getBody());
    }

    public static boolean readTxtFile(String filePath ) {
        try {
            String encoding = "utf-8";
            File file = new File(filePath);
            if (file.isFile() && file.exists()) { // 判断文件是否存在
                InputStreamReader reader = new InputStreamReader(new FileInputStream(file), encoding);
                BufferedReader bufferedReader = new BufferedReader(reader);
                String lineTxt = null;
                int i = 0;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                    System.out.println(i);
                    JSONArray jsonArray = new JSONArray();
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("_id", ++i);
                    jsonObject.put("text", lineTxt);
                    jsonArray.put(jsonObject);
                    String body = jsonArray.toString();
                    System.out.println(body);

                    // 聚类上传API调用
                    // 上传的数据时JSON格式的,必须包含_id(文档的编号,可以采用整数或字符串)和text(文档的内容,原始的文章信息)
                    HttpResponse<JsonNode> clusterPushResponse = Unirest.post(CLUSTER_PUSH)
                        .header("Accept", "application/json")
                        .header("Content-Type","application/json")
                        .header("X-Token", API_TOKEN)
                        .body(body).asJson();
                }
                reader.close();
                return true;
            }else{
                System.out.println("找不到指定的文件");
                return false;
            }
        } catch (Exception e) {
            System.out.println("读取文件内容出错");
            e.printStackTrace();
            return false;
        }
    }
}

构建

下载完后,把解压好的文件夹放到 Eclipse 的 Workspace 文件夹下,再新建一个名字一样的 Java Project。

运行

把 X-Token 改成您自己申请的 Token。把 TASK_ID 换成您自己的 ID。

TASK_ID 是任务的ID,用于唯一识别该聚类任务,可由字母和数字组成。

应用场景示例

请看 在MH370的微博中发现不同话题