此文章由机器翻译。

測試回合

多类 Logistic 回归分类

James McCaffrey

下载代码示例

James McCaffrey我认为 logistic 回归 (LR) 分类是"你好,世界 !"机器学习 (毫升)。在标准的 LR 分类中,目标是预测可以在两个分类值之一一些变量的值。例如,您可能想要预测一个人的性别 (男性或女性) 基于其高度和每年的收入。

多类 LR 分类扩展标准 LR 允许变量来预测有三个或更多值。例如,您可能想要预测一个人的政治倾向 (保守、 温和或自由) 基于年龄、 年收入预测变量,等等。在本文中,我将解释如何多类 LR 工作,向您展示如何执行它使用的 C#。

理解这篇文章将走向何方是的最佳方式是采取看看演示程序中图 1。演示开始通过生成 1000 行的四个预测变量 (也称为特征),合成数据,要预测的变量可以在三个值之一。例如,生成的数据行可能类似于:

5.33  -4.89  0.15  -6.67  0.00  1.00  0.00

多类 Logistic 回归分析在行动
图 1 多类 Logistic 回归分析在行动

前四个值表示已经标准化,所以 0.0 值功能完全平均的真实数据的预测值,大于 0.0 是大于功能平均,大小值和值小于 0.0 都小于功能平均。最后三个值是变量的要预测 N 1 编码的版本。例如,如果你想要预测政治倾向,然后 (1 0 0) 代表保守 (0 1 0) 表示适度和 (0 0 1) 代表自由。

合成数据生成后,它被随机分成 (80%的数据或 800 线) 的训练集和测试集 (其余 200 线)。训练数据用于创建的预测模型和试验数据用于估计新数据模型的预测精度要预测的值还不知道在哪里。

F 功能与 c 类的多类 LR 模型会有 (f * c) 权重和偏置 c。这些都是必须确定的数字常量。对于这个演示中,有 4 * 3 = 12 权重和 3 偏置。培训是估计值的权重和偏置的过程。培训是一个迭代过程和演示将训练迭代 (通常称为毫升文学的时代) 的最大数目设置为 100。 用于训练多类 LR 分类器的技术被称为批处理梯度下降。这种技术需要叫学习速率和重量衰减率两个参数的值。这两个值通常可以找到试验和错误,并演示分配值 0.01 和 0.10,分别。

在训练中,演示显示进度消息每 10 世纪。如果你看看在消息图 1你可以看到,训练融合速度非常快,没有任何改善后第一次 20 世纪。

经过培训完成,演示显示找不到 12 的权重和 3 偏置的最佳值。这些 15 的值定义的多类 LR 模型。演示使用这些值,计算模型 (92.63%正确或 741 个 800) 培训资料和试验数据 (90.00%正确或 180 个 200) 的预测准确性。

这篇文章假设你有中级或高级编程技能,但并不假定你知道任何关于多类 logistic 回归分析。该演示程序编码使用 C# 中,但您应该能够重构演示,其他编程语言没有太多的麻烦。

理解多类 Logistic 回归分析

假设您想要预测基于年龄 (0) 一个人的政治倾向 (保守、 温和的自由的),每年的收入 (1)、 高度 (3) 和教育水平 (4)。你编码政治倾向与三个变量作为 (y0、 y1、 y2),其中保守是 (1,0,0) 中, 度是 (0,1,0) 和自由是 (0,0,1)。针对这一问题的多类 LR 模型将采取的形式:

z0 = (w00)(x0) + (w01)(x1) + (w02)(x2) + b0
y0 = 1.0 / (1.0 + e^-z0)
z1 = (w10)(x0) + (w11)(x1) + (w12)(x2) + b1
y1 = 1.0 / (1.0 + e^-z1)
z2 = (w20)(x0) + (w21)(x1) + (w22)(x2) + b2
y2 = 1.0 / (1.0 + e^-z2)

