分类 深度学习 下的文章

正者,正也。其心以为不然者,天门弗开矣。

作为《深度学习入门》的阅读笔记,本文简略概述深度学习的学习过程,损失函数,梯度法相关的内容,并辅以python实现

需要事先了解的

本文并不是从零开始的,需要实现了解

  • 蜻蜓点水python

    • 蜻蜓点水python_dlc
  • 简略感知机
  • 简易神经网络_推理和正向传播

不同的学习

首先,学习有不同的类型,人想,机器学习,深度学习;

然后,学习的基础是特征向量,机器学习中特征量还是人想出来的,深度学习则没有这一步骤,基本实现了全程机器管理
image-20251018220933182.png

损失函数

有道是能观测就能干涉,能干涉就能控制;那么应该观测什么变量才能知道学习的进度,从而干涉和控制学习的过程呢?这里直接给出答案:损失函数。损失函数理论上可以使用任何函数,这里使用均方误差交叉熵误差

这里有一个问题,为什么不使用精度(即准确度)来最为指标呢?首先说结论:在进行神经网络的学习时,不能将识别精度作为指标,并且对于大部分导数(后面会提到)为0的函数,都不能作为指标,会导致学习无法进行(无法更新参数),原因在了解学习的过程后自然能理解。

均方误差

直接公式,实现如下:
image-20251018223037143.png

  • y_k是前向传播的输出
  • t_k是监督数据的标签(一般是one-hot表示)
  • k是维度
import numpy as np
def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

print(mean_squared_error(np.array([0.1,0.7,0.2]),np.array([0,1,0]))) #判断正确
print(mean_squared_error(np.array([0.1,0.7,0.2]),np.array([1,0,0]))) #判断错误

#0.07000000000000002
#0.67

可以看出,如果判断正确,损失函数会是一个较小的数字,如果判断失败,损失函数会是一个较大的数字。

交叉熵误差

直接公式,实现如下:
image-20251018224310892.png

  • y_k是前向传播的输出
  • t_k是监督数据的标签(一般是one-hot表示)
  • k是维度
def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))

print(cross_entropy_error(np.array([0.1,0.7,0.2]),np.array([0,1,0])))
print(cross_entropy_error(np.array([0.1,0.7,0.2]),np.array([1,0,0])))

#0.3566748010815999
#2.302584092994546
  • 其中delta是为了避免log的参数有0的情况

Mini-Batch学习

和推理的过程一样,学习也可以用批处理的方法完成,不过实现上有些区别;这里以交叉熵误差为例

第一个是损失函数公式的变化:
image-20251018230403347.png

可见其实就是取平均,实现如下:

def cross_entropy_error2(y, t):
    batch_size = 1
    if y.ndim > 1:
        batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

#测试不同维度
print(cross_entropy_error2(np.array([0.1,0.7,0.2]),np.array([0,1,0])))
print(cross_entropy_error2(np.array([0.1,0.7,0.2]),np.array([1,0,0])))
print(cross_entropy_error2(np.array([[0.1,0.7,0.2],[0.1,0.7,0.2]]),np.array([[0,1,0],[0,1,0]])))
print(cross_entropy_error2(np.array([[0.1,0.7,0.2],[0.1,0.7,0.2]]),np.array([[1,0,0],[1,0,0]])))

第二个是训练数据其实还是很多的,想一次性全部学习是高耗能甚至有些不现实的,所以需要使用np.random.choice抽取一个batch来学习

# 这里的路径按本地实际来
from dfs.dataset.mnist import load_mnist
import sys,os
sys.path.append(os.pardir)

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True,
normalize=False,one_hot_label=True)

# 输出各个数据的形状
print(x_train.shape) # (60000, 784)
# 输出各个数据的形状
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)

train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
print(batch_mask) # 输出抽选的索引
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

print(x_batch.shape) # (10, 784)
print(t_batch.shape) # (10,)


#(60000, 784)
#(60000, 10)
#(10000, 784)
#(10000, 10)
#[ 6681  4809 12952  4669  1444 20997 56685 39195 24206  8649]
#(10, 784)
#(10, 10)

第三如果标签不是one-hot表示,有那么一个特殊的实现方法,思路是虽然是求和,但只有正确项会有值

def cross_entropy_error2(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]
    print(y[np.arange(batch_size), t])
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

print(cross_entropy_error2(np.array([[0.1,0.7,0.2],[0.1,0.7,0.2]]),np.array([1,2])))
#[0.7 0.2]
#0.9830561067579126

