基础性的神经网络介绍教程。适合完全入门者快速了解,但深度和新意不足。
目前我们处于围绕人工智能(AI)及其各种流行术语的炒作浪潮之中。
很自然地,许多人会感到与 AI 相关的术语复杂而令人压倒。
本文旨在揭开 AI 应用的神秘面纱,通过在 Ruby 中创建基础人工神经网络(ANN)来深入了解机器学习(ML),并阐明无处不在的"GPT"现象。
跟随我踏上这段启蒙之旅!
人工智能(AI)是计算机科学的一个分支,专注于创建能够执行模拟人类智能任务的"智能机器"。
AI 的历史可追溯到 20 世纪 50 年代,那时早期的 AI 程序和概念开始发展。
一些值得注意的 AI 里程碑包括 20 世纪 70 年代专家系统的发展、20 世纪 90 年代机器学习的兴起,以及最近深度学习和神经网络的进步。
AI 在各行各业具有广泛的实际应用,包括医疗保健、金融、制造、运输和娱乐。它被应用于语音识别、图像分类、自动驾驶车辆、虚拟助手、欺诈检测和推荐系统等领域。
为了解决现实世界的问题,AI 采用各种算法来处理特定场景。以下是一些示例:
👉 基于规则的系统 这些系统通常依赖预定义的规则来基于给定条件做出决策。
它们通常用于医疗诊断和欺诈检测等领域。
👉 自然语言处理 NLP(自然语言处理)在弥合计算机与人类语言之间的差距方面起着至关重要的作用。
情感分析和聊天机器人等应用,包括 GPT(Generative Pre-trained Transformer),都严重依赖 NLP 技术。
👉 搜索引擎 搜索引擎通常采用 AI 技术,包括用于标记化和索引等任务的 NLP(自然语言处理)。
谷歌搜索和微软必应等主要搜索引擎利用这些技术来增强它们的功能。
👉 机器学习 机器学习(ML)是 AI 的另一个重要组成部分,它使 AI 系统能够自动学习并从经验或数据中改进。
它被广泛应用于各个领域,包括模式识别、数据预测和分类。
现在,让我们深入机器学习算法的领域。
💡 机器学习概览
机器学习(ML)是人工智能(AI)的一个子领域,专注于开发算法和模型,使计算机系统能够学习并做出预测或决策,而无需显式编程。
它基于以下观点:机器可以分析和解释复杂的模式和数据,并利用它们来随时间改进其性能或行为。
以下是几种 ML 算法及其受益于机器学习的实际应用:
👉 线性回归 线性回归是一种统计方法,用于确定输入变量与其相应输出之间的最优线性关系。
它在经济学和金融等领域有广泛应用,能够预测房价或股票价格、估计销售收入等。
👉 K 最近邻 K 最近邻(KNN)是用于数据分类的算法,其中分类基于"k 个最近"邻居之间的多数相似性。
推荐系统通常利用 KNN 通过识别具有相似特征或行为的项目或内容来进行个性化推荐。
👉 神经网络 人工神经网络(ANN)是强大的深度学习模型,旨在模拟人脑的结构和功能。
它们由相互连接的神经元层组成,这些神经元利用数学激活函数根据邻近度更新权重并从数据中学习。
ANN 在各个领域有广泛应用,包括图像识别,它们在识别和分类图像中的对象方面表现出色。此外,ANN 还用于语言翻译任务,利用它们处理和理解文本数据的能力。
此外,ANN 被广泛用于大型语言模型(LLM)和 NLP 技术,特别是在基于 GPT(Generative Pre-trained Transformer)等模型的聊天机器人系统的开发中。
这些先进的神经网络架构使聊天机器人能够生成类似人类的响应,并与用户进行自然语言对话。
是的,我们说的就是你,ChatGPT!
AI、ML 和神经网络相关性概览:
📈 线性和非线性关系
在数学中,数据关系可以用包含 x 轴和 y 轴的坐标系定义。
当这些关系呈现线性比例时,意味着输出可以通过遵循与输入的线性比例来预测,我们将这样的关系称为线性关系。
一些线性关系类型可以列举如下:
正比例关系
另一方面,当输出无法通过遵循线性比例来预测时,意味着图形中的线不是直线而是曲线,我们将这样的关系分类为非线性关系。
以下是我们可以列出的一些非线性关系类型:
现在,是时候在这场激动人心的人工神经网络之旅中进一步深入了。
🧠 人工神经网络概览
要理解人工神经网络(ANN),了解人脑的功能至关重要。
🔵 一切都关于神经元
人脑由数十亿个相互连接的神经元组成,通过突触连接相互连接:
每当我们的大脑接收新信息时,就会经历一个过程,其中输入通过相互连接的神经元向前传播。
在此过程中,充当权重的偏差被应用并向后传播,从而产生新知识。
这个迭代过程称为学习或训练。
ANN 本质上由包含权重或偏差的多层相互连接的神经元组成。
类似于人脑,ANN 遵循如下所述的学习过程:
输入和目标被送入学习过程
激活函数应用于向前传播的数据
激活函数导数应用于向后传播的数据
💡 等等……什么是激活函数及其导数?
👉 ANN 和数据关系 在机器学习中,数据可以表现出线性或非线性关系。
线性回归等技术适合捕获线性关系,因为它们能够基于输入和输出之间的线性比例进行预测。
然而,线性回归可能不是预测非线性数据(如模式识别)的理想选择。这正是 ANN 等其他 ML 技术发挥作用的地方。
👉 激活函数 激活函数就像将输入转化为非线性关系,允许创建新知识。
ANN 提供各种可应用的非线性函数,包括双曲正切、ReLU(整流线性单元)、二次函数、Sigmoid 函数等。
每个函数都具有独特的属性和特征,使它们适合不同的场景。
为了本文的简洁起见,我们将使用 Sigmoid 函数,也称为 logistic 函数。
Sigmoid 函数是一种数学函数,以实数作为输入,并将其转换为 0 到 1 之间的另一个数字。
它的特点是 S 形曲线,其输出值表示概率或激活级别。
但在向后传播时,应用 Sigmoid 函数的导数。此导数有助于调整结果数据与误差幅度的偏差速率。
class Calc
def self.sigmoid(number) = 1 / (1 + Math.exp(-number))
def self.sigmoid_derivative(number) = number * (1 - number)
end
在 Ruby 中,我们可以使用简单的 PORO 对象开始建模层和神经元:
class Layer
attr_reader :neurons
attr_accessor :result
def initialize(neurons)
@neurons = neurons
end
end
###########################
class Neuron
attr_reader :weights
def initialize(weights)
@weights = weights
end
end
接下来,我们可以定义 ANN 由 3 层组成:
一个输入层,由 4 个神经元组成,每个包含 3 个权重
一个隐藏层,由 4 个神经元组成,每个包含 4 个权重
以及一个输出层,由 1 个神经元组成,包含 4 个权重
neuron_a = Neuron.new([-0.16595599, -0.70648822, -0.20646505]) neuron_b = Neuron.new([0.44064899, -0.81532281, 0.07763347]) neuron_c = Neuron.new([-0.99977125, -0.62747958, -0.16161097]) neuron_d = Neuron.new([-0.39533485, -0.30887855, 0.370439]) input_layer = Layer.new([neuron_a, neuron_b, neuron_c, neuron_d])
neuron_e = Neuron.new([-0.16595599, -0.70648822, -0.20646505, -0.34093502]) neuron_f = Neuron.new([0.44064899, -0.81532281, 0.07763347, 0.44093502]) neuron_g = Neuron.new([-0.99977125, -0.62747958, -0.16161097, 0.14093502]) neuron_h = Neuron.new([-0.39533485, -0.30887855, 0.370439, -0.54093502]) hidden_layer = Layer.new([neuron_e, neuron_f, neuron_g, neuron_h])
neuron_i = Neuron.new([-0.5910955, 0.75623487, -0.94522481, 0.64093502]) output_layer = Layer.new([neuron_i])
🔵 为 XOR 门定义输入和目标值
本文将设定由我们的 ANN 预测 XOR 门的结果。
inputs = [[0, 0, 1], [0, 1, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 1], [0, 0, 0]] targets = [[0], [1], [1], [1], [1], [0], [0]]
由于机器学习的本质是基于知识进行学习和预测,因此 ANN 主要包含两个过程:
学习过程,这是最重要的过程
预测过程
ANN 已经建模完成,现在该深入了解其中最重要的组成部分:学习过程。之后,我们将通过讲解预测过程来结束本文。
🧠 学习过程
基于已经建模的输入和目标值,可以按如下方式使用 ANN:
network = NeuralNetwork.new([input_layer, hidden_layer, output_layer])
network.learn(inputs, targets, 2_000)
现在来看一下 `learn` 的实现:
class NeuralNetwork def initialize(layers) @layers = layers end
def learn(inputs, targets, times) times.times do layers_with_results = ForwardPropagation.call(@layers, inputs) @layers = BackPropagation.call(inputs, targets, layers_with_results) end end end
首先,将各层和输入传递给名为 `ForwardPropagation` 的组件,它会返回每一层都已计算出结果的各个层
然后,将包含结果的各层连同输入和目标值一起传递给另一个名为 `BackPropagation` 的组件,它会返回调整后的各层,这意味着已经产生了新的知识
💡 请注意,接下来的内容会涉及大量矩阵(多维数组)运算。ANN 在学习过程中高度依赖线性代数
🔵 前向传播
前向传播可以理解为神经网络接收输入,并将其依次传递至每一层的过程。
对于每一层,都会将激活函数(例如 sigmoid)应用于接收到的输入与当前神经元所关联偏置(权重)的乘积。
这一计算会逐层依次执行,使网络能够根据给定的输入生成输出预测。
class ForwardPropagation def self.call(*args) = new(*args).call
def initialize(layers, inputs) @layers = layers.dup @inputs = inputs end
def call @layers.map.with_index do |layer, index| data = index.zero? ? @inputs : @layers[index - 1].result
layer.tap do layer.result = (Matrix[*data] * Matrix[*layer.to_matrix]).map(&Calc.method(:sigmoid)) end end end end
现在,我们可以看到前向传播过程的图示:
完成前向传播后,每一层都会根据 sigmoid 函数应用的非线性归一化得到一个新的预测结果。
但我们还必须将这些结果与目标值进行比较、计算误差幅度,最后确定结果与期望输出(目标值)之间的差距。
没错,我们说的就是反向传播。
反向传播是一个关键过程,其中会为每一层重新计算前向传播得到的预测结果。
这种重新校准会根据计算出的误差调整权重和偏置,目的是缩小预测结果与目标值之间的距离。
前向传播和反向传播的整个过程会不断迭代,直到预测结果与目标值高度接近,或者达到期望的准确率。
这种迭代式的重复过程允许神经网络通过微调权重和偏置,不断改进自身的预测结果。
👉 计算 delta:反向传播从计算 delta 开始。它主要使用 sigmoid 函数的导数,展示每一层的目标值与误差幅度之间相差多远。
def apply_sigmoid_derivative(result) result.to_a.map do |array| array.map do |value| Calc.sigmoid_derivative(value) end end end
@layers.map.with_index do |layer, index| result = index.zero? ? @inputs : previous_layer_of(layer).result
if layer == output_layer error = Matrix[*@target] - Matrix[*output_layer.result]
NaiveMatrixMultiply.call(apply_sigmoid_derivative(output_layer.result).dup, error.to_a) else factor = output_layer.to_matrix.transpose error = Matrix[*delta_output_layer] * Matrix[*factor]
NaiveMatrixMultiply.call(apply_sigmoid_derivative(layer.result).dup, error.to_a) end end
不同层的误差计算方式可能有所不同
输出层的误差基于目标值计算
输入层和隐藏层的误差基于输出层的误差计算
应用 sigmoid 导数后,会返回当前目标值与误差幅度之间的距离(delta)
👉 调整权重:在反向传播期间计算完 delta 后,该过程会通过调整 ANN 所有层中每个相互连接的神经元的权重(偏置)来结束。
利用线性代数原理,可以通过对当前权重矩阵与 delta 矩阵执行矩阵加法来应用这些调整,从而有效更新网络中神经元的权重。
该矩阵加法运算可以确保调整传遍整个网络,使网络能够随着时间推移进行学习和预测。
def adjusted_layer(layer, index) result = index.zero? ? @inputs : previous_layer_of(layer).result delta = delta(layer)
adjustment = Matrix[*result].transpose * Matrix[*delta] adjusted = Matrix[*layer.to_matrix] + adjustment
Layer.from_matrix(adjusted.to_a) end
下图展示了反向传播过程的概览:
在下方的图示中,我们可以看到整个学习过程的概览:
💡 我们的 ANN 可以学习,但它能进行预测吗?
只需使用前向传播过程即可。
🧠 预测过程
与学习过程相比,预测过程确实更简单。
在预测期间,我们只需获取前向传播过程中产生的输出层预测结果;输出层是网络的最后一层。
这样,无需执行学习过程中涉及的大量计算,我们便能快速获得模型针对给定输入的预测结果。
实现起来非常简单:
class Predict def self.call(*args) = new(*args).call
def initialize(layers, inputs) @layers = layers.dup @inputs = inputs end
def call layers_with_results = ForwardPropagation.call(@layers, @inputs) output_layer_result = layers_with_results.last.result
output_layer_result.first end end
如果学习过程可以描述如下:
那么预测过程可以用下图表示:
🧠 Ruby 中的一些测试用例
以下各节给出了两个 Ruby 测试用例示例,展示本文构建的 ANN 所具备的能力。
👉 预测 XOR 门
class TrainXORTest < Test::Unit::TestCase def test_train_xor_gate # Setup...
inputs = [[0, 0, 1], [0, 1, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 1], [0, 0, 0]] targets = Matrix[[0, 1, 1, 1, 1, 0, 0]].transpose.to_a
network = NeuralNetwork.new([layer_a, layer_b, layer_c]) network.learn(inputs, targets, 2_000)
assert_equal 0.05, network.predict([[1, 1, 0]]).round(2) end end
👉 预测水果和蔬菜
class TrainFruitsTest < Test::Unit::TestCase def test_train_fruits_and_vegetables # Setup...
fruits_func = -> do
[(1..3).to_a.shuffle.take(3), 0]
end
vegetables_func = -> do [(7..9).to_a.shuffle.take(3), 1] end
fruits_inputs = 50.times.map { fruits_func.call }
vegetables_inputs = 50.times.map { vegetables_func.call }
inputs = fruits_inputs.to_h.keys + vegetables_inputs.to_h.keys
outputs = fruits_inputs.to_h.values + vegetables_inputs.to_h.values
targets = Matrix[outputs].transpose.to_a
network = NeuralNetwork.new([layer_a, layer_b, layer_c])
network.learn(inputs, targets, 2_000)
fruits_and_vegetables = [
['Apple', fruits_func.call[0]],
['Banana', fruits_func.call[0]],
['Carrot', vegetables_func.call[0]],
['Orange', fruits_func.call[0]],
['Tomato', vegetables_func.call[0]],
['Pineapple', fruits_func.call[0]],
['Potato', vegetables_func.call[0]],
['Cherry', fruits_func.call[0]],
['Garlic', vegetables_func.call[0]],
['Broccoli', vegetables_func.call[0]],
['Peach', fruits_func.call[0]],
['Pear', fruits_func.call[0]],
['Lettuce', vegetables_func.call[0]]
]
fruits = %w[Apple Banana Orange Pineapple Cherry Peach Pear]
vegetables = %w[Carrot Tomato Potato Garlic Broccoli Lettuce]
fruits.each do |fruit|
assert network.predict([fruits_and_vegetables.to_h[fruit]]) < 0.5
end
vegetables.each do |vegetable|
assert network.predict([fruits_and_vegetables.to_h[vegetable]]) > 0.95
end
end
end
在本文中,我们介绍了人工智能、机器学习和神经网络的基本概念,并探讨了它们之间的关系。
此外,我们还深入研究了神经网络的内部工作原理,同时使用 Ruby 构建了一个简单的人工神经网络(ANN),它能够对 XOR 门以及水果和蔬菜之类的数据进行预测。
你可以在我的 ANN 项目 citrine 中找到本文编写的所有代码。我还用 Elixir 编写了一个 ANN。欢迎自行 fork、clone,并探索 ANN 的迷人世界。
本文是在 ChatGPT 的协助下完成的,它帮助对语法进行了一些“润色”。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。