一个简单的逻辑回归模型如何在MNIST上实现92%的分类精度?


64

即使MNIST数据集中的所有图像都居中,具有相似的比例并且面朝上且没有旋转,但它们的笔迹差异很大,这使我感到困惑,线性模型如何实现如此高的分类精度。

据我所能想象的,鉴于明显的笔迹变化,数字应该在784维空间中线性不可分割,即应该有一点点(尽管不是很复杂)非线性边界将不同的数字分开,类似于引人注目的XOR示例,其中正类别和负类别无法通过任何线性分类器分开。在我看来,多类逻辑回归如何在具有完全线性特征(无多项式特征)的情况下产生如此高的准确性令我感到困惑。

例如,给定图像中的任何像素,数字23不同手写体变化可以使该像素发光或不发光。因此,通过一组学习的权重,每个像素可以使数字看起来像2以及3。只有结合像素值,才可以说出数字是2还是3。对于大多数数字对都是如此。因此,逻辑回归如何盲目地将决策独立于所有像素值(根本不考虑像素间的依赖性),从而能够实现如此高的准确性。

我知道我在某个地方错了,或者只是高估了图像中的变化。但是,如果有人可以帮助我直观地了解数字如何“几乎”线性可分,那将是很棒的。


看看教科书《具有稀疏性的统计学习:套索和概化》 3.3.1示例:手写数字web.stanford.edu/~hastie/StatLearnSparsity_files/SLS.pdf
Adrian

我很好奇:像惩罚线性模型(即glmnet)这样的问题在这个问题上做得如何?如果我还记得的话,您所报告的是未经惩罚的样本外准确性。
悬崖AB

Answers:


82

tl; dr即使这是一个图像分类数据集,它仍然是一项非常简单的任务,为此,可以轻松地找到从输入到预测的直接映射


回答:

这是一个非常有趣的问题,由于逻辑回归的简单性,您实际上可以找到答案。

logistic回归所做的是为每个图像接受784输入并将它们与权重相乘以生成其预测。有趣的是,由于输入和输出之间的直接映射(即没有隐藏层),每个权重的值对应于在计算每个类别的概率时要考虑784输入中的每个输入的数量。现在,通过取每个类别的权重并将其重塑为28×28(即图像分辨率),我们可以知道哪些像素对每个类别的计算最重要

再次注意,这些是权重

现在看一看上面的图像,并专注于前两位数字(即零和一)。蓝色权重表示此像素的强度对该类别有很大贡献,而红色值表示该像素起了负面作用。

0

1

2378

通过此操作,您可以看到逻辑回归有很好的机会获得很多正确的图像,这就是为什么它得分很高的原因。


复制上图的代码有些陈旧,但您可以执行以下操作:

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data

# Load MNIST:
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

# Create model
x = tf.placeholder(tf.float32, shape=(None, 784))
y = tf.placeholder(tf.float32, shape=(None, 10))

W = tf.Variable(tf.zeros((784,10)))
b = tf.Variable(tf.zeros((10)))
z = tf.matmul(x, W) + b

y_hat = tf.nn.softmax(z)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(y_hat), reduction_indices=[1]))
optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) # 

correct_pred = tf.equal(tf.argmax(y_hat, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# Train model
batch_size = 64
with tf.Session() as sess:

    loss_tr, acc_tr, loss_ts, acc_ts = [], [], [], []

    sess.run(tf.global_variables_initializer()) 

    for step in range(1, 1001):

        x_batch, y_batch = mnist.train.next_batch(batch_size) 
        sess.run(optimizer, feed_dict={x: x_batch, y: y_batch})

        l_tr, a_tr = sess.run([cross_entropy, accuracy], feed_dict={x: x_batch, y: y_batch})
        l_ts, a_ts = sess.run([cross_entropy, accuracy], feed_dict={x: mnist.test.images, y: mnist.test.labels})
        loss_tr.append(l_tr)
        acc_tr.append(a_tr)
        loss_ts.append(l_ts)
        acc_ts.append(a_ts)

    weights = sess.run(W)      
    print('Test Accuracy =', sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})) 

# Plotting:
for i in range(10):
    plt.subplot(2, 5, i+1)
    weight = weights[:,i].reshape([28,28])
    plt.title(i)
    plt.imshow(weight, cmap='RdBu')  # as noted by @Eric Duminil, cmap='gray' makes the numbers stand out more
    frame1 = plt.gca()
    frame1.axes.get_xaxis().set_visible(False)
    frame1.axes.get_yaxis().set_visible(False)

9
2378

13
当然,这有助于MNIST样本在分类器看到之前进行居中,缩放和对比归一化。您不必解决诸如“如果零的边缘实际上穿过盒子中间该怎么办?”之类的问题。因为预处理器在使所有零看起来都一样方面已经走了很长一段路。
hobbs

1
@EricDuminil我在脚本中添加了您的建议。非常感谢您的输入!:D
Djib2011

1
@NitishAgarwal,如果您认为此答案是您问题的答案,请考虑将其标记为这样。
sintax

7
对于对这种处理感兴趣但又不特别熟悉的人,此答案提供了一个极好的直观的力学示例。
克莱里斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.