【问题标题】:Confusing probabilities from scikit-learn randomforest来自 scikit-learn 随机森林的令人困惑的概率
【发布时间】:2019-03-25 15:50:11
【问题描述】:

我有一个整数值的时间序列,我试图预测。我通过一个滑动窗口来做到这一点,它学习关联 99 个值来预测下一个值。这些值介于 0 和 128 之间。X 的表示是一个由 n 个 99 长的滑动窗口组成的立方体,每个整数都被编码为一个 128 个元素长的热编码向量。这个数组的形状是 (n, 99, 128)。 Y的形状是(n, 128)。我将其视为一个多类问题,因为 Y 可以精确地得出一个结果。

这适用于 Keras/Tensorflow,但是当我尝试使用 scikit-learn 中的 RandomForest 时,它抱怨输入向量是 3D 而不是 2D。因此,我将输入立方体 X 重新整形为形状为 (n, 99 * 128) 的二维矩阵。结果不是很好,为了了解发生了什么,我请求了概率(参见下面的代码)。

def rf(X_train, Y_train, X_val, Y_val, samples):
    clf = RandomForestClassifier(n_estimators=32, n_jobs=-1)
    clf.fit(X_train, Y_train)
    score = clf.score(X_val, Y_val)
    print('Score of randomforest =', score)

    # compute some samples
    for i in range(samples):
        index = random.randrange(0, len(X_val) - 1)
        xx = X_val[index].reshape(1, -1)
        probs = clf.predict_proba(xx)
        pred = clf.predict(xx)
        y_true = np.argmax(Y_val[index])
        y_hat = np.argmax(pred)
        print(index, '-', y_true, y_hat, xx.shape, len(probs))
        print(probs)
        print(pred)

我从predict_proba 得到的输出是:

[array([[0.841, 0.159]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), 
 array([[1.]]), array([[1., 0.]]), array([[1., 0.]]), array([[1., 0.]]),
 array([[1., 0.]]), array([[1., 0.]]), array([[0.995, 0.005]]), array([[0.999,
 0.001]]), array([[0.994, 0.006]]), array([[1., 0.]]), array([[0.994, 0.006]]),
 array([[0.977, 0.023]]), array([[0.999, 0.001]]), array([[0.939, 0.061]]),
 array([[0.997, 0.003]]), array([[0.969, 0.031]]), array([[0.997, 0.003]]),
 array([[0.984, 0.016]]), array([[0.949, 0.051]]), array([[1., 0.]]),
 array([[0.95, 0.05]]), array([[1., 0.]]), array([[0.918, 0.082]]), 
 array([[0.887, 0.113]]), array([[1.]]), array([[0.88, 0.12]]), array([[1.]]),
 array([[0.884, 0.116]]), array([[0.941, 0.059]]), array([[1.]]), array([[0.941,
 0.059]]), array([[1.]]), array([[0.965, 0.035]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]]),
 array([[1.]]), array([[1.]]), array([[1.]]), array([[1.]])]

输出向量的长度是 128 没错,但为什么它由一个列表组成,包含二维数组,有时包含一个元素,有时包含两个元素?据我了解the manual 应该返回一个数组,其维度为# samples * # classes,因此在我的形状示例中为 (1,128)。

谁能帮我指出我做错了什么?

编辑 1

我按照@Vivek Kumar(感谢 Vivek)在他的 cmets 中建议的方式进行了实验。我输入整数序列 (X) 并将它们与序列 (y) 中的下一个整数匹配。这是代码:

def rff(X_train, Y_train, X_val, Y_val, samples, cont=False):
    print('Input data:', X_train.shape, Y_train.shape, X_val.shape, Y_val.shape)
    clf = RandomForestClassifier(n_estimators=64, n_jobs=-1)
    clf.fit(X_train, Y_train)
    score = clf.score(X_val, Y_val)

    y_true = Y_val
    y_prob = clf.predict_proba(X_val)
    y_hat = clf.predict(X_val)
    print('y_true', y_true.shape, y_true)
    print('y_prob', y_prob.shape, y_prob)
    print('y_hat', y_hat.shape, y_hat)
    #sum_prob = np.sum(y_true == y_prob)
    sum_hat = np.sum(y_true == y_hat)
    print('Score of randomforest =', score)
    print('Score y_hat', sum_hat / len(X_val))
    #print('Score y_prob', sum_prob / len(X_val))

    # compute some individual samples
    for i in range(samples):
        index = random.randrange(0, len(X_val) - 1)
        y_true_i = Y_val[index]
        #y_prob_i = y_prob[index]
        y_hat_i = y_hat[index]
        print('{:4d} - {:3d}{:3d}'.format(index, y_true_i, y_hat_i))

它的输出是:

Input data: (4272, 99) (4272,) (1257, 99) (1257,)
y_true (1257,) [ 0  0  0 ... 69 70 70]
y_prob (1257, 29) [[0.09375  0.       0.       ... 0.078125 0.078125 0.015625]
 [0.109375 0.       0.       ... 0.046875 0.0625   0.0625  ]
 [0.125    0.       0.       ... 0.015625 0.078125 0.015625]
 ...
 [0.078125 0.       0.       ... 0.       0.       0.      ]
 [0.046875 0.       0.       ... 0.       0.       0.      ]
 [0.078125 0.       0.       ... 0.       0.       0.      ]]
y_hat (1257,) [81 81 79 ... 67 67 65]
Score of randomforest = 0.20047732696897375
Score y_hat 0.20047732696897375
 228 -  76 77
  51 -  76  0
 563 -  81  0
 501 -   0 77
 457 -  79 79
 285 -  76 77
 209 -  81  0
1116 -  79  0
 178 -  72 77
1209 -  67 65

概率数组的大小一致,但它的形状完全奇怪 (128, 29)。这个 29 是从哪里来的……?然而,报告有一些改进:准确性大大提高。以前是 0.0015 左右,现在是 0.20 左右。

关于概率数组代表什么的任何想法?

编辑 2

我的错误是,从 128 个单热编码值返回到整数时,我没有考虑到我只有 29 个唯一值。 predict_proba 巧妙地预测了这 29 个值,因为这些是它学到的。

剩下的唯一问题是概率预测哪些值?让我们假设要预测的类是 0、101-128,predict_proba 返回索引 0..28 的值。概率到类的映射是什么:0-->0, 1-->101, 2-->102, ... , 29-128?我在手册中找不到任何关于此的提示。

【问题讨论】:

  • 您还需要停止对 y 值的一次性编码。在您当前的输入中,y 的形状为 (n, 128),它是 2-d 触发分类器的多输出模式,而不是多类。对于多类,只需保持数字不变(从 0 到 128 的值),使 y 的形状为 (n,)
  • 另外,在 X 中,如果值(介于 0 到 128 之间)表示它们之间的任何排序,则无需对它们进行任何 one-hot 编码。您可以按原样放置它们。你的 X 可以是形状 (n, 99)。树分类器在处理这个问题上做得很好。看看这些 cmets 是否有意义,否则我会发布一个详细解释这一点的答案。
  • @Vivek Kumar:看我的编辑。由于您的 cmets,我有相当多的改进,但概率的形状完全奇怪。我很想看看你的解释,因为我的印象是分类器的输入应该是一个热编码的。我还认为数据应该是某种标准化的,而我的数据不再是这样了。
  • 我在下面添加了关于我的 cmets 的说明。看看这是否有意义。如果还需要更多支持,我也可以描述代码。
  • 看clf.classes_属性

标签: python scikit-learn random-forest


【解决方案1】:

首先让我们谈谈你的目标y

  • 2-d y 被认为是一个标签指示矩阵,用于 scikit-learn 中的多标签或多输出多类任务。从您的数据来看,情况似乎并非如此,所以我认为您不会想要对y 进行一次性编码。

  • 关于问题中目标的第二件事是,您首先需要决定是要分类还是回归任务。你说你有一个"time series of integer values"。所以问题是这些整数可以在数字上相互比较吗?

示例 1:假设您有一个问题,您希望将某些数据分类为“日本”、“俄罗斯”、“美国”三个国家/地区。

  • 现在这些字符串可以编码为 1(“Japan”)、2(“Russia”)和 3(“USA”),以便它们可以用于机器学习模型。但是我们不能将这些编码作为数字进行比较,因为 2 中的数字大于 1 或小于 3。这里 1,2,3 只是分类数据的数字表示,实际上对它没有任何数字意义。在这种情况下,分类任务适合将数据放入这三个类别中。

  • 但在任何其他情况下,例如预测股票价格或预测温度等,这些数字可以而且应该相互比较,因此应该使用回归(预测实际价值目标)。

示例 2:为了更好地理解,您还可以考虑模型的正确性(损失函数)。让我们假设一个模型可以预测从 1 到 10 的目标,并且特定样本的正确目标是 9。

  • 在分类任务中,只有正确的预测很重要。模型将目标预测为 8 还是 1 无关紧要。

  • 但在回归模型中,如果模型预测输出为 8,那么您可以说它比预测输出为 1 的模型更好。

希望你明白我的意思。因此,对于您的问题,即使您有有限数量的整数 (128) 作为目标,您也需要确定它们在分类或回归中是否有意义。

注意:我目前正在进一步将分类作为您的原始问题。

现在来功能X

如果类别中不存在排序或您无法正确确定排序,则使用单热编码。我上面对类别之间的数值比较给出的解释也适用于此。

  • 考虑三个类别的另一个示例:“高”、“中”、“低”。它们有一个固有的顺序在这里如果你编码为 0(低)、1(中)和 2(高),那么它们可以在数字上进行比较。因此,您可以决定将它们保留为 0、1、2 或 one-hot 编码。

  • 正如我在评论中所说,随机森林对于此类事情非常强大,并且如果对类别进行了策略性编码,则不会对性能产生太大影响。例如,如果您编码 0(高)、1(低)、2(中)等,性能可能会下降。

现在再次谈到你的情况和我从第 1 点提出的问题:这些整数可以在数字上相互比较吗?如果是,则无需对特征进行 one-hot 编码。如果没有,那就去做吧。

【讨论】:

  • 非常感谢您提供这个迷你课程!回答您的问题:是的,可以比较整数。尽管整数代表不同的类别,但这不是回归问题。您的答案的假设是正确的。我将答案标记为正确答案,尽管它只回答了部分问题:我有正确的表示吗?不需要代码:我已经按照您的第一个 cmets 进行了编码,并在我的问题的编辑中进行了报告。仍然很好奇为什么 predict_proba 的形状是 (128, 29) 而不是 (128,),这对我来说毫无意义。
  • @Arnold 是你在问题中所说的 128,29 还是 1257,29?在没有看到实际数据集的情况下,我只能说您的 y_true 可能只包含总共 128 个类中的 29 个不同的类。在这种情况下,模型仅适用于这 29 个类。如果你的数据集不是机密的,那么你可以在这里分享,以便我分析。
  • 这是 (128, 29),我的一个愚蠢的错误,对不起。在这个数据集中使用了 29 个值,每个数据集可能会有所不同。但我不明白:我认为当我呈现一个 99 个值的序列时,RFC 已经学会将其与 128 个中的 one 类相关联(Y 的长度为 128)。所以它应该生成一个包含 128 个概率的向量,或者可能是 29 个但不是 (128, 29)。在我看来,其中一个维度是多余的。我会很高兴有任何解释。
  • @Arnold 很抱歉,但我不明白这一点。来自 RFC 的 predict_proba 的输出如何具有 (128, 29) 的形状?您传入的样本数是 128 吗?你能显示np.unique(y_true).shapeX_val.shape的输出吗?还是分享数据和完整代码?
  • 花费的时间比我预期的要长,对此感到抱歉。请参阅我对问题的第二次编辑。
猜你喜欢
  • 2012-12-20
  • 2015-09-16
  • 2015-03-28
  • 2012-10-24
  • 2013-04-26
  • 2018-01-30
  • 2016-06-22
  • 2012-03-10
  • 2017-03-26
相关资源
最近更新 更多