1. 概述

监督学习是机器学习的一个子集,其核心思想是使用带有标签的数据来训练模型。换句话说,就是通过已有经验数据去“教会”模型如何做出预测。

在本教程中,我们将使用 Kotlin 来实践监督学习。我们会介绍两种算法:一个简单的(线性回归),一个复杂的(人工神经网络)。过程中还会讲解如何准备数据集,因为数据质量对模型性能影响巨大。


2. 算法

监督学习模型基于特定的机器学习算法构建。常见的算法有线性回归、支持向量机、决策树、朴素贝叶斯等。

我们先介绍数据集准备,再讲解两个具体算法。

2.1 数据集准备

一个理想的训练数据集应该是带标签的、特征相关性强、数据清洗完整的。但现实往往不理想,所以数据预处理是关键。

以 Wine Quality 数据集为例,原始数据如下:

Type Acidity Dioxide pH
red 0.27 45 3
white 0.3 14 3.26
0.18 3.22
red 0.26 16 3.17

我们做了以下处理:

✅ 删除缺失“Type”字段的行(该字段重要)
✅ 数值缺失用该列均值填充(如 Dioxide 缺失填 31)
✅ 将“Type”字段转为数值(red=0, white=1)
✅ 对数值型特征进行 Min-Max 归一化(缩放到 [0,1])

处理后数据如下:

Type Acidity Dioxide pH
0 0.75 0.94 0.07
1 1 0 1
1 0 0.52 0.86
0 0.67 0.06 0.68

2.2 模型

模型是将输入数据映射到输出结果的算法。模型训练过程中会调整参数(parameters)以优化输出。此外,还需要在训练前设置超参数(hyperparameters),如神经元数量、激活函数、损失函数等。

例如,人工神经网络模型结构如下图所示:

The Model 2-1

  • 红色数字表示模型参数(训练过程中自动调整)
  • 蓝色数字表示超参数(手动设置)

2.3 线性回归

线性回归是一种基础的监督学习算法,用于预测连续值输出。

常见变体:

  • 简单线性回归:一个输入变量预测一个输出(如:用房屋面积预测房价)
  • 多元线性回归:多个输入变量预测一个输出(如:面积、房间数、位置等预测房价)
  • 多项式回归:输出随输入呈非线性变化(如:房价随面积呈指数增长)
  • 广义线性模型:预测多个输出变量

示意图如下:

Simple Linear Regression Example Baeldung

2.4 人工神经网络(ANN)

人工神经网络是一种更复杂的监督学习算法,适合解决图像识别、语音识别、自然语言处理等任务。

结构包括:

  • 输入层
  • 隐藏层(多个神经元组成)
  • 输出层

神经元之间通过权重连接,训练过程中通过反向传播(backpropagation)调整这些权重以降低损失函数,提高模型精度。

结构示意图如下:

deep neural network


3. 使用 Kotlin 实现线性回归

我们以“工作经验与薪资”为例,实现一个简单的线性回归模型。

3.1 公式

线性回归公式如下:

# 方差
variance = sum((x - meanX)^2)

# 协方差
covariance = sum((x - meanX) * (y - meanY))

# 斜率
slope = covariance / variance

# 截距
yIntercept = meanY - slope * meanX

# 预测公式
dependentVariable = slope * independentVariable + yIntercept

3.2 Kotlin 实现

val xs = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val ys = arrayListOf(25, 35, 49, 60, 75, 90, 115, 130, 150, 200)

val variance = xs.sumByDouble { x -> (x - xs.average()).pow(2) }
val covariance = xs.zip(ys) { x, y -> (x - xs.average()) * (y - ys.average()) }.sum()
val slope = covariance / variance
val yIntercept = ys.average() - slope * xs.average()

val simpleLinearRegression = { x: Int -> slope * x + yIntercept }

3.3 模型评估

我们使用 R² 评估模型效果:

# SST
sst = sum((y - meanY)^2)

# SSR
ssr = sum((y - prediction)^2)

# R²
r² = (sst - ssr) / sst

Kotlin 实现:

val sst = ys.sumByDouble { y -> (y - ys.average()).pow(2) }
val ssr = xs.zip(ys) { x, y -> (y - simpleLinearRegression(x).toDouble()).pow(2) }.sum()
val rsquared = (sst - ssr) / sst

最终 R² = 0.95,说明模型拟合效果非常好。


4. 使用 Deeplearning4j 实现图像分类

我们使用 Zalando MNIST 数据集(服装图片)训练一个卷积神经网络(CNN)进行分类。

4.1 Maven 依赖

<dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>deeplearning4j-core</artifactId>
    <version>1.0.0-beta5</version>
</dependency>
<dependency>
    <groupId>org.nd4j</groupId>
    <artifactId>nd4j-native-platform</artifactId>
    <version>1.0.0-beta5</version>
</dependency>

4.2 加载数据集