另外有个概念会在后面用到这里先提出:

epoch是一个单位。一个epoch表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于 10000笔训练数据,用大小为 100笔数据的mini-batch进行学习时,重复随机梯度下降法100次,所有的训练数据就都被“看过”了 。此时,100次就是一个epoch。

导数和梯度

有了损失函数,就可以观测学习的进度,但是如何在此基础上对其进行控制呢?要做到这点需要解决三个问题,函数的值是会通过自变量的变化而变化的,改哪个变量会使损失函数变小?如何改?要改多少?

直接说结论:修改的变量是权重和偏置,依靠导数计算出的梯度来修改自变量,改多少依赖于人工设定的学习率

导数

不准确的说,导数就是x周围很小范围的斜率(中心差分),既不是x左边的斜率也不是x右边的斜率(前向差分);数值微分和真实的解析解必然是有所差距的。

求导数的实现如下:

def numerical_diff(f,x,h=1e-4):
    return (f(x+h)-f(x-h))/(2*h)
def func_test(x):
    return 2*x+1

numerical_diff(func_test,np.array([2,3]))

偏导数和梯度

多个变量的函数的导数就是偏导数,当然一个偏导数只能对其中一个变量来做,另外的变量就当作常数对待,称为函数对该变量的偏导数,这是解析解的做法;对于数值解则更加的简单一点,固定其他变量,只变化一个变量来求导数就是函数针对这个变量的偏导数。

把所有变量的偏导数组成向量,就是梯度,梯度指向的方向是函数变化(增加)率最大的方向。所以对其取负数,就是损失函数下降最快的方向。

实现如下:

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 test_func(x):
    return x[0]*x[0]+x[1]*x[1]

numerical_gradient(test_func,np.array([3.0,4.0]))
#array([6., 8.])

梯度下降法

按上述说的,按照梯度反方向更新参数就是梯度下降法,公式如下:

image-20251019112529214.png

其中偏导数前面的常数就是学习率,人为指定,规定学习的程度,过大过小都不好,这里默认0.01,实现如下:

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

def function_2(x):
    return x[0]*x[0]+x[1]*x[1]

init_x=np.array([-3.0,4.0])


gradient_descent(function_2,init_x,lr=0.1,step_num=100)
#array([-6.11110793e-10,  8.14814391e-10])

另外,像这种人为指定的参数,称为超参数,像是mini-batch的大小,输入的形状,学习的次数都是超参数

总结和实现

现在就可以继续上章,完成MINIST的学习步骤了,但是还有一些细节需要说明

神经网络的梯度法

在实际的神经网络中要求的是损失函数对于权重的梯度,也就是对权重矩阵中的每个成员求梯度,最后结果还是矩阵:

image-20251019120202053.png

对于实现而言,只要能保证矩阵的形状没有问题即可

学习算法的步骤

image-20251019120525911.png

步骤4则是重复1-3的步骤

而这种随机选取+计算梯度的方法称为:随机梯度下降法(SGD)

实现

先列出代码

# 这里的路径按本地实际来
import numpy as np
from dfs.dataset.mnist import load_mnist
import sys,os
sys.path.append(os.pardir)

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True,
normalize=False,one_hot_label=True)

# 输出各个数据的形状
#print(x_train.shape) # (60000, 784)
# 输出各个数据的形状
#print(t_train.shape) # (60000,10)
#print(x_test.shape) # (10000, 784)
#print(t_test.shape) # (10000,10)

train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
#print(batch_mask)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

print(x_batch.shape) # (10, 784)
print(t_batch.shape) # (10,10)


def cross_entropy_error(y, t):
    batch_size = 1
    if y.ndim > 1:
        batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c) #溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a /sum_exp_a
    return y

def sigmoid(x):
    return 1/(1+np.exp(-x))


class SigMoidLayer:
    def __init__(self,w,b,s="SigMoid"):
        self.layer_name = s
        self.w=w
        self.b = b
    def forward(self,x):
        a=np.dot(x,self.w)+self.b
        return sigmoid(a)

class OutputLayer2:
    def __init__(self,w,b,s="Ouput"):
        self.layer_name = s
        self.w=w
        self.b = b
    def forward(self,x):
        a=np.dot(x,self.w)+self.b
        return softmax(a)

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

