Loading... > 本文所采用chatGPT模型,为openai为chatGPT Plus用户提供的GPT-4。本文使用的chatGPT版本为[ChatGPT May 24 Version](https://help.openai.com/en/articles/6825453-chatgpt-release-notes) > > 本文所采用conda版本为23.3.1,python版本为3.10.6 ## 需求概述 这个活是在某个~~月黑风高的~~晚上,莫名其妙到我手上的。简单来说,**手上有一份word格式题库,需要在这份题库里面抽取题目出成卷子**。时间很赶,需要在短时间内出两套结构相同,题目不一样的卷子。 题库的结构如下图:  可以看到这东西将近一百页(其实满打满算正好一百页)的体量,已经让我根本不想去翻。再粗略翻了以下,发现里面有好几个题型,每个题目还指向一个知识点。想要出一张涵盖所有题型和知识点的题目,光是卷面题型安排就已经能把我累死,更别说题目随机性的事情了。更何况干这个活又没有加班费…… 但是这个活不干也不行,毕竟刚刚入职,哪个比我大的都是我老板。能不能用一些自动化工具来帮我干呢?Python是个好东西,之前为了捣鼓stable diffusion,电脑上也有miniconda环境,但是我不会写python啊。没有关系,chatGPT是会的。 ## 磕磕碰碰的开始 通过观察,题库中同一题型的题目都放在了一起,以形如“〖判断题〗”这样的文字分隔不同题型的段落,每个题目都是以下格式: * 试题编号:[6位数字,各不相同] * 知识点:[某个知识点,大量重复] * [题目本身及答案] 由于每个题型的题目写法都不一样,和最终试卷上的格式也有很大不同,因此,这次我并不追求全自动化出卷,只需要**抽题**,抽完了我再去题库里找出来手动处理。 抛给GPT的第一个问题是:*我想在word中某个章节统计"知识点:"后各单词的出现频率*。啪的一下,很快啊,它就给了我一个回答: > 在 Word 文档中统计某些特定单词或短语的频率需要使用一些特定的工具,比如脚本或者宏。但是,Word 自身并没有提供这种功能。 > > 一个可能的方案是使用 Python,一个被广泛使用的编程语言,来处理 Word 文档并统计单词的频率。这种方法的一个关键部分是使用 `python-docx` 库来读取 Word 文档。然后你可以使用 Python 的 `collections` 库中的 `Counter` 类来统计单词的频率。 > > 下面是一个基本的 Python 脚本,它可以打开一个 Word 文档并统计在"知识点:"之后的各单词出现的频率: > > ```python > from docx import Document > from collections import Counter > > def count_words_after_keyword(doc_path, keyword): > doc = Document(doc_path) > counter = Counter() > > for para in doc.paragraphs: > if keyword in para.text: > words_after_keyword = para.text.split(keyword)[1].split() > counter.update(words_after_keyword) > > return counter > > # 替换以下路径为你的文档路径 > doc_path = "your_document.docx" > keyword = "知识点:" > counter = count_words_after_keyword(doc_path, keyword) > > # 打印单词及其频率 > for word, freq in counter.items(): > print(f"{word}: {freq}") > ``` > > 你需要先安装 `python-docx` 库才能运行上述代码。你可以通过在命令行中运行 `pip install python-docx` 来安装。 > > 注意,这个脚本并不完美,因为它只检查每一段落中的 "知识点:" 关键词。如果 "知识点:" 在一个段落中出现多次,或者跨越多个段落,那么这个脚本可能就不能正确地工作了。为了处理这些更复杂的情况,你可能需要写一个更复杂的脚本,或者使用更复杂的工具。 当时就把我惊呆了,它建议我用python,与我的需求不谋而合(prompt里完全没提),不仅给出了代码,这代码里面甚至还有注释,还将变量单独抽了出来以方便修改,还告诉了我要装什么库,这可用性也太强了吧! 因为题库文档里不止这一百多页的题目(这个文档六百多页),我想把这一部分单独拎到新的word文档上处理,但我又不想在word里面拉呀拉呀拉,于是我问了GPT第二个问题:*我想将word文档中其中100页的内容存到另一个word上*。 > 如果你想将 Word 文档中的某些页面复制到另一个 Word 文档,你可以手动做,也可以使用Python脚本自动化这个过程。这个Python脚本将使用`python-docx`库来处理 Word 文档。 > > 但是,你需要知道的是,`python-docx`并没有直接提供处理页码的功能,因为 Word 文档的页数取决于许多因素,如页面大小,字体大小,间距等。因此,如果你是按照固定的段落数量(例如100个段落代表100页)来处理的话,可以使用如下的Python脚本: > > ```python > from docx import Document > > # 打开源文档 > source_doc = Document('source.docx') > > # 创建新文档 > new_doc = Document() > > # 设置要复制的段落数量 > num_paragraphs = 100 > > # 复制段落 > for i in range(num_paragraphs): > if i < len(source_doc.paragraphs): > text = source_doc.paragraphs[i].text > new_doc.add_paragraph(text) > > # 保存新文档 > new_doc.save('new_doc.docx') > ``` > > 在这个脚本中,我们打开源文档,然后创建一个新的文档。然后,我们复制源文档的前100个段落到新的文档。最后,我们保存新的文档。 > > 这个脚本可能不完全符合你的需求,因为它是按段落而不是按页面来复制的。如果你确实需要按页面来复制,你可能需要使用其他的工具或者手动复制。如果你可以明确每一页大概有多少段落,那么你可以修改脚本中的`num_paragraphs`变量来达到你想要的效果。 我并不能确定一段有多少段落,所以当时就放弃了用python去复制文档,真的去手动拉了。现在想想,**完全可以用一级标题到一级标题的方式去确定复制的范围,让python自个儿去数就好了。** 当我返回去执行第一个回答的代码的时候,python直接给我抛了个错: ```python ImportError: cannot import name 'Document' from partially initialized module 'docx' ``` 我又去问了GPT,它告诉我: > 这个错误可能是由于你的Python环境中存在命名冲突,或者`python-docx`模块没有正确安装。以下是一些可能的解决方法: > > 1. **检查模块安装情况**:确保`python-docx`模块已经正确安装。你可以在Python环境中运行以下命令来检查已安装的模块: > > ```shell > pip list > ``` > > 如果你看不到`python-docx`在列表中,那么你需要安装它: > > ```shell > pip install python-docx > ``` > 2. **避免命名冲突**:确保你的Python脚本的文件名或者其所在的目录名没有与`docx`模块发生冲突。换句话说,你的脚本文件名不应该是`docx.py`,也不应该在名为`docx`的目录下。否则Python在导入模块时可能会混淆你的脚本和`python-docx`模块。 > 3. **检查Python环境**:确保你在运行脚本的Python环境中安装了`python-docx`模块。如果你有多个Python环境(例如,一个系统级别的Python和一个或多个Python虚拟环境),那么你需要确保你在运行脚本的那个环境中安装了`python-docx`模块。 > > 以上都是可能的解决方案,如果这些都无法解决问题,你可能需要更深入地检查你的Python环境或代码。 好家伙,我的确把执行任务的py命名成`docx.py`了,毕竟是处理word文档的py嘛,这么取名也很合情合理……总之,改完名之后,代码顺利运行。 从这三次对话中可以发现,GPT可以很好地理解我的需求,给出可用性和可读性都极高的python代码(它甚至加注释),还可以帮我对报错信息做初步判断。只要我不断地追加和修正需求,GPT完全能写出一些开箱即用的东西来。既然能用,那就继续干活。 ## 来做亿点点调试 先回顾一下这段对话最开始,我给GPT提出的需求: > 我想在word中某个章节统计"知识点:"后各单词的出现频率 在这一部分,我只想让告诉我题库里到底有多少题,按照章节、知识点和题型分类统计。在接下来的对话中,我不断追加了各种条件: > 1. 现在,我需要将统计的结果按照章节标题来划分 > 2. 我的标题都有都以“1.4.*.*”开始,*可能是任何数字 > 3. 在这个的基础上,我还希望统计不同题型的题量。相同题型的题目会放在一起,所以我需要统计两个提醒关键词A和B之间“题目编号”出现的次数,以确定A题型的题目数量 > 4. 我有判断、多选、单选、案例分析、计算和简答六个题型(**注:这个条件描述是错误的,后面会提到其实不止这六种,这导致了抽题的时候出现问题**) > 5. 我的题型都是以这种形式划分的“〖判断题〗” > 6. keyword中的冒号去掉,文档路径为"1.docx" > 7. 我希望在输出的时候,按题型统计同一章节的题目数量,另外,将question_keyword更换为"试题编号" 到这里,GPT已经给出了一份功能很完整的代码: ~~~py import re from docx import Document from collections import Counter, defaultdict def count_words_and_questions(doc_path, keyword, question_keyword): doc = Document(doc_path) chapter_counters = {} question_counters = defaultdict(lambda: defaultdict(int)) current_chapter = None current_question_type = None for para in doc.paragraphs: if re.match(r'1\.4\.\d+\.\d+', para.text): # 开始一个新的章节 current_chapter = para.text chapter_counters[current_chapter] = Counter() current_question_type = None elif current_chapter is not None: if keyword in para.text: # 在当前章节中统计单词 words_after_keyword = para.text.split(keyword)[1].split() chapter_counters[current_chapter].update(words_after_keyword) elif question_keyword in para.text: if current_question_type is not None: # 在当前题型中统计题目数量 question_counters[current_chapter][current_question_type] += 1 else: for question_type in ["判断题", "多选题", "单选题", "案例分析题", "计算题", "简答题"]: if question_type in para.text: current_question_type = question_type break return chapter_counters, question_counters # 替换以下路径为你的文档路径 doc_path = "1.docx" keyword = "知识点" question_keyword = "试题编号" chapter_counters, question_counters = count_words_and_questions(doc_path, keyword, question_keyword) # 打印每个章节的单词及其频率以及各题型的题目数量 for chapter in chapter_counters.keys(): print(chapter) for word, freq in chapter_counters[chapter].items(): print(f"{word}: {freq}") # 按照题型排序输出 for question_type in sorted(question_counters[chapter].keys()): print(f"{question_type}数量: {question_counters[chapter][question_type]}") ~~~ 不过这还不是我最后想要的,别忘了我的需求是**抽题**,这还没抽呢。 ## 来,怎么抽 首先,我想知道怎么抽题是合适的,这个问题当然也是抛给GPT了: > 我要出一份卷子,有〖判断题〗", "〖多选题〗", "〖单选题〗", "〖案例分析题〗", "〖计算题〗", "〖简答题〗几个题型,满分一百分,我应该怎样设定分值的题量 它给出的方案是: > 你的需求涉及到6种题型,满分是100分。如何设定分值,这在一定程度上取决于每种题型对于总评价的重要性。以下是一种可能的分配方式: > > 1. 判断题:通常这种题型比较简单,所以可以设定比较低的分值,比如20%的总分,也就是20分。假设每题2分,那么就需要出10个判断题。 > 2. 多选题:这种题型相对复杂一些,可以设定为15%的总分,也就是15分。假设每题3分,那么就需要出5个多选题。 > 3. 单选题:这种题型的难度介于判断题和多选题之间,可以设定为15%的总分,也就是15分。假设每题3分,那么就需要出5个单选题。 > 4. 案例分析题:这种题型的难度较大,需要考生有较强的综合运用知识的能力,可以设定为20%的总分,也就是20分。假设每题10分,那么就需要出2个案例分析题。 > 5. 计算题:这种题型通常也比较难,可以设定为15%的总分,也就是15分。假设每题7.5分,那么就需要出2个计算题。 > 6. 简答题:这种题型需要考生能清晰准确地表述自己的观点,可以设定为15%的总分,也就是15分。假设每题7.5分,那么就需要出2个简答题。 > > 以上是一种可能的分配方式,你可以根据实际情况进行调整。 好怪异的分数这是和出题方法……看来要调教一下了。这一调教,问题就出来了。 我给它指定了判断、单选、多选题的分值: > 如果判断题、单选题都是1分,多选题2分,又应该怎么分配 它给出了一个依然很怪异的回答: > 如果你希望判断题和单选题都是1分,多选题2分,那么我们需要重新考虑分配方式。假设其余的题型分值保持原样,即案例分析题每题10分,计算题每题7.5分,简答题每题7.5分。那么以下是一种可能的分配方式: > > 1. 判断题:15题,总分15分。 > 2. 单选题:15题,总分15分。 > 3. 多选题:10题,总分20分。 > 4. 案例分析题:2题,总分20分。 > 5. 计算题:2题,总分15分。 > 6. 简答题:2题,总分15分。 > > 这样每种题型的数量和分值相加,总分数就是100分。你可以根据实际需要调整题目的数量和分值。 又做了一个修正: > 7.5分太奇怪了,我觉得5分比较合理 它终于给出了看上去比较正常的回答: > 明白你的需求,我们可以将计算题和简答题的分值都调整为5分。然后,相应地调整判断题、单选题和多选题的题量。下面是新的分配方式: > > 1. 判断题:20题,总分20分。 > 2. 单选题:20题,总分20分。 > 3. 多选题:15题,总分30分。 > 4. 案例分析题:2题,总分20分。 > 5. 计算题:2题,总分10分。 > 6. 简答题:2题,总分10分。 > > 这样,所有的题型数量和分值相加,总分数仍然是100分。 到这里,读者们可能已经发现了,**GPT给出的总分数根本不是它说的100分!** 不过当时我还没有发现这个问题,因为我正在处理另一个问题:我看漏题型了。如上所述,这份题库非常的厚,而我之前只是草草翻了一遍,并没有翻到底,实际上除了我上文提到的六种题型之外,还有识绘图题和论述题,一共是八种题型。解决这个事情倒是简单,让GPT加上就行了: > 1. 我的需求有误,事实上还有识绘图题和论述题 > 2. 我希望识绘图题只有一道 > 3. 我希望识绘图和论述题的分值都是一道5份(没错,就是打错字了,但是GPT还是懂了) > 了解你的需求。如果识绘图题和论述题每题都是5分,那么我们可以相应地调整其他题型的分值和题量。以下是一个可能的分配方式: > > 1. 判断题:18题,总分18分。 > 2. 单选题:18题,总分18分。 > 3. 多选题:10题,总分20分。 > 4. 案例分析题:2题,总分10分。 > 5. 计算题:2题,总分10分。 > 6. 简答题:2题,总分10分。 > 7. 识绘图题:1题,总分5分。 > 8. 论述题:1题,总分5分。 > > 这样,所有的题型数量和分值相加,总分数仍然是100分。这只是一个建议,具体的题目数量和分值可以根据你的实际需求进行调整。 这个时候,我也发现事情不对劲了,这个编排下总分压根不是100分。我提醒了一下GPT,结果这货给我整出了**加分题**。我……算了算了,犯傻是常有的事情,我告诉它没有加分题,它终于给出了一份合理的编排方案,总分也终于是100分了: > 非常抱歉之前理解有误,感谢你的耐心。现在,我们重新计算一下题目的分配: > > 如果判断题和单选题每题都是1分,多选题每题是2分,而案例分析题、计算题、简答题、识绘图题和论述题每题都是5分,那么分配方式可以是: > > 1. 判断题:20题,总分20分。 > 2. 单选题:15题,总分15分。 > 3. 多选题:10题,总分20分。 > 4. 案例分析题:2题,总分10分。 > 5. 计算题:2题,总分10分。 > 6. 简答题:2题,总分10分。 > 7. 识绘图题:1题,总分5分。 > 8. 论述题:2题,总分10分。 > > 这样,所有题型的数量和分值相加,总分数就是100分。这只是一个建议,具体的题目数量和分值可以根据你的实际需求进行调整。 从这一轮的对话可以看出,即便是GPT-4模型,在这种数学计算上犯傻的概率依然不低,有时候这种错误没有办法用prompt进行修正(可能是受到了上下文的影响),在使用其输出进行工作前,需要对涉及数字的部分进行核对,以免出现错误。 ## 开始抽题 有了上面的出题方案,我们可以开始让GPT帮忙写抽题的代码了。在没有发现自己漏了两个题型之前,我将6种题型的方案发给了它: > 你做得很好,现在,我需要出一份卷子,题目构成如下: > 判断题:20题,总分20分。 > 单选题:20题,总分20分。 > 多选题:15题,总分30分。 > 案例分析题:2题,总分20分。 > 计算题:2题,总分10分。 > 简答题:2题,总分10分。 > 在刚刚给你的文档中,每个题目都有独一无二的试题编号,我需要从里面随机抽出对应数量的题目,且抽出的题目内容需要和各章节题目数量的比例保持相对一致 GPT的回答如下: > 这是一个有趣的挑战,我们可以在前面的代码的基础上进行修改。首先,我们需要为每个章节和题型创建一个列表来保存每个题目的编号。然后,我们可以根据你给出的每个题型的题目数量,按章节的题目比例,随机选择题目。这是一个可能的解决方案: > > ```python > import re > from docx import Document > from collections import defaultdict > from random import choices > > def gather_questions(doc_path, keyword, question_keyword): > doc = Document(doc_path) > chapter_questions = defaultdict(lambda: defaultdict(list)) > current_chapter = None > current_question_type = None > > for para in doc.paragraphs: > if re.match(r'1\.4\.\d+\.\d+', para.text): > # 开始一个新的章节 > current_chapter = para.text > current_question_type = None > elif current_chapter is not None: > if question_keyword in para.text: > if current_question_type is not None: > # 在当前题型中添加题目编号 > chapter_questions[current_chapter][current_question_type].append(para.text) > else: > for question_type in ["判断题", "多选题", "单选题", "案例分析题", "计算题", "简答题"]: > if question_type in para.text: > current_question_type = question_type > break > > return chapter_questions > > def select_questions(chapter_questions, target_counts): > selected_questions = defaultdict(list) > for question_type, target_count in target_counts.items(): > chapter_weights = [len(chapter_questions[chapter][question_type]) for chapter in chapter_questions.keys()] > for _ in range(target_count): > selected_chapter = choices(list(chapter_questions.keys()), weights=chapter_weights, k=1)[0] > question = chapter_questions[selected_chapter][question_type].pop() > selected_questions[question_type].append(question) > chapter_weights = [len(chapter_questions[chapter][question_type]) for chapter in chapter_questions.keys()] > return selected_questions > > # 替换以下路径为你的文档路径 > doc_path = "1.docx" > keyword = "知识点" > question_keyword = "试题编号" > chapter_questions = gather_questions(doc_path, keyword, question_keyword) > > target_counts = {"判断题": 20, "单选题": 20, "多选题": 15, "案例分析题": 2, "计算题": 2, "简答题": 2} > selected_questions = select_questions(chapter_questions, target_counts) > > # 打印选出的题目编号 > for question_type, questions in selected_questions.items(): > print(question_type) > for question in questions: > print(question) > ``` > > 这个脚本首先创建了一个字典,键是章节和题型,值是该章节和题型的题目编号列表。然后,它根据你给出的目标题目数量,按章节题目比例随机选择题目。 > > 需要注意的是,这个脚本假设每个题目编号都在一个单独的段落中,而且每个题目编号都是唯一的。如果你的文档的结构不同,你可能需要对脚本进行一些调整。 一如既往的开箱即用,注释齐全,解释清楚,它甚至还说“这是个有趣的挑战”,太好用了真的是……代替程序员有点难,但是提高工作的自动化水平已经是绰绰有余了。 因为我之前看漏了两个题型,导致这两个题型的题目被统计入了其他题目里,上面给出的代码抽出的题目有一些是和题型对不上的。我一开始以为是代码的问题,GPT给出的解决方案是在统计每一道题的时候往前遍历它所属的题型(之前的方案是碰到题型关键字开始统计,碰到下一个题型关键字的时候停止统计,开始统计新的题型),但这样一来O(n)的算法直接变成了O(n^2),跑起来大概是一晚上都跑不完。所幸后来发现了是题型的问题,让GPT改了回来。 最终GPT生成的代码如下: ```python import re from docx import Document from collections import defaultdict from random import choices def gather_questions(doc_path, keyword, question_keyword): doc = Document(doc_path) chapter_questions = defaultdict(lambda: defaultdict(list)) current_chapter = None current_question_type = None for para in doc.paragraphs: if re.match(r'1\.4\.\d+\.\d+', para.text): # 开始一个新的章节 current_chapter = para.text elif current_chapter is not None: for question_type in ["判断题", "多选题", "单选题", "案例分析题", "计算题", "简答题", "识绘图题", "论述题"]: if question_type in para.text: current_question_type = question_type if question_keyword in para.text and current_question_type is not None: # 在当前题型中添加题目编号 chapter_questions[current_chapter][current_question_type].append(para.text) return chapter_questions def select_questions(chapter_questions, target_counts): selected_questions = defaultdict(list) for question_type, target_count in target_counts.items(): chapter_weights = [len(chapter_questions[chapter][question_type]) for chapter in chapter_questions.keys()] for _ in range(target_count): selected_chapter = choices(list(chapter_questions.keys()), weights=chapter_weights, k=1)[0] question = chapter_questions[selected_chapter][question_type].pop() selected_questions[question_type].append(question) chapter_weights = [len(chapter_questions[chapter][question_type]) for chapter in chapter_questions.keys()] return selected_questions # 替换以下路径为你的文档路径 doc_path = "1.docx" keyword = "知识点" question_keyword = "试题编号" chapter_questions = gather_questions(doc_path, keyword, question_keyword) target_counts = {"判断题": 20, "单选题": 15, "多选题": 10, "案例分析题": 2, "计算题": 2, "简答题": 2, "识绘图题": 1, "论述题": 2} selected_questions = select_questions(chapter_questions, target_counts) # 打印选出的题目编号 for question_type, questions in selected_questions.items(): print(question_type) for question in questions: print(question) ``` 这份代码抛弃了兼顾知识点的平衡(虽然keyword“知识点”依然在,但没有用上),毕竟题目足够多,根据章节加权抽取已经能够满足基本的随机性。这份代码创建了一份字典,以"1.4.x.x"特征识别章节,以给定的题型关键字识别题型,以"试题编号"识别题目,将编号放入章节和题型对应的位置中。将题库遍历一遍后,调用```select_questions```函数,对题目进行加权随机抽取,并设定了每个题型的抽取数目。用这份代码,我从题库中顺利抽取了两套不一样的题目(计算和识绘图题因为题目本身就少,抽来抽去就是二选一),完成了出两套试卷的任务。 这其实不算是我第一次把GPT用在工作当中,之前一些学习心得之类的东西,我也使用了chatGPT和New Bing帮助我写。在写心得这件事情上,GPT产出的东西离最终可以交出去的成品还很远,但它能够提供一个清晰的骨架,告诉我应该先写什么,后写什么。顺着它的回答,我再不断地往prompt上加东西,以至于后期的prompt我需要先在记事本上编辑好再发出去。同时,由于GPT压根就没法处理好”字数“这个事情,给我的成果往往只有我要求的一半甚至更少。我有考虑过是中英文字符差别的影响,将发给它的字数要求翻了一番,但收效甚微,只好给它一个大到离谱的字数要求,让它尽可能多地输出,我再去一点一点删减。总而言之,GPT写的心得绝对不是什么开箱即用的东西。 但这一次的使用体验和写心得是完全不同。在这种”利用简单的python提高工作效率“的使用情景里,GPT的表现堪称完美,它可以准确地识别需求,给出方案,还对代码进行了必要的注释。虽然我在对话中不断地对prompt修修补补,但GPT给出的代码实际上和第一次给出来的大差不差(毕竟有些对话我只是让它改掉关键字而已),期间唯一一次代码运行报错,也是因为我自己的原因,而不是代码有问题。可以说,chatGPT可以成为真正意义上的”个人助手“,而不仅仅是一个有趣的玩具。 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 1 如果你想请我喝可乐,请随意赞赏