由于自然语言书写文档的多样性,同样内容的文章往往可以采用不同的词句和语法构成进行书写。人对于文档的理解通常在话题的层面,而并非单一的文档或发贴。这使得我们希望机器自动能够对给定的文本进行话题聚类,将语义上相似的文章归为一类,方便人的浏览查看,可进行话题级的统计分析。比如下面两条微博:
报道的同一个事件,虽然书写形式有所区别,文章标题也不一样,但由于语义的相似性,仍然可以被话题聚类引擎识别出来。目前 BosonNLP 采用的话题聚类算法为无监督算法,即不需要人工数据标注。
该引擎的使用需要以下三个步骤:
以下我们将按照上述的三个步骤来介绍引擎的调用。
http://api.bosonnlp.com/cluster/push/TASK_ID
TASK_ID 是任务的ID,用于唯一识别该聚类任务,可由字母和数字组成。
Warning
对于每次聚类任务应使用不同的 TASK_ID ,以免导致混淆。
JSON 格式的列表。每个元素为上传到聚类引擎的文档编号和内容组成的 JSON 对象,必须包含两个字段:
对于一个聚类任务,建议可上传50条以上,20万条以下内容进行分析。
Note
注意,在文本数量较大时,请分批上传数据。每批最多上传不超过100条文本。
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();
一旦所有待分析的文档都上传到了服务器以后,就可以启动分析模块。聚类任务在后台进行,需要的时间比较长,该接口返回成功时仅表明分析任务已开始。
参数名 | 取值范围 | 默认值 | 作用 |
---|---|---|---|
alpha | (0, 1] | 0.8 | 调节聚类最大cluster大小 |
beta | (0, 1) | 0.45 | 调节聚类平均cluster大小 |
alpha 越大,最大产生的cluster会相应减小; beta 越大,形成cluster越不容易,平均cluster大小会减小。默认参数是在微博文本聚类上得到不错结果的一组经验参数,在其他应用(如新闻文本聚类)可能需要作相应的调整。
Tip
聚类默认参数适用于在同一个关键词(例如“MH370”、“食品安全”等)对应的内容中发现细粒度子话题。对于其他类型的聚类需求,可以通过调节参数的方式调节聚类的粒度。
HttpResponse<String> clusterAnalysisResponse = Unirest
.get("http://api.bosonnlp.com/cluster/analysis/TASK_ID")
.header("Accept", "application/json")
.header("X-Token", "YOUR_API_TOKEN")
.asString();
一般文本聚类任务都用于处理大量的文本,需要比较长的处理时间。通过这个接口,可以查看聚类任务当前的状态信息。
返回 JSON 格式的状态信息。可能的状态列表如下:
名字 | 说明 |
NOT FOUND | (在调用分析时)未找到任何数据 |
RECEIVED | 成功接收到分析请求 |
RUNNING | 数据分析正在进行中 |
ERROR | 分析遇到错误退出 |
DONE | 分析已完成 |
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 来取得聚类的结果数据。
返回 JSON 格式的列表,包含以下字段:
字段 | 类型 | 说明 |
---|---|---|
_id | 与上传_id类型相同 | 该cluster最具代表性的文档 |
num | int | 该cluster包含的文档数目 |
list | 列表 | 所有属于该cluster的文档 _id |
Note
上述的返回结果中忽略了独立的评论,即每个聚类结果至少包含2条评论。
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 对应的文本和聚类分析结果。
详细的 Python SDK 文本聚类文档请看 这里 。
HttpResponse<String> clusterClearResponse = Unirest
.get("http://api.bosonnlp.com/cluster/clear/TASK_ID")
.header("Accept", "application/json")
.header("X-Token", "YOUR_API_TOKEN")
.asString();
# -*- 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 的 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。