慎独削砌儒门:给予蟒蛇的萝莉与工具

\[\newcommand{\b}{\mathbf} \]

II.感知机

感知机输入多个信号,输出一个信号。一个最基础的感知机由权重和阈值两部分组成:若输入关于权重的线性组合高于阈值则输出 \(1\),此时神经元被认为激活;否则输出 \(0\),则神经元未被激活。与门、或门都可以被看作基础的感知机。

感知机的一种数学描述是,有权重向量 \(\b w\) 和偏置 \(b\),对于输入向量 \(\b x\),使用 \([b+\b w\cdot\b x>0]\) 作为感知机输出。

感知机有其局限性,例如其仅能将线性空间二分割,故异或门无法实现。

单层感知机无法描述异或门,但是通过多层感知机的叠加,异或门即可得以实现。

使用 sigmoid 函数(下文中出现)的双层感知机是图灵机(?)

III.神经网络

神经网络最基础的分层,是输入层、中间层、输出层三层,层标号自 \(0\) 开始。

偏置 \(b\) 也可以被看作是一个恒一神经元以 \(b\) 的权重对感知机的输入。非首层神经元上有“激活函数”,对输入的组合进行转换:例如,使用 \(h(x)=[x>0]\) 的阶跃函数时,即得朴素感知机。

sigmoid 函数 \(h(x)=\dfrac1{1+e^{-x}}\) 也是常见的激活函数。其是一个具有良好平滑性的函数。

神经网络的激活函数必然都是非线性函数,否则加深神经网络的层数就没有影响了。

最近大家比较喜欢 ReLU 函数 \(h(x)=\max(x,0)\)

\(W^{(i)}\)\(i-1\) 层至 \(i\) 层的转移,则其是线性变换,可以用矩阵描述;令 \(\b x^{(i)}\)\(i\) 层值向量(用行向量描述),\(\b b^{(i)}\)\(i\) 层偏置,则神经网络基础公式即为:

\[\b x^{(i)}=h(\b x^{(i-1)}W^{(i)}+\b b^{(i)}) \]

隐藏层的激活函数约定俗成地记作 \(h(x)\),而输出层为统一也有其激活函数,记作 \(\sigma(x)\)。输出层的激活函数设计与神经网络待解决的问题相关:分类问题(对输入离散地归类)往往用 softmax 函数 \(\sigma(\b x)=\dfrac{\exp(\b x)}{\|\exp(\b x)\|_1}\),而回归问题(用输入预测连续输出)往往用恒等函数。

计算 \(\exp\) 有可能爆精度。通过上下同乘 \(C\) 加以处理,最终可以得到修正的 softmax \(\sigma(\b x)=\dfrac{\exp(\b x+C\b1)}{\|\exp(\b x+C\b1)\|_1}\),使用 \(C\)\(-\|\b x\|_\infty\) 即可。

softmax 的输出总是可以被解释为一组概率密度分布。因为 softmax 有一定计算量,softmax 有可能被换为朴素的 argmax。分类问题中,输出层的神经元数目常常即为类别数目。

将多个向量并列拼成矩阵,可以缩短处理时间,被称作批处理。

IV.神经网络的学习

神经网络不重要,重要的神经网络的参数。参数没法人脑拟合,要靠算法自己学习。

方法一是靠人脑提取某些特征量,然后针对特征量跑机器学习。方法二是直接把数据丢到神经网络(深度学习)上跑参数。

损失函数是用于量化神经网络性能的函数。例如,均方误差 \(\dfrac12\sum(t_i-y_i)^2\),其中 \(t_i\) 是监督数据,\(y_i\) 是输出数据;在分类问题中,softmax 函数的输出是概率密度向量,监督数据使用 one-hot 表示,即除了关键位为 \(1\) 外其余位置都是 \(0\)。例如,交叉熵误差 \(E=-\sum t_i\ln y_i\)。加上 epsilon 可以避免出现负无穷。

单个数据的损失函数按照上述方式计算;总数据集损失函数用所有单个函数的损失函数求平均得到。

使用损失函数而非正确预测数目,原因是正确预测数目的梯度在很多地方为零,不好优化。

同理,使用 sigmoid 函数而非阶跃函数,因为阶跃函数几乎处处导数为零。

计算机算导数更喜欢用数值微分,即 \(\dfrac{f(x+h)-f(x)}h\)(称为前向微分)或更常见的 \(\dfrac{f(x+h)-f(x-h)}{2h}\)(称作中心微分)

梯度法使用梯度,沿着负梯度方向走,寻找损失函数最小化。

走多远?往往有一个参数,是 \(10^{-2}\)\(10^{-3}\),然后乘以负梯度并移动。

神经网络的梯度是把 \(W\) 当成向量处理。

