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

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

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


目录


一、Smile 简介

Smile(Statistical Machine Intelligence and Learning Engine) 是一个快速、全面的纯 JVM 统计机器学习与数据挖掘库,由 Haifeng Li 主导开发。它覆盖了分类、回归、聚类、关联规则、特征选择、可视化、数学/线性代数等全套能力。

1.1 关键认知

  • 纯 JVM、无 native 也能跑大部分算法 :分类(逻辑回归)、聚类(KMeans)等用纯 Java 实现,无需加载原生库;只有回归/矩阵运算依赖 bytedeco openblas 原生后端(本工程已配好)。
  • API 极其「函数式」 :算法几乎都是静态方法,如 LogisticRegression.fit(x, y)KMeans.lloyd(data, k)RidgeRegression.fit(formula, df)------像调数学函数一样训练模型,代码可读性高。
  • 最适合「读源码学算法」:Smile 的实现非常直白易懂,是理解 LR/KMeans/决策树等算法原理的绝佳教材。
  • 与 Weka(GUI 工作台)、Tribuo(类型安全)并列,是 Java 生态三大经典 ML 库之一。

1.2 资源

  • 官网:https://haifengl.github.io/
  • GitHub:https://github.com/haifengl/smile

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、核心功能概览

功能域 典型算法 核心 API
分类 逻辑回归、SVM、随机森林、决策树 smile.classification.*,如 LogisticRegression.fit
聚类 K-Means、层次聚类、DBSCAN smile.clustering.KMeans.lloyd
回归 岭回归、LASSO、线性回归 smile.regression.RidgeRegression.fit
数学/线性代数 矩阵、向量、特征值 smile.math.matrix.Matrix
可视化 散点/折线/曲面 smile.plot(基于 Swing)+ 任意 JDK 画布

本工程演示分类、聚类、回归、可视化四条最常用主线。


三、核心概念与数据结构

概念 说明
静态 fit Smile 训练入口统一为静态方法 fit(...),返回模型对象(如 LogisticRegressionKMeansLinearModel)。
double[][] 特征矩阵 分类/聚类的数据载体:x[i] 是第 i 个样本的特征向量,y[i] 是标签(分类)或被预测值(回归)。
DataFrame + Formula 回归 API(2.x)走「公式」路线:Formula.lhs("y") 表示以 y 为响应变量,其余列自动作为自变量;DataFrame.of(data, "x1","y") 构造带列名的数据框。
predict 模型推断:model.predict(x) 返回类别/预测值;kmeans.predict(d) 返回所属簇下标。
openblas 后端 回归/矩阵运算依赖 bytedeco 的 openblas 原生库(org.bytedeco:openblas + 平台 classifier)。分类/聚类不需要它。
可视化 Smile 自带 smile.plot 基于 Swing(需显示环境);离线场景可用 JDK BufferedImage 做等效散点图(本工程采用此法,必定可跑)。

四、Java 环境准备

4.1 依赖(Maven)

xml 复制代码
<properties>
    <java.version>17</java.version>
    <smile.version>2.6.0</smile.version>
    <openblas.version>0.3.26-1.5.10</openblas.version>
    <javacpp.platform>windows-x86_64</javacpp.platform>
</properties>

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

    <!-- Smile 核心:分类 / 聚类 / 回归 / 数学 -->
    <dependency>
        <groupId>com.github.haifengl</groupId>
        <artifactId>smile-core</artifactId>
        <version>${smile.version}</version>
    </dependency>

    <!-- 线性代数后端:回归/矩阵需要 openblas 原生库(与 JavaCPP 工程一致) -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>openblas</artifactId>
        <version>${openblas.version}</version>
    </dependency>
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>openblas</artifactId>
        <version>${openblas.version}</version>
        <classifier>${javacpp.platform}</classifier>
    </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>

4.2 端口与应用名

src/main/resources/application.yml

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

分类与聚类不需要 openblas 原生库 即可运行;只有回归(RidgeRegression/LinearModel)在矩阵运算时需要它。若你只跑分类/聚类,可移除 openblas 依赖。


五、核心 API 说明

5.1 分类 LogisticRegression.fit

java 复制代码
double[][] x = ...;   // n×d 特征
int[] y = ...;        // n 标签(0/1)
LogisticRegression model = LogisticRegression.fit(x, y);
int pred = model.predict(x[0]);   // 预测类别

5.2 聚类 KMeans.lloyd

java 复制代码
double[][] data = ...;           // 仅特征,无标签
KMeans kmeans = KMeans.lloyd(data, 3);   // 聚成 3 簇
int[] sizes = new int[3];
for (double[] d : data) sizes[kmeans.predict(d)]++;

5.3 回归 RidgeRegression.fit(DataFrame + Formula)

