1、跟随梯度(数值微分)
不需要随机寻找权重,可以直接计算最好的方向,这个方向就是损失函数的梯度。梯度就是在每个维度上偏导数形成的向量。
计算梯度有两种方法,一种是缓慢的近似方法,即数值梯度法,另一种是分析梯度法,需要使用微分,虽然计算迅速,结果精确但是实现时容易出错
对上述公式编写一个函数 def eval_numerical_gradient(f,x) h = 0.00001 return (f(x+h))/h
实际中使用中心插值公式比较好
def eval_numerical_graient(f,x): h = 0.0001 return (f(x+h)-f(x-h))/(2*h)
梯度
def numerical_gradient(f,x): h = 0.0001 grad = np.zeros_like(x) for idx in range(x.size) tmp_val = x[idx]#f(x+h)的计算 x[idx]=tmp_val + h fxh1 = f(x)#F(x-h)的计算 x[idx]=tmp_val - h fxh2 =f(x) grad[idx]=(fxh1-fxh2)/(2*h) x[idx]= tmp_val return grad
函数numericalgradient(fx)中的实现看上去比较复杂,其实就是针对x中的每一个都去做一下单个的 eval numerical gradient运算罢了。其中,np.zeros_like(x)会生成一个形状与x相同且所有元素都为0的数组
梯度下降算法
def gradient_descent(f,init_x,lr=0.01,step_num=100): x =init x for i in range(step_num): grad = numerical_gradient (f,x) x -= lr*grad return x
其中,参数/是要进行最优化的函数,init_x是初始值,lr表示learningrate(其是个超参数,需要自己调整),step_num 代表梯度下降法的重复数。
2、np.nditer是 NumPy 中一个强大的迭代器,用于高效地遍历 ndarray
(多维数组)的元素。它允许我们以灵活和高效的方式访问数组中的每个元素,支持多维数组的遍历。
遍历顺序:可以指定遍历元素的顺序。默认情况下是 C 风格(行优先)顺序,但可以通过设置 order
参数为 'F'
来使用 Fortran 风格(列优先)。
for x in np.nditer(arr, order='F'): print(x)
3、op_flags=['readwrite"]表示不仅可以对a进行read(读取),还可以 write(写入),即相当于在创建这个迭代器的时候,我们就规定好了其具有哪些权限。
4、 print(it.multi_index)表示输出元素的索引,可以看到输出的结果都是 index。
5、it.iternext()表示进入下一次迭代,如果不加这一条语句的话,输出的结果就会一直都是(0,0)
定义一个简单的神经网络,在_init_初始化方法里,我们初始化一个符合高斯分布的W矩阵,其大小为(2.3),并且为了保证效果的可靠性,设置了random:seed(0)方法以保证每一次随机的W都是一致的;另外在Forward方法里,实现了前向传播:在Loss方法里,通过Forward 方法得到预测过softmax方法转为相加之和为1的概率矩阵,之后再通过cross_entropy_error 方法计算损失值loss。目的就是通过W的调整(利用梯度下降法)使得值不断减少。
class simpleNet: def_init_(self): np.random.seed(0 ) self.w = np.random.randn(2 ,3) def forward(self,x): return np.dot(x,self.W) def loss(self,x,y): z = self.forward(x) p =_softmax(z) loss = cross_entropy_error(p,y) return loss
#观察随机的W是哪些值
net = simpleNet()
print(net.w)
X= np.array([[0,6,0.9]])
p = net,predict(X)
print('预测值为:'P)
print('预测的类别为:',np.argmax(p))
#此时的损失值 Loss:
y=np.array([0,0,1])#输入正确类别
print(net.loss(x,y))
#基于数值微分的梯度下降算法来进行优化
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
def gradient_descent(f,init_x,lr=0.01,step_num=1000):
x =init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= lr*grad
return x
f = lambda w: net.loss(x,y)
dw = gradient_descent(f,net.W) #主要需要更新的是W
print(dw)
5、基于数值微分的反向传播
尝试使用基于数值微分的方法实现手写数字识别、并使用mini-batch提升计算性能,使用的优化方法是随机梯度下降法(SGD)
激活函数relu的实现流程
def _relu(in_data): return np.maximum(0,in_data)
激活函数Softmax的实现流程:
def _softmax(x): if x.ndim == 2: c = np.max(x,axis=1) x=x.T-c#溢出对策 y=np.exp(x)/np.sum(np.exp(x),axis=0) return y.T c = np.max(x) exp_x = np.exp(x-c) return exp_x/np.sum(exp_x)
计算基于小批次的损失函数的损失值,p为预测值。y为真实值
def cross_entropy_error(p,y): delta = 1e-7 batch_size = p.shape[0] return -np.sum(y *np,log(p + delta))/ 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
6、定义神经网络
首先在类TwoLayerNet中定义第一个初始化函数,函数接受4个参数:input_size 代表输入的神经元个数;hidden size 代表隐藏层神经元的个数;output_size 代表输出层神经元的个数;最后的weight init std 则是为了防止权重太大其默认值为 0.01。那么对于手写数字识别 MNIST来说:input_size 的值就可以设置为 784原因是28*28=784;而output_size 则可以设置为 10,因为其是一个 10 分类的问题(对应于数字 0~9)params是一个字典,里面存储的是权重以及偏移量的值,W1的形状是(inputsize,hidden size),W2 的形状是(hidden_size,output size)
def init (self, input_size,hidden_size,output_size, weight_init_std=0.01):# 初始化权重 self.params ={} self,params[ 'W1']=weight_init_std * np.random.randn(input_size, hidden_size) self.params['bl']= np.zeros (hidden_size) self.params[ 'W2']= weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2']= np.zeros(output_size)
对应于这个TwoLayerNet类中的前向传播算法:前向传播的实现方式已在本章的前向传播中重点阐述过,其核心思想就是通过矩阵运算计算出结果,再通过激活函数丰富其“表达能力”,最后通过sofmax函数计算概率输出结果。
def predict(self, x): W1,W2 = self.params['w1'], self.params['w2'] b1,b2 = self.params['b1'], self.params['b2') a1 = np.dot(x,W1)+ b1 z1 =_relu(a1) a2=np.dot(z1,W2)+ b2 p=_softmax(a2) return p
计算损失值
#x:输入数据,y:监督数据 def loss(self,x,y): p = self.predict(x) return cross_entropy_error(p, y)
参数W是一个伪参数,另外因为名字重复问题,numerical_gradient 函数与类中的 numerical_gradient重名了,类中函数调用的是我们之前实现的数值微分的函数,而非类函数自调用。
#x:输入数据,Y: 监督数据 def numerical_gradient(self,x,y) loss_W=lambda w:self.loss(x,y) grads ={} grads【'w1']= numerical _gradient(loss_W, self .params['w1']) grads【'b1']= numerical_gradient(loss_W, self .params[ 'b1'] grads['w2']=numerical_gradient(loss_W, self .params['W2']) grads['b2']=numerical_gradient(loss_w, self .params['b2']) return grads
最后,我们来实现准确度函数。这个实现方式比较简单,对于p来说就是预测值的短阵,我们取每一行的最大值的索引与真实值中每一行最大值的索引,如果两者的值相同,则说明预测准确。
def accuracy(self,x,t): p= self.predict(x) p = np.argmax(y,axis=1) y =np,argmax(t,axis=1) accuracy = np.sum(p == y)/ float(x,shape[0]) return accuracy
查看损失值。通过PyTorch 导人 MNIST数据源,接着,我们需要定义训练集和测试集。
x_train = train_dataset.train_data.numpy(),reshape(-1 28*28) y_train_tmp = train_dataset.train.labels,reshape(train_dataset.train labels.shape[0],1) y_train = torch.zeros(y_train_tmp.shape[0], 10).scatter_(1, y_train _tmp, 1).numpy() x_test = test_dataset.test_data.numpy().reshape(-1,28*28) y_test_tmp = test_dataset.test_labels.reshape(test _dataset.test labels.shape[0],1) y_test = torch.zeros(y_test_tmp.shape[0],10).scatter_(1,y_test_tmp, 1).numpy()
初始化超参数,输出loss
#超参数 iters_num = 1000 # 适当设定循环的次数 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.001 network = TwoLayerNet(input_size = 784,hidden_size=50,output_size=10) for i in range(iters_num): batch_mask = np.random.choice(train_size,batch_size) x_batch = x_train[batch_mask] y_batch = y_train[batch_mask] grad = network.numerical_gradient(x_batch,y_batch) for key in ('W1','b1','W2','b2'): network.params[key] -= learning_rate*grad[key] #记录学习过程 loss = network.loss(x_batch,y_batch) if i % 100 == 0: print(loss)
7、基于测试值的评价
过拟合是指:基于训练集的数据,神经网络可以正确识别,但是对于训练数据集以的数据(比如测试集),就无法识别了。神经网络学习的目的就是需要掌握泛化能力,因此要评价神经网络的泛化能力,就须使用不包含在训练数据中的数据。
epoch 是指,当一个完整的数据集通过了神经网络一次并且返回了一次时,这个过程称为一个 epoch。然而,当一个 epoch 对于计算机而太庞大的时候,就需要将它分成多个小块。下面我们来举例说明,对于10000笔训练数用大小为100的 batch size 进行学习的时候,重复随机梯度下降法 100次,所有的训练数就都学习了一次,此时100次就是一个epoch。
为什么要使用多于一个epoch?
在神经网络中,不仅传递完整的数据集一次是不够的,而且我们还需要将完整的数据集在同样的神经网络中传递多次。但是请记住,我们使用的是有限的数据集,并且我们使用的是一个迭代过程,即梯度下降。因此仅仅更新权重一次或者说使用一个epoch是不够的。随着epoch数量的增加,神经网络中权重的更新次数也在增加,曲线从欠拟合变为过拟合。
1)batchsize:批大小。在深度学习中,一般采用SGD训练,即每次训练都在训练集中提取 batchsize 个样本进行训练。
2)iteration:1个iteration 等于使用 batchsize 个样本训练一次。
3)epoch:1个epoch等于使用训练集中的全部样本训练一次。
增加epoch后 train_size = x_train.shape[0] iters_num = 600 learning_rate = 0.001 epoch = 5 batch_size = 100 network = TwoLayerNet(input_size = 784,hidden_size=50,output_size=10) for i in range(epoch): print('current epoch is :', i) for num in range(iters_num): batch_mask = np.random.choice(train_size,batch_size) x_batch = x_train[batch_mask] y_batch = y_train[batch_mask] grad = network.numerical_gradient(x_batch,y_batch) for key in ('W1','b1','W2','b2'): network.params[key] -= learning_rate*grad[key] loss = network.loss(x_batch,y_batch) if num % 100 == 0: print(loss) print(network.accuracy(x_test,y_test))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律