Java中集成Weka 技术教程:从入门到工程实践

Java中集成Weka 技术教程:从入门到工程实践

本文是一篇结构化的 Weka 机器学习库技术教程,涵盖简介、核心功能、API 说明、基础使用示例、完整可运行示例工程、业务场景实战、常见问题与最佳实践,以及 Weka 与其它 ML 库的关系定位。


目录


一、Weka 简介

Weka(Waikato Environment for Knowledge Analysis) 是新西兰怀卡托大学开发、现属经典机器学习工作台的 Java 库,自带图形界面 Explorer,覆盖数据预处理、分类、回归、聚类、关联规则、可视化全流程。

1.1 关键认知

  • 灵魂是 Instances :一个「关系型」数据容器(列 = Attribute,行 = Instance),几乎所有 Weka 算法都以 Instances 为输入。
  • 算法体系完备:J48(C4.5 决策树)、RandomForest、SMO(SVM)、IBk(KNN)、多层感知机等一应俱全。
  • 自带 GUI Explorerjava -cp weka.jar weka.gui.Main 可可视化探索数据、训练模型、看结果------教学与快速实验神器。
  • 成熟稳定:3.8.x 是长期支持(LTS)分支,生态资料丰富。

1.2 资源

  • 官网:https://www.cs.waikato.ac.nz/ml/weka/
  • 文档:https://weka.sourceforge.io/doc.stable/
  • GitHub:https://github.com/Waikato/weka-3.8

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、核心功能概览

功能域 典型算法 核心类
分类 J48 决策树、RandomForest、SMO、IBk weka.classifiers.trees.J48...bayes.NaiveBayes
回归 线性回归、M5P weka.classifiers.functions.LinearRegression
聚类 EM、SimpleKMeans weka.clusterers.SimpleKMeans
评估 交叉验证、准确率、混淆矩阵 weka.classifiers.Evaluation
数据 ARFF/CSV 读写、属性定义 weka.core.InstancesAttributeDenseInstance
可视化 Explorer 图形界面 weka.gui.Main

本工程演示J48 决策树分类 + 10 折交叉验证主线。


三、核心概念:Instances 关系型数据容器

概念 说明
Attribute 一列(属性)。数值型 new Attribute("x1");名义型 new Attribute("class", List<String>)(类别值列表)。
Instances 数据集:由 ArrayList<Attribute> 定义列结构,再 add(Instance) 填充行。相当于一张关系表。
Instance / DenseInstance 一行:new DenseInstance(weight, double[] vals),数组下标对应 Attributes 顺序。
setClassIndex(idx) 标记哪一列是「类别列」------监督学习必须设置(通常最后一列)。
Classifier 算法基类:J48 等继承它,统一 buildClassifier(Instances) / classifyInstance(Instance)
Evaluation 评估器:eval.crossValidateModel(cls, data, folds, Random) 做交叉验证;eval.pctCorrect() 得正确率。
setOptions Weka 算法普遍支持 setOptions(String[]),等价于 GUI 里勾选的参数。

四、Java 环境准备

4.1 依赖(Maven)

xml 复制代码
<properties>
    <java.version>17</java.version>
    <weka.version>3.8.6</weka.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Weka 核心(含全部经典算法 + GUI) -->
    <dependency>
        <groupId>nz.ac.waikato.cms.weka</groupId>
        <artifactId>weka-stable</artifactId>
        <version>${weka.version}</version>
        <exclusions>
            <!-- flatlaf 只是 GUI 外观,核心算法不依赖;排除后离线构建更干净 -->
            <exclusion>
                <groupId>com.formdev</groupId>
                <artifactId>flatlaf</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

本工程排除了 flatlaf(GUI 外观),核心算法(J48/Instances)不受影响;如需启动 GUI Explorer 可自行加回。

4.2 端口与应用名

src/main/resources/application.yml

yaml 复制代码
server:
  port: 1001
spring:
  application:
    name: weka-demo
logging:
  level:
    com.badao: info

4.3 启动 GUI(可选,需桌面环境)

bash 复制代码
java -cp weka-stable-3.8.6.jar weka.gui.Main

五、核心 API 说明

5.1 定义属性与数据集

java 复制代码
ArrayList<Attribute> atts = new ArrayList<>();
atts.add(new Attribute("x1"));
atts.add(new Attribute("x2"));
ArrayList<String> classVals = new ArrayList<>();
classVals.add("A");
classVals.add("B");
atts.add(new Attribute("class", classVals));   // 名义型类别列

