import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
# simpleNet类
class simpleNet:
def __init__(self):
self.W = np.random.rand(2, 3) # 随机形状为2*3的权重参数
def predict(self, x): # 方法一:预测
return np.dot(x, self.W) #表示输入 x 与权重 W 之间的点积,这相当于神经网络的前向传播。
def loss(self, x, t): # 方法二:求损失函数值 x接受输入数据,t为真实值标签
z = self.predict(x)
y = softmax(z) # softmax的输出总和=1,可将softmax函数输出的结果称为概率
loss = cross_entropy_error(y, t) # 交叉熵损失函数,值越小,表示越接近真实值
return loss
net = simpleNet()#生成
print(net.W) #输出权重参数
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
print(np.argmax(p))#最大的索引值
t=np.array([0,0,1])#正确标签为2
print(net.loss(x,t))#求损失函数值
#求梯度:
def f(W): #f(W)是为参数W可以在求权重函数中使用的兼容参数,类似于将W重构成f(W)?
return net.loss(x,t)
dW=numerical_gradient(f,net.W)
print(dW)
common中用到的的function和gradient
import numpy as np
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # 溢出对策
return np.exp(x) / np.sum(np.exp(x))
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
代码详解:
第一段:simpleNet
这段代码实现了一个简单的神经网络(simpleNet
类),并展示了如何计算神经网络的预测、损失函数值以及权重的梯度。接下来,我将逐行解释代码的作用和它的核心概念。
1. 导入模块
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
import sys, os
: 导入sys
和os
模块,允许你在程序中操作系统路径、环境变量等。sys.path.append(os.pardir)
: 将父目录添加到模块搜索路径中,以便可以访问父目录中的模块。os.pardir
是上级目录的路径。import numpy as np
: 导入 NumPy 库,常用于数组操作和数学计算。from common.functions import softmax, cross_entropy_error
: 从common.functions
模块中导入softmax
和cross_entropy_error
函数。softmax
将网络的输出转化为概率分布,cross_entropy_error
用于计算交叉熵损失。from common.gradient import numerical_gradient
: 从common.gradient
模块中导入numerical_gradient
函数,用于计算梯度。
2. simpleNet
类
class simpleNet:
def __init__(self):
self.W = np.random.rand(2, 3) # 随机形状为2*3的权重参数
class simpleNet:
定义了一个名为simpleNet
的类,这个类是简单的神经网络模型。def __init__(self):
构造函数初始化模型参数。self.W = np.random.rand(2, 3)
: 初始化权重W
,它是一个 2×32 \times 3 的随机矩阵,表示有 2 个输入和 3 个输出神经元。
3. predict
方法
def predict(self, x): # 方法一:预测
return np.dot(x, self.W) # 表示输入 x 与权重 W 之间的点积,这相当于神经网络的前向传播。
def predict(self, x):
定义了一个方法predict
,用于计算神经网络的输出。return np.dot(x, self.W)
: 计算输入x
和权重矩阵W
的点积。点积相当于神经网络的前向传播过程,得出每个神经元的激活值。
4. loss
方法
def loss(self, x, t): # 方法二:求损失函数值 x 接受输入数据,t 为真实标签
z = self.predict(x)
y = softmax(z) # softmax的输出总和=1,可将 softmax 函数输出的结果称为概率
loss = cross_entropy_error(y, t) # 交叉熵损失函数,值越小,表示越接近真实值
return loss
def loss(self, x, t):
定义了一个计算损失的函数。x
是输入数据,t
是真实标签。z = self.predict(x)
: 调用predict
方法计算输入x
的预测值z
。y = softmax(z)
: 使用softmax
函数将输出z
转化为概率分布。softmax
函数将模型的原始输出转化为每个类别的概率。loss = cross_entropy_error(y, t)
: 使用交叉熵损失函数计算预测概率y
和真实标签t
之间的差异。交叉熵损失值越小,表示预测越接近真实值。return loss
: 返回损失值。
5. 实例化并测试网络
net = simpleNet() # 生成 simpleNet 类的实例
print(net.W) # 输出权重参数
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
print(np.argmax(p)) # 打印最大值的索引
net = simpleNet()
: 创建一个simpleNet
类的实例,初始化网络的权重。print(net.W)
: 打印权重W
,它是一个 2×32 \times 3 的随机矩阵。x = np.array([0.6, 0.9])
: 定义输入数据x
,它是一个包含两个元素的数组。p = net.predict(x)
: 使用predict
方法计算输入x
的预测结果p
。print(p)
: 打印预测结果p
。print(np.argmax(p))
: 打印p
中最大的值的索引。np.argmax(p)
返回数组p
中最大元素的索引,通常用于分类任务,表示预测的类别。
6. 计算损失
t = np.array([0, 0, 1]) # 正确标签为2(即第三类)
print(net.loss(x, t)) # 求损失函数值
t = np.array([0, 0, 1])
: 定义真实标签t
,这里标签为[0, 0, 1]
,表示类别 2 的独热编码。print(net.loss(x, t))
: 计算输入数据x
与标签t
之间的交叉熵损失,并打印出来。
7. 计算梯度
def f(W): # f(W) 是一个可以在求权重函数中使用的兼容参数,类似于将 W 重构成 f(W)?
return net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW)
def f(W):
定义了一个函数f(W)
,该函数用于计算给定权重W
下的损失值。这个函数将net.loss(x, t)
封装在其中,并接受W
作为参数。dW = numerical_gradient(f, net.W)
: 使用numerical_gradient
函数计算损失函数f(W)
对权重W
的数值梯度。numerical_gradient
会通过数值差分方法计算梯度。print(dW)
: 打印计算得到的梯度dW
,它表示每个权重参数对于损失函数的敏感程度。
总结
这段代码展示了一个简单的神经网络的实现,包括了:
- 网络的初始化(权重的随机生成)。
- 前向传播过程(通过点积和
softmax
函数得到预测)。 - 损失函数的计算(使用交叉熵损失)。
- 计算损失对权重的梯度(使用数值梯度)。
通过这些步骤,代码展示了如何用 Python 构建一个简单的神经网络,并计算其梯度,从而为后续的优化(如梯度下降)做好准备。
function:激活函数softmax和求交叉熵误差
这段代码实现了两个常用的函数:softmax 和 cross_entropy_error,它们在神经网络中用于分类任务。接下来我会逐行解释这两个函数的作用和实现细节。
1. Softmax 函数
def softmax(x):
if x.ndim == 2:
x = x.T # 转置,使得每一列代表一个样本
x = x - np.max(x, axis=0) # 减去每列的最大值,避免溢出
y = np.exp(x) / np.sum(np.exp(x), axis=0) # 对每列应用 softmax
return y.T # 转置回原来的形状
x = x - np.max(x) # 溢出对策,减去最大值避免指数溢出
return np.exp(x) / np.sum(np.exp(x)) # 计算softmax
解释:
-
softmax 函数将一个向量或者矩阵(代表每个类的分数)转换成概率分布。它常用于神经网络的输出层,将原始的网络输出(称为“logits”)转换为类的概率。
-
if x.ndim == 2:
:检查输入x
的维度。如果x
是二维数组(形状为batch_size x class_num
),即处理的是多个样本(一个小批量的数据),则执行以下代码:x = x.T
:转置矩阵,使得每一列代表一个样本的数据。x = x - np.max(x, axis=0)
:减去每列的最大值,防止在计算指数时溢出。因为大数的指数值会导致计算中的溢出。y = np.exp(x) / np.sum(np.exp(x), axis=0)
:对每列的值应用 softmax 函数,得到每个类别的概率。np.exp(x)
对每个元素求指数,np.sum(np.exp(x), axis=0)
是对每列进行求和。return y.T
:最后将矩阵转置回原来的形状。
-
x = x - np.max(x)
:如果x
是一个一维数组(单个样本),则直接减去最大值,避免指数计算时的溢出。 -
return np.exp(x) / np.sum(np.exp(x))
:计算 softmax 输出,返回每个类别的概率。
Softmax 特点:
- 输入值经过 softmax 函数后,输出的概率值总和为 1。
- 它将每个输出值转换为一个介于 0 和 1 之间的值,表示该类的预测概率。
2. 交叉熵损失函数(Cross-Entropy Error)
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size) # 如果标签是1D,转换为2D
y = y.reshape(1, y.size) # 如果输出是1D,转换为2D
if t.size == y.size:
t = t.argmax(axis=1) # 将标签转换为类索引(对于one-hot编码)
batch_size = y.shape[0] # 获取批次大小
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size # 计算平均交叉熵损失
解释:
-
if y.ndim == 1:
:检查y
是否是一维数组。如果y
是一维数组,表示只有一个样本,接着将标签和预测的y
重塑为二维数组,便于处理。 -
if t.size == y.size:
:检查t
和y
的尺寸。如果标签t
和预测概率y
的尺寸相同,则说明标签是 one-hot 编码。例如,标签为[0, 0, 1]
,表示类别 2。argmax(axis=1)
将标签从 one-hot 编码转换为类别索引。即t.argmax(axis=1)
变为2
。 -
batch_size = y.shape[0]
:获取样本的批次大小,即y
的第一维的大小。 -
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
:np.arange(batch_size)
:生成批次大小的数组(从 0 到batch_size-1
),表示样本的索引。y[np.arange(batch_size), t]
:从预测概率y
中选取对应类别t
的概率值。t
是每个样本的类别索引,y
是一个矩阵,y[i, t[i]]
会返回样本i
在类别t[i]
上的预测概率。np.log(y[np.arange(batch_size), t] + 1e-7)
:对每个样本的预测概率取对数,1e-7
是防止概率值为 0,导致对数函数计算出无穷大。np.sum(...)/batch_size
:求和并计算平均值,返回批次的平均交叉熵损失。
交叉熵损失:
交叉熵损失函数衡量了预测概率分布与实际标签之间的差异,特别适用于分类问题。它的值越小,表示模型的预测越准确。对于二分类任务和多分类任务,交叉熵是常用的损失函数。
总结:
- Softmax 函数将神经网络的原始输出转化为概率分布,用于分类问题。
- Cross-Entropy Error 计算模型的输出概率与实际标签之间的差异,用于量化模型的预测误差。
这两个函数常常一起使用,尤其是在多类分类任务中,softmax 用于生成分类概率,交叉熵损失用于衡量预测与真实标签的差异。
求函数的梯度值
这段代码实现了 数值梯度 的计算。数值梯度是通过有限差分方法来近似计算梯度的,常用于验证反向传播算法的正确性。接下来我将详细解释这段代码的每一部分。
1. numerical_gradient(f, x)
函数的作用
-
函数输入:
f
: 目标函数。它接受一个输入x
,并返回该输入对应的损失值。x
: 参数x
,是我们要计算梯度的输入,通常是模型的参数(如权重和偏置)。
-
函数输出:
grad
: 数值梯度,表示目标函数对每个参数x
的导数,形状与x
相同。
2. 初始化和设置
h = 1e-4 # 设定一个小的值,用于计算有限差分
grad = np.zeros_like(x) # 创建一个与x相同形状的零矩阵,用于存储计算出来的梯度
h = 1e-4
: 设定一个很小的值h
,用于在计算梯度时做微小的偏移。h
是差分方法中的步长,用来近似导数。grad = np.zeros_like(x)
: 创建一个与x
形状相同的零矩阵grad
,用来存储计算得到的梯度。
3. 使用 np.nditer
迭代 x
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
np.nditer(x)
是 NumPy 中的一个迭代器,用于遍历x
中的每一个元素。flags=['multi_index']
: 允许获取每个元素的多维索引。op_flags=['readwrite']
: 允许对x
中的元素进行读取和修改。
4. 计算每个元素的数值梯度
while not it.finished:
idx = it.multi_index # 获取当前元素的多维索引
tmp_val = x[idx] # 保存当前元素的值
x[idx] = float(tmp_val) + h # 将当前元素加上h
fxh1 = f(x) # 计算 f(x + h)
x[idx] = tmp_val - h # 将当前元素减去h
fxh2 = f(x) # 计算 f(x - h)
grad[idx] = (fxh1 - fxh2) / (2 * h) # 通过中心差分法计算梯度
x[idx] = tmp_val # 还原当前元素的值
it.iternext() # 移动到下一个元素
-
while not it.finished:
: 这是一个循环,直到迭代器遍历完x
中的所有元素。 -
idx = it.multi_index
: 获取当前元素的索引。 -
tmp_val = x[idx]
: 保存当前元素的原始值,以便在计算后将其还原。 -
x[idx] = float(tmp_val) + h
: 将当前元素的值加上h
,然后调用目标函数f(x)
计算其值fxh1
。 -
fxh1 = f(x)
: 计算函数在x + h
处的值。 -
x[idx] = tmp_val - h
: 将当前元素的值减去h
,然后计算函数f(x)
在x - h
处的值fxh2
。 -
fxh2 = f(x)
: 计算函数在x - h
处的值。 -
grad[idx] = (fxh1 - fxh2) / (2 * h)
: 使用中心差分法计算梯度。中心差分法通过(f(x+h) - f(x-h)) / (2 * h)
近似计算导数。 -
x[idx] = tmp_val
: 还原当前元素的值,以便继续计算其他元素的梯度。 -
it.iternext()
: 移动到下一个元素,继续计算梯度。
5. 返回结果
return grad
grad
是一个与x
形状相同的矩阵,包含了x
中每个元素的数值梯度。
数值梯度的原理
数值梯度通过有限差分方法来近似计算。对于给定的函数 f(x)
,某个元素 x_i
的导数可以通过以下公式来近似:
其中:
x + h
和x - h
分别表示对x_i
添加和减去微小偏移量h
后的值。(f(x + h) - f(x - h)) / (2h)
是使用中心差分法近似计算的梯度。
使用场景
数值梯度主要用于验证反向传播算法的正确性。在训练神经网络时,计算梯度是一个关键步骤。反向传播算法是基于链式法则计算的梯度,而数值梯度可以作为一种“手工”计算梯度的方式,帮助我们检查反向传播是否实现正确。
总结
- 这个
numerical_gradient
函数通过对x
中每个元素添加和减去一个小的h
来计算数值梯度,采用了中心差分法。 - 数值梯度对于调试和验证梯度计算的正确性非常有用,特别是在训练神经网络时。