从零开始构建简易AI问答系统

查看 84|回复 8
作者:恋丶你的语   
一、非正文部分
    现在ChatGPT特别火爆,他可以帮助我们的工作带来许多的便捷。在此之前我很早就想拥有自己的AI,哪怕是最简单的那种,奈何,大学我选了财务管理专业(高风险低收益{:1_908:}  ),在梦想的道路上越跑越远。{:1_889:} 。在大二偶然之间了解到了财务数据分析这块的知识,然后我开始的我得Python半吊子学习,啥都学学,如今我已大四,还有几个月就要毕业,对未来挺焦虑的,于是我静下心来,用自己这段时间所学的知识来尝试完成自己的小愿望,于是就有了这篇文章。大佬勿喷,业余学习
二、基本思路
    我的想法是一个可以能够自我学习会计知识并可以问答的程序,但是我没有那么多时间去整理这些相关的资料,于是让他直接获取百度百科的数据。基本思路如下:
      1、爬取百度百科相关词条的网页内容,可以使用Python中的爬虫框架,例如Scrapy或BeautifulSoup等。
      2、对网页内容进行自然语言处理和数据清洗,将有用的信息提取出来,并存储在数据库或本地文件中。
      3、使用机器学习算法,如神经网络、随机森林等对得到的数据进行训练。
      4、开发一个问答系统,将用户问题输入进去,然后将输入的问题与训练好的模型匹配,以便回答用户问题。
    当然,这个项目具体实现上还有很多需要考虑的细节,比如如何避免数据爬取被反爬虫,如何处理查询不到的问题等等,不过这个只是一个简易的AI就不考虑那么多,而且我知识量也不够。
三、代码部分
  3.1然后根据思路写出基本的代码,然后在根据思路进行调整:
[Python] 纯文本查看 复制代码
# 导入需要用到的库
import requests
from bs4 import BeautifulSoup
import jieba
from gensim.models.word2vec import Word2Vec
import numpy as np
import pandas as pd
from sklearn.svm import SVC
from sklearn.metrics.pairwise import cosine_similarity
# 爬虫部分:爬取百度百科的网页内容,并进行数据清洗和预处理
def get_wiki_content(keyword):
    url = 'https://baike.baidu.com/item/' + keyword
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    content = soup.find_all(class_="para")
    content_list = []
    for para in content:
        para = para.get_text().strip()
        if para:
            content_list.append(para)
    full_content = ''.join(content_list)
    return full_content
# 分词和向量化部分:使用jieba对爬取的数据进行分词,然后使用Word2Vec模型将分词结果转化为词向量
class Word2VecVectorizer:
    def __init__(self):
        self.word2vec = None
    def fit(self, sentences):
        w2v = Word2Vec(sentences=sentences, size=256, min_count=5, workers=4)
        self.word2vec = dict(zip(w2v.wv.index2word, w2v.wv.vectors))
    def transform(self, sentences):
        vectors = []
        for sentence in sentences:
            sentence_vec = []
            for word in sentence:
                if word in self.word2vec:
                    sentence_vec.append(self.word2vec[word])
            if len(sentence_vec) > 0:
                sentence_vec = np.mean(sentence_vec, axis=0)
            else:
                sentence_vec = np.zeros(256)
            vectors.append(sentence_vec)
        return np.array(vectors)
# 机器学习部分:使用SVM算法对文本向量训练模型,并使用余弦相似度计算输入问题与已知问题的相似度
class QuestionAnswerSystem:
    def __init__(self):
        self.vectorizer = None
        self.clf = None
        self.question_vectors = None
        self.answer_vectors = None
        self.df = None
    def fit(self, questions, answers):
        self.vectorizer = Word2VecVectorizer()
        self.vectorizer.fit(questions)
        self.question_vectors = self.vectorizer.transform(questions)
        self.answer_vectors = self.vectorizer.transform(answers)
        self.clf = SVC(kernel='linear', probability=True)
        self.clf.fit(self.question_vectors, range(len(questions)))
    def ask(self, question, n=5):
        question = list(jieba.cut(question))
        question_vec = self.vectorizer.transform([question])[0]
        probas = self.clf.predict_proba([question_vec])[0]
        top_n = np.argsort(probas)[-n:]
        sims = cosine_similarity([question_vec], self.question_vectors[top_n])[0]
        best_match = np.argmax(sims)
        return self.df.iloc[top_n[best_match]]['answer']