Instances data = new Instances("weka-demo", atts, 0);

5.2 填充样本(DenseInstance)

java 复制代码
Random rnd = new Random(42);
for (int i = 0; i < 200; i++) {
    boolean pos = i < 100;
    double cx = pos ? 2.0 : -2.0;
    double[] vals = new double[data.numAttributes()];
    vals[0] = cx + rnd.nextGaussian() * 0.6;
    vals[1] = cx + rnd.nextGaussian() * 0.6;
    vals[2] = pos ? 0.0 : 1.0;          // 名义类别取「索引值」而非字符串
    data.add(new DenseInstance(1.0, vals));
}
data.setClassIndex(data.numAttributes() - 1);   // 最后一列是类别

5.3 训练 J48 + 交叉验证

java 复制代码
J48 cls = new J48();
cls.setOptions(new String[]{"-C", "0.25", "-M", "2"});   // 置信因子 / 最小样本数
cls.buildClassifier(data);

Evaluation eval = new Evaluation(data);
eval.crossValidateModel(cls, data, 10, new Random(1));     // 10 折
System.out.println("正确率=" + eval.pctCorrect());

5.4 读取 ARFF 文件(常见用法)

java 复制代码
Instances data = new Instances(new BufferedReader(new FileReader("data.arff")));
data.setClassIndex(data.numAttributes() - 1);

六、基础使用示例

6.1 J48 决策树分类(最小可用)

java 复制代码
ArrayList<Attribute> atts = new ArrayList<>();
atts.add(new Attribute("x1"));
atts.add(new Attribute("x2"));
ArrayList<String> cv = new ArrayList<>(); cv.add("A"); cv.add("B");
atts.add(new Attribute("class", cv));
Instances data = new Instances("demo", atts, 0);
Random rnd = new Random(42);
for (int i = 0; i < 200; i++) {
    boolean pos = i < 100;
    double cx = pos ? 2.0 : -2.0;
    double[] v = new double[3];
    v[0] = cx + rnd.nextGaussian() * 0.6;
    v[1] = cx + rnd.nextGaussian() * 0.6;
    v[2] = pos ? 0.0 : 1.0;
    data.add(new DenseInstance(1.0, v));
}
data.setClassIndex(2);

J48 j48 = new J48();
j48.buildClassifier(data);
Evaluation eval = new Evaluation(data);
eval.crossValidateModel(j48, data, 10, new Random(1));
System.out.println("正确率=" + eval.pctCorrect());

七、完整可运行示例工程

工程目录:

复制代码
wekaDemo/
├── pom.xml
├── src/main/java/com/badao/demo/
│   ├── DemoApplication.java
│   ├── config/WekaConfig.java               # 就绪日志 + GUI 启动提示
│   ├── service/WekaService.java            # J48 分类 + 10折交叉验证
│   └── controller/WekaController.java
└── src/test/java/com/badao/demo/WekaAllFeatureTest.java

7.1 启动类

java 复制代码
package com.badao.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

7.2 配置(就绪 + GUI 提示)

java 复制代码
package com.badao.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;

@Slf4j
@Configuration
public class WekaConfig {
    @PostConstruct
    public void init() {
        log.info("Weka 经典 ML 工作台已就绪(纯 JVM;启动 GUI: java -cp weka.jar weka.gui.Main)");
    }
}

7.3 核心 Service(J48 + 交叉验证)

java 复制代码
package com.badao.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import weka.classifiers.Evaluation;
import weka.classifiers.trees.J48;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instances;

import java.util.ArrayList;
import java.util.Random;

@Slf4j
@Service
public class WekaService {

    public String info() {
        return "Weka 3.8.6 经典机器学习工作台(纯 JVM,自带 GUI Explorer;适合 ML 全流程入门)";
    }

