当前位置: 首页 > news >正文

关于我用AI编写了一个聊天机器人……(11)

本次依旧使用豆包实现代码。

概述

这个聊天机器人主要通过以下步骤实现问答功能:

  1. 从训练数据文件加载问答对
  2. 对问题进行预处理和关键词提取
  3. 计算每个关键词的 IDF(逆文档频率)值
  4. 接收用户输入并进行处理
  5. 首先尝试精确匹配已有问题
  6. 精确匹配失败则使用 TF-IDF 算法进行相似性匹配
  7. 返回最匹配的答案

下面详细解析各个部分的实现。

核心功能模块解析

1. 文本预处理函数

字符串转小写
string toLower(const string& str) {string result = str;for (string::size_type i = 0; i < result.length(); ++i) {result[i] = tolower(result[i]);}return result;
}

这个函数将输入字符串转换为全小写形式,目的是实现大小写不敏感的匹配,提高关键词匹配的准确性。

关键词提取
vector<string> extractKeywords(const string& text) {vector<string> keywords;string word;for (string::const_iterator it = text.begin(); it != text.end(); ++it) {if (isalnum(*it)) {word += *it;} else if (!word.empty()) {keywords.push_back(toLower(word));word.clear();}}if (!word.empty()) {keywords.push_back(toLower(word));}return keywords;
}

这个函数从文本中提取关键词,规则是:

  • 只保留字母和数字(isalnum 检查)
  • 其他字符作为分隔符
  • 将提取的词转为小写后返回

2. TF-IDF 算法实现

TF-IDF(词频 - 逆文档频率)是一种用于信息检索与数据挖掘的常用加权技术,代码中通过getBestAnswerByTFIDF函数实现:

string getBestAnswerByTFIDF(const vector<string>& userKeywords,const map<string, vector<string> >& qas,const map<string, vector<string> >& questionKeywords,const map<string, double>& idfValues) {// 实现细节见原代码
}

这个函数的工作流程分为三步:

步骤 1:计算用户问题的 TF-IDF 向量
  • 词频 (TF):关键词在用户问题中出现的频率除以问题总词数
  • 逆文档频率 (IDF):从预计算的 idfValues 中获取
  • TF-IDF:TF 与 IDF 的乘积
步骤 2:计算每个问题的相似度

使用余弦相似度公式计算用户问题与训练数据中每个问题的相似度:

相似度 = 点积(userTFIDF, questionTFIDF) / (||userTFIDF|| × ||questionTFIDF||)

其中点积是两个向量对应元素乘积的和,||x|| 是向量的模长。

步骤 3:找到最佳匹配
  • 选取相似度最高的问题
  • 如果相似度超过阈值(0.2),返回对应的答案
  • 否则返回空字符串表示没有找到匹配

3. 主程序逻辑

main 函数实现了系统的整体流程:

  1. 数据加载:从 training_data.txt 文件加载问答对
  2. 数据预处理:提取关键词,计算文档频率和 IDF 值
  3. 交互循环
    • 接收用户输入
    • 先尝试精确匹配
    • 精确匹配失败则使用 TF-IDF 进行模糊匹配
    • 返回最佳答案或提示无法回答

训练数据格式

系统使用 training_data.txt 文件存储训练数据,格式如下:

Q:问题1
A:回答1
(空行)
Q:问题2
A:回答2第一行
回答2第二行
(空行)
...
  • 问题行以 "Q:" 开头
  • 回答行以 "A:" 开头
  • 回答可以是多行
  • 空行表示一个问答对的结束

    系统特点与改进方向

    特点

  • 结合精确匹配和模糊匹配两种方式
  • 使用 TF-IDF 算法处理语义相似的问题

    改进方向

  • 增加停用词过滤(如 "的"、"是" 等无意义词)
  • 实现更复杂的文本预处理(如词干提取)
  • 调整相似度阈值以优化系统性能
  • 增加机器学习模型提高匹配准确性