# 主程序部分:调用以上函数完成问答系统的搭建
if __name__ == '__main__':
    # 爬取需要用到的数据
    wiki_content = get_wiki_content('会计')
    # 对数据进行预处理
    questions = ['什么是会计', '什么是账户', '什么是分录', '什么是财务报表']
    answers = ['会计是什么', '账户是什么', '分录是什么', '财务报表是什么']
    # 训练问答系统
    qas = QuestionAnswerSystem()
    qas.fit(questions, answers)
    qas.df = pd.DataFrame({'question': questions, 'answer': answers})
    # 进行测试
    while True:
        question = input('请输入你的问题:')
        if question == 'bye':
            break
        answer = qas.ask(question)
        print('答案为:', answer)
3.2 加入神经网络模型
   我们可以发现他只能回答一些基础的问题。然后我们在此基础上加入神经网络模型进一步完善它。于是我使用预训练的语言模型(如BERT)来对问题和答案进行编码,然后将编码后的结果输入到一个神经网络中进行分类。代码如下,其中使用Hugging Face库来加载BERT模型。
[Python] 纯文本查看 复制代码
#  导入需要用到的库
import requests
from bs4 import BeautifulSoup
import jieba
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
import torch
from transformers import BertTokenizer, BertModel
from torch.utils.data import TensorDataset, DataLoader
import torch.nn.functional as F
# 爬虫部分:爬取百度百科的网页内容,并进行数据清洗和预处理
def get_wiki_content(keyword):
    url = 'https://baike.baidu.com/item/' + keyword
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    content = soup.find_all(class_="para")
    content_list = []
    for para in content:
        para = para.get_text().strip()
        if para:
            content_list.append(para)
    full_content = ''.join(content_list)
    return full_content
# 编码器部分:使用预训练的BERT模型来对文本进行编码
class BertEncoder:
    def __init__(self):
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        self.model = BertModel.from_pretrained('bert-base-chinese')
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    def encode(self, sentence):
        input_ids = torch.tensor([self.tokenizer.encode(sentence, add_special_tokens=True)], dtype=torch.long).to(self.device)
        with torch.no_grad():
            outputs = self.model(input_ids)
            encoded = outputs[0][:, 0, :].cpu().numpy()
        return encoded
# 神经网络部分:使用全连接层对文本向量训练模型,并使用余弦相似度计算输入问题与已知问题的相似度
class QuestionAnswerSystem:
    def __init__(self):
        self.encoder = None
        self.clf = None
        self.question_vectors = None
        self.answer_vectors = None
        self.df = None
    def fit(self, questions, answers):
        self.encoder = BertEncoder()
        self.question_vectors = self.encoder.encode(questions)
        self.answer_vectors = self.encoder.encode(answers)
        self.clf = torch.nn.Sequential(torch.nn.Linear(768, 256),
                                        torch.nn.ReLU(),
                                        torch.nn.Linear(256, len(questions)),
                                        torch.nn.Softmax())
        self.clf.to(self.encoder.device)
        train_data = TensorDataset(torch.tensor(self.answer_vectors, dtype=torch.float32), torch.tensor(range(len(questions)), dtype=torch.long))
        train_loader = DataLoader(train_data, batch_size=32)
        criterion = torch.nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(self.clf.parameters(), lr=1e-3)
        num_epochs = 5
        for epoch in range(num_epochs):
            for inputs, labels in train_loader:
                inputs, labels = inputs.to(self.encoder.device), labels.to(self.encoder.device)
                outputs = self.clf(inputs)
                loss = criterion(outputs, labels)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
    def ask(self, question, n=5):
        question_vec = self.encoder.encode(question)
        inputs = torch.tensor(question_vec, dtype=torch.float32).to(self.encoder.device)
        outputs = self.clf(inputs)
        probas = F.softmax(outputs, dim=1).detach().cpu().numpy()[0]
        top_n = np.argsort(probas)[-n:]
        sims = cosine_similarity([question_vec], self.question_vectors[top_n])[0]
        best_match = np.argmax(sims)
        return self.df.iloc[top_n[best_match]]['answer']
