
目录一、实验目的二、实验准备三、实验内容1. 生成螺旋状数据集2. 打印数据集3. 编程实现仿射层-Affine类传播层-Sigmoid类损失函数相关类三层神经网络类-ThreeLayerNet随机梯度下降法的类-SGD训练过程绘制迭代效果图四、实验心得一、实验目的利用神经网络识别螺旋状数据集python实现正确理解深度学习所需的数学知识。二、实验准备根据GPU安装pytorch版本实现GPU运行实验代码配置环境用来运行 Python、Jupyter Notebook和相关库等相关库。三、实验内容资源获取关注公众号【科创视野】回复 深度学习1.生成螺旋状数据集1利用numpy库生成螺旋状数据集python源码如下# coding: utf-8 import numpy as np def load_data(seed2020264): #生成数据集 np.random.seed(seed) #设置随机数种子 N 100 # 各类的样本数 DIM 2 # 数据的元素个数 CLS_NUM 3 # 类别数 x np.zeros((N*CLS_NUM, DIM)) t np.zeros((N*CLS_NUM, CLS_NUM), dtypenp.int) for j in range(CLS_NUM): for i in range(N):#N*j, N*(j1)): rate i / N radius 1.0*rate theta j*4.0 4.0*rate np.random.randn()*0.2 ix N*j i x[ix] np.array([radius*np.sin(theta), radius*np.cos(theta)]).flatten() t[ix, j] 1 return x, t解释1.代码中导入了numpy库用于生成和处理数组。2.load_data函数该函数用于生成数据集。接受一个seed参数用于设置随机数生成的种子以确保结果的可重复性。3.设置参数在函数内部定义了几个参数包括样本数N、数据的元素个数DIM和类别数CLS_NUM。这些参数用于确定生成数据的规模和属性。4.初始化数组通过np.zeros函数创建了两个数组x和t用于存储生成的样本和对应的标签。5.数据生成循环通过两个嵌套的循环依次生成每个类别的样本。外层循环遍历类别数内层循环生成每个类别中的样本。6.样本生成在内层循环中首先根据当前类别和样本索引计算出一个比例rate用于确定样本的半径。然后根据一定的规则计算样本的极坐标位置半径和角度并引入一定的随机扰动。最后将样本的极坐标位置转换为笛卡尔坐标位置并存储在数组x中。7.标签生成在内层循环中通过将当前样本所属类别对应的位置设为1将标签存储在数组t中。8.返回结果最后函数返回生成的样本数组x和标签数组t。2.打印数据集在加载完数据集后利用plt将生成的数据集打印出来python源码如下# coding: utf-8 import sys sys.path.append(..) # 为了引入父目录的文件而进行的设定 import matplotlib.pyplot as plt x, t load_data() print(x, x.shape) # (300, 2) print(t, t.shape) # (300, 3) # 绘制数据点 N 100 CLS_NUM 3 markers [o, x, ^] for i in range(CLS_NUM): plt.scatter(x[i*N:(i1)*N, 0], x[i*N:(i1)*N, 1], s40, markermarkers[i]) plt.show()解释1.导入sys和matplotlib.pyplot库。sys库用于在代码中添加父目录的路径而matplotlib.pyplot库用于数据可视化。2.添加父目录路径通过sys.path.append(..)语句将父目录路径添加到代码中。这样做是为了能够引入父目录中的文件这里是为了引入之前定义的load_data()函数。3.调用load_data()函数通过调用load_data()函数生成数据集的特征数组x和标签数组t。4.打印数组形状通过print()语句打印出数据集特征数组x和标签数组t的形状。x.shape输出的结果是(300, 2)表示x数组有300行和2列t.shape输出的结果是(300, 3)表示t数组有300行和3列。这里的形状信息给出了生成数据集的维度信息。5.绘制数据点接下来通过使用matplotlib.pyplot库来绘制数据集的散点图。循环遍历每个类别利用plt.scatter()函数绘制对应类别的数据点。函数中的参数包括样本的x坐标和y坐标使用不同的标记形状markers[i]和尺寸s40来区分不同类别的数据点。6.显示图像最后通过plt.show()函数显示绘制的图像将数据集的散点图展示出来。结果图为3. 编程实现仿射层-Affine类class Affine: def __init__(self,W,b): self.params [W,b]#保存参数 self.grads [np.zeros_like(W),np.zeros_like(b)]#保存梯度 self.x None def forward(self,x): W,b self.params out np.dot(x,W) b self.x x return out def backward(self,dout): W,b self.params dx np.dot(dout,W.T) dW np.dot(self.x.T,dout) db np.sum(dout,axis0) self.grads[0][...] dW self.grads[1][...] db return dx解释1.Affine类表示神经网络中的仿射层Affine Layer。类的初始化方法__init__该方法在创建Affine类的实例时被调用。它接受两个参数W和b分别表示仿射层的权重和偏置。在方法中首先创建了一个params列表用于保存权重和偏置参数。然后创建了一个grads列表用于保存权重和偏置参数的梯度。最后初始化了一个x变量并将其设为None。2.前向传播方法forward该方法接受一个输入x并根据保存的权重和偏置参数进行仿射变换。首先从params列表中获取权重W和偏置b。然后通过计算输入x与权重W的乘积并加上偏置b得到输出out。最后将输入x保存在self.x变量中并返回输出out。3.反向传播方法backward该方法接受一个上游梯度dout并根据保存的权重和输入x计算梯度。首先从params列表中获取权重W和偏置b。然后通过上游梯度dout与权重W的转置的乘积得到对输入x的梯度dx。接下来计算权重W的梯度dW通过将输入x的转置与上游梯度dout的乘积得到。最后计算偏置b的梯度db通过对上游梯度dout按列求和得到。4.更新梯度和返回梯度在方法的最后通过将权重和偏置的梯度分别赋值给self.grads列表中对应的元素来更新梯度信息。使用[...]操作符可以确保在赋值时不改变梯度数组的形状和数据类型。最后返回输入的梯度dx以便反向传播给前一层。传播层-Sigmoid类class Sigmoid: def __init__(self): self.params [] self.grads [] self.out None def forward(self,x): out 1 / (1 np.exp(-x)) self.out out return out def backward(self,dout): dx dout * (1.0 - self.out) * self.out return dx解释1.Sigmoid类表示一个Sigmoid函数。类的初始化在__init__方法中定义了三个实例变量params、grads和out分别用于存储参数、梯度和前向传播的输出结果。这些变量在类的实例化时被创建并初始化为空。2.前向传播在forward方法中接收输入x作为参数。通过应用Sigmoid函数的定义计算出输出out即 1 / (1 np.exp(-x))。然后将计算结果赋值给实例变量self.out以便在反向传播中使用并返回输出out。3.反向传播在backward方法中接收反向传播的上游梯度dout作为参数。通过应用Sigmoid函数的导数公式计算出输入x的梯度dx即 dout * (1.0 - self.out) * self.out。然后返回计算得到的梯度dx。损失函数相关类def softmax(x): if x.ndim 1: x x - np.max(x) x np.exp(x)/np.sum(np.exp(x)) elif x.ndim 2: x x - x.max(axis 1,keepdims True) x np.exp(x) x / x.sum(axis1, keepdimsTrue) return 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(dim1) batch_size y.shape[0] #没看懂为啥 return -np.sum(np.log(y[np.arange(batch_size), t] 1e-7)) / batch_size class SoftmaxWithLoss: def __init__(self): self.params [] self.grads [] self.y None #softmx的输出 self.t None #监督标签 def forward(self,x,t): self.t t self.y softmax(x) if self.t.size self.y.size: self.t self.t.argmax(axis1) loss cross_entropy_error(self.y,self.t) return loss def backward(self,dout 1): batch_size self.t.shape[0] dx self.y.copy() dx[np.arange(batch_size),self.t] - 1 dx * dout dx dx/batch_size return dx解释1.softmax函数实现了Softmax函数的计算接受一个数组x作为输入根据输入的维度情况进行不同的计算。当x的维度是1维时首先将x减去最大值然后计算每个元素的指数并除以所有指数的和得到Softmax函数的输出。当x的维度是2维时首先将x每行减去对应行的最大值然后计算每个元素的指数并除以每行指数的和得到Softmax函数的输出。最后返回计算得到的Softmax函数的输出。2. cross_entropy_error函数:实现交叉熵损失函数的计算。接受两个数组y和t作为输入根据输入的维度情况进行不同的计算。首先根据输入的维度情况将t的形状调整为和y相同的形状以便进行计算。如果t的大小和y的大小相同说明t是以one-hot向量形式表示的监督标签这里将其转换为对应的类别索引。接着根据批量的大小计算交叉熵损失通过对y使用np.arange(batch_size)和t的索引取出正确类别的预测概率并计算其对数然后求和并取负数最后除以批量大小得到平均损失。最后返回计算得到的交叉熵损失。3. SoftmaxWithLoss类:这个类实现了Softmax with Loss层包含了前向传播和反向传播的计算。__init__方法用于初始化类的实例变量包括参数列表params、梯度列表grads以及用于保存Softmax函数的输出y和监督标签t的变量。forward方法用于执行前向传播计算接受输入x和监督标签t作为参数。在该方法中首先将t赋值给实例变量self.t然后使用softmax函数计算x的Softmax输出y。接着根据t的维度情况将t转换为类别索引形式。最后调用cross_entropy_error函数计算Softmax with Loss的损失并返回。backward方法用于执行反向传播计算接受一个可选的上游梯度dout作为参数默认为1。在该方法中首先获取监督标签的批量大小然后创建一个梯度副本dx并将其初始化为Softmax函数输出y的副本。接下来根据监督标签的索引在dx中将正确类别的位置减去1以计算Softmax with Loss层的梯度。然后将梯度乘以上游梯度dout并除以批量大小以获得平均梯度。最后返回计算得到的梯度dx。三层神经网络类-ThreeLayerNetclass ThreeLayerNet: def __init__(self,input_size,hidden_size1,hidden_size2,output_size): I,H1,H2,O input_size,hidden_size1,hidden_size2,output_size #初始化权重和偏置 W1 0.01 * np.random.randn(I,H1) #形状I*H b1 np.zeros(H1) W2 0.01 * np.random.randn(H1,H2) b2 np.zeros(H2) W3 0.01 * np.random.randn(H2,O) b3 np.zeros(O) #生成层 self.layers [ Affine(W1,b1), Sigmoid(), Affine(W2,b2), Sigmoid(), Affine(W3,b3) ] #Softmax With Loss层和其他层的处理方式不同 #所以不将它放在layers列表中而是单独存储在变量loss_layer中 self.loss_layer SoftmaxWithLoss() self.params,self.grads [],[] for layer in self.layers: self.params layer.params self.grads layer.grads def predict(self,x): for layer in self.layers: x layer.forward(x) return x def forward(self,x,t): score self.predict(x) loss self.loss_layer.forward(score,t) return loss def backward(self,dout 1): dout self.loss_layer.backward(dout) for layer in reversed(self.layers): dout layer.backward(dout) return dout解释1.这里我实现了一个三层神经网络的类ThreeLayerNet该类包含了网络的初始化、前向传播、反向传播和预测等方法。在初始化方法__init__中定义了神经网络的结构和初始化权重和偏置。input_size表示输入层的大小hidden_size1和hidden_size2表示两个隐藏层的大小output_size表示输出层的大小。2.权重的初始化采用了高斯分布随机初始化通过np.random.randn生成服从标准正态分布的随机数并乘以0.01进行缩放。偏置初始化为全零向量。3.下面生成了三个层的实例并按照顺序存储在self.layers列表中分别是全连接层Affine、激活函数层Sigmoid和输出层Softmax With Loss。为了方便参数更新将各层的参数和梯度分别存储在self.params和self.grads列表中。4.predict方法用于进行前向传播通过遍历self.layers列表依次调用每个层的前向传播方法forward并将输出作为下一层的输入最终返回最后一层的输出结果。5.forward方法在进行预测的同时计算了损失值。首先调用predict方法获取输出结果然后将输出结果和目标值t传入损失层self.loss_layer的前向传播方法forward计算得到损失值并返回。6.backward方法用于进行反向传播接收一个梯度dout作为输入该梯度的默认值为1。首先将梯度传递给损失层self.loss_layer的反向传播方法backward得到更新后的梯度。然后按照相反的顺序遍历self.layers列表依次调用每个层的反向传播方法backward将更新后的梯度传递给前一层最终返回最初输入层的梯度。随机梯度下降法的类-SGDclass SGD: 随机梯度下降法Stochastic Gradient Descent def __init__(self, lr0.01): self.lr lr def update(self, params, grads): for i in range(len(params)): params[i] - self.lr * grads[i]解释1.这里我实现了随机梯度下降法Stochastic Gradient DescentSGD的类SGD用于更新神经网络的参数。在初始化方法__init__中定义了学习率lr默认值为0.01。学习率控制了每次参数更新的步长。2.update方法接收两个参数params是网络中的参数列表grads是对应参数的梯度列表。该方法根据SGD的更新规则对每个参数进行更新。3.在循环中遍历了参数列表params和梯度列表grads的索引。对于每个参数和对应的梯度使用梯度乘以学习率的方式更新参数。这里采用了原地更新即直接在参数列表中更新参数的值。4.通过减去学习率乘以梯度实现了参数的更新。训练过程#1.设定超参数 max_epoch 300 batch_size 30 hidden_size 10 learning_rate 3.5 #2.读入数据生成模型和优化器 x,t load_data() model ThreeLayerNet(input_size2,hidden_size1hidden_size,hidden_size2hidden_size,output_size3) optimizer SGD(lrlearning_rate) #学习用的变量 data_size len(x) max_iters data_size // batch_size total_loss 0 loss_count 0 loss_list [] for epoch in range(max_epoch): #3.打乱数据 idx np.random.permutation(data_size) x x[idx] t t[idx] for iters in range(max_iters): batch_x x[iters*batch_size:(iters1)*batch_size] batch_t t[iters*batch_size:(iters1)*batch_size] #4.计算梯度更新参数 loss model.forward(batch_x,batch_t) model.backward() optimizer.update(model.params,model.grads) total_loss loss loss_count 1 #5.定期输出学习过程 if (iters1)%10 0: avg_loss total_loss / loss_count print(| epoch %d | iterations %d / %d | loss %0.2f% (epoch1,iters 1,max_iters,avg_loss)) loss_list.append(avg_loss) total_loss,loss_count 0,0解释1.这里我实现了一个训练过程的循环其中包含了数据处理、模型的前向传播、反向传播以及参数更新的步骤。首先在代码中设定了一些超参数包括最大迭代次数max_epoch、批大小batch_size、隐藏层大小hidden_size和学习率learning_rate。接下来通过调用load_data函数读取数据然后创建了一个ThreeLayerNet类的实例model指定了输入层大小为2、两个隐藏层大小为hidden_size、输出层大小为3的网络结构。同时创建了一个SGD类的实例optimizer传入学习率learning_rate。接着初始化了一些用于学习过程的变量包括数据集大小data_size、每个迭代中的最大批次数max_iters、总损失total_loss、损失计数loss_count和损失列表loss_list。2.下面是主要的训练循环通过max_epoch控制迭代次数。在每个迭代中首先进行数据的打乱操作使用np.random.permutation对数据索引进行随机排列然后根据打乱后的索引重新排列输入数据x和目标数据t实现数据的随机化。然后在每个迭代中根据最大批次数max_iters遍历数据集。每次迭代从数据集中选取一批数据包括输入数据batch_x和目标数据batch_t并进行以下步骤调用模型的forward方法计算当前批次的损失值并返回该损失值。调用模型的backward方法根据损失值进行反向传播计算参数的梯度。调用优化器的update方法根据梯度更新模型的参数。3.累计当前批次的损失值到total_loss中并增加loss_count计数器。如果当前批次的迭代次数是10的倍数输出当前迭代的平均损失值并将其添加到loss_list列表中。将total_loss和loss_count重置为0为下一个迭代做准备。训练循环的目的是通过多次迭代和参数更新逐渐减小损失值使模型适应训练数据实现模型的训练过程。输出的学习过程中的损失值可以用于监控训练的进展。运行迭代300次的结果图如下x, t load_data() # 绘制数据点 N 100 CLS_NUM 3 markers [o, x, ^] # 绘制决策边界 h 0.001 x_min, x_max x[:, 0].min() - .1, x[:, 0].max() .1 y_min, y_max x[:, 1].min() - .1, x[:, 1].max() .1 xx, yy np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) X np.c_[xx.ravel(), yy.ravel()] score model.predict(X) predict_cls np.argmax(score, axis1) Z predict_cls.reshape(xx.shape) plt.contourf(xx, yy, Z) for i in range(CLS_NUM): plt.scatter(x[i*N:(i1)*N, 0], x[i*N:(i1)*N, 1], s40, markermarkers[i]) plt.axis(off) # 是否关闭坐标轴 plt.show()解释1.这里用于绘制数据点和模型的决策边界。首先调用load_data函数加载数据并将输入数据赋值给变量x目标数据赋值给变量t。接着定义了一些用于绘制的参数。N表示每个类别的数据点数量CLS_NUM表示类别的数量markers是绘制数据点时使用的标记符号。然后通过指定步长h和输入数据的范围创建了一个网格xx和yy用于在整个输入空间上生成一组点。这些点将用于计算模型的预测结果并绘制决策边界。2.通过调用模型的predict方法对生成的点进行预测。X是一个二维数组每一行表示一个点的坐标。将这些点作为输入得到模型的预测结果score其中score是一个二维数组表示每个点属于不同类别的概率。3.使用np.argmax函数找到每个点概率最大的类别索引得到预测的类别标签predict_cls。然后将predict_cls重新调整为与网格一样的形状得到二维数组Z用于绘制决策边界。4.使用plt.contourf函数绘制决策边界通过填充不同区域的颜色来表示不同的类别。5.接下来使用循环遍历每个类别并使用plt.scatter函数绘制每个类别的数据点。通过切片操作x[i*N:(i1)*N, 0]和x[i*N:(i1)*N, 1]选择属于当前类别的数据点的坐标并使用对应的标记符号进行绘制。6.最后通过plt.axis(off)设置是否关闭坐标轴并调用plt.show()显示绘制的图像。由此产生的图像可以看到相较于两层神经网络的效果更好三层神经网络的结果如下所示绘制迭代效果图# loss_list----记录300次迭代次数 import numpy as np import matplotlib.pyplot as plt #正确显示中文和负号 plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]False # 数据准备 x3range(1,301) y3loss_list # 设置画布大小 plt.figure(figsize(12, 5)) # plot 画x与y和x与z的关系图 plt.plot(x3,y3,label损失函数,color#1F77B4, linewidth1,marker,markersize3) # 设置x轴标签、坐标轴范围坐标轴刻度坐标轴刻度旋转角度 plt.xlabel(iterations(x10),size14) plt.xlim(0,300) plt.xticks([0,50,100,150,200,250,300],rotation0,size12) # # 设置y轴标签、坐标轴范围坐标轴刻度坐标轴刻度旋转角度 plt.ylabel(loss,size14) plt.ylim(0,1.2) plt.yticks([0,0.2,0.4,0.6,0.8,1.0,1.2],rotation0,size12) #标题 plt.title(损失函数,size18) # 紧凑布局自动调整图形、坐标轴、标签之间的距离对于多个子图时尤其有用。 plt.tight_layout() # 设置显示图例要在plt.plot 时设置 labelxxx才能显示图例 plt.legend() #加网格线 plt.grid(True) # 保存图像可以是任意后缀名dpi设置图像清晰度 #plt.savefig(./fig1.pdf, dpi600) #要放在plt.show()之前否作保存的图像为空白 # 显示图像 plt.show()解释1.设置中文和负号显示通过设置plt.rcParams[font.sans-serif][SimHei]和plt.rcParams[axes.unicode_minus]False来确保图表中的中文和负号能够正确显示。2.数据准备定义了一个x轴的范围从1到300以及一个y轴的数据列表loss_list用于记录300次迭代的损失函数值。3.设置画布大小通过plt.figure(figsize(12, 5))设置绘图画布的大小为宽度12英寸、高度5英寸。4.绘制曲线使用plt.plot(x3, y3, label损失函数, color#1F77B4, linewidth1, marker, markersize3)绘制曲线x轴为迭代次数y轴为损失函数值。label损失函数用于在图例中显示曲线的标签color#1F77B4设置曲线的颜色linewidth1设置曲线的线宽marker表示不显示数据点的标记markersize3设置数据点的大小。5.设置坐标轴和刻度使用plt.xlabel(iterations(x10), size14)设置x轴的标签为iterations(x10)plt.xlim(0, 300)设置x轴的范围为0到300plt.xticks([0,50,100,150,200,250,300],rotation0,size12)设置x轴的刻度为[0, 50, 100, 150, 200, 250, 300]rotation0表示刻度标签不旋转size12表示刻度标签的字体大小。6.设置y轴的标签和刻度同理。设置标题使用plt.title(损失函数, size18)设置图表的标题为损失函数size18表示标题的字体大小。7.调整布局使用plt.tight_layout()自动调整图形、坐标轴、标签之间的距离使其紧凑显示。8.显示图例使用plt.legend()显示图例前提是在绘制曲线时设置了label损失函数。添加网格线使用plt.grid(True)添加网格线。实验结果如下四、实验心得通过这次实验我成功创建了一个用于识别螺旋状的数据集三层神经网络并对深度学习所需的数学知识有了更深入的理解。一开始我选择了ReLU激活函数但是在调整学习率时无法找到合适的参数。因此改用Sigmoid作为激活函数。通过建立三层神经网络我发现之前适用于两层神经网络的学习率并不适用于三层神经网络需要重新寻找适合的学习率而学习率设置得太小会导致学习的收敛速度变慢。通过对比两层和三层神经网络的训练结果我发现它们之间存在明显的差异特别是在中心点区域。这说明增加网络的层数可以更好地拟合复杂的数据集但也需要仔细调整参数以确保网络的有效训练。两层神经网络结果三层神经网络结果