class TestMNIST:
    # 正常情况下,权重或者偏置都应该是学习的结果,这里直接赋值只是一种假设
    def __init__(self):
        self.rate=0.01
        self.grads_b = {}
        self.grads_w = {}
        self.network_w = {}
        self.network_b = {}
        self.network_w['L1'] = np.random.randn(784,50)
        self.network_b['L1'] = np.zeros(50)
        self.network_w['L2'] = np.random.randn(50,100)
        self.network_b['L2'] = np.zeros(100)

        # 输出层
        self.network_w['L3'] = np.random.randn(100,10)
        self.network_b['L3'] = np.zeros(10)

        self.layers=['L1','L2']

    def forward(self,x):
        tmp=x
        for l in self.layers:
            tmp = SigMoidLayer(self.network_w[l],self.network_b[l],l).forward(tmp)
            #print(l,tmp)
        tmp = OutputLayer2(self.network_w['L3'],self.network_b['L3'],'L3').forward(tmp)
        return tmp

    def loss(self,x,t):
        return cross_entropy_error(self.forward(x),t)

    def grad(self,x,t):
        loss_W = lambda W: self.loss(x, t)

        self.grads_w={}
        self.grads_b={}

        self.grads_w['L1']=numerical_gradient(loss_W,self.network_w['L1'])
        self.grads_b['L1']=numerical_gradient(loss_W,self.network_b['L1'])
        print('L1 Done.')

        self.grads_w['L2']=numerical_gradient(loss_W,self.network_w['L2'])
        self.grads_b['L2']=numerical_gradient(loss_W,self.network_b['L2'])
        print('L2 Done.')
        self.grads_w['L3']=numerical_gradient(loss_W,self.network_w['L3'])
        self.grads_b['L3']=numerical_gradient(loss_W,self.network_b['L3'])
        print('L3 Done.')

    def update(self):
        self.network_w['L1']-=self.rate*self.grads_w['L1']
        self.network_b['L1']-=self.rate*self.grads_b['L1']
        self.network_w['L2']-=self.rate*self.grads_w['L2']
        self.network_b['L2']-=self.rate*self.grads_b['L2']
        self.network_w['L3']-=self.rate*self.grads_w['L3']
        self.network_b['L3']-=self.rate*self.grads_b['L3']



nett = TestMNIST()
#测试代码
#nett.forward(np.random.randn(100,784))

# 学习过程
mini_batch_size = x_train.shape[0]
select_size = 100
loss_point =[]
train_time=10000

for i in range(train_time):
    # 选择数据
    mask = np.random.choice(mini_batch_size, select_size)
    x_batch = x_train[mask]
    t_batch = t_train[mask]
    print(x_batch.shape,t_batch.shape)
    print("Loop",i+1)
    # 损失函数
    loss_p=nett.loss(x_batch,t_batch)
    loss_point.append(loss_p)
    print("Loss:",loss_p)
    # 计算梯度
    nett.grad(x_batch,t_batch)
    # 更新参数
    nett.update()



#推理过程
batch_size=100
accuracy_cnt=0

for i in range(0,len(x_test),batch_size):
    x_batch = x_test[i:i+batch_size]
    t_batch = t_test[i:i+batch_size]

    y_batch = nett.forward(x_batch)
    #print(y_batch.shape)
    p=np.argmax(y_batch,axis=1)
    t=np.argmax(t_batch,axis=1)
    accuracy_cnt+=(np.sum(p==t)/batch_size)

print("Accuracy:" + str(float(accuracy_cnt) / (len(x_test)/batch_size)))


import matplotlib.pyplot as plt
 # 生成数据
xx = np.arange(0, train_time)
# 绘制图形
plt.plot(xx, loss_point)
plt.show()
  • 首先要注意的是矩阵的形状一定要“严丝合缝”,这是做矩阵计算的基础

    • np.zeros(50)是创建0矩阵的方法,第一个参数是shape
  • 然后当运行该代码时可以发现一点,就是随机梯度下降的处理速度十分缓慢,所以在之后会需要反向传播这一方法来加速梯度的计算

评价方法

评价一个神经网络的方法就是评价它的泛化能力,也就是对于陌生数据的识别精度:

  • 如果只对训练数据识别精度较好,则是发生了过拟合
  • 一般来说,当学习经过一个epoch,就可以对测试数据和训练数据进行推理,然后进行可视化对比,这里只给出伪代码:
    image-20251019192442837.png

image-20251019192500604.png

epoch是一个单位。一个epoch表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于 10000笔训练数据,用大小为 100笔数据的mini-batch进行学习时,重复随机梯度下降法100次,所有的训练数据就都被“看过”了。此时,100次就是一个epoch。

不幸的结果