# 主程序部分:调用以上函数完成问答系统的搭建
if __name__ == '__main__':
    # 爬取需要用到的数据
    wiki_content = get_wiki_content('会计')
    # 对数据进行预处理
    questions = ['什么是会计', '什么是账户', '什么是分录', '什么是财务报表']
    answers = ['会计是什么', '账户是什么', '分录是什么', '财务报表是什么']
    # 训练问答系统
    qas = QuestionAnswerSystem()
    qas.fit(questions, answers)
    qas.df = pd.DataFrame({'question': questions, 'answer': answers})
    # 进行测试
    while True:
        question = input('请输入你的问题:')
        if question == 'bye':
            break
        answer = qas.ask(question)
        print('答案为:', answer)
    我们利用BERT模型来对问题和答案进行编码,然后使用全连接层对编码后的文本向量进行分类。这样的神经网络效果较好,可以更准确地回答问题。但是,由于BERT模型比较复杂,因此训练和推断速度都比较慢,需要耐心等待。
3.3 添加将训练结果保存到excel文件中的功能
   为了实现将训练结果保存到Excel文件中的功能,我们可以使用pandas库。在下面的代码中,我们对问答系统类(QuestionAnswerSystem)进行一些改进,添加了一个新的方法 save_result_to_excel。这个方法接受一个Excel文件名作为参数,并将问题和答案保存到该文件中。在方法中,我们将DataFrame类型的数据转换为Excel文件,并将其保存在指定的文件名中。下面是修改后的代码:
[Python] 纯文本查看 复制代码# 导入需要用到的库
import requests
from bs4 import BeautifulSoup
import jieba
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
import torch
from transformers import BertTokenizer, BertModel
from torch.utils.data import TensorDataset, DataLoader
import torch.nn.functional as F
# 爬虫部分:爬取百度百科的网页内容,并进行数据清洗和预处理
def get_wiki_content(keyword):
    url = 'https://baike.baidu.com/item/' + keyword
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    content = soup.find_all(class_="para")
    content_list = []
    for para in content:
        para = para.get_text().strip()
        if para:
            content_list.append(para)
    full_content = ''.join(content_list)
    return full_content
# 编码器部分:使用预训练的BERT模型来对文本进行编码
class BertEncoder:
    def __init__(self):
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        self.model = BertModel.from_pretrained('bert-base-chinese')
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    def encode(self, sentence):
        input_ids = torch.tensor([self.tokenizer.encode(sentence, add_special_tokens=True)], dtype=torch.long).to(self.device)
        with torch.no_grad():
            outputs = self.model(input_ids)
            encoded = outputs[0][:, 0, :].cpu().numpy()
        return encoded
