JavaRush /Java 博客 /Random-ZH /Java 开发人员的机器学习,第 2 部分

Java 开发人员的机器学习,第 2 部分

已在 Random-ZH 群组中发布
Java 开发人员的机器学习,第 1 部分
Java 开发人员的机器学习,第 2 部分 - 1

目标函数估计

让我们回想一下,目标函数,也称为预测函数,是准备或训练过程的结果。从数学上讲,挑战是找到一个以变量作为输入х并返回预测值的函数у
Java 开发人员的机器学习,第 2 部分 - 2
在机器学习中,成本函数(J(θ))用于计算给定目标函数的误差值或“成本”。
Java 开发人员的机器学习,第 2 - 3 部分
成本函数显示模型与训练数据的拟合程度。为了确定上面所示的目标函数的成本,有必要计算每个示例 house 的平方误差(i)。误差是示例中房屋的计算值у与实际值之间的差距。 yi
面向 Java 开发人员的机器学习,第 2 - 4 部分
例如,面积为1330 的房屋的实际价格 = 6,500,000 €。训练后的目标函数预测的房价之间的差异为€7,032,478:差异(或误差)为€532,478。您还可以在上图中看到这种差异。每个价格区域训练对的差异(或误差)显示为垂直红色虚线。计算出经过训练的目标函数的成本后,您需要对示例中每个房屋的平方误差求和并计算主值。价格值越小(J(θ)),我们的目标函数的预测就越准确。清单3显示了成本函数的简单 Java 实现,它将目标函数、训练数据列表以及与其关联的标签作为输入。预测值将在循环中计算,误差将通过减去实际价格值(从标签中获取)来计算。随后,将误差的平方相加并计算误差值。成本将作为类型值返回double

清单-3

public static double cost(Function<ltDouble[], Double> targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels) {
 int m = dataset.size();
 double sumSquaredErrors = 0;

 // рассчет квадрата ошибки («разницы») для каждого тренировочного примера и //добавление его к сумме
 for (int i = 0; i < m; i++) {
 // получаем вектор признаков из текущего примера
 Double[] featureVector = dataset.get(i);
 // предсказываем meaning и вычисляем ошибку базируясь на реальном
 //значении (метка)
 double predicted = targetFunction.apply(featureVector);
 double label = labels.get(i);
 double gap = predicted - label;
 sumSquaredErrors += Math.pow(gap, 2);
 }

 // Вычисляем и возращаем meaning ошибки (чем меньше тем лучше)
 return (1.0 / (2 * m)) * sumSquaredErrors;
}
有兴趣阅读有关 Java 的内容吗?加入Java 开发者小组!

学习目标函数

虽然成本函数有助于评估目标函数和 theta 参数的质量,但您仍然需要找到最合适的 theta 参数。您可以为此使用梯度下降算法。

梯度下降

梯度下降最小化成本函数。这意味着它用于(J(θ))根据训练数据查找具有最小成本的 theta 参数。以下是计算新的、更合适的 theta 值的简化算法:
Java 开发人员的机器学习,第 2 - 5 部分
因此,theta 向量的参数将随着算法的每次迭代而改进。学习系数α指定每次迭代的计算次数。可以一直进行这些计算,直到找到“好的”theta 值。例如,下面的线性回归函数具有三个 theta 参数:
面向 Java 开发人员的机器学习,第 2 - 6 部分
在每次迭代中,将为每个 theta 参数计算一个新值:、和。每次迭代之后,可以使用新的 theta 向量0 , θ 1 , θ 2 }创建一个新的、更合适的实现。清单-4显示了梯度衰减算法的 Java 代码。回归函数的 Theta 将使用训练数据、标记数据、学习率进行训练。结果将是使用 theta 参数改进的目标函数。该方法将被一次又一次调用,传递新的目标函数和先前计算中的新 theta 参数。这些调用将重复,直到配置的目标函数达到最小稳定状态: θ0θ1θ2LinearRegressionFunction(α)train()

清单 4