java 复制代码
double[][] data = ...;                 // 列: x1, y
DataFrame df = DataFrame.of(data, "x1", "y");
Formula formula = Formula.lhs("y");    // 以 y 为响应变量
LinearModel model = RidgeRegression.fit(formula, df);
double pred = model.predict(new double[]{ xi });   // 注意只传自变量列

model.predict(new double[]{xi}) 只传自变量 (不含 y)。Formula.lhs("y")y 作为目标,其余列作特征。

5.4 散点可视化(JDK BufferedImage,离线可跑)

java 复制代码
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(Color.WHITE); g.fillRect(0, 0, w, h);
g.setColor(Color.RED);
g.fillOval(px - 3, py - 3, 6, 6);   // 把数据点映射到像素
g.dispose();
ImageIO.write(img, "PNG", new File(outPath));

六、基础使用示例

6.1 逻辑回归分类(最小可用)

java 复制代码
int n = 200;
double[][] x = new double[n][2];
int[] y = new int[n];
Random rnd = new Random(42);
for (int i = 0; i < n; i++) {
    boolean pos = i < n / 2;
    double cx = pos ? 2.0 : -2.0;
    x[i][0] = cx + rnd.nextGaussian() * 0.5;
    x[i][1] = cx + rnd.nextGaussian() * 0.5;
    y[i] = pos ? 1 : 0;
}
LogisticRegression model = LogisticRegression.fit(x, y);
System.out.println("预测第0个样本: " + model.predict(x[0]));

6.2 KMeans 聚类

java 复制代码
double[][] data = ...;                       // 仅特征
KMeans kmeans = KMeans.lloyd(data, 3);
System.out.println("簇0包含点: " + kmeans.predict(data[0]));

七、完整可运行示例工程

工程目录:

复制代码
smileDemo/
├── pom.xml
├── src/main/java/com/badao/demo/
│   ├── DemoApplication.java
│   ├── config/SmileConfig.java              # 就绪日志(纯JVM无需native)
│   ├── service/SmileService.java           # 分类/聚类/回归/可视化
│   └── controller/SmileController.java
└── src/test/java/com/badao/demo/SmileAllFeatureTest.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 配置(纯 JVM 说明)

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 SmileConfig {
    @PostConstruct
    public void init() {
        log.info("Smile 统计机器学习库已就绪(纯 JVM 实现,无需加载 native 库;代码干净,适合读源码学算法)");
    }
}

7.3 核心 Service(四类能力)

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

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import smile.classification.LogisticRegression;
import smile.clustering.KMeans;
import smile.data.DataFrame;
import smile.data.formula.Formula;
import smile.regression.LinearModel;
import smile.regression.RidgeRegression;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Random;

@Slf4j
@Service
public class SmileService {

    public String info() {
        return "Smile 2.6.0 统计机器学习库(纯 JVM,无需 native);覆盖分类/聚类/回归/可视化";
    }

    public String classificationDemo() {
        int n = 200;
        double[][] x = new double[n][2];
        int[] y = new int[n];
        Random rnd = new Random(42);
        for (int i = 0; i < n; i++) {
            boolean pos = i < n / 2;
            double cx = pos ? 2.0 : -2.0;
            double r = 0.5 + rnd.nextDouble() * 0.5;
            double theta = rnd.nextDouble() * Math.PI * 2;
            x[i][0] = cx + r * Math.cos(theta) + rnd.nextGaussian() * 0.3;
            x[i][1] = cx + r * Math.sin(theta) + rnd.nextGaussian() * 0.3;
            y[i] = pos ? 1 : 0;
        }
        LogisticRegression model = LogisticRegression.fit(x, y);
        int correct = 0;
        for (int i = 0; i < n; i++) if (model.predict(x[i]) == y[i]) correct++;
        return String.format("逻辑回归分类:%d 样本,训练集准确率 %.2f%%", n, correct * 100.0 / n);
    }

    public String clusteringDemo() {
        int n = 300;
        double[][] data = new double[n][2];
        Random rnd = new Random(7);
        for (int i = 0; i < n; i++) {
            int c = i % 3;
            double cx = (c == 0) ? -3 : (c == 1) ? 0 : 3;
            double cy = (c == 0) ? -3 : (c == 1) ? 3 : -3;
            data[i][0] = cx + rnd.nextGaussian() * 0.4;
            data[i][1] = cy + rnd.nextGaussian() * 0.4;
        }
        KMeans kmeans = KMeans.lloyd(data, 3);
        int[] sizes = new int[3];
        for (double[] d : data) sizes[kmeans.predict(d)]++;
        return String.format("KMeans(k=3) 聚类:%d 点 -> 簇大小 [%d, %d, %d]", n, sizes[0], sizes[1], sizes[2]);
    }

    public String regressionDemo() {
        int n = 100;
        double[][] data = new double[n][2];
        Random rnd = new Random(11);
        for (int i = 0; i < n; i++) {
            double xi = rnd.nextDouble() * 10 - 5;
            data[i][0] = xi;
            data[i][1] = 2.5 * xi + 1.0 + rnd.nextGaussian() * 1.5;
        }
        DataFrame df = DataFrame.of(data, "x1", "y");
        Formula formula = Formula.lhs("y");
        LinearModel model = RidgeRegression.fit(formula, df);
        double sse = 0;
        for (int i = 0; i < n; i++) {
            double pred = model.predict(new double[]{data[i][0]});
            sse += (pred - data[i][1]) * (pred - data[i][1]);
        }
        return String.format("岭回归(Ridge)拟合 y=2.5x+1:%d 样本,RMSE=%.3f", n, Math.sqrt(sse / n));
    }

    public String scatterPlot(String outPath) throws Exception {
        int n = 300;
        double[][] data = new double[n][2];
        int[] label = new int[n];
        Random rnd = new Random(7);
        for (int i = 0; i < n; i++) {
            int c = i % 3;
            double cx = (c == 0) ? -3 : (c == 1) ? 0 : 3;
            double cy = (c == 0) ? -3 : (c == 1) ? 3 : -3;
            data[i][0] = cx + rnd.nextGaussian() * 0.4;
            data[i][1] = cy + rnd.nextGaussian() * 0.4;
            label[i] = c;
        }
        int w = 400, h = 400;
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = img.createGraphics();
        g.setColor(Color.WHITE); g.fillRect(0, 0, w, h);
        Color[] colors = {Color.RED, Color.GREEN, Color.BLUE};
        for (int i = 0; i < n; i++) {
            int px = (int) ((data[i][0] + 6) / 12.0 * w);
            int py = (int) ((6 - data[i][1]) / 12.0 * h);
            g.setColor(colors[label[i]]);
            g.fillOval(px - 3, py - 3, 6, 6);
        }
        g.dispose();
        File f = new File(outPath);
        if (f.getParentFile() != null) f.getParentFile().mkdirs();
        ImageIO.write(img, "PNG", f);
        return String.format("散点可视化完成 -> %s", outPath);
    }
}

7.4 HTTP 演示接口

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

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

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

@RestController
@RequestMapping("/smile")
public class SmileController {

    @Autowired
    private SmileService smileService;

    @GetMapping("/info")      public Map<String, Object> info()    { return wrap(smileService::info); }
    @GetMapping("/classify")  public Map<String, Object> classify(){ return wrap(smileService::classificationDemo); }
    @GetMapping("/cluster")   public Map<String, Object> cluster() { return wrap(smileService::clusteringDemo); }
    @GetMapping("/regress")   public Map<String, Object> regress() { return wrap(smileService::regressionDemo); }
    @GetMapping("/scatter")   public Map<String, Object> scatter(@RequestParam String out) {
        return wrap(() -> smileService.scatterPlot(out));
    }

    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 smileDemo
mvn compile
mvn spring-boot:run          # 端口 1000

curl "http://localhost:1000/smile/info"
curl "http://localhost:1000/smile/classify"
curl "http://localhost:1000/smile/cluster"
curl "http://localhost:1000/smile/regress"
curl "http://localhost:1000/smile/scatter?out=./out/scatter.png"

7.6 单元测试

全部用例纯内存、无网络/无原生依赖(回归依赖 openblas,已配好),mvn test 直接 BUILD SUCCESS


八、业务场景实战

8.1 场景一:用户分群(KMeans 客户聚类)

业务:电商按「消费频次 / 客单价」把用户聚成高/中/低价值簇,做差异化运营。

java 复制代码
double[][] userFeat = ...;   // 每行: [消费频次, 客单价]
KMeans km = KMeans.lloyd(userFeat, 3);
// km.predict(userFeat[i]) == 0/1/2 -> 对应 高/中/低 价值群

8.2 场景二:信用评分 / 风控分类(逻辑回归)

业务:银行用逻辑回归对用户「好/坏」二分类,输出违约概率。

java 复制代码
// x: [年龄, 收入, 负债比, 历史逾期次数], y: 0/1
LogisticRegression model = LogisticRegression.fit(x, y);
double prob = model.predict(xNew);   // 概率/类别,用于自动化初审

8.3 场景三:销量预测(岭回归)

业务:零售按历史销量 + 促销力度预测下月销量,做备货。

java 复制代码
double[][] d = ...;   // 列: promo, season, sales
LinearModel m = RidgeRegression.fit(Formula.lhs("sales"), DataFrame.of(d, "promo", "season", "sales"));
double forecast = m.predict(new double[]{ promoNew, seasonNew });

8.4 场景四:异常检测(聚类离群点)

业务:服务器指标聚成正常簇,远离簇中心的点判为异常告警。

java 复制代码
KMeans km = KMeans.lloyd(metrics, 5);
for (double[] point : metrics) {
    int c = km.predict(point);
    double dist = distanceToCentroid(point, km, c);  // 计算到簇中心距离
    if (dist > threshold) alert(point);
}

8.5 场景五:数据探索可视化(散点报告)

业务:数据科学家在生成报告时把高维样本投影到 2D 散点,标色后导出 PNG 给业务方看。

java 复制代码
// 复用 SmileService.scatterPlot(outPath),JDK 原生绘制,无 GUI 依赖
smileService.scatterPlot("report/scatter.png");

九、常见问题与最佳实践

现象 原因 / 解决
回归报 UnsatisfiedLinkError / openblas 找不到 漏了 org.bytedeco:openblas平台 classifier (如 windows-x86_64);或分类/聚类不需要但误删。
RidgeRegression.fit 报列名错误 Formula.lhs("y") 的列名必须与 DataFrame.of(data, "x1","y") 的列名一致。
model.predict 维度报错 回归 predict 只传自变量new double[]{xi}),不要含 y 列。
分类/聚类很慢 数据规模大时可用 KMeans.kmeans(...) 其它初始化,或降采样;Smile 多为单机内存算法。

