简介:这份文档面向数据挖掘与机器学习初学者,系统讲解WEKA这一开放数据挖掘工作平台的操作入门知识,帮助读者快速建立对数据挖掘工具链的基本认知。压缩包内共1个doc文件,约172KB,内容涵盖WEKA的术语体系与数据格式规范,包括实例、属性、关系等核心概念,以及ARFF文件的头信息与数据信息结构、属性声明顺序、numeric与nominal等数据类型说明。文档还介绍了数据预处理、分类、回归、聚类、关联规则与可视化等主要功能,并给出CSV、Excel、Matlab数据转换为ARFF的实用方法,便于读者将自有数据导入WEKA开展实验。目前已有791人学习下载,适合作为数据挖掘课程配套阅读或自学入门参考。
1. weak操作入门:从 Weka 的 ARFF 到 CSV 数据挖掘的第一公里
很多人第一次打开 Weka,看到那个带鸟的界面,以为拖个 CSV 进去就能跑分类,结果点开 Explorer 的 Preprocess 面板,文件选完直接弹一句“not recognized as an ARFF file”。这不是软件坏了,是 Weka 只认自家那套 ARFF 格式,而手头的数据八成是 CSV。weak 操作入门(数据挖掘)这件事,说白了就是先把数据从 CSV 搬进 ARFF,再让 Weka 的算法跑起来,最后看懂它吐出来的结果。它解决的是“数据挖掘工具链第一公里”的问题,适合刚接触数据挖掘、手里有 CSV 但不知道怎么喂给 Weka 的人,也适合想用 Java 代码把 Weka 嵌进自己流程的开发者。这一章先把概念和选型理由讲清楚,后面几章再动手。
2. ARFF 与 CSV 的格式差异:为什么 Weka 不直接吃 CSV
2.1 ARFF 的头部声明到底在声明什么
ARFF 全称 Attribute-Relation File Format,是 Weka 的原生数据格式。它和 CSV 最大的区别在于:CSV 只有数据,ARFF 在数据前面加了一段头部,用来声明关系名、属性名、属性类型。Weka 的算法在训练前需要知道每一列是数值型还是分类型,因为数值型走的是距离计算,分类型走的是取值枚举,两者在内部的处理路径完全不同。CSV 没有这个声明,Weka 只能靠猜,猜错了就会把分类标签当成连续值去回归,结果自然不对。
一个标准的 ARFF 头部长这样:
@relation weather @attribute outlook {sunny, overcast, rainy} @attribute temperature numeric @attribute humidity numeric @attribute windy {TRUE, FALSE} @attribute play {yes, no} @data sunny,85,85,FALSE,no sunny,80,90,TRUE,no overcast,83,86,FALSE,yes@relation后面跟的是数据集名称,随便起但别带空格。@attribute每行声明一个属性,花括号里是分类型属性的所有可能取值,numeric表示数值型。@data之后才是真正的数据行,每行的列顺序必须和属性声明顺序一致。这里有个容易翻车的点:分类型属性的取值如果写成了{sunny,overcast,rainy},数据行里出现Sunny就会报错,Weka 对大小写敏感。
2.2 CSV 转 ARFF 的三种路径与选型
把 CSV 变成 ARFF,常见做法有三条:用 Weka 自带的 ArffViewer 手动另存、用 Python 的arff库脚本转换、用 Java 调 Weka 的CSVLoader类。手动另存适合一次性小数据,超过几千行就卡得难受。Python 脚本适合批量处理,但要注意arff库对中文和特殊字符的处理有时会出问题。Java 调CSVLoader是最稳的,因为 Weka 本身就是 Java 写的,CSVLoader能自动推断属性类型,还能通过setNominalAttributes手动指定哪些列是分类型。
我一般会先看数据量:小于 5000 行且只做一次,直接 ArffViewer 另存;大于 5000 行或者要反复跑,写个 Python 脚本;如果是要集成到 Java 项目里,直接用CSVLoader。下面给一个 Python 转换的代码块,用的是标准库csv加手写 ARFF 头部,不依赖第三方包,避免环境问题。
import csv def csv_to_arff(csv_path, arff_path, relation_name, nominal_cols): """ csv_path: 输入 CSV 路径 arff_path: 输出 ARFF 路径 relation_name: ARFF 关系名 nominal_cols: 字典,键是列索引,值是取值列表,例如 {0: ['sunny','overcast','rainy']} """ with open(csv_path, 'r', encoding='utf-8') as f: reader = csv.reader(f) header = next(reader) # 第一行作为属性名 rows = list(reader) with open(arff_path, 'w', encoding='utf-8') as f: f.write(f"@relation {relation_name}\n\n") for i, col_name in enumerate(header): if i in nominal_cols: values = ','.join(nominal_cols[i]) f.write(f"@attribute {col_name} {{{values}}}\n") else: f.write(f"@attribute {col_name} numeric\n") f.write("\n@data\n") for row in rows: f.write(','.join(row) + '\n') # 调用示例 csv_to_arff( 'iris.csv', 'iris.arff', 'iris', {4: ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']} )这段代码的逻辑很直白:先读 CSV 的头部拿到列名,再逐列判断是否在nominal_cols里,是就写成花括号枚举,不是就写numeric。nominal_cols这个参数是关键,它决定了哪些列被当成分类属性。如果不传,所有列都会变成numeric,分类标签列就会出错。调用示例里{4: [...]}表示第 5 列(索引从 0 开始)是分类标签,取值是三个鸢尾花品种。转换完之后用 Weka 打开iris.arff,Preprocess 面板应该能正常显示所有属性的直方图。
注意:CSV 里的空值在 ARFF 里要写成
?,否则 Weka 会报解析错误。上面的代码没有处理空值,实际用的时候要在写数据行之前把空字符串替换成?。
3. 用 Weka Explorer 跑通第一个分类任务:从加载到评估
3.1 加载 ARFF 并检查属性类型
打开 Weka 3.8 以上的版本,点 Explorer,在 Preprocess 面板点 Open file,选刚才生成的iris.arff。加载成功后左侧 Attributes 列表会显示所有属性名,点每一个属性,右侧会显示它的直方图。这里要重点看分类标签那一列:如果直方图是几个离散的柱子,说明类型正确;如果是一条连续的曲线,说明被当成了数值型,需要回到 ARFF 文件把@attribute那行改成花括号枚举。
检查完属性类型,点一下 Edit 按钮,能看到实际的数据行。如果数据行里出现了?,Weka 会把它当成缺失值处理,大部分算法能容忍缺失值,但缺失比例太高会影响结果。我一般会先看缺失值比例,超过 30% 的列直接考虑删掉。
3.2 选择分类器并设置关键参数
切到 Classify 面板,点 Choose,展开 trees 分组,选J48。J48 是 Weka 里的决策树算法,对应 C4.5,适合入门,因为它输出的树结构可以直接看懂。选完之后在 Test options 里选Cross-validation,Folds 填 10,这是最常用的评估方式。然后点 Start,右侧 Classifier output 会输出一大段结果。
J48 有几个参数值得调:-C是剪枝置信度,默认 0.25,调小会让树更简单,调大容易过拟合;-M是每个叶节点的最小实例数,默认 2,数据量小的时候可以调到 5 以上防止过拟合。这些参数在 Choose 旁边的文本框里直接改,比如-C 0.1 -M 5。
3.3 看懂输出里的关键指标
输出里最该看的是这几行:
Correctly Classified Instances 144 96 % Incorrectly Classified Instances 6 4 % Kappa statistic 0.94 Mean absolute error 0.04 Root mean squared error 0.18Correctly Classified Instances是准确率,但别只看这个。Kappa statistic衡量的是分类器比随机猜测好多少,0.94 说明很好,如果低于 0.4 就要警惕。Mean absolute error和Root mean squared error是误差指标,越小越好。再往下看 Confusion Matrix,能看出哪一类容易被分错。比如 Iris 数据集里,versicolor 和 virginica 经常互相混淆,因为它们的特征重叠度高。
提示:如果准确率很高但 Kappa 很低,说明数据类别不平衡,多数类占了主导,这时候要看每个类别的召回率,不能只看总体准确率。
4. 用 Java 代码调用 Weka:把分类器嵌进自己的流程
4.1 引入 Weka 依赖并读取 ARFF
在 Java 项目里用 Weka,最直接的方式是加 Maven 依赖:
<dependency> <groupId>nz.ac.waikato.cms.weka</groupId> <artifactId>weka-stable</artifactId> <version>3.8.6</version> </dependency>版本号用 3.8.x 系列,稳定且文档全。加完依赖后,读取 ARFF 的代码:
import weka.core.Instances; import weka.core.converters.ConverterUtils.DataSource; public class WekaDemo { public static void main(String[] args) throws Exception { DataSource source = new DataSource("iris.arff"); Instances data = source.getDataSet(); // 设置最后一列为分类标签 if (data.classIndex() == -1) { data.setClassIndex(data.numAttributes() - 1); } System.out.println("实例数: " + data.numInstances()); System.out.println("属性数: " + data.numAttributes()); } }DataSource会自动识别 ARFF 格式,getDataSet()返回Instances对象。setClassIndex这行很关键,它告诉 Weka 哪一列是预测目标。如果不设,后面跑分类器会报“class index is not set”。
4.2 训练 J48 并输出评估结果
接着上面的代码,加训练和评估:
import weka.classifiers.trees.J48; import weka.classifiers.Evaluation; import java.util.Random; // 继续在 main 方法里 J48 tree = new J48(); tree.setOptions(new String[]{"-C", "0.25", "-M", "2"}); tree.buildClassifier(data); Evaluation eval = new Evaluation(data); eval.crossValidateModel(tree, data, 10, new Random(1)); System.out.println(eval.toSummaryString()); System.out.println(eval.toMatrixString());buildClassifier是训练,crossValidateModel做 10 折交叉验证。Random(1)里的种子固定住,保证每次跑结果一致,方便调试。toSummaryString输出准确率、Kappa 等,toMatrixString输出混淆矩阵。这段代码跑出来的结果应该和 Explorer 里手动点出来的差不多,差异来自随机种子和参数微调。
4.3 用训练好的模型预测新数据
训练完的模型可以直接用来预测:
import weka.core.Instance; import weka.core.DenseInstance; // 构造一条新数据,属性顺序和 ARFF 一致 double[] values = new double[data.numAttributes()]; values[0] = data.attribute(0).indexOfValue("sunny"); values[1] = 85; values[2] = 85; values[3] = data.attribute(3).indexOfValue("FALSE"); values[4] = weka.core.Utils.missingValue(); // 标签未知 Instance newInst = new DenseInstance(1.0, values); newInst.setDataset(data); double pred = tree.classifyInstance(newInst); System.out.println("预测类别: " + data.classAttribute().value((int) pred));分类型属性的值要用indexOfValue转成内部索引,不能直接填字符串。标签列填missingValue(),因为预测的时候标签是未知的。classifyInstance返回的是类别索引,用classAttribute().value()转回可读的字符串。
注意:
DenseInstance的构造函数第一个参数是权重,一般填 1.0。如果数据稀疏,用SparseInstance更省内存。
5. 避坑与排查:weak 操作入门最常见的五个翻车点
5.1 现象:Weka 报 “not recognized as an ARFF file”
原因:文件扩展名是.csv或者文件内容头部不是@relation开头。Weka 的 ArffViewer 只认 ARFF 格式,CSV 文件即使改扩展名也没用,因为内容结构不对。
解决:用第 2 章的 Python 脚本转成 ARFF,或者用 Weka 自带的CSVLoader在 Java 代码里加载 CSV 后另存为 ARFF。检查文件第一行是不是@relation,不是就说明格式没转对。
5.2 现象:分类准确率异常高,但实际预测全是同一类
原因:分类标签列被当成了数值型属性,J48 把它当回归树去拟合,输出的“类别”其实是连续值的取整。或者数据类别极度不平衡,多数类占比超过 90%,分类器直接全预测多数类。
解决:检查 ARFF 里标签列的@attribute声明,必须是花括号枚举。如果是数值型,改回枚举。类别不平衡的话,在 Classify 面板的 More options 里选Cost-sensitive evaluation,或者用SpreadSubsample过滤器做欠采样。
5.3 现象:Java 代码里setClassIndex之后仍然报 “class index is not set”
原因:setClassIndex必须在Instances对象上调用,而且索引是从 0 开始的。如果数据有 5 列,标签在最后一列,索引是 4,不是 5。另外,如果先调用了buildClassifier再调setClassIndex,顺序反了也会报错。
解决:在读取数据之后立刻设置setClassIndex(data.numAttributes() - 1),然后再做任何过滤或训练。如果数据经过过滤器处理,过滤后的Instances对象要重新设置一次。
5.4 现象:CSV 里的中文属性名在 ARFF 里变成乱码
原因:CSV 文件的编码是 GBK,Python 脚本用 UTF-8 读取,或者 ARFF 文件写入时用了系统默认编码。Weka 对 UTF-8 支持最好,GBK 容易出问题。
解决:统一用 UTF-8。Python 脚本里open加encoding='utf-8',CSV 文件本身也另存为 UTF-8。如果 CSV 是 GBK 且不能改,用encoding='gbk'读取,写入 ARFF 时用encoding='utf-8'。
5.5 现象:交叉验证结果每次跑都不一样
原因:crossValidateModel的随机种子没有固定,或者用了默认的Random()不带参数。Weka 的交叉验证会随机打乱数据,种子不同,划分不同,结果就有波动。
解决:new Random(1)固定种子,1 可以换成任意整数,只要每次跑用同一个就行。如果数据量很小,波动大是正常的,可以多跑几次取平均,或者改用Percentage split并固定随机种子。
6. 进阶技巧:用 Filter 做属性选择与数据清洗
6.1 用 AttributeSelection 筛掉无用属性
数据挖掘里属性太多会拖慢训练速度,还容易过拟合。Weka 的AttributeSelection面板可以自动选属性。在 Preprocess 面板点 Choose,选weka.filters.supervised.attribute.AttributeSelection,然后点它旁边的文本框配置评估器和搜索方法。常用组合是CfsSubsetEval加BestFirst,前者评估属性子集的预测能力,后者做搜索。配置完点 Apply,属性列表会只剩被选中的列。
Java 代码里对应的写法:
import weka.filters.supervised.attribute.AttributeSelection; import weka.attributeSelection.CfsSubsetEval; import weka.attributeSelection.BestFirst; AttributeSelection filter = new AttributeSelection(); CfsSubsetEval eval = new CfsSubsetEval(); BestFirst search = new BestFirst(); filter.setEvaluator(eval); filter.setSearch(search); filter.setInputFormat(data); Instances filteredData = Filter.useFilter(data, filter);setInputFormat是必须的,它让过滤器知道数据的结构。Filter.useFilter返回过滤后的新数据集,原来的data不变。过滤后的数据要重新setClassIndex,因为列数变了。
6.2 用 Discretize 把连续值转成区间
有些算法(比如 Apriori 关联规则)只接受分类型属性,数值型必须先离散化。weka.filters.unsupervised.attribute.Discretize可以把连续值分成若干个区间。默认分 10 个箱,可以用-B参数改箱数,比如-B 5分成 5 段。
import weka.filters.unsupervised.attribute.Discretize; Discretize disc = new Discretize(); disc.setOptions(new String[]{"-B", "5"}); disc.setInputFormat(data); Instances discretized = Filter.useFilter(data, disc);离散化之后,原本的数值列会变成(inf-10]、(10-20]这样的区间标签。箱数不是越多越好,5 到 10 之间比较常用,具体看数据分布。如果数据本身就有明显的分界点,可以用-R参数指定要离散化的列,不指定的列保持不变。
6.3 一个完整的验证习惯
我自己的习惯是:每次转换完 ARFF,先用 Explorer 的 Preprocess 面板过一遍,看属性类型和直方图;然后跑一次 J48 的 10 折交叉验证,记下准确率和 Kappa;再用 Java 代码跑同样的数据和参数,对比结果是否一致。如果两边差异超过 2%,说明某一步的参数或数据划分不一致,要回去查。这个习惯帮我省了很多“为什么代码跑出来和界面不一样”的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取