随笔 - 70  文章 - 0  评论 - 0  阅读 - 1861 

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))
复制代码

 

 

 

posted on   风起-  阅读(20)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示