# ML **Repository Path**: limingth/ML ## Basic Information - **Project Name**: ML - **Description**: The Machine Learning Way - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-15 - **Last Updated**: 2024-12-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ML ### 介绍 The Machine Learning Way ### 参考 https://www.freecodecamp.org/chinese/news/how-to-add-two-numbers-using-machine-learning/ ### pure-add.py ``` import numpy as np from keras.models import Sequential from keras.layers import Dense # 定义训练样本的数量,这里设置为1000个样本 num_samples = 1000 # 生成随机的训练输入数据,形状为 (1000, 2),即1000个样本,每个样本有2个特征 # 特征值是在 [0, 1) 区间内均匀分布的随机数 X_train = np.random.rand(num_samples, 2) # 计算训练数据对应的目标输出,也就是每对输入特征值(两个数)的和 # 通过对 X_train 二维数组按列取元素并相加来实现 y_train = X_train[:, 0] + X_train[:, 1] # 创建一个Sequential顺序模型,Sequential模型是Keras中一种简单的线性堆叠模型结构 # 方便按照顺序依次添加神经网络的各层 model = Sequential() # 向模型中添加第一个全连接层(Dense层) # 参数 8 表示该层有8个神经元,input_shape=(2,) 定义了输入数据的形状,即每个输入样本有2个特征 # activation='relu' 表示使用ReLU(Rectified Linear Unit)作为该层的激活函数 # ReLU函数能增加模型的非线性表达能力,对于处理线性不可分的数据很有帮助 model.add(Dense(8, input_shape=(2,), activation='relu')) # 再向模型中添加第二个全连接层,该层只有1个神经元,通常用于输出最终的预测结果 # 这里没有指定激活函数,对于回归任务(此处是预测两数之和,属于回归问题),常不使用激活函数或者使用线性激活函数(默认等同于没有激活函数) model.add(Dense(1)) # 编译模型,配置训练过程中需要使用的损失函数和优化器 # loss='mse' 表示使用均方误差(Mean Squared Error)作为损失函数,用于衡量模型预测结果与真实结果之间的差距 # optimizer='adam' 表示使用Adam优化算法来更新模型的权重,Adam是一种自适应学习率的优化算法,通常能较快地收敛且效果较好 model.compile(loss='mse', optimizer='adam') # 定义每次训练时使用的批量大小,即将训练数据分成若干个大小为32的批次进行训练 batch_size = 32 # 定义训练的轮数,即整个训练数据集将被重复训练的次数,这里设置为100轮 epochs = 100 # 使用训练数据对模型进行训练 # X_train 是输入的训练数据,y_train 是对应的真实目标输出 # batch_size 指定了训练的批量大小,epochs 指定了训练轮数,verbose=1 表示在训练过程中显示详细的训练进度信息(例如每一轮的损失值等) model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1) # 定义测试输入数据,是一个二维数组,包含两个样本,每个样本有2个特征 # 这里手动设置了两组输入数据,用于测试模型对两数求和的预测能力 test_input = np.array([[1, 2], [0.3, 0.4]]) # 使用训练好的模型对测试输入数据进行预测,得到预测结果 predicted_sum = model.predict(test_input) # 打印提示信息 print("Predicted sums:") # 打印模型对测试输入数据预测得到的两数之和的结果 print(predicted_sum) ``` ### my-pure-add.py ``` import numpy as np # 激活函数及其导数 # ReLU(Rectified Linear Unit)激活函数定义,它将小于0的值置为0,大于等于0的值保持不变 def relu(x): return np.maximum(0, x) # ReLU激活函数的导数定义,当输入x大于0时,导数为1,否则为0 # 通过将布尔值(x > 0)转换为浮点数(astype(float))来实现,True转换为1.0,False转换为0.0 def relu_derivative(x): return (x > 0).astype(float) # 恒等函数,直接返回输入值,这里作为输出层的激活函数(对于回归任务,类似两数求和这种,可直接用恒等函数) def identity_function(x): return x # 恒等函数的导数,恒为1,因为其斜率始终为1 def identity_derivative(x): return 1 # 神经网络模型 class SimpleNeuralNetwork: def __init__(self, input_nodes, hidden_nodes, output_nodes): # 记录输入层节点数量 self.input_nodes = input_nodes # 记录隐藏层节点数量 self.hidden_nodes = hidden_nodes # 记录输出层节点数量 self.output_nodes = output_nodes # 初始化权重和偏置 # 随机初始化从输入层到隐藏层的权重矩阵,形状为 (输入节点数, 隐藏节点数) # 乘以0.01是为了将权重初始值缩小,避免初始权重过大导致训练不稳定等问题 self.weights_input_to_hidden = np.random.randn(self.input_nodes, self.hidden_nodes) * 0.01 # 随机初始化从隐藏层到输出层的权重矩阵,形状为 (隐藏节点数, 输出节点数) # 同样乘以0.01进行缩放 self.weights_hidden_to_output = np.random.randn(self.hidden_nodes, self.output_nodes) * 0.01 # 初始化隐藏层的偏置向量,长度为隐藏节点数,初始值全为0 self.bias_hidden = np.zeros(self.hidden_nodes) # 初始化输出层的偏置向量,长度为输出节点数,初始值全为0 self.bias_output = np.zeros(self.output_nodes) def feedforward(self, X): # 前向传播计算隐藏层的输出 # 先计算输入层与输入到隐藏层权重矩阵的乘积,再加上隐藏层的偏置,最后通过ReLU激活函数得到隐藏层的输出 self.hidden_layer = relu(np.dot(X, self.weights_input_to_hidden) + self.bias_hidden) # 计算输出层的输出 # 将隐藏层的输出与隐藏到输出层的权重矩阵相乘,再加上输出层的偏置,最后通过恒等函数得到最终输出层的输出 self.output_layer = identity_function(np.dot(self.hidden_layer, self.weights_hidden_to_output) + self.bias_output) return self.output_layer def train(self, X, y, epochs, learning_rate, batch_size): # 获取输入数据的样本数量(即行数) num_samples = X.shape[0] for epoch in range(epochs): # 创建一个从0到样本数量 - 1的索引数组,用于后续打乱数据顺序 indices = np.arange(num_samples) # 对索引数组进行随机打乱,实现每个epoch训练时数据顺序不同,增加随机性,有助于模型更好地学习 np.random.shuffle(indices) # 根据打乱后的索引重新排列输入数据X X = X[indices] # 根据打乱后的索引重新排列目标输出数据y y = y[indices] # 用于累计每个batch的损失,以便后续计算平均损失 total_loss = 0 for i in range(0, num_samples, batch_size): # 取出一个batch的输入数据,范围是从i到i + batch_size(左闭右开) batch_X = X[i:i+batch_size] # 取出对应batch的目标输出数据 batch_y = y[i:i+batch_size] # 进行前向传播,得到该batch的输出结果 output = self.feedforward(batch_X) # 计算该batch的预测误差,即真实输出与预测输出的差值 error = batch_y - output # 计算均方误差(Mean Squared Error)作为损失函数,先对误差求平方,再求平均 loss = np.mean(np.square(error)) # 将该batch的损失累加到total_loss中 total_loss += loss # 计算输出层的误差项(delta),根据链式法则,等于误差乘以输出层激活函数的导数 output_delta = error * identity_derivative(output) # 计算隐藏层的误差,通过输出层的误差项与隐藏到输出层权重矩阵的转置相乘得到 hidden_error = output_delta.dot(self.weights_hidden_to_output.T) # 计算隐藏层的误差项(delta),根据链式法则,等于隐藏层误差乘以隐藏层激活函数的导数 hidden_delta = hidden_error * relu_derivative(self.hidden_layer) # 更新隐藏层到输出层的权重,根据梯度下降算法,使用学习率乘以隐藏层输出的转置与输出层误差项的乘积来更新权重 self.weights_hidden_to_output += np.dot(self.hidden_layer.T, output_delta) * learning_rate # 更新输入层到隐藏层的权重,同样依据梯度下降算法,用学习率乘以输入数据batch_X的转置与隐藏层误差项的乘积来更新权重 self.weights_input_to_hidden += np.dot(batch_X.T, hidden_delta) * learning_rate # 更新隐藏层的偏置,将隐藏层误差项按列求和(axis=0),再乘以学习率来更新偏置 self.bias_hidden += np.sum(hidden_delta, axis=0) * learning_rate # 更新输出层的偏置,将输出层误差项求和,再乘以学习率来更新偏置 self.bias_output += np.sum(output_delta) * learning_rate # 打印每个epoch的平均损失,平均损失等于累计的总损失除以batch的数量(num_samples // batch_size) print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss / (num_samples // batch_size)}") # 创建神经网络实例 # 设置输入层节点数量为2,对应输入的两个数(两数求和任务) input_nodes = 2 # 设置隐藏层节点数量为8 hidden_nodes = 8 # 设置输出层节点数量为1,因为输出就是两数之和这一个结果 output_nodes = 1 neural_network = SimpleNeuralNetwork(input_nodes, hidden_nodes, output_nodes) # 创建1000对随机数字作为输入 num_samples = 1000 # 生成形状为 (1000, 2) 的随机输入数据,每个样本有2个特征,特征值在 [0, 1) 区间均匀分布 X_train = np.random.rand(num_samples, 2) # 计算对应的目标输出,即每对输入数字的和,通过按列取元素相加得到 y_train = X_train[:, 0] + X_train[:, 1] # 训练神经网络 # 设置训练的轮数为100 epochs = 100 # 设置学习率为0.001,学习率控制每次权重更新的步长大小 learning_rate = 0.001 # 设置每个batch的大小为32,即每次用32个样本进行一次权重更新 batch_size = 32 neural_network.train(X_train, y_train.reshape(num_samples, 1), epochs, learning_rate, batch_size) # 测试神经网络 # 定义测试输入数据,包含两个样本,每个样本有2个特征 test_input = np.array([[1, 2], [0.3, 0.4]]) # 使用训练好的神经网络进行前向传播,得到预测的两数之和的结果 predicted_sum = neural_network.feedforward(test_input) # 打印提示信息 print("Predicted sums:") # 打印预测的两数之和的结果 print(predicted_sum) ``` ### 整体分析: * 代码功能概述: 这段代码实现了一个简单的全连接神经网络,用于解决两数求和的问题。首先定义了所需的激活函数及其导数,接着创建了一个自定义的神经网络类,在类中实现了网络的初始化、前向传播以及训练(包含反向传播更新权重和偏置)的功能。然后生成了随机的训练数据,并利用这些数据对神经网络进行训练,最后使用给定的测试数据进行预测并输出结果。 * 模型结构与原理: - 网络结构:该神经网络包含一个输入层(2 个节点,对应两个输入的数)、一个隐藏层(8 个节点,使用 ReLU 激活函数)以及一个输出层(1 个节点,使用恒等函数作为激活函数)。 - 训练原理:在训练过程中,每个 epoch 先对数据进行随机打乱,再按 batch_size 分批处理数据。对于每一批数据,先进行前向传播得到预测输出,计算与真实输出的误差,接着通过反向传播算法,依据链式法则计算各层的误差项(delta),然后根据梯度下降算法,利用这些误差项来更新权重和偏置,不断迭代这个过程以减小损失函数(均方误差)的值,使得模型能够学习到输入的两个数与它们之和的映射关系。 * 可能的改进方向: - 超参数调整:可以尝试不同的学习率、隐藏层节点数量、batch_size 以及训练的轮数等超参数组合,通过交叉验证等方式找到更优的参数设置,提高模型的预测准确性和收敛速度。 - 优化算法改进:可以考虑使用更先进的优化算法替代简单的梯度下降(虽然这里用了学习率自适应的更新方式,但还有其他更好的优化算法),如 Adagrad、Adadelta、RMSProp 等,进一步提升训练效果。 - 模型结构拓展:根据具体需求,可尝试增加更多的隐藏层、改变激活函数或者添加正则化项等来增强模型的表达能力,防止过拟合,以应对更复杂的数据或任务场景。