    public String classifyJ48() throws Exception {
        // 1) 定义属性:两个数值特征 + 一个名义类别(A/B)
        ArrayList<Attribute> atts = new ArrayList<>();
        atts.add(new Attribute("x1"));
        atts.add(new Attribute("x2"));
        ArrayList<String> classVals = new ArrayList<>();
        classVals.add("A");
        classVals.add("B");
        atts.add(new Attribute("class", classVals));

        // 2) 构造数据集并填充样本(两个线性可分的高斯团)
        Instances data = new Instances("weka-demo", atts, 0);
        Random rnd = new Random(42);
        for (int i = 0; i < 200; i++) {
            boolean pos = i < 100;
            double cx = pos ? 2.0 : -2.0;
            double[] vals = new double[data.numAttributes()];
            vals[0] = cx + rnd.nextGaussian() * 0.6;
            vals[1] = cx + rnd.nextGaussian() * 0.6;
            vals[2] = pos ? 0.0 : 1.0;           // 类别列取名义值索引
            data.add(new DenseInstance(1.0, vals));
        }
        data.setClassIndex(data.numAttributes() - 1);

        // 3) 训练 J48(C4.5)
        J48 cls = new J48();
        cls.setOptions(new String[]{"-C", "0.25", "-M", "2"});
        cls.buildClassifier(data);

        // 4) 10 折交叉验证评估
        Evaluation eval = new Evaluation(data);
        eval.crossValidateModel(cls, data, 10, new Random(1));

        return String.format("J48 决策树(10 折交叉验证):正确率 %.2f%%\n摘要:\n%s",
                eval.pctCorrect(), eval.toSummaryString());
    }
}

7.4 HTTP 演示接口

java 复制代码
package com.badao.demo.controller;

import com.badao.demo.service.WekaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/weka")
public class WekaController {

    @Autowired
    private WekaService wekaService;

    @GetMapping("/info")      public Map<String, Object> info()    { return wrap(wekaService::info); }
    @GetMapping("/classify")  public Map<String, Object> classify(){ return wrap(wekaService::classifyJ48); }

    private Map<String, Object> wrap(Op op) {
        Map<String, Object> resp = new HashMap<>();
        try {
            resp.put("success", true);
            resp.put("message", op.run());
        } catch (Exception e) {
            resp.put("success", false);
            resp.put("message", e.getMessage());
        }
        return resp;
    }

    @FunctionalInterface
    private interface Op { String run() throws Exception; }
}

7.5 运行与调用

bash 复制代码
cd wekaDemo
mvn compile
mvn spring-boot:run          # 端口 1001

curl "http://localhost:1001/weka/info"
curl "http://localhost:1001/weka/classify"

7.6 单元测试

全部用例纯内存、无网络/无原生依赖,mvn test 直接 BUILD SUCCESS(输出打印正确率与摘要)。


八、业务场景实战

8.1 场景一:客户流失预测(决策树可解释分类)

业务 :电信运营商用 J48 决策树预测客户是否流失,并看树结构解释原因(年龄/套餐/投诉次数)。

java 复制代码
// 数值特征 + 名义类别("churn"/"stay") -> Instances
J48 tree = new J48();
tree.setOptions(new String[]{"-C", "0.25", "-M", "2"});
tree.buildClassifier(data);
System.out.println(tree);          // 直接打印决策树规则,可解释性强
// 业务方可读:if 投诉次数>3 and 套餐=低端 -> 流失概率高

8.2 场景二:医疗/风控可解释建模

业务:对「可解释性」要求高的行业,用 J48 而非黑盒模型,输出可读规则供人工复核。

java 复制代码
Evaluation eval = new Evaluation(data);
eval.crossValidateModel(tree, data, 10, new Random(1));
System.out.println(eval.toSummaryString());
System.out.println(eval.toMatrixString());   // 混淆矩阵

8.3 场景三:ARFF 数据集快速实验(Explorer GUI)

业务:数据科学家把 CSV 转 ARFF,丢进 Explorer 拖选算法、看结果,不必写代码。

bash 复制代码
# CSV -> ARFF 可用 Weka 的 converters,或代码:
Instances data = new Instances(new BufferedReader(new FileReader("data.arff")));
data.setClassIndex(data.numAttributes() - 1);
# 然后启动 GUI: java -cp weka-stable-3.8.6.jar weka.gui.Main

8.4 场景四:模型参数网格搜索(setOptions)

业务 :调 J48 的置信因子 -C 与最小样本数 -M,找最优泛化配置。

java 复制代码
for (double c = 0.1; c <= 0.5; c += 0.1) {
    J48 j = new J48();
    j.setOptions(new String[]{"-C", String.valueOf(c), "-M", "2"});
    j.buildClassifier(data);
    Evaluation e = new Evaluation(data);
    e.crossValidateModel(j, data, 10, new Random(1));
    System.out.println("C=" + c + " acc=" + e.pctCorrect());
}

8.5 场景五:批量预测新数据