流程:

  • 从完整数据集中选出一小部分作为训练数据,剩下的作为测试数据。
  • 用正态分布初始化 \(W\),用 \(\b0\) 初始化 \(\b b\)
  • 跑。
  • 适当调参避免过拟合。

V.误差反向传播法

考虑用一个内向树描述某种计算流程:内向树上每个点接受若干点的输入,并通过某种运算变成输出供给一个点。

现在我们想知道内向树上每个输入对最终输出的贡献。因为是内向树,则终点只有一个方向可以溯源至我们好奇的变量,于是计算终点在其它东西都固定时的偏导,然后回退一步,再计算一步偏导,再回退……将所有的单步偏导求积,即得快速的总梯度计算方法。

单步偏导,如果将总流程描述为若干步加法与乘法的组合(此时采用 sigmoid 函数或 ReLU 函数均可),则每一步都可以算得导数的解析解。

花了好几天一边摆一边对着 std 抄,捣鼓出了一份能在约 20s 内训练出成功率在 98% 附近的神经网络。

mnist.py

# 这份代码是直接从这本书上附的代码集中粘出来的,唯一的作用是下载 mnist 数据集并保存在 mnist.pkl 文件中。但是 mnist 网站好像下不下来数据,因此我选择手动下了一份数据集
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np


url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784


def _download(file_name):
    file_path = dataset_dir + "/" + file_name
    
    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")
    
def download_mnist():
    for v in key_file.values():
       _download(v)
        
def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")
    
    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")    
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")
    
    return data
    
def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])    
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])
    
    return dataset

def init_mnist():
    # download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T
    

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """读入MNIST数据集
    
    Parameters
    ----------
    normalize : 将图像的像素值正规化为0.0~1.0
    one_hot_label : 
        one_hot_label为True的情况下,标签作为one-hot数组返回
        one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
    flatten : 是否将图像展开为一维数组
    
    Returns
    -------
    (训练图像, 训练标签), (测试图像, 测试标签)
    """
    if not os.path.exists(save_file):
        init_mnist()
        
    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)
    
    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0
            
    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])
    
    if not flatten:
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label']) 


if __name__ == '__main__':
    init_mnist()

Functions.py

import numpy as np
def Identity_Function(x):
	return x
def Softmax_Function(x):
	if x.ndim==2: # case when x is batched
		x=np.exp(x-np.max(x,axis=1).reshape(-1,1))
		return x/np.sum(x,axis=1).reshape(-1,1)
	x=np.exp(x-np.max(x))
	return x/np.sum(x)
def Sigmoid(x):
	return 1.0/(1.0+np.exp(x))
def Sigmoid_Grad(x):
	return (1.0-Sigmoid(x))*Sigmoid(x)
def ReLU(x):
	return np.max(x,0)
def ReLU_Grad(x):
	grad=np.zeros(x)
	grad[x>=0]=1
	return grad
def Mean_Squared_Error(y,t):
	return 0.5*np.sum((y-t)**2)
def Cross_Entropy_Error(y,t):
	if y.ndim==1:
		y=y.reshape(1,-1)
		t=t.reshape(1,-1)
	batchsize=len(y)
	# print("Fuctions:",y.shape,t.shape)
	# print(y[0],t[0],np.log(y[np.arange(batchsize),t]+1e-7)[0])
	return -np.sum(np.log(y[np.arange(batchsize),t]+1e-7))/batchsize

Layers.py

import numpy as np
from Functions import *
class ReLULayer:
	def __init__(self):
		self.neg=None
	def forward(self,ip):
		self.neg=ip<=0
		op=ip.copy()
		op[self.neg]=0
		return op
	def backward(self,ip):
		op=ip.copy()
		op[self.neg]=0
		return op
class SigmoidLayer:
	def __init__(self):
		self.sig=None
	def forward(self,ip):
		op=Sigmoid(ip)
		self.sig=op.copy()
		return op
	def backward(self,ip):
		op=ip*(1.0-self.sig)*self.sig
		return op
class AffineLayer:
	def __init__(self,W,b):
		self.W=W
		self.b=b
		self.dW=None
		self.db=None
		self.x=None
	def forward(self,ip):
		self.x=ip.copy()
		# print(self.x.shape,self.W.shape,self.b.shape)
		return np.dot(ip,self.W)+self.b
	def backward(self,ip):
		tag=False
		if ip.ndim==1:
			tag=True
			ip=ip.reshape(1,-1)
		self.dW=np.dot(self.x.T,ip)
		self.db=np.sum(ip,axis=0)
		op=np.dot(ip,self.W.T)
		if tag:
			op=op.flatten()
		return op
