为了账号安全,请及时绑定邮箱和手机立即绑定

用Ruby理解神经网络

神经网络是现代人工智能和机器学习的重要组成部分。它们受到人脑结构的启发,用于识别模式、做出预测和处理复杂数据。这些网络由相互连接的人工神经元组成,处理信息的方式与生物神经元相似。神经网络在计算机视觉、自然语言处理、金融预测和机器人技术等领域表现出色。随着计算能力的增强和算法的改进,神经网络变得越来越强大,带来了自动驾驶汽车、实时翻译和个性化推荐等领域的重大进展。

在这篇文章里,我们将看看神经网络是怎么运作的,并用Ruby实现一个简单的神经网络。

神经网络是什么?

一个神经网络由多层组成,这些层由神经元(也称为节点)通过权重连接。它通常有三种类型的层。

  1. 输入层:接收原始数据。
  2. 隐藏层:进行加权连接和激活函数的计算。
  3. 输出层:输出最终结果。

每个神经元接收并处理输入,应用一个激活器,并将结果传给下一层的神经元。

激活函数是什么?
神经网络中的激活函数

一个激活函数是用于神经元输出的一种函数。它决定神经元是否应该被激活。有多种不同的激活函数,每个都有其独特的优缺点。

一个好的激活函数应具备的特性

一个好的激活函数应该是这样的:

  • 非线性:这一点很重要,因为它允许网络学习数据中的复杂模式。
  • 可微分:这一点在训练过程中很重要,因为它允许我们计算损失函数的梯度。
  • 计算效率:这一点很重要,因为神经网络可能非常大,我们需要能够快速计算激活函数。
流行的激活函数介绍

Sigmoid (Sigmoid函数)

  • 输出一个介于 0 到 1 之间的值。
  • 常用于网络的 output layer 对二元分类问题的处理,其中 output layer 即输出层。
    class Sigmoid
      def self.call(x)
        1.0 / (1.0 + Math.exp(-x))
      end
    end

点击全屏模式,然后退出全屏。

双曲正切函数 (Tanh)

  • 输出一个介于 -1到1 之间的值。
  • 和 Sigmoid 类似,但它是以0为中心的,这使得训练起来更容易。
    class 双曲正切
      def self.调用(x)
        # 计算x的双曲正切值
        Math.tanh(x)
      end
    end

进入全屏 退出全屏

ReLU(线性整流单元)

  • 如果输入为负数,则输出 0 ,如果输入为正数,则输出 其本身的值
  • 常用于网络中的 隐藏层 中,以帮助避免梯度消失问题。
定义一个ReLU类
  定义一个类方法,名称为call,参数为x
  返回0和x中的最大值

全屏,退出全屏

### softmax(多分类)

* 将输出转换成概率。
* 计算量大。
# softmax激活函数
class Softmax
  def self.call(values)
    exp_values = values.map { |v| Math.exp(v) }
    sum_exp = exp_values.sum
    exp_values.map { |v| v / sum_exp }
  end
end

puts Softmax.call([2.0, 1.0, 0.1]).inspect # 输出一个总和为1的概率分布

进入全屏 退出全屏

激活函数是神经网络的重要组成部分,让它们能够高效地学习数据中的复杂模式。

## 实现一个简单的神经网络项目,用 Ruby 编写

我们来建这样一个**单层神经网络**,它接收两个输入并预测一个输出。
class SimpleNeuralNetwork
  attr_accessor :weights, :bias

  def initialize
    @weights = [rand, rand]  # 两个随机权重
    @bias = rand             # 随机偏差
  end

  # 前向传播计算
  def forward(inputs)
    sum = inputs[0] * @weights[0] + inputs[1] * @weights[1] + @bias
    Sigmoid.call(sum)  # 采用Sigmoid激活函数
  end

  # 训练模型
  def train(inputs, target, learning_rate = 0.1)
    prediction = forward(inputs)
    error = target - prediction

    # 更新权重和偏差
    @weights[0] += learning_rate * error * inputs[0]
    @weights[1] += learning_rate * error * inputs[1]
    @bias += learning_rate * error
  end
end

# 代码示例
nn = SimpleNeuralNetwork.new
puts nn.forward([1, 0])  # 输出预测结果
nn.train([1, 0], 1)       # 使用预期输出1进行训练模型
puts nn.forward([1, 0])  # 查看新预测结果