和原著对比,这里的效果差了很多(最重要的是运行时间巨长!!!!整整2个星期),原因会放在未来探索,可能的原因一方面是初始化参数的问题,一方面可能是层级结构的问题。准确度在:Accuracy:0.7011999999999997 而不是0.9以上,梯度下降的也不太对,如下图。

但不管怎么样,SGD是有效果的,接下来需要解决其速度太慢的问题。
image-20251110204818780.png

image-20251103191147939.png

化其万物而不知其禅之者,焉知其所终?焉知其所始?正而待之而已耳。

作为《深度学习入门》的阅读笔记,本文简略概述正向传播和激活函数的内容,并辅以python实现

需要事先了解的

本文并不是从零开始的,需要实现了解

  • 蜻蜓点水python

    • 蜻蜓点水python_dlc
  • 简略感知机

学习和推理

和求解机器学习问题的步骤(分成学习和推理两个阶段进行)一样,使用神经网络解决问题时,也需要首先使用训练数据(学习数据)进行权重参数的学习;进行推理时,使用刚才学习到的参数,对输入数据进行分类。

首先,通过机器学习解决问题通常分为两个阶段:学习推理

  • 对于感知机或者神经网络,学习的过程就是调整参数的过程,是需要事先执行的步骤;
  • 在完成学习后,使用确定的参数解决实际问题的过程,称为推理;在神经网络或感知机中,通过前向传播来实现;

从感知机到神经网络

回顾下异或问题,在感知机的实现中,输入先通过与与非门或门的处理,再使用其输出经过与门的处理就完成了异或的操作:

image-20251009221438817.png

这种多层感知机就是一种二层神经网络层数的算法和感知机的层数算法一致,只是表示法不同:

image-20251018113047122.png

先解释结构,x1 x2的位置称为输入层,y所在位置称为输出层,两层之间的不管有多少层都是中间层或者隐藏层

不同点在于门的运算过程都跑到上去了,回顾感知机的实现,所谓的实现就是不同权重的调整,所以边上既包含了参数也包含了固定的传播算法,节点或者称神经元上包含了输出输入的结果,但也包含了一些其他处理过程,如激活函数

一般而言,“朴素感知机”是指单层网络,指的是激活函数使用了阶跃函数(后述)的模型。“多层感知机”是指神经网络,即使用sigmoid函数(后述)等平滑的激活函数的多层网络。

传播算法和激活函数

首先,回顾下感知机中的权重和偏置和感知机的实现:

image-20251009215857997.png

简单来说感知机的实现就是该公式的实现,其中b是被称为偏置的参数,用于控制神经元被激活的容易程度;而w1和w2是表示各个信号的权重的参数,用于控制各个信号的重要性。 而b+w1x1+w2x2就是层和层之间的传播算法,从下图可以看出,偏置也可作为神经元为1而权重为b进行传播:

image-20251018114534693.png

解决了传播问题,再看一眼公式可以发现0和1的判断是依赖于b+w1x1+w2x2的输出的,而这0和1的判断规则也不一定是以公式中的方式出现的,是可以记作函数h(x)来表示的,这个函数就是激活函数,激活函数的输出才是神经元最后表示的输出。

简而言之:激活函数,是用来决定何时激活信号的函数

image-20251018115114656.png

阶跃函数

就从感知机使用的激活函数来看:

image-20251018115844401.png

该函数称为阶跃函数;实现和图像如下,注意到这并不是一个平滑的函数

def step_function(x):
    y = x > 0
    return y.astype(int)
x = np.array([-1.0,1.0,2.0])
print(x)
print(x>0)
y = step_function(x)
print(type(y))

#[-1.  1.  2.]
#[False  True  True]
#<class 'numpy.ndarray'>

import matplotlib.pyplot as plt
x = np.arange(-5.,5.,0.1)
y = step_function(x)
plt.plot(x,y)
plt.ylim(-0.1,1.1)
plt.show()

image-20251018120028301.png

Sigmoid函数

Sigmoid函数是神经网络经常使用一个函数,公式,实现和图像如下:

image-20251018121412781.png

def sigmoid(x):
    return 1/(1+np.exp(-x))

x = np.arange(-5.,5.,0.1)
y = sigmoid(x)
plt.plot(x,y)
plt.ylim(-0.1,1.1)
plt.show()

image-20251018141612162.png

函数对比

image-20251018121517952.png

上面说到,阶跃函数并不是平滑的函数,而sigmoid是,然后二者都是非线性函数,这里先给出结论:

  • 神经网络的激活函数必须使用非线性函数。