代码

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
using namespace std;// 将字符串转换为小写
string toLower(const string& str) {string result = str;for (string::size_type i = 0; i < result.length(); ++i) {result[i] = tolower(result[i]);}return result;
}// 从字符串中提取关键词
vector<string> extractKeywords(const string& text) {vector<string> keywords;string word;for (string::const_iterator it = text.begin(); it != text.end(); ++it) {if (isalnum(*it)) {word += *it;} else if (!word.empty()) {keywords.push_back(toLower(word));word.clear();}}if (!word.empty()) {keywords.push_back(toLower(word));}return keywords;
}// 计算TF-IDF并返回最佳匹配答案
string getBestAnswerByTFIDF(const vector<string>& userKeywords,const map<string, vector<string> >& qas,const map<string, vector<string> >& questionKeywords,const map<string, double>& idfValues) {// 计算用户问题的TF-IDF向量map<string, double> userTFIDF;for (vector<string>::const_iterator kit = userKeywords.begin(); kit != userKeywords.end(); ++kit) {const string& keyword = *kit;// 计算词频(TF)double tf = 0.0;for (vector<string>::const_iterator it = userKeywords.begin(); it != userKeywords.end(); ++it) {if (*it == keyword) tf++;}tf /= userKeywords.size();// 获取IDF值double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}// 计算TF-IDFuserTFIDF[keyword] = tf * idf;}// 计算每个问题的相似度map<string, double> similarityScores;for (map<string, vector<string> >::const_iterator pit = questionKeywords.begin(); pit != questionKeywords.end(); ++pit) {const string& question = pit->first;const vector<string>& keywords = pit->second;// 计算问题的TF-IDF向量map<string, double> questionTFIDF;for (vector<string>::const_iterator kit = keywords.begin(); kit != keywords.end(); ++kit) {const string& keyword = *kit;// 计算词频(TF)double tf = 0.0;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {if (*it == keyword) tf++;}tf /= keywords.size();// 获取IDF值double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}// 计算TF-IDFquestionTFIDF[keyword] = tf * idf;}// 计算余弦相似度double dotProduct = 0.0;double userNorm = 0.0;double questionNorm = 0.0;// 计算点积和范数for (map<string, double>::const_iterator uit = userTFIDF.begin(); uit != userTFIDF.end(); ++uit) {const string& keyword = uit->first;double userWeight = uit->second;userNorm += userWeight * userWeight;map<string, double>::const_iterator qit = questionTFIDF.find(keyword);if (qit != questionTFIDF.end()) {dotProduct += userWeight * qit->second;}}for (map<string, double>::const_iterator qit = questionTFIDF.begin(); qit != questionTFIDF.end(); ++qit) {double questionWeight = qit->second;questionNorm += questionWeight * questionWeight;}userNorm = sqrt(userNorm);questionNorm = sqrt(questionNorm);// 计算相似度double similarity = 0.0;if (userNorm > 0 && questionNorm > 0) {similarity = dotProduct / (userNorm * questionNorm);}similarityScores[question] = similarity;}// 找到相似度最高的问题string bestQuestion;double maxSimilarity = 0.0;for (map<string, double>::const_iterator it = similarityScores.begin(); it != similarityScores.end(); ++it) {if (it->second > maxSimilarity) {maxSimilarity = it->second;bestQuestion = it->first;}}// 如果相似度足够高,返回对应的答案if (maxSimilarity >= 0.2) { // 相似度阈值map<string, vector<string> >::const_iterator ansIt = qas.find(bestQuestion);if (ansIt != qas.end() && !ansIt->second.empty()) {return ansIt->second[0]; // 假设第一个答案是最佳答案}}return ""; // 没有找到匹配
}int main() {// 存储训练数据map<string, string> exactAnswers;       // 精确匹配回答map<string, vector<string> > qas;        // 问题-回答映射map<string, vector<string> > questionKeywords; // 问题-关键词映射map<string, int> documentFrequency;     // 关键词-文档频率映射// 加载训练数据ifstream trainingFile("training_data.txt");if (trainingFile.is_open()) {string line;string question = "";bool readingAnswer = false;int totalDocuments = 0;while (getline(trainingFile, line)) {// 空行表示一个问答对结束if (line.empty()) {question = "";readingAnswer = false;continue;}// 问题行以Q:开头if (line.substr(0, 2) == "Q:") {question = line.substr(2);readingAnswer = false;totalDocuments++;}// 回答行以A:开头else if (line.substr(0, 2) == "A:") {if (!question.empty()) {string answer = line.substr(2);// 保存精确匹配回答exactAnswers[question] = answer;// 保存问题-回答映射qas[question].push_back(answer);// 提取关键词并保存vector<string> keywords = extractKeywords(question);questionKeywords[question] = keywords;// 更新文档频率set<string> uniqueKeywords;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {uniqueKeywords.insert(*it);}for (set<string>::const_iterator it = uniqueKeywords.begin(); it != uniqueKeywords.end(); ++it) {documentFrequency[*it]++;}}readingAnswer = true;}// 多行回答的后续行else if (readingAnswer && !question.empty()) {exactAnswers[question] += "\n" + line;qas[question].back() += "\n" + line;}}trainingFile.close();cout << "已加载 " << exactAnswers.size() << " 条训练数据" << endl;// 计算IDF值map<string, double> idfValues;for (map<string, int>::const_iterator it = documentFrequency.begin(); it != documentFrequency.end(); ++it) {const string& keyword = it->first;int df = it->second;// IDF公式: log(总文档数 / (包含该词的文档数 + 1)) + 1double idf = log((double)totalDocuments / (df + 1)) + 1;idfValues[keyword] = idf;}// 聊天界面cout << "Hello! 输入 'exit' 结束对话。" << endl;string input;while (true) {cout << "You: ";getline(cin, input);if (input == "exit") {cout << "Robot: Goodbye!" << endl;break;}// 精确匹配map<string, string>::const_iterator exactIt = exactAnswers.find(input);if (exactIt != exactAnswers.end()) {cout << "Robot: " << exactIt->second << endl;continue;}// 关键词匹配 (TF-IDF)vector<string> userKeywords = extractKeywords(input);string bestAnswer = getBestAnswerByTFIDF(userKeywords, qas, questionKeywords, idfValues);if (!bestAnswer.empty()) {cout << "Robot: " << bestAnswer << endl;continue;}// 没有找到匹配cout << "Robot: I don't know how to answer this question." << endl;}} else {cout << "无法打开训练文件 training_data.txt" << endl;}return 0;
}

也可以去我主页找相应资源下载

感谢阅读!

http://www.lryc.cn/news/592543.html

相关文章:

  • 《每日AI-人工智能-编程日报》--2025年7月18日
  • [JS逆向] 微信小程序逆向工程实战
  • 加速度计和气压计、激光互补滤波融合算法
  • 6月零售数据超预期引发市场波动:基于AI多因子模型的黄金价格解析
  • # Redis-stable 如何在Linux系统上安装和配置
  • 编译器没找到 esp_http_client.h,
  • 算法竞赛备赛——【图论】求最短路径——小结
  • 【CF】⭐Day104——Codeforces Round 840 (Div. 2) CE (思维 + 分类讨论 | 思维 + 图论 + DP)
  • 数据结构入门:像整理收纳一样简单!
  • 文件流导出文件
  • spring boot 实战之分布式锁
  • 【Nginx】nginx+lua+redis实现限流
  • docker,防火墙关闭后,未重启docker,导致端口映射失败
  • 产品需求文档(PRD)格式全解析:从 RP 到 Word 的选择与实践
  • 前端性能优化“核武器”:新一代图片格式(AVIF/WebP)与自动化优化流程实战
  • 新手向:图片批量裁剪工具
  • 力扣 hot100 Day48
  • AWS(基础)
  • (nice!!!)(LeetCode 每日一题) 2163. 删除元素后和的最小差值 (贪心+优先队列)
  • #vscode# #SSH远程# #Ubuntu 16.04# 远程ubuntu旧版Linux
  • 网工知识——vlan技术
  • go安装使用gin 框架
  • 在 Jenkins 中使用 SSH 部署密钥
  • mac系统安装、启动Jenkins,创建pytest接口自动化任务
  • 周志华《机器学习导论》第9章 聚类
  • 一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo
  • 【Lua】闭包可能会导致的变量问题
  • python-pptx 的layout 布局
  • 人工智能概念之九:深度学习概述
  • JavaSE -- 对象序列化和反序列化详细讲解