# 神经网络部分:使用全连接层对文本向量训练模型,并使用余弦相似度计算输入问题与已知问题的相似度
class QuestionAnswerSystem:
    def __init__(self):
        self.encoder = None
        self.clf = None
        self.question_vectors = None
        self.answer_vectors = None
        self.df = None
    def fit(self, questions, answers):
        self.encoder = BertEncoder()
        self.question_vectors = self.encoder.encode(questions)
        self.answer_vectors = self.encoder.encode(answers)
        self.clf = torch.nn.Sequential(torch.nn.Linear(768, 256),
                                        torch.nn.ReLU(),
                                        torch.nn.Linear(256, len(questions)),
                                        torch.nn.Softmax())
        self.clf.to(self.encoder.device)
        train_data = TensorDataset(torch.tensor(self.answer_vectors, dtype=torch.float32), torch.tensor(range(len(questions)), dtype=torch.long))
        train_loader = DataLoader(train_data, batch_size=32)
        criterion = torch.nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(self.clf.parameters(), lr=1e-3)
        num_epochs = 5
        for epoch in range(num_epochs):
            for inputs, labels in train_loader:
                inputs, labels = inputs.to(self.encoder.device), labels.to(self.encoder.device)
                outputs = self.clf(inputs)
                loss = criterion(outputs, labels)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
    def ask(self, question, n=5):
        question_vec = self.encoder.encode(question)
        inputs = torch.tensor(question_vec, dtype=torch.float32).to(self.encoder.device)
        outputs = self.clf(inputs)
        probas = F.softmax(outputs, dim=1).detach().cpu().numpy()[0]
        top_n = np.argsort(probas)[-n:]
        sims = cosine_similarity([question_vec], self.question_vectors[top_n])[0]
        best_match = np.argmax(sims)
        return self.df.iloc[top_n[best_match]]['answer']
    # 新增方法:将问答结果保存到Excel文件中
    def save_result_to_excel(self, filename='qa_result.xlsx'):
        df_result = self.df.copy()
        df_result.columns = ['Question', 'Answer']
        df_result.to_excel(filename, index=False)
# 主程序部分:调用以上函数完成问答系统的搭建
if __name__ == '__main__':
    # 爬取需要用到的数据
    wiki_content = get_wiki_content('会计')
    # 对数据进行预处理
    questions = ['什么是会计', '什么是账户', '什么是分录', '什么是财务报表']
    answers = ['会计是什么', '账户是什么', '分录是什么', '财务报表是什么']
    # 训练问答系统
    qas = QuestionAnswerSystem()
    qas.fit(questions, answers)
    qas.df = pd.DataFrame({'question': questions, 'answer': answers})
    # 将结果保存到Excel文件中
    qas.save_result_to_excel('qa_result.xlsx')
    # 进行测试
    while True:
        question = input('请输入你的问题:')
        if question == 'bye':
            break
        answer = qas.ask(question)
        print('答案为:', answer)
在主程序部分中,新增了一行代码 qas.save_result_to_excel('qa_result.xlsx'),将问答结果保存到Excel文件中。可以将文件名设置为自己喜欢的名称,并在文件保存位置执行该代码。执行完毕后,在指定的文件夹中会生成一个名为 qa_result.xlsx 的文件,其中包含了训练结果的问题和答案。
3.4 想要更好的学习,我们需要对该程序在对话过程中就对数据进行数据标注和检验
   实现问答系统的数据标注和检验功能,我们需要对 QuestionAnswerSystem 进行一些改进。
   首先,为了方便用户对答案进行标注,我们需要在 ask 方法中添加一个新参数 is_correct,用于接收用户对答案是否正确的标注。我们还需要将问题、答案和标注组成的列表存储到 df 属性中,以便后续使用。
   其次,我们可以新增一个方法 check_accuracy,用于计算问答系统的准确率。该方法会基于已标注数据计算平均准确率,并将准确率打印出来。同时,我们也可以通过 save_result_to_excel 方法保存标注后的结果到Excel文件中。下面是修改后的代码:
[Python] 纯文本查看 复制代码
# 导入需要用到的库
import requests
from bs4 import BeautifulSoup
import jieba
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
import torch
from transformers import BertTokenizer, BertModel
from torch.utils.data import TensorDataset, DataLoader
import torch.nn.functional as F
# 爬虫部分:爬取百度百科的网页内容,并进行数据清洗和预处理
def get_wiki_content(keyword):
    url = 'https://baike.baidu.com/item/' + keyword
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    content = soup.find_all(class_="para")
    content_list = []
    for para in content:
        para = para.get_text().strip()
        if para:
            content_list.append(para)
    full_content = ''.join(content_list)
    return full_content