原因是由线性函数的性质造成的,如果把中间层合并在一起,所做的处理更加类似于一个超级复杂的复合函数,但是线性函数再怎么复合,都可以找到一个参数来等价的表示这些复合的操作,对于中间层来说就是可以用一层来代表N层,这样中间层的存在意义就没有了,神经网络也就无法发挥多层网络带来的优势。下图为简单的举例:

scanner_20251018_122457.jpg

ReLU函数

sigmoid函数很早就开始被使用了,而最近则主要使用ReLU(Rectified Linear Unit)函数。

公式,实现和图像如下:

image-20251018141008571.png

def relu(x):
    return np.maximum(0,x)
x = np.arange(-5.,5.,0.1)
y = relu(x)
plt.plot(x,y)

image-20251018141528425.png

神经网络和矩阵

矩阵乘法

回顾一下numpy的相关操作,shape表示数组形状dim表示数组维数;多维数组可以代表矩阵,横为行竖为列;

image-20251018150234147.png

然后简单了解下矩阵乘法

image-20251018150255344.png

代码如下:

A=np.array([[1,2,3],[4,5,6]])
B=np.array([[1,2],[3,4],[5,6]])
print(A.shape,B.shape)

C=np.dot(A,B)
print(C)
print(C.shape)

#(2, 3) (3, 2)
#[[22 28]
# [49 64]]
#(2, 2)

在矩阵的乘积运算中,对应维度的元素个数要保持一致 :

image-20251018151409921.png

神经网络和矩阵

从下图可见,之前所说层与层之间的传播算法,可以统统用矩阵乘法表示。而这一层接一层的传播就是所谓的(前)正向传播

image-20251018151655115.png

前向传播

前向传播用语言表达比较繁琐,直接看图和代码则比较方便,无非是一层接一层的矩阵计算和激活函数计算:
image-20251018153304137.png

image-20251018153324040.png

输出层的激活函数

激活函数已经了解,但是输出层的激活函数稍微有些不同,但也只是输出函数类型的问题。

首先 ,对于机器学习解决的问题有两种,一种是所谓分类问题,就好比数字识别,从图片判断是0-9的哪一个,还有一种是回归问题,好比从历史数据判断今天地铁的客流量,一个是选择题,一个是填空题。

在输出层,对于选择题使用softmax函数,对于回归问题使用恒等函数

顺带一提,输出层神经元的数量在分类问题中取决于分类的个数。

恒等函数

恒等函数图像如下,其实就是将输入原样输出,这里便不列出代码identity_function

image-20251018154912288.png

SoftMax函数

对于分类使用的SoftMax函数则有一些说法,先看公式和基本实现:

image-20251018155139122.png

首先这涉及了前一层的所有输入,类比sql语句,这便成为全连接,意思是每一个输出都完整的接受了上一个输入层中的所有数据;而计算时用所有输入做分母,其中一个做分子,这又有点概率的意思,事实上也是这样

最后,SoftMax函数一般只用于学习阶段,因为在推理过程中,一般而言只把输出值最大的神经元所对应的类别作为识别结果,使用softmax会造成额外的计算量

下面是代码实现:

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a=np.sum(exp_a)
    y=exp_a/sum_exp_a
    return y

a = np.array([0.3,2.9,4.0])
print(softmax(a))

#下两行会引发溢出异常
a = np.array([1010,1000,990])
print(softmax(a))

这样是用问题的,因为对于指数函数有溢出的风险,但如果按如下步骤进行改造则可以解决这个问题:

image-20251018185006687.png

其中 C'一般取a_n这个数组中的最大值取反,这样削减一下大小,就不会有溢出的问题了:

# softmax的改进变形
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c) #溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a /sum_exp_a

    return y

a = np.array([1010,1000,990])
print(softmax(a))

#[9.99954600e-01 4.53978686e-05 2.06106005e-09]

总结和阶段性实现

目前,一个支持前向传播的神经网络便可以实现,但是目前只能假设一个权重参数(因为没有学习阶段的支持),然后通过矩阵乘法和中间层和输出层的激活函数来实现前向传播。

所以目前以MNIST数据集为例来实现一个简易的前向传播

测试数据和训练数据

机器学习中,一般将数据分为训练数据(监督数据 )测试数据两部分来进行学习和实验等。

  • 首先,使用训练数据进行学习,寻找最优的参数;
  • 然后,使用测试数据评价训练得到的模型的实际能力

