基于C#构建一个简单的循环神经网络,模拟对话

news2024/11/22 8:57:35

循环神经网络(Recurrent Neural Network, RNN)是一种用于处理序列数据的神经网络模型。与传统的前馈神经网络不同,RNN具有内部记忆能力,可以捕捉到序列中元素之间的依赖关系。这种特性使得RNN在自然语言处理、语音识别、时间序列预测等需要考虑上下文信息的任务中表现出色。

RNN的基本结构

RNN的基本结构包括输入层、隐藏层和输出层。在处理序列数据时,RNN会按照序列的时间顺序逐个处理每个元素。对于序列中的每一个时间步,RNN不仅会接收该时间步的输入,还会接收上一个时间步的隐藏状态作为输入。这样,通过将之前的信息传递给后续的处理步骤,RNN能够利用历史信息来影响当前的输出。

方法

  • InitializeWeightsAndBiases():使用随机值初始化权重矩阵和偏置向量。
  • Sigmoid():激活函数,用于隐藏层的非线性变换。
  • RandomMatrix():生成指定大小的随机矩阵,用于权重的初始化。
  • Softmax():通常用于多分类问题中的输出层,将输出转换为概率分布。
  • Forward():前向传播方法,根据输入数据计算每个时间步的输出。它会更新隐藏状态,并最终返回所有时间步的输出列表。
  • Backward():反向传播方法,用于根据预测输出与目标输出之间的差异调整模型参数。它计算梯度并更新权重和偏置。
  • UpdateWeights():根据计算出的梯度更新模型的权重和偏置。
  • Train():训练模型的方法,通过多次迭代(epoch)对输入数据进行前向传播和反向传播,以优化模型参数。
  • Predict():预测方法,根据输入数据返回每个时间步的预测结果索引,即输出概率最高的类别。

说明

这只是一个基础的 RNN 模型实现,实际应用中可能需要考虑更多的优化技术,比如使用长短期记忆网络(LSTM)、门控循环单元(GRU)等更复杂的架构来改善性能。

using System;
using System.Linq;
using System.Collections.Generic;

namespace Project.NeuralNetwork
{
    /// <summary>
    /// 构建神经网络
    /// </summary>
    public class RnnModel
    {
        /// <summary>
        /// 输入层大小
        /// </summary>
        private readonly int _inputSize;

        /// <summary>
        /// 隐藏层大小
        /// </summary>
        private readonly int _hiddenSize;

        /// <summary>
        /// 输出层大小
        /// </summary>
        private readonly int _outputSize;

        /// <summary>
        /// 输入到隐藏层的权重
        /// </summary>
        private double[,] _weightsInputHidden;

        /// <summary>
        /// 隐藏层到隐藏层的权重
        /// </summary>
        private double[,] _weightsHiddenHidden;

        /// <summary>
        /// 隐藏层到输出层的权重
        /// </summary>
        private double[,] _weightsHiddenOutput;

        /// <summary>
        /// 隐藏层偏置
        /// </summary>
        private double[] _biasHidden;

        /// <summary>
        /// 输出层偏置
        /// </summary>
        private double[] _biasOutput;

        /// <summary>
        /// 隐藏层状态
        /// </summary>
        private double[] _hiddenState;

        /// <summary>
        /// 初始化模型的构造函数
        /// </summary>
        /// <param name="inputSize"></param>
        /// <param name="hiddenSize"></param>
        /// <param name="outputSize"></param>
        public RnnModel(int inputSize, int hiddenSize, int outputSize)
        {
            _inputSize = inputSize;
            _hiddenSize = hiddenSize;
            _outputSize = outputSize;
            InitializeWeightsAndBiases();
        }

        /// <summary>
        /// 初始化权重和偏置
        /// </summary>
        private void InitializeWeightsAndBiases()
        {
            _weightsInputHidden = RandomMatrix(_inputSize, _hiddenSize);
            _weightsHiddenHidden = RandomMatrix(_hiddenSize, _hiddenSize);
            _weightsHiddenOutput = RandomMatrix(_hiddenSize, _outputSize);
            _biasHidden = new double[_hiddenSize];
            _biasOutput = new double[_outputSize];
        }

        /// <summary>
        /// 激活函数
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        private double Sigmoid(double x)
        {
            return 1 / (1 + Math.Exp(-x));
        }