在这里,wij 是与特征变量的权重值关联我和类变量 j 和 bj 是与类变量 j 关联的偏置值。

多类 LR 所示的示例图 2。一个训练数据项目有四个后面跟着三个输出值 (1,0,0) 的预测值 (-5.40、-5.20、 5.30 5.10)。预测值是任意的但你可以想象他们代表一个人的年龄大于平均、 收入是低于平均、 高度大于平均教育水平是远低于平均水平,和人的保守的政治倾向。

多类逻辑回归的数据结构
图 2 多类逻辑回归的数据结构

每个权重矩阵的三列对应于三类值之一。每个列中的四个值对应于四个预测的 x 值。偏见数组保存附加的、 特殊的重量 — — 一个用于每个类 — — 这并不是一个预测与相关联。

请注意偏见数组可能已经存储为一个权重矩阵中的附加行。这往往是做在研究论文中,因为它简化了数学方程。但出于演示目的实施,维护一个单独的权重矩阵和数组是稍微容易理解,在我看来偏见。

在多类 LR 输出值被计算每个类。在图 2,计算出的输出值 0.35 0.33 0.32)。输出值的总和为 1.0,并且可以被解释为概率。因为最后输出值 (勉强) 最大的三个,你得出结论输出对应于 (0,0,1)。在此示例中,计算的输出匹配的三个输出在培训数据项目中,因此该模型取得了比较准确的预测。

通过首先总结产品的每个输入的值倍其相应的权重值,然后添加相应的偏置值计算输出值。这些款项的产品通常被称为 z 值。Z 值来喂养什么叫做逻辑斯谛乙状结肠函数:1.0 / (1.0 + e ^-z) 其中 e 是数学常数和 ' ^' 意味着求幂运算。虽然它不是从显然可以看出图 2,物流的乙状结肠函数的结果总是会介于 0.0 和 1.0 之间。

每个后勤乙状结肠值用于计算最终的输出值。总结后勤乙状结肠值并将其用作除数。这个过程叫做 softmax 函数。如果你是新到所有这些 LR 概念,他们可以是非常令人困惑在第一次。但是,如果你看一遍中的示例图 2 几次,你最终会看到 LR 不是它第一次出现的那么复杂。

权重和偏置值从哪里来?确定这些值的过程被称为培养模式。这个想法是使用一套培训具有已知输入和输出值,然后尝试不同的权重和偏置值,直到你在培训数据中查找计算的输出与 (通常称为目标值) 的正确的输出值之间的误差减到最小的一组值的数据。

它并不可行的计算权重的确切值必须估计偏差,所以权重和偏置。有几个所谓的数值优化技术,可以用来做到这一点。常用的方法包括 L BFGS 算法、 迭代加权的最小二乘方法和粒子群优化算法。该演示程序使用一种技术,已令人困惑称为 (最小化计算和已知的输出值之间的误差) 两个梯度下降和梯度上升 (最大化权重和偏置是最优的概率)。

演示的程序结构

演示程序,与一些小的编辑,以节省空间,结构在图 3。若要创建该演示程序,我发起了 Visual Studio,选择 C# 控制台应用程序模板。我的名字 LogisticMultiClassGradient 项目。演示有没有重大的.NET 依赖关系,因此,任何版本的 Visual Studio 将工作。该演示是太长,无法显示其全部内容,但所有的源代码是本文附带的下载中提供。我去掉了所有常规错误检查,以尽可能突出核心内容。

图 3 演示程序结构

