线性回归适用于输出连续值的情景;softmax回归适用于输出像图像类别这样的离散值。和线性回归不同,softmax回归的输出单元从一个变成多个,且引入了softmax运算使输出更适合离散值的预测和训练。
假设训练数据集中图像的真实标签为狗、猫和鸡(假设可以用四像素表示出这三种动物),这些标签分别对应离散值为y1,y2,y3(通常使用离散的数值表示类别,例如y1=1,y2=2,y3=3)。虽然可以使用回归模型来建模,并将预测值就近定点化到1,2,3这三个离散值之中,但这种连续值到离散值的转化通常会影响分类质量。
因为有4种特征和3种输出动物类别,所以权重包含12个标量(带下标的w,)、偏差包含3个标量(带下标的b,),且对每个输入计算o1,o2,o3这三个输出:
softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出o1,o2,o3的计算都要依赖于所有的输入x1,x2,x3,x4,softmax回归的输出层也是一个全连接层。
既然分类问题需要得到离散的预测输出,一个简单的办法是将输出值当作预测类别是i的置信度,并将值最大的输出所对应的类作为预测输出。
然而,直接使用输出层的输出有两个问题。一方面,由于输出层的输出值的范围不确定,很难在直观上判断这些值的意义(若输出值分别为、10、);另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
softmax运算符解决了以上两个问题,通过下式将输出值变换成值为正且和为1的概率分布:
交叉熵损失函数
我们已经知道,softmax运算将输出变换成一个合法的类别预测分布。实际上,真实标签也可以用类别分布表示:对于样本i,构造向量,使其第(样本i类别的离散数值)个元素为1,其余为0.这样训练目标可以设为使预测概率分布尽可能接近真实的标签概率分布。
线性回归中使用平方损失函数。然而,想要预测分类结果正确,其实并不需要预测概率完全等于标签概率。例如,在图像分类的例子里,如果,那么只需比其他两个预测值、大就可以。即使,值为0.6,不管其他两个预测值为多少,类别预测均正确。而平方损失则过于严格,例如==0.2比=0,=0.4的损失要小很多,虽然两者都有同样正确的分类。
可以通过使用更适合衡量两个概率分布差异的测量函数改善上述问题。其中交叉熵(cross entropy)是一个常用的衡量方法:
交叉熵只关心对正确类别的分类的预测概率,因为只要其值足够大,就可以确保分类结果正确。当遇到一个样本有多个标签时,例如图像里含有不止一个物体时,交叉熵同样只关心对图像中出现的物体类别的预测概率。
softmax回归代码:
%matplotlib inline
import d2lzh as d2l
from mxnet.gluon import data as gdata
import sys
import time
#获取数据集
mnist_train = gdata.vision.FashionMNIST(train=True)
mnist_test = gdata.vision.FashionMNIST(train=False)
# 本函数已保存在d2lzh包中方便以后使用
def get_fashion_mnist_labels(labels):
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
# 本函数已保存在d2lzh包中方便以后使用
def show_fashion_mnist(images, labels):
d2l.use_svg_display()
# 这里的_表示我们忽略(不使用)的变量
_, figs = d2l.plt.subplots(1, len(images), figsize=(12, 12))
for f, img, lbl in zip(figs, images, labels):
f.imshow(img.reshape((28, 28)).asnumpy())
f.set_title(lbl)
f.axes.get_xaxis().set_visible(False)
f.axes.get_yaxis().set_visible(False)
# 读取小批量
batch_size = 256
transformer = gdata.vision.transforms.ToTensor()
if sys.platform.startswith('win'):
num_workers = 0 # 0表示不用额外的进程来加速读取数据
else:
num_workers = 4
train_iter = gdata.DataLoader(mnist_train.transform_first(transformer),
batch_size, shuffle=True,
num_workers=num_workers)
test_iter = gdata.DataLoader(mnist_test.transform_first(transformer),
batch_size, shuffle=False,
num_workers=num_workers)
由于并未按照书中方法使用FashionMNIST数据集,故将我使用FashionMNIST数据集的方法展示如下:
FashionMNIST数据集下载地址:https://github.com/zalandoresearch/fashion-mnist
在data数据集中会有FashionMNIST数据集(需要解压缩):
此方法参考链接:https://blog.csdn.net/CBCZJL/article/details/104414904
打开压缩文件
def data_load(path, kind):
images_path = os.path.join(path,'%s-images-idx3-ubyte.gz' % kind)
labels_path = os.path.join(path,'%s-labels-idx1-ubyte.gz' % kind)
with gzip.open(labels_path,'rb') as lbpath:
labels = np.frombuffer(lbpath.read(),dtype=np.uint8, offset=8)
with gzip.open(images_path,'rb') as impath:
images = np.frombuffer(impath.read(),dtype=np.uint8, offset=16).reshape(len(labels),784)
return images, labels
读取转化数据
X_train, y_train = data_load('E:/FashionMNIST','train')
X_test, y_test = data_load('E:/FashionMNIST','t10k')
X_train_tensor = torch.from_numpy(X_train).to(torch.float32).view(-1,1,28,28)*(1/255)
X_test_tensor = torch.from_numpy(X_test).to(torch.float32).view(-1,1,28,28)*(1/255)
y_train_tensor = torch.from_numpy(y_train).to(torch.float32).view(-1,1)
y_test_tensor = torch.from_numpy(y_test).to(torch.float32).view(-1,1)
mnist_train = torch.utils.data.TensorDataset(X_train_tensor, y_train_tensor)
mnist_test = torch.utils.data.TensorDataset(X_test_tensor, y_test_tensor)
batch_size = 256
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False)
softmax回归的简洁实现
%matplotlib inline
import d2lzh as d2l
from mxnet import gluon, init
from mxnet.gluon import loss as gloss, nn
# 获取和读取数据
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 定义和初始化模型
net = nn.Sequential()
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))
# softmax和交叉熵损失函数
loss = gloss.SoftmaxCrossEntropyLoss()
# 定义优化算法
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1})
# 训练模型
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, trainer)