程序使用IKAnalyzerV2012_U6中文分词器,因此要在Java的工作目录下添加IKAnalyzer2012_u6.jar,因为这个分词器依赖于Lucene,所以也要添加lucene-core-3.6.2.jar,当然,weka.jar也是必须的。我这有已经下载好的资源
链接:https://pan.baidu.com/s/1F0EOyF4VxH4bRh6tFb0gXA 密码:n2m9
把IKAnalyzer的stopword.dic和IKAnalyzer.cfg.xml复制到项目src根目录下
这里使用复旦大学李荣陆提供的文本分类语料库测试语料,下载网址为http://www.nlpir.org/?action-viewnews-itemid-103(可能有时候进不去,刷新一下就好了)
我把下载好的测试包解压到F盘进行测试,读者可自行解压,在输入命令时更改相应路径即可,下面是源程序代码(代码来自袁梅宇的《数据挖掘与机器学习WEKA应用技术与实践》,进行了小部分修改)
package wekalearning.classifiers;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.wltea.analyzer.lucene.IKAnalyzer;
import weka.classifiers.Classifier;
import weka.classifiers.trees.J48;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.StringToWordVector;
public class MessageClassifier implements Serializable {
private static final long serialVersionUID = -6705084686587638940L;
private Instances m_Data = null;// 迄今收集到的训练数据
private StringToWordVector m_Filter = new StringToWordVector();// 用于生成单词计数的过滤器
private Classifier m_Classifier = new J48();// 实际的分类器
private boolean m_UpToData;// 模型是否为最新
/**
* 构建空训练集
* @throws Exception
*/
public MessageClassifier() throws Exception {
String nameOfDataset = "MessageClassificationProblem";
List<Attribute> attributes = new ArrayList<Attribute>();// 创建的属性列表
attributes.add(new Attribute("Message", (List<String>) null));// 添加属性以保存文本信息
List<String> classValues = new ArrayList<String>();// 添加类别属性
classValues.add("miss");
classValues.add("hit");
attributes.add(new Attribute("Class", classValues));
m_Data = new Instances(nameOfDataset, (ArrayList<Attribute>) attributes, 100);// 创建初始容量为100的数据集
m_Data.setClassIndex(m_Data.numAttributes() - 1);// 设置类别索引
}
/**
* 使用给定的训练文本信息更新模型
* @param message
* @param classValue
* @throws Exception
*/
public void updateData(String message, String classValue) throws Exception {
Instance instance = makeInstance(message, m_Data);// 把文本信息转换为实例
instance.setClassValue(classValue);// 为实例设置类别值
m_Data.add(instance);// 添加实例到训练数据
m_UpToData = false;
System.err.println("更新模型成功!");
}
/**
* 分类给定的文本消息
* @param message
* @throws Exception
*/
public void classifyMessage(String message) throws Exception {
if (m_Data.numInstances() == 0) {// 检查是否已构建分类器
throw new Exception("无可用分类器!");
}
if (!m_UpToData) {// 检查分类器和过滤器是否为最新
m_Filter.setInputFormat(m_Data);// 初始化过滤器,并告知输入格式
Instances filteredData = Filter.useFilter(m_Data, m_Filter);// 从训练数据生成单词计数
m_Classifier.buildClassifier(filteredData);// 重建分类器
m_UpToData = true;
}
Instances testset = m_Data.stringFreeStructure();// 形成单独的小测试集,该文本信息不会添加到m_Data的字符串属性当中
Instance instance = makeInstance(message, testset);// 使文本信息成为实例
m_Filter.input(instance);// 过滤实例
Instance filteredInstance = m_Filter.output();
double predicted = m_Classifier.classifyInstance(filteredInstance);// 获取预测类别值的索引
System.err.println("文本信息分类为:" + m_Data.classAttribute().value((int) predicted));
}
/**
* 将文本信息转换为实例的方法
* @param text
* @param data
* @return
*/
private Instance makeInstance(String text, Instances data) {
Instance instance = new DenseInstance(2);// 创建一个属性数量为2,权重为1,全部值都为缺失的实例
Attribute messageAtt = data.attribute("Message");// 设置文本信息属性的值
instance.setValue(messageAtt, messageAtt.addStringValue(text));
instance.setDataset(data);// 让实例能够访问数据集中的属性信息
return instance;
}
/**
* 主方法
* 可以识别以下参数
* -E
* 文本是否为英文。默认为中文,省略该参数
* -m 文本信息文件
* 指向一个文件,其中包含待分类的文本信息,或用于更新模型的文本信息
* -c 类别标签
* 如果要更新模型,使用本参数输入文本信息的类别标签。省略表示需要对该文本进行分类
* -t 模型文件
* 包含模型的文件。如果不存在则自动创建。
* @param options 命令行选项
*/
public static void main(String[] options) {
try {
String messageName = Utils.getOption('m', options);// 读入文本信息文件,存储为字符串
if (messageName.length() == 0) {
throw new Exception("必须提供文本信息文件的名称!");
}
FileReader m = new FileReader(messageName);
StringBuffer message = new StringBuffer();
int l;
while ((l = m.read()) != -1) {
message.append((char) l);
}
m.close();
boolean isEnglish = Utils.getFlag('E', options);// 检查文本是否为英文
if (!isEnglish) {// 只有汉字需要进行中午分词
Analyzer ikAnalyzer = new IKAnalyzer();
Reader reader = new StringReader(message.toString());
TokenStream stream = (TokenStream) ikAnalyzer.tokenStream("", reader);
CharTermAttribute termAtt = (CharTermAttribute) stream.addAttribute(CharTermAttribute.class);
message = new StringBuffer();
while (stream.incrementToken()) {
message.append(termAtt.toString() + " ");
}
}
String classValue = Utils.getOption('c', options);// 检查是否已给定类别值
String modelName = Utils.getOption('t', options);// 如果模型文件存在,则读入,否则创建新的模型文件
if (modelName.length() == 0) {
throw new Exception("必须提供模型文件的名称!");
}
MessageClassifier messageCl;
try {
ObjectInputStream modelInObjectFile = new ObjectInputStream(new FileInputStream(modelName));
messageCl = (MessageClassifier) modelInObjectFile.readObject();
modelInObjectFile.close();
} catch (FileNotFoundException e) {
messageCl = new MessageClassifier();
}
if (classValue.length() != 0) {
messageCl.updateData(message.toString(), classValue);
} else {
messageCl.classifyMessage(message.toString());
}
ObjectOutputStream modelOutObjectFile = new ObjectOutputStream(new FileOutputStream(modelName));
modelOutObjectFile.writeObject(messageCl);
modelOutObjectFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码编译无错误
接下来就是运行的时候啦~~
在Run Configuration下对Arguement进行设置,填入MessageClassifier -m F:\tc-corpus-answer\answer\C5-Education\C5-Education001.txt -c miss -t messageclassifier.model,手工修改目录对模型进行训练(也可以采用自动化训练,这里为了节省时间只使用了几个文档进行训练),成功后有红色的“更新模型成功!”
完成对模型的训练后,接下来进行测试,在上述位置填入MessageClassifier -m F:\tc-corpus-answer\answer\C5-Education\C5-Education016.txt -t messageclassifier.model,我们可以看到成功分类为miss
程序的效率并不高,仅做练习使用,不建议在大的工程项目当中使用~