进入全屏,或退出全屏

### 解释: 

### (或直接使用空标题)###

1. 我们用**随机的权重和偏置值**来初始化网络。
2. **前向传播**计算加权和,并应用Sigmoid激活函数。
3. **训练过程**根据误差来调整权重。

## 用 Ruby 实现的简易感情分析神经网络模型

在本教程中,我们将用 Ruby 从零开始构建一个神经网络,该网络能够分析文本输入的情绪。我们的网络将能够将文本分类为负面、中性或正面。虽然这只是一个简化的实现,但它为理解情感分析和神经网络的工作方式提供了一个很好的起点。
require 'matrix'
require 'set'

class SentimentNeuralNetwork
  def initialize
    @vocabulary = Set.new
    @word_to_index = {}
    @input_size = 0
    @hidden_size = 64
    @output_size = 3  # 消极、中性、积极

    # 预训练的权重将在词汇构建完成后进行初始化
    @weights1 = nil
    @weights2 = nil
    @bias1 = nil
    @bias2 = nil
  end

  def preprocess_text(text)
    # 转换为小写并拆分成单词
    words = text.downcase.gsub(/[^a-z\s]/, '').split

    # 移除常见停用词
    stop_words = Set.new(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'])
    words.reject! { |word| stop_words.include?(word) }

    words
  end

  def build_vocabulary(training_texts)
    training_texts.each do |text|
      words = preprocess_text(text)
      @vocabulary.merge(words)
    end

    @vocabulary.each_with_index do |word, index|
      @word_to_index[word] = index
    end

    @input_size = @vocabulary.size
    initialize_weights
  end

  def initialize_weights
    # 使用Xavier/Glorot方法初始化权重
    xavier_init1 = Math.sqrt(6.0 / (@input_size + @hidden_size))
    xavier_init2 = Math.sqrt(6.0 / (@hidden_size + @output_size))

    @weights1 = Matrix.build(@input_size, @hidden_size) { rand(-xavier_init1..xavier_init1) }
    @weights2 = Matrix.build(@hidden_size, @output_size) { rand(-xavier_init2..xavier_init2) }
    @bias1 = Matrix.build(1, @hidden_size) { 0.0 }
    @bias2 = Matrix.build(1, @output_size) { 0.0 }
  end

  def text_to_vector(text)
    vector = Array.new(@input_size, 0)
    words = preprocess_text(text)

    words.each do |word|
      if @word_to_index.key?(word)
        vector[@word_to_index[word]] += 1
      end
    end

    # 归一化处理向量
    sum = vector.sum.to_f
    sum = 1.0 if sum == 0
    vector.map! { |x| x / sum }

    vector
  end

  def sigmoid(x)
    1.0 / (1.0 + Math.exp(-x))
  end

  def softmax(x)
    exp_x = x.map { |val| Math.exp(val) }
    sum = exp_x.sum
    exp_x.map { |val| val / sum }
  end

  def forward(input_vector)
    input_matrix = Matrix[input_vector]

    # 隐藏层神经元
    hidden = (input_matrix * @weights1 + @bias1).map { |x| sigmoid(x) }

    # 输出层神经元
    output = (hidden * @weights2 + @bias2).to_a[0]
    softmax(output)
  end

  def analyze_sentiment(text)
    # 将文本转换成向量
    input_vector = text_to_vector(text)

    # 前向传播
    output = forward(input_vector)

    # 获取预测结果
    sentiment_index = output.index(output.max)
    sentiment = ['negative', 'neutral', 'positive'][sentiment_index]
    confidence = output[sentiment_index]

    {
      sentiment: sentiment,
      confidence: confidence,
      probabilities: {
        negative: output[0],
        neutral: output[1],
        positive: output[2]
      }
    }
  end

  # 使用一些基本示例预训练网络
  def pretrain
    training_data = [
      ["I love this, it's amazing!", "positive"],
      ["This is great!", "positive"],
      ["What a wonderful day", "positive"],
      ["I don't like this at all", "negative"],
      ["This is terrible", "negative"],
      ["I hate this", "negative"],
      ["It's okay", "neutral"],
      ["This is fine", "neutral"],
      ["Not bad, not great", "neutral"]
    ]

    build_vocabulary(training_data.map(&:first))

    # 简单的训练循环(实际实现中将会更复杂)
    training_data.each do |text, label|
      input_vector = text_to_vector(text)
      target = case label
               when "negative" then [1, 0, 0]
               when "neutral" then [0, 1, 0]
               when "positive" then [0, 0, 1]
               end

      # 更新权重(简化训练过程)
      output = forward(input_vector)
      error = target.zip(output).map { |t, o| t - o }

      # 在完整实现中,这里会执行反向传播
    end
  end
end

# 示例用法
if __FILE__ == $0
  # 创建和训练网络
  network = SentimentNeuralNetwork.new
  network.pretrain

  # 测试一些示例
  test_texts = [
    "I really love this product!",
    "This is the worst experience ever",
    "It's an okay service, nothing special",
    "The quality is amazing",
    "I'm not sure how I feel about this"
  ]

  puts "\n情感分析的结果:\n\n"
  test_texts.each do |text|
    result = network.analyze_sentiment(text)
    puts "文本:#{text}"
    puts "情感:#{result[:sentiment]} (置信度:#{(result[:confidence] * 100).round(2)}%)"
    puts "概率:"
    result[:probabilities].each do |sentiment, prob|
      puts " #{sentiment}:#{(prob * 100).round(2)}%"
    end
    puts "\n"
  end
end

进入全屏,退出全屏

## 实现的拆解

### 1. 网络架构

我们的神经网络分为三层。
def initialize
  @词汇 = Set.new
  @单词索引 = {}
  @输入大小 = 0          # 根据词汇表大小设置
  @隐藏层大小 = 64        # 隐藏层中有64个神经元
  @输出大小 = 3         # 3种可能的输出(负面、中立、正面)

  # 权重和偏置
  @权重1 = nil
  @权重2 = nil
  @偏置1 = nil
  @偏置2 = nil
end

切换到全屏 退出全屏

* **输入层**:大小与词汇量有关
* **隐藏层**:包含64个使用Sigmoid激活函数的神经元
* **输出层**:包含3个使用Softmax激活函数的神经元

### 2. 文本预处理步骤

文本预处理这一步对于将原始文本转换成我们神经网络能理解的格式非常重要。
def preprocess_text(text)
  # 将文本转换为小写并拆分成单词
  words = text.downcase.gsub(/[^a-z\s]/, '').split

  # 移除这些常见的停用词
  stop_words = Set.new([]) 
  stop_words = Set.new(['the', 'a', 'an', 'and', 'or', 'but', 'in', 
                       'on', 'at', 'to', 'for', 'of', 'with', 'by'])
  words.reject! { |word| stop_words.include?(word) }

  words
end

切换到全屏 退出全屏

这种方法。

1. 将文本转为小写
2. 用正则表达式去掉标点
3. 按单词分隔
4. 删除常见停用词

### 3\. 词汇积累

词汇系统将单词转换成数字向量:

def build_vocabulary(training_texts)
training_texts.each do |text|
words = preprocess_text(text)
@vocabulary.merge(words)
end

@vocabulary.each_with_index do |word, index|
@word_to_index[word] = index
end

@input_size = @vocabulary.size
initialize_weights
end


构建词汇表的过程如下:`build_vocabulary` 方法接受一个文本列表 `training_texts`,对每个文本进行预处理(`preprocess_text` 方法),并将处理后的词汇合并到 `@vocabulary` 中。然后,它会为词汇表中的每个单词分配一个唯一的索引,并存储在 `@word_to_index` 中。最后,`@input_size` 被设置为词汇表的大小,随后初始化权重(`initialize_weights` 方法)。

进入全屏模式 退出全屏

这会创建单词与索引的映射关系,来生成用于文本的输入向量。

### 4\. 权重初始化

我们采用Xavier/Glorot初始化方法来提升训练的稳定性,使之更加稳定。
def initialize_weights
  # 初始化权重
  xavier_init1 = Math.sqrt(6.0 / (@input_size + @hidden_size))  # Xavier初始化1
  xavier_init2 = Math.sqrt(6.0 / (@hidden_size + @output_size))  # Xavier初始化2

  @weights1 = Matrix.build(@input_size, @hidden_size) { rand(-xavier_init1..xavier_init1) }  # 权重矩阵1
  @weights2 = Matrix.build(@hidden_size, @output_size) { rand(-xavier_init2..xavier_init2) }  # 权重矩阵2
  @bias1 = Matrix.build(1, @hidden_size) { 0.0 }  # 偏置矩阵1
  @bias2 = Matrix.build(1, @output_size) { 0.0 }  # 偏置矩阵2
end

点击进入全屏模式,点击退出全屏模式.

### 5\. 文本转矢量

将输入的文本转换成数值向量:
def text_to_vector(text)
  向量表示 = Array.new(@input_size, 0)
  词 = 预处理文本(text)

  词.each do |word|
    if @word_to_index.key?(word)
      向量表示[@word_to_index[word]] += 1
    end
  end

  # 归一化该向量
  总和值 = 向量表示.sum.to_f
  如果 总和值 == 0
    总和值 = 1.0
  向量表示.map! { |x| x / 总和值 }

  向量表示
end

全屏显示 退出全屏

### 6. 前向传播算法

网络的前向传播可以这样理解:
def forward(input_vector)
  # 定义前向传播方法,输入为向量
  input_matrix = Matrix[input_vector]

  # 隐藏层(隐层)计算隐藏层的输出
  hidden = (input_matrix * @weights1 + @bias1).map { |x| sigmoid(x) }

  # 输出层(输出)计算输出层的输出
  output = (hidden * @weights2 + @bias2).to_a[0]
  softmax(output)
end

全屏,退出全屏

### 7\. 情感分析

分析文本情感的一个主要方法是:
def analyze_sentiment(text)
  # 将文本转为向量
  input_vector = text_to_vector(text)

  # 前向计算
  output = forward(input_vector)

  # 得到预测
  sentiment_index = output.index(output.max)
  sentiment = ['negative', 'neutral', 'positive'][sentiment_index]
  confidence = output[sentiment_index]

  {
    sentiment: sentiment,
    confidence: confidence,
    probabilities: {
      negative: output[0],
      neutral: output[1],
      positive: output[2]
    }
  }
end

点击进入全屏,需要退出时请点击退出全屏

## 上网

这里是如何使用情感分析网络的:
# 创建并训练网络
network = SentimentNeuralNetwork.new
network.pretrain

# 分析一些文本
result = network.analyze_sentiment("我觉得这个产品真的很好!")
puts result[:sentiment]           # => "positive"
puts result[:confidence]          # => 置信度
puts result[:probabilities]       # => 所有概率的值


点击全屏 点击退出全屏

## 限制及可能的改进方向

这个实现有几点需要注意的地方:

1. **简单架构**:网络采用了一种简单的前馈架构。更复杂的架构如LSTM或Transformer模型表现会更好。

2. **简短训练**:预训练非常简单。一个生产系统需要:

* 增加训练数据集的规模
* 正确的反向传播算法
* 交叉验证
* 学习率优化
3. **基本文本处理** :文本预处理可以进一步优化:

3. **基本文本处理**:文本预处理可以进一步优化,

* 更佳的分词处理
* 词形归一化
* N-gram 支持(连续词组模型)
* 词向量
4. **不理解上下文**:网络不理解上下文、讽刺或复杂语言模式。

这实现为理解情感分析如何通过神经网络工作提供了基础。虽然它还不能直接用于生产,但它展示了关键概念,并可以进一步扩展应用于更复杂的情况。

记得,实际中的情感分析系统通常使用更高级的技术和预训练模型,但构建一个简单的版本有助于理解基本原理。

## 总结

神经网络是解决复杂问题的强大工具,它们在本文中发挥了作用。本文中,我们在Ruby中实现了一个简单的神经网络,探索了不同的激活函数,并观察了学习的过程。虽然Ruby不是深度学习中最常见的语言,这种实现帮助我们更好地理解神经网络的工作机制。

## 参考资料

1. Ian Goodfellow, Yoshua Bengio, Aaron Courville - 《深度学习》(MIT出版社)
2. Michael Nielsen - 《神经网络与深度学习》(书籍)
3. Andrew Ng - 《机器学习系列课程》(Coursera)
4. Geoffrey Hinton - 《神经网络课程讲义》
5. Ruby 官方文档 - <https://ruby-doc.org/>
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消