在处理测试数据过程中,如果发现效果很好,则说明该模型的泛化能力很好,即面对未知数据表现很好;反面的,如果只对训练数据表现好,则说明泛化能力差,这种现象称为过拟合

MNIST数据集的读取

读取该数据集需要一些工具函数的辅助,这里直接使用《深度学习入门》中提供的工具实现:

# 这里的路径按本地实际来
from dfs.dataset.mnist import load_mnist
import sys,os
sys.path.append(os.pardir)

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True,
normalize=False,one_hot_label=False)

# 输出各个数据的形状
print(x_train.shape) # (60000, 784)
# 输出各个数据的形状
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)
  • train代表训练数据,test表示测试数据
  • x代表输入的图片,t代表最终的标签(在one_hot_label是false时会在显示标签0-9,在true时会返回一个10位数组,在标签数字对应的位置是1,其余为0)
from PIL import Image

def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

img = x_train[0]
label = t_train[0]
print(label) # 5
print(img.shape) # (784,)
img = img.reshape(28, 28) # 把图像的形状变成原来的尺寸
print(img.shape) # (28, 28)
img_show(img)
  • 该数据集中,输入的是一堆图片和图片对应的标签,在这里将图片展开为一维数组(flatten=True),并使用原始像素信息,而不是将0-255的颜色值压缩到0-1的范围(normalize=false),而将数据压缩到0-1范围这种操作称为标准化或正交化
one-hot表示是仅正确解标签为1,其余皆为0的数组,就像[0,0,1,0,0,0,0,0,0,0]这样。当one_hot_label为False时,只是像 7、 2这样简单保存正确解标签;当 one_hot_label为 True时,标签则保存为one-hot表示。

实现

首先,由于并没有学习这一步骤,所以会给所有权重和偏置设置符合正态分布的默认值;

然后因为传播的实现就是矩阵乘法的实现,现在假设使用的是3层神经网络,则三个权重矩阵的形状要符合矩阵乘法的要求,所以假设矩阵形状如下:
image-20251018201706760.png

最后,由于一个图片一个图片处理过于缓慢,而矩阵乘法却能吃到SIMD的优化福利,所以会使用批处理的方法,即如下图所示,一次性打包处理100张图像,为此,可以把x的形状改为100 × 784,将100张图像打包作为输入数据:
image-20251018201945559.png

如此实现如下,首先是神经网络的实现和使用

import numpy as np
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c) #溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a /sum_exp_a
    return y

def sigmoid(x):
    return 1/(1+np.exp(-x))

class SigMoidLayer:
    def __init__(self,w,b,s="SigMoid"):
        self.layer_name = s
        self.w=w
        self.b = b
    def forward(self,x):
        a=np.dot(x,self.w)+self.b
        return sigmoid(a)

class OutputLayer2:
    def __init__(self,w,b,s="Ouput"):
        self.layer_name = s
        self.w=w
        self.b = b
    def forward(self,x):
        a=np.dot(x,self.w)+self.b
        return softmax(a)
class TestMNIST:
    # 正常情况下,权重或者偏置都应该是学习的结果,这里直接赋值只是一种假设
    def __init__(self):
        self.network_w = {}
        self.network_b = {}
        self.network_w['L1'] = np.random.randn(784,50)
        self.network_b['L1'] = 1
        self.network_w['L2'] = np.random.randn(50,100)
        self.network_b['L2'] = 1

        # 输出层
        self.network_w['L3'] = np.random.randn(100,10)
        self.network_b['L3'] = 1

        self.layers=['L1','L2']

    def forward(self,x):
        tmp=x
        for l in self.layers:
            tmp = SigMoidLayer(self.network_w[l],self.network_b[l],l).forward(tmp)
            print(l,tmp)
        tmp = OutputLayer2(self.network_w['L3'],self.network_b['L3'],'L3').forward(tmp)
        return tmp


nett = TestMNIST()
#测试代码
#nett.forward(np.random.randn(100,784))

batch_size=100

for i in range(0,len(x_test),batch_size):
    x_batch = x_test[i:i+batch_size]
    #print(x_batch.shape)
    y_batch = nett.forward(x_batch)
    p=np.argmax(y_batch,axis=1)
    print(p)
#[8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
# 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
# 8 8 8 8 8 8 6 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8]

可见结果并不好,都只会输出8,即使如此也要测一下准确率:

accuracy_cnt=0