# 编码器部分:使用预训练的BERT模型来对文本进行编码
class BertEncoder:
    def __init__(self):
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        self.model = BertModel.from_pretrained('bert-base-chinese')
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    def encode(self, sentence):
        input_ids = torch.tensor([self.tokenizer.encode(sentence, add_special_tokens=True)], dtype=torch.long).to(self.device)
        with torch.no_grad():
            outputs = self.model(input_ids)
            encoded = outputs[0][:, 0, :].cpu().numpy()
        return encoded
# 神经网络部分:使用全连接层对文本向量训练模型,并使用余弦相似度计算输入问题与已知问题的相似度
class QuestionAnswerSystem:
    def __init__(self):
        self.encoder = None
        self.clf = None
        self.question_vectors = None
        self.answer_vectors = None
        self.df = None
    def fit(self, questions, answers):
        self.encoder = BertEncoder()
        self.question_vectors = self.encoder.encode(questions)
        self.answer_vectors = self.encoder.encode(answers)
        self.clf = torch.nn.Sequential(torch.nn.Linear(768, 256),
                                        torch.nn.ReLU(),
                                        torch.nn.Linear(256, len(questions)),
                                        torch.nn.Softmax())
        self.clf.to(self.encoder.device)
        train_data = TensorDataset(torch.tensor(self.answer_vectors, dtype=torch.float32), torch.tensor(range(len(questions)), dtype=torch.long))
        train_loader = DataLoader(train_data, batch_size=32)
        criterion = torch.nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(self.clf.parameters(), lr=1e-3)
        num_epochs = 5
        for epoch in range(num_epochs):
            for inputs, labels in train_loader:
                inputs, labels = inputs.to(self.encoder.device), labels.to(self.encoder.device)
                outputs = self.clf(inputs)
                loss = criterion(outputs, labels)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
    def ask(self, question, n=5, is_correct=None):
        question_vec = self.encoder.encode(question)
        inputs = torch.tensor(question_vec, dtype=torch.float32).to(self.encoder.device)
        outputs = self.clf(inputs)
        probas = F.softmax(outputs, dim=1).detach().cpu().numpy()[0]
        top_n = np.argsort(probas)[-n:]
        sims = cosine_similarity([question_vec], self.question_vectors[top_n])[0]
        best_match = np.argmax(sims)
        if is_correct is not None:
            self.df = self.df.append({'question': question, 'answer': self.df.iloc[top_n[best_match]]['answer'], 'is_correct': bool(is_correct)}, ignore_index=True)
        return self.df.iloc[top_n[best_match]]['answer']
    # 新增方法:将问答结果保存到Excel文件中
    def save_result_to_excel(self, filename='qa_result.xlsx'):
        df_result = self.df.copy()
        df_result.columns = ['Question', 'Answer', 'Is Correct']
        df_result.to_excel(filename, index=False)
    # 新增方法:检验问答系统的准确率
    def check_accuracy(self):
        accuracy = self.df['is_correct'].mean()
        print('问答系统的准确率为: {:.2f}%'.format(accuracy*100))