using System;
namespace LogisticMultiClassGradient
{
  class LogisticMultiProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin classification demo");
      ...
      Console.WriteLine("End demo");
      Console.ReadLine();
    }
    public static void ShowData(double[][] data,
      int numRows, int decimals, bool indices) { . . }
    public static void ShowVector(double[] vector,
      int decimals, bool newLine) { . . }
    static double[][] MakeDummyData(int numFeatures,
      int numClasses, int numRows, int seed) { . . }
    static void SplitTrainTest(double[][] allData,
      double trainPct, int seed, out double[][] trainData,
      out double[][] testData) { . . }
  }
  public class LogisticMulti
  {
    private int numFeatures;
    private int numClasses;
    private double[][] weights; // [feature][class]
    private double[] biases;    // [class]
    public LogisticMulti(int numFeatures,
      int numClasses) { . . }
    private double[][] MakeMatrix(int rows,
      int cols) { . . }
    public void SetWeights(double[][] wts,
      double[] b) { . . }
    public double[][] GetWeights() { . . }
    public double[] GetBiases() { . . }
    private double[] ComputeOutputs(double[] dataItem) { . . }
    public void Train(double[][] trainData, int maxEpochs,
      double learnRate, double decay) { . . }
    public double Error(double[][] trainData) { . . }
    public double Accuracy(double[][] trainData) { . . }
    private static int MaxIndex(double[] vector) { . . }
    private static int MaxIndex(int[] vector) { . . }
    private int[] ComputeDependents(double[] dataItem) { . . }
  }
}

模板代码加载后,在解决方案资源管理器窗口中用鼠标右键单击文件 Program.cs 讨论,又改名了到更具描述性的 LogisticMultiProgram.cs,Visual Studio 将自动重命名类程序对我来说。 在编辑器窗口顶部的源代码,我删除所有不需要使用语句,留下只是一个顶级的 System 命名空间的引用。

LogisticMultiProgram 类包含 MakeDummyData、 SplitTrainTest、 ShowData 和 ShowVector 的帮助器方法。这些方法创建和显示的综合数据。所有分类逻辑都包含在一个名为 LogisticMulti 的程序定义的类。

Main 方法创建合成数据包含这些语句:

int numFeatures = 4;
int numClasses = 3;
int numRows = 1000;
double[][] data = MakeDummyData(numFeatures, 
  numClasses, numRows, 0);

方法 MakeDummyData 生成一组随机权重和偏置,然后对于每一行的数据,生成随机的输入的值,结合权重和偏差的输入的值,并计算一些相应的 N 1 编码的输出值。合成数据拆分成 80%培训和 20%测试集,像这样:

double[][] trainData;
double[][] testData;
SplitTrainTest(data, 0.80, 7, out trainData, out testData);
ShowData(trainData, 3, 2, true);
ShowData(testData, 3, 2, true);

参数,值 7 是一个随机的种子,用只是因为它提供了一个好看的演示。多值分类器 LR 是创建和培训这些语句:

LogisticMulti lc = new LogisticMulti(numFeatures, numClasses);
int maxEpochs = 100;
double learnRate = 0.01;
double decay = 0.10;
lc.Train(trainData, maxEpochs, learnRate, decay);

训练参数 maxEpochs (100)、 学习速率 (0.01) 和重量衰变率 (0.10) 的值测定试验和错误。优化大多数毫升训练方法通常需要一些试验,以获得良好的预测精度。

经过训练后,最佳权重和偏置值存储在 LogisticMulti 对象中。他们正在检索并显示像这样:

double[][] bestWts = lc.GetWeights();
double[] bestBiases = lc.GetBiases();
ShowData(bestWts, bestWts.Length, 3, true);
ShowVector(bestBiases, 3, true);

使用一个 void 的火车方法与 Get 方法相结合的一种替代设计旨在使其返回最佳权重矩阵和最好的偏见数组作为输出参数,或在一个联合数组,请重构方法,列车。评价训练好的模型质量就像这样:

double trainAcc = lc.Accuracy(trainData, weights);
Console.WriteLine(trainAcc.ToString("F4"));
double testAcc = lc.Accuracy(testData, weights);
Console.WriteLine(testAcc.ToString("F4"));

测试数据的模型精度较相关的两个精度值。它为您提供一个粗略的估计,如何准确的模型会时提出了一种未知的输出值的新数据。

实施多类 LR 培训

