梯度指示的方向是各点处的函数值减小最多的方向
构建一个二层神经网络
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 将父目录添加到系统路径,以便导入父目录中的文件
from common.functions import *
from common.gradient import numerical_gradient
# 定义一个名为 TwoLayerNet 的类,用于构建两层神经网络
class TwoLayerNet:
# 初始化方法,在创建对象时自动调用
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 初始化神经网络的参数
self.params = {} #创建一个空字典
# 随机初始化第一层的权重矩阵,使用正态分布,标准差为 weight_init_std
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
# 初始化第一层的偏置向量为全零
self.params['b1'] = np.zeros(hidden_size)
# 随机初始化第二层的权重矩阵,使用正态分布,标准差为 weight_init_std
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
# 初始化第二层的偏置向量为全零
self.params['b2'] = np.zeros(output_size)
# 预测方法,输入数据 x 进行前向传播得到预测结果
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 = sigmoid(a1)
# 第二层的线性计算和 softmax 函数得到最终预测结果
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# 计算损失的方法,输入数据 x 和监督数据 t
def loss(self, x, t):
y = self.predict(x) # 进行预测得到预测结果
# 计算交叉熵损失
return cross_entropy_error(y, t)
# 计算准确率的方法,输入数据 x 和监督数据 t
def accuracy(self, x, t):
y = self.predict(x) # 进行预测得到预测结果
y = np.argmax(y, axis=1) # 找出预测结果中每个样本的最大概率类别
t = np.argmax(t, axis=1) # 找出监督数据中每个样本的真实类别
#预测类别: [1 0 2 0 1]
#真实类别: [2 0 1 0 1]
#共五个列表,预测类别意思是:预测分别第1,0,2,0,1个是正确的
#用这个和真实类别去做比较,即可得出准确率
# 计算准确率
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# 数值微分求梯度(out)
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t) # 定义一个以权重 W 为参数的损失函数,调用了上面的方法
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
# 计算梯度的方法(误差反向传播法),输入数据 x 和监督数据 t
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2'] # 获取第一层和第二层的权重矩阵
b1, b2 = self.params['b1'], self.params['b2'] # 获取第一层和第二层的偏置向量
grads = {} # 用于存储梯度的字典
batch_num = x.shape[0] # 获取批量数据的数量
# 前向传播
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# 反向传播
dy = (y - t) / batch_num # 计算输出层的误差
grads['W2'] = np.dot(z1.T, dy) # 计算第二层权重的梯度
grads['b2'] = np.sum(dy, axis=0) # 计算第二层偏置的梯度
da1 = np.dot(dy, W2.T) # 计算第一层的误差
dz1 = sigmoid_grad(a1) * da1 # 计算第一层激活函数的导数乘误差
grads['W1'] = np.dot(x.T, dz1) # 计算第一层权重的梯度
grads['b1'] = np.sum(dz1, axis=0) # 计算第一层偏置的梯度
return grads
下图是整个学习过程 (调用了上图中的类里的方法,创建了实例)
# coding: utf-8
import sys, os
# 导入系统和操作系统相关的模块
sys.path.append(os.pardir)
# 将父目录添加到系统路径,以便能够导入父目录中的模块或文件
import numpy as np
# 导入 NumPy 库,用于数值计算和数组操作
import matplotlib.pyplot as plt
# 导入 Matplotlib 的 pyplot 模块,用于绘图
from dataset.mnist import load_mnist
# 从 'dataset.mnist' 模块中导入 'load_mnist' 函数,用于加载 MNIST 数据集
from two_layer_net import TwoLayerNet
# 从 'two_layer_net' 模块中导入 'TwoLayerNet' 类,用于构建两层神经网络
# 读入 MNIST 数据集,并进行数据标准化和独热编码标签处理
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
# 创建一个 TwoLayerNet 类的实例,设置输入层大小、隐藏层大小和输出层大小
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) # 调用了二层神经网络
# 设定训练的迭代次数
iters_num = 10000
# 获取训练集数据的数量
train_size = x_train.shape[0]
# 设定每次训练使用的小批量数据的大小
batch_size = 100 # 迭代10000次,一批为100.
# 设定学习率
learning_rate = 0.1
# 初始化用于存储训练损失值的列表
train_loss_list = []
# 初始化用于存储训练准确率的列表
train_acc_list = []
# 初始化用于存储测试准确率的列表
test_acc_list = []
# 计算每个 epoch 包含的迭代次数,每轮epoch都要遍历整个数据集的数据,故总量÷一批
# 也就计算出每轮epoch要迭代训练多少次
iter_per_epoch = max(train_size / batch_size, 1)
# 开始训练的循环
for i in range(iters_num):
# 从训练集数据数量中随机选择 batch_size 个索引,也就是100个
batch_mask = np.random.choice(train_size, batch_size)
# 根据随机选择的索引获取小批量的训练数据
x_batch = x_train[batch_mask]
# 根据随机选择的索引获取小批量的训练标签
t_batch = t_train[batch_mask]
# 可以计算出关于这一小批数据的梯度
# grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 通过循环更新神经网络的参数
for key in ('W1', 'b1', 'W2', 'b2'):
# 从当前参数的值中减去学习率乘以对应梯度的值,以实现对参数的调整,从而使神经网络在训练过程中逐渐优化性能。
# 若梯度值为正,也就是说损失函数曲线向上的,递增的,故应该减少当前参数,使得损失函数的值变小(逆向)
network.params[key] -= learning_rate * grad[key]
# 计算当前小批量数据的损失值,由于network是哪个二层神经元的实例,故可以直接调用那个类里的方法
loss = network.loss(x_batch, t_batch) # 计算交叉熵损失
# 将损失值添加到列表中
train_loss_list.append(loss)
# 如果当前迭代次数是每个 epoch 迭代次数的整数倍,说明该换轮了
if i % iter_per_epoch == 0:
# 计算整个训练集的准确率,非batch
train_acc = network.accuracy(x_train, t_train)
# 计算测试集的准确率
test_acc = network.accuracy(x_test, t_test)
# 将训练集准确率添加到列表
train_acc_list.append(train_acc)
# 将测试集准确率添加到列表
test_acc_list.append(test_acc)
# 打印训练集和测试集的准确率
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 定义绘图的标记
markers = {'train': 'o', 'test': 's'}
# 创建表示 epochs 的 x 轴数据
x = np.arange(len(train_acc_list))
# 绘制训练集准确率曲线,并添加标签
plt.plot(x, train_acc_list, label='train acc')
# 绘制测试集准确率曲线,虚线,并添加标签
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
# 设置 x 轴标签
plt.xlabel("epochs")
# 设置 y 轴标签
plt.ylabel("accuracy")
# 设置 y 轴范围
plt.ylim(0, 1.0)
# 显示图例,位置在右下角
plt.legend(loc='lower right')
# 显示图形
plt.show()
数据准备
- 使用
load_mnist
函数加载MNIST数据集,并对数据进行预处理,包括归一化输入数据(使每个像素值处于0到1之间)和将标签转换为one-hot编码形式。 - 加载的数据被分为训练集和测试集,训练集包含60,000张图像,测试集包含10,000张图像。
模型定义
- 实例化
TwoLayerNet
类(代码图1),创建一个两层神经网络模型。 - 输入层大小为784(对应28x28像素的图像),隐藏层大小为50,输出层大小为10(对应10个数字类别)。
训练设置
- 设定训练的总迭代次数为10,000次。
- 每次迭代使用的小批量数据大小(batch size)为100。
- 学习率为0.1。
- 初始化列表来记录训练过程中的损失值和准确率。(方便查看,绘图用)
训练过程
- 在给定的迭代次数内,每次迭代执行以下步骤:
- 随机抽取一个小批量的训练数据及其对应的标签。
- 计算该小批量数据的梯度。
- 利用这些梯度来更新网络的参数(权重和偏置),这里使用的是随机梯度下降(SGD)算法。
- 计算该小批量数据的损失值,并将其添加到损失值列表中。
- 每完成一个epoch(即遍历完整个训练集),计算并记录训练集和测试集上的准确率,并打印这些准确率。
可视化结果
- 使用Matplotlib绘制训练集和测试集准确率随训练周期变化的趋势图。
具体细节
-
数据加载与预处理:
load_mnist(normalize=True, one_hot_label=True)
:加载MNIST数据集,对数据进行归一化处理,并将标签转换为one-hot编码形式。
-
网络初始化:
TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
:创建一个两层神经网络,输入层接收784维的向量(28x28像素的图像展平后的向量),隐藏层有50个神经元,输出层有10个神经元(分别对应0-9这10个数字)。
-
训练过程:
- 小批量随机梯度下降:每次迭代都从训练集中随机抽取100个样本组成一个小批量。
- 梯度计算:使用
network.gradient(x_batch, t_batch)
计算梯度。这个方法可能使用了反向传播算法。 - 参数更新:使用SGD算法更新网络参数。对于每一个权重矩阵和偏置向量,通过减去学习率乘以相应的梯度来更新其值。
- 损失计算:在每次迭代中,计算当前小批量数据的损失值,并将其添加到
train_loss_list
列表中。 - 准确率计算:每完成一个epoch,计算训练集和测试集的准确率,并将它们分别添加到
train_acc_list
和test_acc_list
列表中。
-
结果可视化:
- 绘制训练集和测试集准确率随训练周期变化的趋势图。通过这种方式,可以直观地看到模型在训练过程中的表现以及是否出现过拟合现象。
文件的读取和写入(比较简单):
def read_file_example(filename):
# 使用 'with' 语句自动管理文件的打开和关闭
with open(filename, 'r') as file:
# 一次性读取全部内容
all_content = file.read() # 读取文件的所有内容
print("All Content:") # 输出提示信息
print(all_content) # 打印文件内容
# 回到文件开始位置,以便可以再次读取
file.seek(0) # 将文件指针移到文件的开头
# 逐行读取
print("\nReading Line by Line:") # 输出提示信息
for line in file: # 遍历文件中的每一行
print(line.strip()) # 打印去除行尾换行符的每一行
# 回到文件开始位置,以便可以再次读取
file.seek(0) # 将文件指针移到文件的开头
# 读取特定数量的字符
print("\nFirst 10 Characters:") # 输出提示信息
print(file.read(10)) # 读取文件的前10个字符并打印
# 回到文件开始位置,以便可以再次读取
file.seek(0) # 将文件指针移到文件的开头
# 读取所有行到列表
print("\nLines as List:") # 输出提示信息
lines = file.readlines() # 读取文件中的所有行,并将每一行作为一个字符串元素存入列表
for line in lines: # 遍历列表中的每一行
print(line.strip()) # 打印去除行尾换行符的每一行
# 调用函数,传入文件名
filename = 'testpy.txt'
read_file_example(filename)
import os
def write_to_file(filename, data):
"""
写入文件的函数。
# :param filename: 文件名
# :param data: 要写入的数据
"""
# 使用 'with' 语句自动管理文件的打开和关闭
with open(filename, 'w', encoding='utf-8') as file:
# 写入数据
file.write(data)
print(f"Data written to file: {filename}")
def append_to_file(filename, data):
"""
向文件追加数据的函数。
:param filename: 文件名
:param data: 要追加的数据
"""
# 使用 'with' 语句自动管理文件的打开和关闭
with open(filename, 'a', encoding='utf-8') as file:
# 追加数据
file.write(data)
print(f"Data appended to file: {filename}")
def write_binary_data(filename, data):
"""
写入二进制数据的函数。
:param filename: 文件名
:param data: 要写入的二进制数据
"""
# 使用 'with' 语句自动管理文件的打开和关闭
with open(filename, 'wb') as file:
# 写入二进制数据
file.write(data)
print(f"Binary data written to file: {filename}")
# 调用函数
if __name__ == '__main__':
# 文本数据写入
text_filename = 'testpy.txt'
text_data = "Hello, this is an example of writing text to a file.\n"
write_to_file(text_filename, text_data)
# 追加文本数据
append_text_data = "\nThis text was appended to the file."
append_to_file(text_filename, append_text_data)