for i in range(0,len(x_test),batch_size):
    x_batch = x_test[i:i+batch_size]
    y_batch = nett.forward(x_batch)
    p=np.argmax(y_batch,axis=1)
    accuracy_cnt += np.sum(p == t_test[i:i+batch_size])
print("Accuracy:" + str(float(accuracy_cnt) / len(x_test)))
#Accuracy:0.0965

太好了,基本都对不上。

夫道未始有封,言未始有常,为是而有畛也。

作为《深度学习入门》的阅读笔记,本文简略概述感知机,并辅以python实现

Python基础

再简单的描述离不开基本的语言基础,确保了解《蜻蜓点水-python》中的内容在进行下去

逻辑运算和感知机

如上所述,python中有逻辑运算,电路中也有逻辑运算,并以“门”的形式呈现,有电流则为1(True),无电流则为0(True)。

如下图所示,三种门的符号,真值表就不再赘述,可以用python快速验证
image-20251009214626196.png

感知机的历史1957提出,是神经网络的起源的算法之一。

感知机接受多个信号,并输出一个信号。

感知机实现

先说实现的思路,上述三种逻辑运算都可以用同一种逻辑表达,区别只在于特定参数的不同

image-20251009215857997.png

b称为偏置,w称为权重,二者控制了函数的不同逻辑表示

下面直接给出代码实现:

  • 初始化函数设定了权重和偏置
  • forward函数在深度学习中代表正向传播,这里借用作为感知机的处理函数
  • 逻辑如公式,但将等号给了1
class Perceptron:
    def __init__(self, w=np.array([0,0]),delta=-1.):
        self.w=w
        self.delta = delta
    def forward(self,x):
        tmp=self.w*x
        if np.sum(tmp) + self.delta < 0:
            return 0
        else:
            return 1
andclass = Perceptron(w=np.array([0.5,0.5]))
print(andclass.forward(np.array([0,0])))
print(andclass.forward(np.array([1,0])))
print(andclass.forward(np.array([0,1])))
print(andclass.forward(np.array([1,1])))
# 0
# 0
# 0
# 1
orclass = Perceptron(w=np.array([1,1]))
print(orclass.forward(np.array([0,0])))
print(orclass.forward(np.array([1,0])))
print(orclass.forward(np.array([0,1])))
print(orclass.forward(np.array([1,1])))

# 0
# 1
# 1
# 1
nandclass = Perceptron(w=np.array([-0.5,-0.5]),delta=0.9)
print(nandclass.forward(np.array([0,0])))
print(nandclass.forward(np.array([1,0])))
print(nandclass.forward(np.array([0,1])))
print(nandclass.forward(np.array([1,1])))

# 1
# 1
# 1
# 0

目前感知机的局限

可以对当前感知机的输入和输出作图,可得:
image-20251009220709154.png

总之,有办法做一条直线将0和1的结果划分开,能这么表示的称为线性感知机

但是这样的感知机无法表示XOR操作,也就是异或操作:

image-20251009220911874.png

XOR感知机的实现

该问题由1969年被提出,直到1986年被解决,这段时间人工智能的发展是停滞的。

解决的方法就是使用多层感知机,即单独的“门”无法解决的问题,就用多个连接一起解决,直接给方法:
image-20251009221438817.png

用代码表示就是:

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

进一步的实现:

#def XOR(x1, x2):
#    s1 = NAND(x1, x2)
#    s2 = OR(x1, x2)
#    y = AND(s1, s2)
#    return y
class XOR:
    def __init__(self):
        self.nandclass = Perceptron(w=np.array([-0.5,-0.5]),delta=0.9)
        self.orclass = Perceptron(w=np.array([1,1]))
        self.andclass = Perceptron(w=np.array([0.5, 0.5]))
    def forward(self,x):
        s1 = self.nandclass.forward(x)
        s2 = self.orclass.forward(x)
        tmp = self.andclass.forward(np.array([s1,s2]))
        return tmp

print("XOR: ")
print(XOR().forward(np.array([0,0])))
print(XOR().forward(np.array([1,0])))
print(XOR().forward(np.array([0,1])))
print(XOR().forward(np.array([1,1])))

#XOR: 
#0
#1
#1
#0

其他问题

多层感知机的层数

按权重算,2层,因为有两套权重参数

按节点(神经元)算,3层,从输入,到中间结果,到输出,有三个阶段

多层感知机的进一步

感知机通过叠加层能够进行非线性的表示,理论上还可以表示计算机进行的处理

多层感知机可以认为是一种神经网络

道在屎溺。作为《深度学习入门》的阅读笔记,本文简略概述python

算数计算