java 复制代码
// 训练后对新样本 classifyInstance
for (Instance inst : newData) {
    double predIdx = tree.classifyInstance(inst);     // 返回类别索引
    String label = newData.classAttribute().value((int) predIdx);
}

九、常见问题与最佳实践

现象 原因 / 解决
Exception: class index is negative 忘记 data.setClassIndex(...);监督学习必须标记类别列。
名义类别填充报错 DenseInstance 的类别值填索引 (0/1),不是字符串;字符串用 Instance.setValue(attr, "A")
GUI 启动报 flatlaf 缺失 本工程排除了 flatlaf;加回依赖即可 java -cp weka.jar weka.gui.Main
中文属性名乱码 ARFF 文件用 UTF-8 且首行声明 @relation;建议属性名用英文。
交叉验证结果抖动 固定 Random(seed),保证可复现。

最佳实践

  1. 务必 setClassIndex:这是 Weka 最常见的坑。
  2. 名义值用索引DenseInstance(double[]) 里名义列填索引;更安全的写法是 inst.setValue(attr, "A")
  3. 优先 GUI 探索:建模前期用 Explorer 试算法、看指标,比直接写代码快。
  4. 可解释优先选 J48:对审计/业务解释要求高的场景,决策树优于黑盒。

十、Weka 与其它 ML 库的关系

复制代码
   Java 三大经典 ML 库
   ┌─────────┬─────────┬─────────┐
   │ Smile   │ Weka    │ Tribuo  │
   │ 函数式  │ GUI工作台│ 类型安全│
   │ 易读源码│ 全流程   │ 泛型约束│
   └─────────┴─────────┴─────────┘
  • Weka:GUI 探索 + 全流程算法,教学与快速实验首选,决策树可解释性强。
  • Smile:API 简洁,适合嵌入服务、学算法。
  • Tribuo:类型安全,适合工程化强约束生产。
  • 三者均属「经典 ML」,与「深度学习」DJL/DeepLearning4J 互补。

附录:API 速查表

能力 核心 API 关键要点
数值属性 new Attribute("x1") 单列数值
名义属性 new Attribute("class", List<String>) 类别值列表
数据集 new Instances(name, atts, capacity) 关系型容器
样本行 new DenseInstance(weight, double[]) 下标对齐 Attributes
设类别列 data.setClassIndex(idx) 监督学习必设
决策树 new J48().buildClassifier(data) C4.5 实现
设参数 cls.setOptions(new String[]{...}) 等价 GUI 勾选
评估 new Evaluation(data).crossValidateModel(cls, data, folds, rnd) 交叉验证
正确率 eval.pctCorrect() 百分比
读 ARFF new Instances(new BufferedReader(new FileReader("x.arff"))) 常见数据入口

通用范式

java 复制代码
// 1) 定义 Attributes -> 2) 构造 Instances -> 3) add DenseInstance -> 4) setClassIndex
// 5) buildClassifier -> 6) Evaluation.crossValidateModel
Instances data = new Instances("demo", atts, 0);
// ... add rows ...
data.setClassIndex(data.numAttributes() - 1);
J48 cls = new J48();
cls.buildClassifier(data);
Evaluation eval = new Evaluation(data);
eval.crossValidateModel(cls, data, 10, new Random(1));
相关推荐
Full Stack Developme1 小时前
Tomcat 如何处理HTTP请求
java·http·tomcat
爱敲键盘的猴子1 小时前
Java并发编程 -- synchronized
java·java并发编程
snow@li2 小时前
Spring 项目 Java 访问修饰符 + 非访问修饰符全景梳理详解
java·后端·spring
大模型念念2 小时前
Spring 框架入门:从零开始构建你的第一个应用
java·后端·spring
莫名的好感°2 小时前
数据分析求职全流程:不同阶段该选什么辅导服务
数据挖掘·数据分析
雨落在了我的手上2 小时前
Java数据结构(九):栈和队列
java·开发语言·数据结构
j7~2 小时前
【C语言】《C语言自定义类型(结构体+联合体+枚举类型)》--详解
c语言·开发语言·深度学习·结构体·位段·联合体·枚举类型
Escalating_xu2 小时前
C++11 彻底吃透 emplace_back 与 push_back(最全闭环:左右值|深浅拷贝|移动构造|万能引用|完美转发)
开发语言·c++
春生野草2 小时前
个人笔记--大顶堆和基数排序
java·数据结构·算法