Weka数据挖掘入门:CSV转ARFF与分类实战

发布时间:2026/10/3 19:03:31
Weka数据挖掘入门:CSV转ARFF与分类实战 简介这份文档面向数据挖掘与机器学习初学者系统讲解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 不直接吃 CSV2.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,yesrelation后面跟的是数据集名称随便起但别带空格。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, encodingutf-8) as f: reader csv.reader(f) header next(reader) # 第一行作为属性名 rows list(reader) with open(arff_path, w, encodingutf-8) as f: f.write(frelation {relation_name}\n\n) for i, col_name in enumerate(header): if i in nominal_cols: values ,.join(nominal_cols[i]) f.write(fattribute {col_name} {{{values}}}\n) else: f.write(fattribute {col_name} numeric\n) f.write(\ndata\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.arffPreprocess 面板应该能正常显示所有属性的直方图。注意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-validationFolds 填 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 groupIdnz.ac.waikato.cms.weka/groupId artifactIdweka-stable/artifactId version3.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 文件的编码是 GBKPython 脚本用 UTF-8 读取或者 ARFF 文件写入时用了系统默认编码。Weka 对 UTF-8 支持最好GBK 容易出问题。解决统一用 UTF-8。Python 脚本里open加encodingutf-8CSV 文件本身也另存为 UTF-8。如果 CSV 是 GBK 且不能改用encodinggbk读取写入 ARFF 时用encodingutf-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%说明某一步的参数或数据划分不一致要回去查。这个习惯帮我省了很多“为什么代码跑出来和界面不一样”的后悔药。希望帮到你。本文还有配套的精品资源点击获取