print(1 - 2)
print(4 * 5)
print(7 / 5)
print(3 ** 2) # 乘方
-1
20
1.4
9

数据类型和Type函数

type(10)
int



type(2.718)
float



type("hello")
str



type(True)
bool


变量

x = 100
y = 3.14
print(x * y)
type(x * y)
# 是注释标识
314.0

float


列表

a = [1,2,3,4,5]
print(a)
len(a)
[1, 2, 3, 4, 5]

5



print(a[4])
a[4]=99
print(a)
5
[1, 2, 3, 4, 99]


# 切片
a[0:2] # 获取索引为0到2(不包括2!)的元素
[1, 2]



a[1:]  # 获取从索引为1的元素到最后一个元
[2, 3, 4, 99]



a[:3]  # 获取从第一个元素到索引为3(不包括3!)的元素
[1, 2, 3]



a[:-1] # 获取从第一个元素到最后一个元素的前一个元素之间的元素
[1, 2, 3, 4]



a[:-2] # 获取从第一个元素到最后一个元素的前二个元素之间的元素
[1, 2, 3]


字典

me = {'height':180} # 生成字典
print(me)
{'height': 180}


me['height']=70
print(me['height'])
70

布尔型

hungry = True
sleepy = False
type(hungry)
bool



not hungry
False



hungry and sleepy
False



hungry or sleepy
True


if语句

if hungry:
    print('hungry')
elif sleepy:
    print('sleepy')
else:
    print('not hungry')
hungry

for 语句

for i in [1,2,3]:
    print(i)
1
2
3

函数

def hello(object):
    print("Hello " + object + "!")
hello("cat!")
Hello cat!!

class Man:
    def __init__(self, name):
        self.name = name
        print("Initialized!")
    def hello(self):
        print("Hello " + self.name + "!")
    def goodbye(self):
        print("Good-bye " + self.name + "!")

m = Man("David")
m.hello()
m.goodbye()
Initialized!
Hello David!
Good-bye David!

Numpy 速成

import numpy as np

x = np.array([1.,2.,3.])
print(x)
type(x)
y = np.array([2.0, 4.0, 6.0])
[1. 2. 3.]

以下是对应元素的运算,如果元素个数不同,程序就会报错,所以元素个数保持一致非常重要

print(x+y)
print(x-y)
print(x/y)
print(x*y)
[3. 6. 9.]
[-1. -2. -3.]
[0.5 0.5 0.5]
[ 2.  8. 18.]

NumPy数组不仅可以进行element-wise运算,也可以和单一的数值(标量)组合起来进行运算

x/2.0
array([0.5, 1. , 1.5])


Numpy的N维数组

A = np.array([[1, 2], [3, 4]])
B = np.array([[3, 0],[0, 6]])
print(A)
[[1 2]
 [3 4]]


A.shape
(2, 2)



A.dtype
dtype('int32')



print(A+B)
[[ 4  2]
 [ 3 10]]


print(A*B)
[[ 3  0]
 [ 0 24]]


print(A*10)
[[10 20]
 [30 40]]

广播

A = np.array([[1, 2], [3, 4]])
B = np.array([10, 20])
A * B
array([[10, 40],
       [30, 80]])


访问元素

X = np.array([[51, 55], [14, 19], [0, 4]])
X[0][1]
55



X[0]
array([51, 55])



for row in X:
    print(row)
[51 55]
[14 19]
[0 4]


X = X.flatten()
print(X)
[51 55 14 19  0  4]

还有一些特殊的访问方法:

X[np.array([0, 2, 4])]
array([51, 14,  0])



X > 15
array([ True,  True, False,  True, False, False])



X[X>15]
array([51, 55, 19])


Matplolib 速成

import matplotlib.pyplot as plt
 # 生成数据
x = np.arange(0, 6, 0.1) # 以0.1为单位,生成0到6的数据
y1 = np.sin(x)
y2 = np.cos(x)
# 绘制图形
plt.plot(x, y1)
plt.show()

test_python_59_0.png

plt.plot(x, y1, label="sin")
plt.plot(x, y2, linestyle = "--", label="cos") # 用虚线绘制
plt.xlabel("x") # x轴标签
plt.ylabel("y") # y轴标签
plt.title('sin & cos') # 标题
plt.legend() #图例
plt.show()


test_python_60_0.png

from matplotlib.image import imread
img = imread('../dataset/lena.png') # 读入图像(设定合适的路径!)
plt.imshow(img)
plt.show()


test_python_61_0.png