public static LinearRegressionFunction train(LinearRegressionFunction targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels,
 double alpha) {
 int m = dataset.size();
 double[] thetaVector = targetFunction.getThetas();
 double[] newThetaVector = new double[thetaVector.length];

 // вычисление нового значения тета для каждого element тета массива
 for (int j = 0; j < thetaVector.length; j++) {
 // сумируем разницу ошибки * признак
 double sumErrors = 0;
 for (int i = 0; i < m; i++) {
 Double[] featureVector = dataset.get(i);
 double error = targetFunction.apply(featureVector) - labels.get(i);
 sumErrors += error * featureVector[j];
 }

 //вычисляем новые значения тета
 double gradient = (1.0 / m) * sumErrors;
 newThetaVector[j] = thetaVector[j] - alpha * gradient;
 }

 return new LinearRegressionFunction(newThetaVector);
}
为了确保成本不断降低,您可以J(θ)在每个训练步骤之后运行成本函数。每次迭代之后,成本应该会降低。如果这种情况没有发生,则意味着学习系数的值太大,算法根本就错过了最小值。在这种情况下,梯度衰减算法会失败。下图显示了使用新计算的 theta 参数(从起始 theta 向量 开始)的目标函数{1.0, 1.0}。左列显示了 50 次迭代后的预测函数图;200 次重复后的中间列;以及 1000 次重复后的右列。从这些我们可以看到,每次迭代后价格都会下降,并且新的目标函数拟合得越来越好。重复 500-600 次后,theta 参数不再发生显着变化,价格达到稳定的平台。此后,通过这种方式就无法提高目标函数的精度。
面向 Java 开发人员的机器学习,第 2 - 7 部分
在这种情况下,即使经过 500-600 次迭代后成本不再显着下降,目标函数仍然不是最优的。这表明存在差异。在机器学习中,术语“不一致”用于表示学习算法没有找到数据中的潜在趋势。根据现实生活经验,较大房产的每平方米价格可能会下降。由此我们可以得出结论,用于目标函数学习过程的模型与数据拟合得不够好。这种差异通常是由于模型过于简化造成的。这发生在我们的例子中,目标函数太简单了,它使用单个参数进行分析 - 房子的面积。但这些信息不足以准确预测房屋的价格。

添加功能并缩放它们

如果您发现您的目标函数与您要解决的问题不对应,则需要进行调整。纠正不一致的常见方法是向特征向量添加附加特征。在房屋价格的示例中,您可以添加房间数量或房屋年龄等特征。也就是说,{size}您可以使用具有多个值的向量,而不是使用具有一个特征值的向量来描述一栋房子,例如,{size, number-of-rooms, age}. 在某些情况下,可用训练数据中的特征数量不够。那么值得尝试使用使用现有多项式特征计算的多项式特征。例如,您有机会扩展用于确定房屋价格的目标函数,使其包含平方米 (x2) 的计算特征:
面向 Java 开发人员的机器学习,第 2 - 8 部分
使用多个特征需要特征缩放,特征缩放用于标准化不同特征的范围。因此, size 2属性的取值范围明显大于size属性的取值范围。如果没有特征缩放,大小2将不适当地影响成本函数。size 2属性引入的误差将明显大于size属性引入的误差。下面给出一个简单的特征缩放算法:
面向 Java 开发人员的机器学习,第 2 - 9 部分
FeaturesScaling该算法在下面示例代码的 类中实现。本课程FeaturesScaling介绍了一种用于创建根据训练数据调整的缩放函数的商业方法。在内部,训练数据实例用于计算平均值、最小值和最大值。生成的函数采用特征向量并生成具有缩放特征的新特征向量。特征缩放对于学习过程和预测过程都是必要的,如下所示:
// создание массива данных
List<ltDouble[]> dataset = new ArrayList<>();
dataset.add(new Double[] { 1.0, 90.0, 8100.0 }); // feature vector of house#1
dataset.add(new Double[] { 1.0, 101.0, 10201.0 }); // feature vector of house#2
dataset.add(new Double[] { 1.0, 103.0, 10609.0 }); // ...
//...

// создание меток
List<ltDouble> labels = new ArrayList<>();
labels.add(249.0); // price label of house#1
labels.add(338.0); // price label of house#2
labels.add(304.0); // ...
//...

// создание расширенного списка признаков
Function<ltDouble[], Double[]> scalingFunc = FeaturesScaling.createFunction(dataset);
List<ltDouble[]> scaledDataset = dataset.stream().map(scalingFunc).collect(Collectors.toList());

// создаем функцию которая инициализирует теты и осуществляет обучение //используя коэффициент обучения 0.1

LinearRegressionFunction targetFunction = new LinearRegressionFunction(new double[] { 1.0, 1.0, 1.0 });
for (int i = 0; i < 10000; i++) {
 targetFunction = Learner.train(targetFunction, scaledDataset, labels, 0.1);
}

// делаем предсказание стоимости дома с площадью 600 m2
Double[] scaledFeatureVector = scalingFunc.apply(new Double[] { 1.0, 600.0, 360000.0 });
double predictedPrice = targetFunction.apply(scaledFeatureVector);
随着越来越多的特征被添加,目标函数的拟合度也会增加,但要小心。如果你走得太远并添加太多特征,你最终可能会学习到一个过度拟合的目标函数。

过度匹配和交叉验证

当目标函数或模型与训练数据拟合得太好以至于捕获了训练数据中的噪声或随机变化时,就会发生过度拟合。下面最右图显示了过度拟合的示例:
Java 开发人员的机器学习,第 2 部分 - 10
然而,过拟合模型在训练数据上表现良好,但在真实的未知数据上表现不佳。有多种方法可以避免过度拟合。
  • 使用更大的数据集进行训练。
  • 使用较少的功能,如上图所示。
  • 使用考虑正则化的改进机器学习算法。