        /// <summary>
        /// 生成随机矩阵
        /// </summary>
        /// <param name="rows"></param>
        /// <param name="cols"></param>
        /// <returns></returns>
        private double[,] RandomMatrix(int rows, int cols)
        {
            var matrix = new double[rows, cols];
            var random = new Random();
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    matrix[i, j] = random.NextDouble() * 2 - 1; // [-1, 1]
                }
            }
            return matrix;
        }

        /// <summary>
        /// 前向传播
        /// </summary>
        /// <param name="inputs"></param>
        /// <returns></returns>
        public List<double[]> Forward(List<double[]> inputs)
        {
            _hiddenState = new double[_hiddenSize];
            var outputs = new List<double[]>();
            foreach (var input in inputs)
            {
                var hidden = new double[_hiddenSize];
                for (int h = 0; h < _hiddenSize; h++)
                {
                    hidden[h] = _biasHidden[h];
                    for (int i = 0; i < _inputSize; i++)
                    {
                        hidden[h] += _weightsInputHidden[i, h] * input[i];
                    }
                    for (int hh = 0; hh < _hiddenSize; hh++)
                    {
                        hidden[h] += _weightsHiddenHidden[hh, h] * _hiddenState[hh];
                    }
                    hidden[h] = Sigmoid(hidden[h]);
                }
                _hiddenState = hidden;
                var output = Output(hidden);
                outputs.Add(output);
            }
            return outputs;
        }

        /// <summary>
        /// 输出层
        /// </summary>
        /// <param name="h"></param>
        /// <returns></returns>
        private double[] Output(double[] h)
        {
            double[] y = new double[_outputSize];
            for (int i = 0; i < _outputSize; i++)
            {
                double sum = _biasOutput[i];
                for (int j = 0; j < _hiddenSize; j++)
                {
                    sum += h[j] * _weightsHiddenOutput[j, i];
                }
                y[i] = sum;
            }
            return Softmax(y);
        }

        /// <summary>
        /// 输出层的激活函数
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        private double[] Softmax(double[] x)
        {
            double max = x.Max();
            double expSum = x.Select(xi => Math.Exp(xi - max)).Sum();
            return x.Select(xi => Math.Exp(xi - max) / expSum).ToArray();
        }

        /// <summary>
        /// 反向传播
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="targets"></param>
        /// <param name="outputs"></param>
        /// <param name="learningRate"></param>
        private void Backward(List<double[]> inputs, List<double[]> targets, List<double[]> outputs, double learningRate)
        {
            //输入到隐藏层的梯度
            double[,] dWeightsInputHidden = new double[_inputSize, _hiddenSize];
            //隐藏层到隐藏层的梯度
            double[,] dWeightsHiddenHidden = new double[_hiddenSize, _hiddenSize];
            //隐藏层到输出层的梯度
            double[,] dWeightsHiddenOutput = new double[_hiddenSize, _outputSize];
            //隐藏层的偏置
            double[] dBiasHidden = new double[_hiddenSize];
            //输出层的偏置
            double[] dBiasOutput = new double[_outputSize];

            for (int t = inputs.Count - 1; t >= 0; t--)
            {
                double[] targetVector = new double[_outputSize];
                Array.Copy(targets[t], targetVector, _outputSize);
                // 计算输出层的误差
                for (int o = 0; o < _outputSize; o++)
                {
                    dBiasOutput[o] = outputs[t][o] - targetVector[o];
                }
                // 计算隐藏层到输出层的梯度
                for (int o = 0; o < _outputSize; o++)
                {
                    for (int h = 0; h < _hiddenSize; h++)
                    {
                        dWeightsHiddenOutput[h, o] += dBiasOutput[o] * _hiddenState[h];
                    }
                }
                // 计算隐藏层的偏置
                double[] dh = new double[_hiddenSize];
                for (int h = 0; h < _hiddenSize; h++)
                {
                    double error = 0;
                    for (int o = 0; o < _outputSize; o++)
                    {
                        error += dBiasOutput[o] * _weightsHiddenOutput[h, o];
                    }
                    dh[h] = error * (_hiddenState[h] * (1 - _hiddenState[h]));
                }
                for (int h = 0; h < _hiddenSize; h++)
                {
                    dBiasHidden[h] += dh[h];
                }
                //计算输入到隐藏层的梯度
                for (int h = 0; h < _hiddenSize; h++)
                {
                    for (int i = 0; i < _inputSize; i++)
                    {
                        dWeightsInputHidden[i, h] += dh[h] * inputs[t][i];
                    }
                }
                // 计算输入到隐藏层的梯度
                if (t > 0)
                {
                    for (int h = 0; h < _hiddenSize; h++)
                    {
                        for (int hh = 0; hh < _hiddenSize; hh++)
                        {
                            dWeightsHiddenHidden[hh, h] += dh[h] * _hiddenState[hh];
                        }
                    }
                }
            }
            // 更新权重和偏置
            UpdateWeights(dWeightsInputHidden, dWeightsHiddenHidden, dWeightsHiddenOutput, dBiasHidden, dBiasOutput, learningRate);
        }

        /// <summary>
        /// 更新权重
        /// </summary>
        /// <param name="dWxh"></param>
        /// <param name="dWhh"></param>
        /// <param name="dWhy"></param>
        /// <param name="dbh"></param>
        /// <param name="dby"></param>
        /// <param name="learningRate"></param>
        private void UpdateWeights(double[,] dWeightsInputHidden, double[,] dWeightsHiddenHidden, double[,] dWeightsHiddenOutput, double[] dBiasHidden, double[] dBiasOutput, double learningRate)
        {
            // 更新输入到隐藏层的权重
            for (int i = 0; i < _inputSize; i++)
            {
                for (int h = 0; h < _hiddenSize; h++)
                {
                    _weightsInputHidden[i, h] -= learningRate * dWeightsInputHidden[i, h];
                }
            }
            //更新隐藏层到隐藏层的权重
            for (int h = 0; h < _hiddenSize; h++)
            {
                for (int hh = 0; hh < _hiddenSize; hh++)
                {
                    _weightsHiddenHidden[h, hh] -= learningRate * dWeightsHiddenHidden[h, hh];
                }
            }
            //更新隐藏层到输出层的权重
            for (int h = 0; h < _hiddenSize; h++)
            {
                for (int o = 0; o < _outputSize; o++)
                {
                    _weightsHiddenOutput[h, o] -= learningRate * dWeightsHiddenOutput[h, o];
                }
            }
            //更新隐藏层的偏置
            for (int h = 0; h < _hiddenSize; h++)
            {
                _biasHidden[h] -= learningRate * dBiasHidden[h];
            }
            //更新输出层的偏置
            for (int o = 0; o < _outputSize; o++)
            {
                _biasOutput[o] -= learningRate * dBiasOutput[o];
            }
        }

        /// <summary>
        /// 训练
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="targets"></param>
        /// <param name="epochs"></param>
        /// <param name="learningRate"></param>
        public void Train(List<List<double[]>> inputs, List<List<double[]>> targets, double learningRate, int epochs)
        {
            for (int epoch = 0; epoch < epochs; epoch++)
            {
                for (int i = 0; i < inputs.Count; i++)
                {
                    List<double[]> input = inputs[i];
                    List<double[]> target = targets[i];
                    List<double[]> outputs = Forward(input);
                    Backward(input, target, outputs, learningRate);
                }
            }
        }

        /// <summary>
        /// 预测
        /// </summary>
        /// <param name="inputs"></param>
        /// <returns></returns>
        public int[] Predict(List<double[]> inputs)
        {
            var output = Forward(inputs);
            var predictedIndices = output.Select(o => Array.IndexOf(o, o.Max())).ToArray();
            return predictedIndices;
        }
    }
}
  • 准备训练数据
  • 训练网络
  • 测试并输出结果
public static void Rnn_Predict()
{
    // 定义数据集
    var data = new List<Tuple<string[], string[]>>
    {
        Tuple.Create(new string[] { "早安" }, new string[] { "早上好" }),
        Tuple.Create(new string[] { "午安" }, new string[] { "中午好" }),
        Tuple.Create(new string[] { "晚安" }, new string[] { "晚上好" }),
        Tuple.Create(new string[] { "你好吗?" }, new string[] { "我很好,谢谢。" })
    };

    // 创建词汇表
    var allWords = data.SelectMany(t => t.Item1.Concat(t.Item2)).Distinct().ToList();
    var wordToIndex = allWords.ToDictionary(word => word, word => allWords.IndexOf(word));

    // 将字符串转换为one-hot编码
    List<List<double[]>> inputsData = new List<List<double[]>>();
    List<List<double[]>> targetsData = new List<List<double[]>>();

    foreach (var item in data)
    {
        var inputSequence = item.Item1.Select(word => OneHotEncode(word, wordToIndex)).ToList();
        var targetSequence = item.Item2.Select(word => OneHotEncode(word, wordToIndex)).ToList();

        inputsData.Add(inputSequence);
        targetsData.Add(targetSequence);
    }

    double[] OneHotEncode(string word, Dictionary<string, int> wordToIndex)
    {
        var encoding = new double[wordToIndex.Count];
        encoding[wordToIndex[word]] = 1;
        return encoding;
    }

    //开始训练
    int inputSize = allWords.Count;
    int hiddenSize = allWords.Count;
    int outputSize = allWords.Count;
    RnnModel model = new RnnModel(inputSize, hiddenSize, outputSize);
    int epochs = 10000;
    double learningRate = 0.1;
    model.Train(inputsData, targetsData, learningRate, epochs);

    //预测
    while (true)
    {
        Console.Write("你: ");
        string userInput = Console.ReadLine();
        if (userInput.ToLower() == "exit")
        {
            break;
        }
        if (!allWords.Contains(userInput))
        {
            Console.WriteLine("对不起,我不认识这些词。");
            continue;
        }
        var testInput = new List<double[]> { OneHotEncode(userInput, wordToIndex) };
        var prediction = model.Predict(testInput);
        var predictedWords = prediction.Select(index => allWords[index]).ToArray();
        Console.WriteLine($"机器人: {string.Join(", ", predictedWords)}");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2245222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

并行优化策略

并行优化策略汇总 并行优化策略 数据并行&#xff08;DP&#xff09; 将数据集分散到m个设备中&#xff0c;进行训练。得到训练数据后在进行allreduce操作。确保每个worker都有相同模型参数。 整体流程如下 若干块计算GPU&#xff0c;如图中GPU0~GPU2&#xff1b;1块梯度收集…

解决 Android 单元测试 No tests found for given includes:

问题 报错&#xff1a; Execution failed for task :testDebugUnitTest. > No tests found for given includes: 解决方案 1、一开始以为是没有给测试类加public修饰 2、然后替换 Test 注解的包可以解决&#xff0c;将 org.junit.jupiter.api.Test 修改为 org.junit.Tes…

知识见闻 - 数学: 均方根 Root Mean Square

What is Root Mean Square (RMS)? 在统计学上&#xff0c;均方根&#xff08;RMS&#xff09;是均方的平方根&#xff0c;而均方是一组数值的平方的算术平均数。均方根也称为二次均值&#xff0c;是指数为 2 的广义均值的一种特例。均方根也被定义为基于一个周期内瞬时值的平方…

寻找用户推荐人(考点:ifnull)【SQL+Pandas】

今天尝试刷一下力扣的sql面试题&#xff0c;这个写法我也是第一次见 题目是 我们需要在这个表中查出referee_id&#xff01;2的 正确写法是 select name from customer where ifnull(referee_id,0) ! 2 -- 不等于还可以这么写&#xff1a;<>

Java Database Connectivity (JDBC + Servlet)

Java Database Connectivity (JDBC)是一个Java API&#xff0c;用于与数据库进行连接和操作。通过JDBC&#xff0c;Java程序可以与各种关系型数据库进行通信&#xff0c;执行SQL查询、更新数据等操作。 一、Java连接数据库两种方式 ​​​​​ ​​ 二、Java中…

【Python爬虫实战】深入解析 Scrapy 爬虫框架:高效抓取与实战搭建全指南

&#x1f308;个人主页&#xff1a;易辰君-CSDN博客 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html ​ 目录 前言 一、Srapy简介 &#xff08;一&#xff09;什么是Srapy &#xff08;二&#xff09;Scrapy 的设计目标 …

编程之路,从0开始:动态内存管理

Hello&#xff0c;大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路。 我们今天来学习C语言中的动态内存管理。 目录 1、为什么要有动态内存管理&#xff1f; 2、malloc和free &#xff08;1&#xff09;malloc函数 &…

初始Python篇(4)—— 元组、字典

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 元组 相关概念 元组的创建与删除 元组的遍历 元组生成式 字典 相关概念 字典的创建与删除 字典的遍历与访问 字典…

d3-ease 的各种方法和示例

D3.js中的d3-ease模块提供了多种缓动函数&#xff0c;用于实现平滑的动画过渡效果。这些缓动函数通过扭曲时间控制动画中运动的方法&#xff0c;使得动画更加自然和流畅。以下是D3中常见的一些ease方法和示例代码&#xff1a; 线性缓动&#xff08;linear&#xff09;&#xff…

HTML5拖拽API学习 托拽排序和可托拽课程表

文章目录 前言拖拽API核心概念拖拽式使用流程例子注意事项综合例子&#x1f330; 可拖拽课程表拖拽排序 前言 前端拖拽功能让网页元素可以通过鼠标或触摸操作移动。HTML5 提供了标准的拖拽API&#xff0c;简化了拖放操作的实现。以下是拖拽API的基本使用指南&#xff1a; 拖拽…

Throwable、IO流、Java虚拟机

Error和Exception stream结尾都是字节流&#xff0c;reader和writer结尾都是字符流 两者的区别就是读写的时候一个是按字节读写&#xff0c;一个是按字符。 实际使用通常差不多。 在读写文件需要对内容按行处理&#xff0c;比如比较特定字符&#xff0c;处理某一行数据的时候一…

lanqiao OJ 364 跳石头

这个题目的条件是移动的石头数量给定&#xff0c;但是最小移动距离的最大值我们不知道&#xff0c;所以要通过mid来“猜测”。如果当前的mid需要移动的最小石头数量超过给定数&#xff0c;则mid不成立&#xff0c;需要缩小&#xff0c;反之则增大mid&#xff0c;直至找到一个最…

「一」HarmonyOS端云一体化概要

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 「目录」 「一」HarmonyOS端云一体化概要 「二」体验HarmonyOS端云一…

Shell基础(7)

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

音视频pts/dts

现在的视频流有两个非常重要的时间戳&#xff0c;pts和dts&#xff0c;其中pts是显示的时候用&#xff0c;dts在解码的时候用。 pts很好理解&#xff0c;按照pts的顺序以及duration不间断的display就可以了。 dts在解码的时候用&#xff0c;那么这句话怎么理解&#xff0c;解…

sql server怎样用sql profiler捕获带变量值的慢sql

一 新建跟踪 点击工具-SQL Server Profiler&#xff1a; 点击文件-新建跟踪的按钮&#xff1a; 在‘事件选择’选项卡只选择如下两项内容&#xff08;RPC:Completed,SQL:BatchCompleted&#xff09;&#xff0c;把多余的取消勾选&#xff1a; 然后勾选上面截图中右下方的‘显示…

二叉树——输出叶子到根节点的路径

目录 代码 算法思想 例子 思维拓展 代码 int LeaveBit(Bitree T,int flag,int g) {if (!T) {return 0;}if (T->rchild NULL && T->lchild NULL) {//cout << "empty:" << T->data << endl;s.push(T->data);while (!s.emp…

深入理解Spring(三)

目录 2.1.3、Spring配置非自定义Bean 1)配置Druid数据源交由Spring管理 2)配置Connection交由Spring管理 3)配置日期对象交由Spring管理 4)配置MyBatis的SqlSessionFactory交由Spring管理 2.1.4、Bean实例化的基本流程 1)Bean信息定义对象-BeanDefinition 2)DefaultLi…

React Native 基础

React 的核心概念 定义函数式组件 import组件 要定义一个Cat组件,第一步要使用 import 语句来引入React以及React Native的 Text 组件: import React from react; import { Text } from react-native; 定义函数作为组件 const CatApp = () => {}; 渲染Text组件

SpringBoot,IOC,DI,分层解耦,统一响应

目录 详细参考day05 web请求 1、BS架构流程 2、RequestParam注解 完成参数名和形参的映射 3、controller接收json对象&#xff0c;使用RequestBody注解 4、PathVariable注解传递路径参数 5、ResponseBody&#xff08;return 响应数据&#xff09; RestController源码 6、统一响…