class SoftmaxLayer:
	def __init__(self):
		self.y=None
		self.t=None
	def forward(self,ip,tp):
		self.y=Softmax_Function(ip)
		self.t=tp
		op=Cross_Entropy_Error(self.y,self.t)
		return op
	def backward(self,ip=1):
		batchsize=len(self.t)
		op=self.y.copy()
		op[np.arange(batchsize),self.t]-=1
		op/=batchsize
		return op

NeuroNetwork.py

import numpy as np
from Layers import *

class NeuroNetwork:
	def __init__(self,layertypes,layersizes,weight_init_std=0.01,givenW=None,givenb=None):
		self.n=len(layersizes)
		self.W=[]
		self.b=[]
		self.layers=[]
		for i in range(self.n-1):
			if givenW is None and givenb is None:
				self.W.append(np.random.randn(layersizes[i],layersizes[i+1])*weight_init_std)
				self.b.append(np.zeros(layersizes[i+1]))
			else:
				self.W.append(givenW[i])
				self.b.append(givenb[i])
			self.layers.append(AffineLayer(self.W[i],self.b[i]))
			if i+1!=self.n-1:
				self.layers.append(layertypes[i]())
		self.lastlayer=layertypes[-1]()
	def predict(self,x):
		for i in self.layers:
			x=i.forward(x)
		return x
	def InterpretPrediction(self,x,num=3):
		y=self.predict(x)
		y=Softmax_Function(y)
		arr=[]
		for i in range(len(y)):
			arr.append((y[i],i))
		arr=reversed(sorted(arr))
		print("Prediction|",end='')
		for i in range(num):
			print(int(i[0]*100),"%%being result ",i[1],sep='',end='')
		print()
	def calcloss(self,x,t):
		y=self.predict(x)
		return self.lastlayer.forward(y,t)
	def accuracy(self,x,t): #in this case x must be batched
		y=self.predict(x)
		y=np.argmax(y,axis=1)
		return 1.0*np.sum(y==t)/len(x)
	def gradient(self,x,t):
		self.calcloss(x,t)
		ip=1
		ip=self.lastlayer.backward(ip)
		for i in reversed(self.layers):
			ip=i.backward(ip)
		gradW=[]
		gradb=[]
		for i in range(0,self.n-1):
			gradW.append(self.layers[i*2].dW)
			gradb.append(self.layers[i*2].db)
		return gradW,gradb

NeuroTrain.py

import numpy as np
from mnist import load_mnist
from NeuroNetwork import *

nwk=NeuroNetwork(layertypes=[ReLULayer,SoftmaxLayer],layersizes=[784,50,10])
(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True, one_hot_label=False)
itertime=10000
batchsize=100
trainsize=len(x_train)
learningrate=0.1
reviewsize=max(1,int(trainsize/batchsize))

batchsucceedrate=[]
trainsucceedrate=[]
batchx=[]
trainx=[]
print(trainsize,batchsize,reviewsize)
for i in range(itertime):
	ind=np.random.choice(trainsize,batchsize)
	x_batch=x_train[ind]
	t_batch=t_train[ind]
	batchsucceedrate.append(nwk.accuracy(x_batch,t_batch))
	batchx.append(i)
	gradW,gradb=nwk.gradient(x_batch,t_batch)
	for j in range(0,nwk.n-1):
		nwk.W[j]-=learningrate*gradW[j]
		nwk.b[j]-=learningrate*gradb[j]
	# print(i,i%reviewsize)
	if i%reviewsize==0:
		trainsucceedrate.append(nwk.accuracy(x_train,t_train))
		print(trainsucceedrate[-1])
		trainx.append(i)

for i in nwk.W:
	print(i.shape)

import pickle

with open("neuroW.pkl","wb") as f:
	pickle.dump(nwk.W,f)
with open("neurob.pkl","wb") as g:
	pickle.dump(nwk.b,g)

import matplotlib.pyplot as plt
plt.plot(batchx,batchsucceedrate,label="batchsuccessrate")
plt.plot(trainx,trainsucceedrate,label="trainsuccessrate")
plt.legend()
plt.show()

NeuroTest.py

import pickle
import numpy as np
from mnist import load_mnist
from NeuroNetwork import *

with open("neuroW.pkl","rb") as f:
    W=pickle.load(f)
with open("neurob.pkl","rb") as g:
    b=pickle.load(g)
    
nwk=NeuroNetwork(layertypes=[ReLULayer,SoftmaxLayer],layersizes=[784,50,10],givenW=W,givenb=b)

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

testsize=len(x_test)
testbatch=100


ind=np.random.choice(testsize,testbatch)
x_batch=x_test[ind]
t_batch=t_test[ind]

print(nwk.accuracy(x_batch,t_batch))
posted @ 2024-07-07 17:06  Troverld  阅读(3)  评论(0编辑  收藏  举报