# 主程序部分:调用以上函数完成问答系统的搭建
if __name__ == '__main__':
    # 爬取需要用到的数据
    wiki_content = get_wiki_content('会计')
    # 对数据进行预处理
    questions = ['什么是会计', '什么是账户', '什么是分录', '什么是财务报表']
    answers = ['会计是什么', '账户是什么', '分录是什么', '财务报表是什么']
    # 训练问答系统
    qas = QuestionAnswerSystem()
    qas.fit(questions, answers)
    qas.df = pd.DataFrame({'question': questions, 'answer': answers})
    # 进行测试
    while True:
        question = input('请输入你的问题:')
        if question == 'bye':
            break
        is_correct = input('答案是否正确?(y/n)')
        if is_correct.lower() == 'y':
            is_correct = True
        elif is_correct.lower() == 'n':
            is_correct = False
        else:
            is_correct = None
        answer = qas.ask(question, is_correct=is_correct)
        print('答案为:', answer['answer'])
    # 检验准确率并保存标注后的数据到Excel文件中
    qas.check_accuracy()
    qas.save_result_to_excel('qa_result.xlsx')
    在主程序部分中,我们新增了两个输入提示,以便用户标注答案是否正确,同时也新增了两行代码来调用 check_accuracy 和 save_result_to_excel 方法,以检验问答系统的准确率,并将标注后的结果保存到Excel文件中。现在,当运行程序并回答每个问题时,程序会提示你输入答案是否正确,你可以输入 "y" 或 "n" 来对答案进行标注。程序会将问题、答案和标注组成的记录存储到 df 属性中,并在每次执行 ask 方法时更新 df。执行 check_accuracy 方法时,程序将基于已标注数据计算准确率,并将其打印出来。执行 save_result_to_excel 方法时,程序将标注后的数据写入到Excel文件中以便后续使用。到此为止,我们的程序的一些功能也就基本实现了。当然还存在许多的问题,希望大家可以一起学习,一起进步。
四、最后的最后
   为了更好的让他学习方便,于是最后增加一个适配的Webj界面。要将该程序适配为web界面,需要使用web框架来搭建一个网站,并将问答系统集成到网站中。可以使用Python中的Flask框架。以下是实现步骤:
1、导入Flask和其他需要用到的库:
[Python] 纯文本查看 复制代码
from flask import Flask, render_template, request
import pandas as pd
from qa_system import QuestionAnswerSystem
其中qa_system.py文件是之前所提到的问答系统代码。
2、创建Flask应用:
[Python] 纯文本查看 复制代码
app = Flask(__name__)
3、加载问答系统:
[Python] 纯文本查看 复制代码
qas = QuestionAnswerSystem()
qas.fit(questions, answers)
qas.df = pd.DataFrame({'question': questions, 'answer': answers})
其中,questions和answers是之前已经定义过的问题列表和答案列表。
4、创建路由函数:
[Python] 纯文本查看 复制代码
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        question = request.form.get('question')
        is_correct = request.form.get('is_correct')
        if is_correct.lower() == 'y':
            is_correct = True
        elif is_correct.lower() == 'n':
            is_correct = False
        else:
            is_correct = None
        answer = qas.ask(question, is_correct=is_correct)
        return render_template('index.html', answer=answer['answer'])
    return render_template('index.html')
这个路由函数包含了两个请求方法:GET和POST。当用户访问网页时,会发送一个GET请求,会渲染index.html页面;当用户提交问题时,会发送一个POST请求,会通过问答系统获取答案,并将答案渲染到页面中。question和is_correct是从表单中获取的参数。
5、编写HTML模板:
  这里写了一个简单的模板作为参考,可以根据需求进行修改。
[HTML] 纯文本查看 复制代码
   
    问答系统

   
        
        
        正确
        
        错误
        提交
   
    {% if answer %}
    {{ answer }}
    {% endif %}

这个模板包含了一个表单,用户可以在这里输入问题。表单中还包含了两个单选框,用户在回答问题后可以选择是否正确。如果问答系统能够成功获取到答案,就会将答案渲染到页面中。
6、运行Flask应用:
[Python] 纯文本查看 复制代码if __name__ == '__main__':
    app.run()
7、运行这个应用之后,通过浏览器访问网址http://127.0.0.1:5000/,即可看到刚才编写的页面。最终大功告成,最后希望大佬勿喷。

宋体, 问答

wzbAwxl   

真厉害啊 学到了
满不懂   

太强大了,收藏学习!!!
dork   

收藏学习!
juy   

不错呀大有帮助
a2523188267   

感谢,能打包一系列源码部署就好了
losidk   

被你的介绍感动到了,太厉害了
minizhur   

学习学习,我也正在学习
siliconxu   

感谢分享!如果能够对代码做更详细的解释那就更适合新手学习了。
您需要登录后才可以回帖 登录 | 立即注册