LogisticMulti 类的构造函数定义如下:

public LogisticMulti(int numFeatures, int numClasses)
{
  this.numFeatures = numFeatures;
  this.numClasses = numClasses;
  this.weights = MakeMatrix(numFeatures, numClasses);
  this.biases = new double[numClasses];
}

方法 MakeMatrix 是一个为数组的数组样式矩阵分配内存的 helper 方法。权重矩阵和偏见数组隐式初始化为 0.0 的所有值。一些研究人员喜欢的替代方法是显式地初始化权重和偏置到小 (通常介于 0.001 和 0.01) 之间的随机值。

方法 ComputeOutputs 的定义提出了图 4。该方法返回一个数组的值,另一个用于每个类,每个值介于 0.0 和 1.0 和值总和为 1.0 之间。

图 4 方法 ComputeOutputs

private double[] ComputeOutputs(double[] dataItem)
{
  double[] result = new double[numClasses];
  for (int j = 0; j < numClasses; ++j) // compute z
  {
    for (int i = 0; i < numFeatures; ++i)
      result[j] += dataItem[i] * weights[i][j];
    result[j] += biases[j];
  }
  for (int j = 0; j < numClasses; ++j) // 1 / 1 + e^-z
    result[j] = 1.0 / (1.0 + Math.Exp(-result[j]));
  double sum = 0.0; // softmax scaling
  for (int j = 0; j < numClasses; ++j)
    sum += result[j];
  for (int j = 0; j < numClasses; ++j)
    result[j] = result[j] / sum;
  return result;
}

类定义包含一个类似的方法,ComputeDependents:

private int[] ComputeDependents(double[] dataItem)
{
  double[] outputs = ComputeOutputs(dataItem); // 0.0 to 1.0
  int maxIndex = MaxIndex(outputs);
  int[] result = new int[numClasses];
  result[maxIndex] = 1;
  return result;
}

方法 ComputeDependents 返回其中一个值是 1,其他值为 0 的整数数组。 这些计算出的输出值可比作中训练数据,以确定有否模型作出正确的预测,反过来可以用来计算预测精度的已知的目标输出值。

表示在非常高的级别伪代码中,火车的方法:

loop maxEpochs times
  compute all weight gradients
  compute all bias gradients
  use weight gradients to update all weights
  use bias gradients to update all biases
end-loop

每个权重和偏置值有关联的梯度值。渐变松散地说,是一个值,该值指示相隔多么遥远,和在什么方向 (正数或负数) 计算的输出值与目标输出值进行比较。例如,假设一个重量,如果所有其他权重和偏置值保持不变,计算的输出值为 0.7 的目标输出值是 1.0。计算的值是太小了,所以梯度是价值约 0.3,将被添加到重量。如果的权重值增加,会增加计算的输出值。我漏掉了一些细节,但其基本思想是相当简单。

梯度训练背后的数学运算使用微积分,并且是非常复杂的但幸运的是,你不需要完全理解数学运算,以实现的代码。方法火车的定义的开头:

public void Train(double[][] trainData, int maxEpochs,
  double learnRate, double decay)
{
  double[] targets = new double[numClasses];
  int msgInterval = maxEpochs / 10;
  int epoch = 0;
  while (epoch < maxEpochs)
  {
    ++epoch;
...

目标数组将举行训练数据项目中存储的正确的输出值。变量 msgInterval 控制次数显示进度消息。然后,将显示进度消息:

if (epoch % msgInterval == 0 && epoch != maxEpochs)
{
  double mse = Error(trainData);
  Console.Write("epoch = " + epoch);
  Console.Write(" error = " + mse.ToString("F4"));
  double acc = Accuracy(trainData);
  Console.WriteLine(" accuracy = " + acc.ToString("F4"));
}

因为毫升的训练通常涉及一些审判和显示进度消息的错误是非常有用的。接下来,分配的权重和偏置的梯度的存储:

double[][] weightGrads = MakeMatrix(numFeatures, numClasses);
double[] biasGrads = new double[numClasses];

请注意这些出现在主要的 while 循环内的分配。因为 C# 初始化数组为 0.0,初始化所有渐变。备用方法是以分配空间外面 while 循环,然后调用帮助器方法的名字,如 ZeroMatrix 和 ZeroArray。接下来,所有的权重计算渐变:

for (int j = 0; j < numClasses; ++j) {
  for (int i = 0; i < numFeatures; ++i) {
    for (int r = 0; r < trainData.Length; ++r) {
      double[] outputs = ComputeOutputs(trainData[r]);
        for (int k = 0; k < numClasses; ++k)
          targets[k] = trainData[r][numFeatures + k];
        double input = trainData[r][i];
        weightGrads[i][j] += (targets[j] - outputs[j]) * input;
    }
  }
}

此代码是多类 LR 的心。每个重量梯度是本质上的区别目标输出值和计算出的输出值。在相反的方向,应调整差值乘以相关的输入值,要考虑到的输入可以是负值,这意味着重量的事实。

我经常使用的一个有趣的替代是忽略输入值的大小,并使用它的标志:

double input = trainData[r][i];
int sign = (input > 0.0) ? 1 : -1;
weightGrads[i][j] += (targets[j] - outputs[j]) * sign;

以我的经验,这种技术经常导致更好的模型。接下来,所有的偏见,梯度计算:

for (int j = 0; j < numClasses; ++j) {
  for (int i = 0; i < numFeatures; ++i) {
    for (int r = 0; r < trainData.Length; ++r) {
      double[] outputs = ComputeOutputs(trainData[r]);
      for (int k = 0; k < numClasses; ++k)
        targets[k] = trainData[r][numFeatures + k];
      double input = 1; // 1 is a dummy input
      biasGrads[j] += (targets[j] - outputs[j]) * input;
    }
  }
}

如果你检查的代码,你可以看到内循环,通过计算权重梯度,可以执行计算偏差梯度。我分开两个梯度计算为清楚起见,这会降低性能。此外,可以删除乘法所隐含的输入值为 1。它,也增加了为清楚起见。接下来,权重已更新:

for (int i = 0; i < numFeatures; ++i) {
  for (int j = 0; j < numClasses; ++j) {
    weights[i][j] += learnRate * weightGrads[i][j];
    weights[i][j] *= (1 - decay);  // wt decay
  }
}

后增加或减少基于学习的权重及其梯度,权重值率分数降低使用重量衰变率。例如,演示使用 0.10,所以乘以一个典型的体重衰减值 (1-0.10) 乘以 0.90,即减少了 10%。重量衰减也被称为正则化。这项技术防止失控的权重值。方法火车最后更新的偏差值:

...
    for (int j = 0; j < numClasses; ++j) {
      biases[j] += learnRate * biasGrads[j];
      biases[j] *= (1 - decay);
    }
  } // While
} // Train

培训技术更新类成员权重矩阵和偏见中的数组的地方。这些值定义的多类 LR 模型,并可以使用 Get 方法检索。

结束语

有两个主要变化的梯度的训练,被称为批处理和随机。这篇文章给出了批量版本之间的差异计算和所有培训项目的都目标输出梯度在那里通过求和计算。在随机梯度的训练,梯度估计利用只是单独训练数据项目。一些实验的基础,应用于多类 LR 时批处理培训似乎给出了更精确的模型,但需要较长时间比随机培训。这是相当令人吃惊,因为随机培训通常适用于批量训练神经网络。


博士。 James McCaffrey 为在华盛顿州雷蒙德市的微软研究院工作和曾在几个 Microsoft 产品包括 Internet Explorer 和 Bing。 博士。 麦卡弗里也可以拨打 jammc@microsoft.com

衷心感谢以下 Microsoft 技术专家对本文的审阅:托德 · 贝略和艾莉森溶胶