private const val OFFSET_SIZE = 4
private const val NUM_ITEMS_OFFSET = 4
private const val ITEMS_SIZE = 4
private const val ROWS = 28
private const val COLUMNS = 28
private const val IMAGE_OFFSET = 16
private const val IMAGE_SIZE = ROWS * COLUMNS

fun getDataSet(): MutableList<List<String>> {
    val labelsFile = File("train-labels-idx1-ubyte")
    val imagesFile = File("train-images-idx3-ubyte")

    val labelBytes = labelsFile.readBytes()
    val imageBytes = imagesFile.readBytes()

    val byteLabelCount = Arrays.copyOfRange(labelBytes, NUM_ITEMS_OFFSET, NUM_ITEMS_OFFSET + ITEMS_SIZE)
    val numberOfLabels = ByteBuffer.wrap(byteLabelCount).int

    val list = mutableListOf<List<String>>()

    for (i in 0 until numberOfLabels) {
        val label = labelBytes[OFFSET_SIZE + ITEMS_SIZE + i]
        val startBoundary = i * IMAGE_SIZE + IMAGE_OFFSET
        val endBoundary = i * IMAGE_SIZE + IMAGE_OFFSET + IMAGE_SIZE
        val imageData = Arrays.copyOfRange(imageBytes, startBoundary, endBoundary)

        val imageDataList = imageData.map { it.toString() }.toMutableList()
        imageDataList.add(label.toString())
        list.add(imageDataList)
    }
    return list
}

4.3 构建 CNN 模型

private fun buildCNN(): MultiLayerNetwork {
    val multiLayerNetwork = MultiLayerNetwork(NeuralNetConfiguration.Builder()
        .seed(123)
        .l2(0.0005)
        .updater(Adam())
        .weightInit(WeightInit.XAVIER)
        .list()
        .layer(0, buildInitialConvolutionLayer())
        .layer(1, buildBatchNormalizationLayer())
        .layer(2, buildPoolingLayer())
        .layer(3, buildConvolutionLayer())
        .layer(4, buildBatchNormalizationLayer())
        .layer(5, buildPoolingLayer())
        .layer(6, buildDenseLayer())
        .layer(7, buildBatchNormalizationLayer())
        .layer(8, buildDenseLayer())
        .layer(9, buildOutputLayer())
        .setInputType(InputType.convolutionalFlat(28, 28, 1))
        .backprop(true)
        .build())
    multiLayerNetwork.init()
    return multiLayerNetwork
}

4.4 训练模型

private fun learning(cnn: MultiLayerNetwork, trainSet: RecordReaderDataSetIterator) {
    for (i in 0 until 10) {
        cnn.fit(trainSet)
    }
}

4.5 测试模型

private fun testing(cnn: MultiLayerNetwork, testSet: RecordReaderDataSetIterator) {
    val evaluation = Evaluation(10)
    while (testSet.hasNext()) {
        val next = testSet.next()
        val output = cnn.output(next.features)
        evaluation.eval(next.labels, output)
    }
    println(evaluation.stats())
    println(evaluation.confusionToString())
}

4.6 运行流程

val dataset = getDataSet()
dataset.shuffle()
val trainDatasetIterator = createDatasetIterator(dataset.subList(0, 50_000))
val testDatasetIterator = createDatasetIterator(dataset.subList(50_000, 60_000))

val cnn = buildCNN()
learning(cnn, trainDatasetIterator)
testing(cnn, testDatasetIterator)

4.7 测试结果

训练后模型表现如下:

==========================Scores========================================
 # of classes:    10
 Accuracy:        0.8609
 Precision:       0.8604
 Recall:          0.8623
 F1 Score:        0.8608
========================================================================
   Predicted:         0      1      2      3      4      5      6      7      8      9
   Actual:
0  0          |     855      3     15     33      7      0     60      0      8      0
1  1          |       3    934      2     32      2      0      5      0      2      0
2  2          |      16      2    805      8     92      1     59      0      7      0
3  3          |      17     19      4    936     38      0     32      0      1      0
4  4          |       5      5     90     35    791      0    109      0      9      0
5  5          |       0      0      0      0      0    971      0     25      0     22
6  6          |     156      8    105     36     83      0    611      0     16      0
7  7          |       0      0      0      0      0     85      0    879      1     23
8  8          |       5      2      1      6      2      5      8      1    889      2
9  9          |       0      0      0      0      0     18      0     60      0    938

准确率 86%,说明模型在图像分类任务中表现良好。


5. 总结

本文介绍了如何在 Kotlin 中使用监督学习算法进行建模:

  • 使用线性回归实现了简单预测模型
  • 使用 Deeplearning4j 构建并训练了 CNN 图像分类器
  • 展示了数据预处理、模型构建、训练和评估的完整流程

完整源码可在 GitHub 获取。


原始标题:Introduction to Supervised Learning in Kotlin