最佳实践

  1. 区分依赖:分类/聚类纯 Java 即可;回归/矩阵才需要 openblas,按需引入。
  2. DataFrame + Formula 是 2.x 主流 :新代码优先走 DataFrame.of + Formula,可读性好且列名清晰。
  3. 读源码学算法 :Smile 实现直白,遇到不懂的算法直接看 LogisticRegression/KMeans 源码。
  4. 可视化离线化 :生产无显示环境时,用 JDK BufferedImage 而非 smile.plot(Swing)。

十、Smile 与其它 ML 库的关系

复制代码
   Java 三大经典 ML 库
   ┌─────────┬─────────┬─────────┐
   │ Smile   │ Weka    │ Tribuo  │
   │ 函数式  │ GUI工作台│ 类型安全│
   │ 易读源码│ 全流程   │ 泛型约束│
   └─────────┴─────────┴─────────┘
           均被 DJL/SmartJavaAI 调用或并列使用
  • Smile:API 最像「数学函数」,适合学算法、做轻量预测服务。
  • Weka:自带 GUI Explorer,适合探索式建模与教学。
  • Tribuo :Oracle 出品,类型安全(Dataset<Label>),适合工程化强约束场景。
  • 三者都属于「经典 ML」;与「深度学习」的 DJL/DeepLearning4J 互补。

附录:API 速查表

能力 核心 API 关键要点
分类 LogisticRegression.fit(double[][] x, int[] y) 返回 LogisticRegressionpredict(x) 得类别
聚类 KMeans.lloyd(double[][] data, int k) 返回 KMeanspredict(d) 得簇下标
回归 RidgeRegression.fit(Formula, DataFrame) Formula.lhs("y") 指定响应变量
数据框 DataFrame.of(double[][] data, String... cols) 列名用于 Formula 匹配
公式 Formula.lhs("y") y 为目标,其余为自变量
预测 model.predict(double[]) 回归只传自变量列
可视化 new BufferedImage(w,h,TYPE_INT_RGB) + Graphics2D 离线散点方案
线性代数 smile.math.matrix.Matrix 矩阵运算后端为 openblas

通用范式

java 复制代码
// 分类/聚类:特征矩阵 + 静态 fit
LogisticRegression m = LogisticRegression.fit(x, y);
// 回归:DataFrame + Formula 路线
LinearModel m2 = RidgeRegression.fit(Formula.lhs("y"), DataFrame.of(data, "x1", "y"));
相关推荐
wuqingshun3141591 小时前
MyBatis 中#()和$()的区别是什么?
java
小Ti客栈1 小时前
SpringBoot 事件发布机制:原理与应用
java·spring boot·spring
BerrySen1781 小时前
迈向 Next-Gen Java:企业级高并发架构演进与大模型 Agent 落地深度实战
java·开发语言·架构
technology_x2 小时前
2026年财务报表分析软件测评:兼容与安全解析
java·服务器·前端
兰令水2 小时前
hot100【acm版】【2026.7.25/26打卡-java版本】
java·算法·排序算法
Nebula_g2 小时前
Java实现本地Socket通信(三)
java·开发语言·学习·socket·可视化
一水2 小时前
java运行排错,新码旧jar
java·开发语言·jar
wuqingshun3141592 小时前
JAVA中的注解原理是什么?
java
Python+993 小时前
Java 编程语言入门指南
java·开发语言