如果预测算法过度拟合训练数据,则有必要消除不利于其准确性的特征。困难在于找到比其他特征对预测准确性有更显着影响的特征。如图所示,可以使用图表直观地确定过度拟合。这对于具有 2 或 3 个坐标的图形效果很好,如果您使用 2 个以上的特征,则绘制和评估图形会变得困难。在交叉验证中,您可以在训练过程完成后使用算法未知的数据重新测试训练后的模型。可用的标记数据应分为 3 组:
  • 训练数据;
  • 验证数据;
  • 测试数据。
在这种情况下,表征房屋特征的标记记录的 60% 应在目标算法的训练变体过程中使用。训练过程结束后,应使用剩余数据的一半(之前未使用过的)来验证训练后的目标算法在未知数据上是否表现良好。通常,选择使用性能优于其他算法的算法。剩余的数据用于计算最终选定模型的误差值。还有其他交叉验证技术,例如k-fold。不过,我不会在本文中描述它们。

机器学习工具和Weka框架

大多数框架和库都提供了大量的机器学习算法。此外,它们还提供了一个方便的高级接口来训练、测试和处理数据模型。Weka 是最流行的 JVM 框架之一。Weka 是一个实用的 Java 库,包含用于验证模型的图形测试。下面的示例使用 Weka 库创建包含特征和标签的训练数据集。方法setClassIndex()- 用于标记。在 Weka 中,标签被定义为一个类:
// определяем атрибуты для признаков и меток
ArrayList<ltAttribute> attributes = new ArrayList<>();
Attribute sizeAttribute = new Attribute("sizeFeature");
attributes.add(sizeAttribute);
Attribute squaredSizeAttribute = new Attribute("squaredSizeFeature");
attributes.add(squaredSizeAttribute);
Attribute priceAttribute = new Attribute("priceLabel");
attributes.add(priceAttribute);


// создаем и заполняем список признаков 5000 примеров
Instances trainingDataset = new Instances("trainData", attributes, 5000);
trainingDataset.setClassIndex(trainingSet.numAttributes() - 1);
Instance instance = new DenseInstance(3);

instance.setValue(sizeAttribute, 90.0);
instance.setValue(squaredSizeAttribute, Math.pow(90.0, 2));
instance.setValue(priceAttribute, 249.0);
trainingDataset.add(instance);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 101.0);
...
数据集和样本对象可以保存并从文件加载。Weka 使用ARFF(属性关系文件格式),Weka 图形基准支持该格式。该数据集用于训练 Weka 中称为分类器的目标函数。首先,您必须定义目标函数。下面的代码LinearRegression将创建分类器的实例。该分类器将使用 进行训练buildClassifier()。该方法buildClassifier()根据训练数据选择 theta 参数,以搜索最佳目标模型。使用Weka,您不必担心设置学习率或迭代次数。Weka 还独立执行特征缩放。
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
一旦完成这些设置,目标函数就可以用来预测房屋的价格,如下所示:
Instances unlabeledInstances = new Instances("predictionset", attributes, 1);
unlabeledInstances.setClassIndex(trainingSet.numAttributes() - 1);
Instance unlabeled = new DenseInstance(3);
unlabeled.setValue(sizeAttribute, 1330.0);
unlabeled.setValue(squaredSizeAttribute, Math.pow(1330.0, 2));
unlabeledInstances.add(unlabeled);

double prediction = targetFunction.classifyInstance(unlabeledInstances.get(0));
Weka 提供了一个类Evaluation来测试经过训练的分类器或模型。在下面的代码中,使用选定的验证数据数组来避免错误结果。测量结果(错误成本)将显示在控制台上。通常,评估结果用于比较使用不同机器学习算法或其变体训练的模型:
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
上面的示例使用线性回归,它根据输入值预测数值,例如房屋价格。线性回归支持连续数值的预测。要预测二进制值(“是”和“否”),您需要使用其他机器学习算法。例如,决策树、神经网络或逻辑回归。
// использование логистической регрессии
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);
例如,您可以使用其中一种算法来预测电子邮件是否为垃圾邮件,或预测天气,或预测房屋是否会畅销。如果你想教你的算法预测天气或房子的销售速度,你需要一个不同的数据集,例如topseller:
// использование атрибута маркера topseller instead of атрибута маркера цена
ArrayList<string> classVal = new ArrayList<>();
classVal.add("true");
classVal.add("false");

Attribute topsellerAttribute = new Attribute("topsellerLabel", classVal);
attributes.add(topsellerAttribute);
该数据集将用于训练新的分类器topseller。一旦经过训练,预测调用应该返回一个可用于获取预测值的标记类别索引。
int idx = (int) targetFunction.classifyInstance(unlabeledInstances.get(0));
String prediction = classVal.get(idx);

结论

尽管机器学习与统计学密切相关并使用许多数学概念,但机器学习工具包允许您开始将机器学习集成到您的程序中,而无需深入的数学知识。然而,您对底层机器学习算法(例如我们在本文中探讨的线性回归算法)了解得越多,您就越能够选择正确的算法并对其进行调整以获得最佳性能。 英文翻译。作者:Gregor Roth,JavaWorld 软件架构师。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION