缘由:在学习深度学习入门:基于Python的理论与实现时,没有看懂第四章交叉熵误差函数,当监督数据采用非one-hot表示时。所以做了一个小测试案例,终于明白了,花了我几个小时的时间,学习不能着急呀。
一、结果和细节分析
注:使用mnist手写数字体数据集。
(1)、交叉熵误差函数
输入数据只有一个时:y和t都是1*n的矩阵,只需要求一行的相应和就可以得到这个数据对应的误差。
采用mini_batch批处理N个数据时:y有多行多列,每一行代表一个数据。计算第一行时,n先取0表示第一行,k=1,2,3,…来求解第一行相应的和;之后n=1表示第二行,k=1,2,3,…计算第二行相应的和;以此类推得到每一行的和,再将上一步求解的每一行的和全部相加,最后除于数据量N,再取负号得到平均每个数据对应的误差。
(2)、监督数据采用one-hot表示
根据交叉熵误差公式,t矩阵与y矩阵做运算时,t中为0的位置计算之后仍然为0,t中只有为1的位置才是有效计算值。
重点:t矩阵中元素为1的位置对应的下标索引就表示该行对应的图片的正确数字是多少。比如第一行表示正确数字是1, 第二行表示正确数字是3,第三行表示正确数字是0。也就是说只有t中正确数字
所对应位置才会用来计算损失函数的值。也就是y中只有(0,1),(1,3),(2,0)
位置对应的数据才用来计算误差。
实现代码:
def cross_entropy_error_one_hot(self, y, t):
# 监督数据采用one-hot表示
if y.ndim == 1:
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
(3)、监督数据采用非one-hot表示
采用上面的例子,y不变,t形式改变了,t里面存储的就是正确的数字,即t=[1, 3, 0]
。
这个先来代码,再解释为啥子这么写嘛。
def cross_entropy_error(y, t):
if y.ndim == 1:
# 变成二维
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
batch_size = y.shape[0]
return - np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
解释第一步:
y[np.arange(batch_size), t]
假设batch_size=3,这里np.arange(batch_size)
会生成数组[0,1 ,2]。之后[np.arange(batch_size), t]
会将[0,1,2]
和t中正确的解数字[1,3,0]
进行对应组合,得到坐标(0,1)
,(1,3)
,(2,0)
。最后用这些坐标到y矩阵里获取对应位置的元素值,比如y(0,1)取第一行第二列的值0.90。所以这行代码会生成numpy数组[0.90,0.01,0.90]
。之后用这个数组取对数,求和,除于数据量,取负号。
总结:殊途同归,最后都是用[0.90,0.01,0.90]计算。采用one-hot时,t里面的1对应的位置提供了行坐标和列坐标去取y里面的值;采用非one-hot时,np.arange(batch_size)提供了行坐标,t提供列坐标,组合之后去取y里面的值。所取到的数据都是一样的。
(4)、合并两个代码
def cross_entropy_error(self, y, t):
"""
通用的交叉熵误差函数
思想:将one-hot表示的t变为非one-hot表示
"""
if y.ndim == 1:
# 变成二维
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
# y.size会求解y里面总共有多少个元素
# if成立,说明t使用one-hot形式表示,需要变为非one-hot表示
if y.size == t.size:
t = t.argmax(axis=1) # 返回一个由每一行的最大值对应的下标索引组成的numpy数组
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
二、源代码
# coding:utf-8
import os, sys
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
import numpy as np
from common.functions import softmax
class simpleNet(object):
"""
只要一层的网络
"""
def __init__(self):
# 初始化权重和偏置
self.w = np.random.randn(784,10) # 高斯分布来初始化权重
self.b = np.zeros(10) # 偏置全部设置为0
def predict(self, x):
y = np.dot(x, self.w)
y += self.b
return y
def cross_entropy_error_one_hot(self, y, t):
# 监督数据采用one-hot表示
if y.ndim == 1:
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
def cross_entropy_error_not_one_hot(self, y, t):
print("in cross_entropy_error_not_one_hot: ")
if y.ndim == 1:
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
# 先观察y和t的形状
print("shape of y: ", y.shape)
print("shape of t: ", t.shape)
batch_size = y.shape[0] # 一次批处理的数据量
print("batch_size:" ,batch_size)
# return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
# 1. 处理y[np.arange(batch_size), t]
tmp = y[np.arange(batch_size), t]
print("t: ", t, "\n")
print("y: ", y, "\n")
print("tmp: ", tmp)
# 2. 取对数
tmp2 = np.log(tmp)
print("\ntmp2:", tmp2)
# 3. 求和在除数据量,可以得到平均单个数据的损失
loss = -np.sum(tmp2) / batch_size
print("\nloss: ", loss)
return loss
def cross_entropy_error(self, y, t):
"""
通用的交叉熵误差函数
思想:将one-hot表示的t变为非one-hot表示
"""
if y.ndim == 1:
# 变成二维
y = y.reshape(1, y.size)
t = t.reshape(1, t.size)
# y.size会求解y里面总共有多少个元素
# if成立,说明t使用one-hot形式表示,需要变为非one-hot表示
if y.size == t.size:
t = t.argmax(axis=1) # 返回一个由每一行的最大值对应的下标索引组成的numpy数组
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
if __name__ == "__main__":
# 监督数据(正确解标签)label_train和label_test采用非one-hot表示
(img_train, label_train), (img_test, label_test) = load_mnist(normalize=True, one_hot_label=False)
# print("label_train: ", label_train) # label_train的值就是正确解对应的数字
net = simpleNet()
# 随机选择数据
train_size = img_train.shape[0]
batch_size = 10 # 选择10个数据
batch_mask = np.random.choice(train_size, batch_size)
img_train = img_train[batch_mask]
t = label_train[batch_mask]
# 训练数据正向传播
y = net.predict(img_train)
y = softmax(y) # 通过网络正向传播得到的y值
print("in main: ")
print(y.shape)
print(t.shape)
print('-'*40, '\n')
loss = net.cross_entropy_error_not_one_hot(y, t)
运行结果分析: