现在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/,即可看到刚才编写的页面。最终大功告成,最后希望大佬勿喷。