ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用Python将数据挖掘考试题PDF转化为智能复习系统:从文本提取到考点聚类

用Python将数据挖掘考试题PDF转化为智能复习系统:从文本提取到考点聚类 简介数据挖掘考试题PDF是一份面向计算机科学、数据科学及相关专业学生的数据挖掘课程复习资料适合备考、自测与考前冲刺。内容紧密围绕分类与聚类、聚类算法比较、关联规则分析、层次聚类、相似度计算、支持度与置信度等核心考点展开涵盖选择题、填空题、判断题与综合题并附有参考答案与解析能够帮助学习者系统梳理知识点、熟悉典型题型并掌握解题思路。资源为单个PDF文件大小约367KB内容紧凑便于打印或移动端随时翻阅。目前已有58人浏览学习适合在期末复习或考研准备阶段用于查漏补缺尤其可强化对K均值与DBSCAN差异、凝聚与分裂层次聚类、Ward方法、组平均策略以及“啤酒与尿布”关联规则案例的理解是一份高性价比的应试资料。1. 数据挖掘考试题.pdf从一份试卷到可刷题的复习系统期末前一周你手头只有一份《数据挖掘考试题.pdf》——或许是从教学平台下载的或许是朋友拍的扫描件。它看起来像一堵墙文字缩成一团表格对不齐复制出来全是乱码更麻烦的是你明明知道里面考了K-Means和Apriori但你没时间和勇气从头到尾重读一遍。下面这套流程不是让你去“背题”而是把这套PDF当作数据挖掘课程里最真实的“数据集”用Python把它解析成结构化题目再用TF-IDF、聚类这些你正要复习的算法来告诉你到底该复习什么。整个过程能跑通适合期末复习、助教出题以及想验证自己toolchain的人。2. 解析PDF用pdfplumber把考试题从图片和表格中剥离出来2.1 为什么先用pdfplumber而不是PyPDF2在处理《数据挖掘考试题.pdf》这类文件时最常见的错误是一开始就上PyPDF2或pypdf。它们对简单纯文本PDF确实有效但遇到分栏、表格、空行错乱时提取结果会丢掉阅读顺序。pdfplumber基于pdfminer.six额外提供了版面分析能力能够感知字符坐标和线框位置所以对试卷常见的“题号 选项”排版更友好。安装只需要一条命令pip install pdfplumber pypdf2我用pdfplumber提取所有文本的底子是这样写的import pdfplumber def extract_pdf_text(pdf_path): pages [] with pdfplumber.open(pdf_path) as pdf: print(f总页数: {len(pdf.pages)}) for i, page in enumerate(pdf.pages): text page.extract_text(x_tolerance1, y_tolerance3) pages.append(f PAGE {i1} \n{text}) return \n.join(pages) with open(exam_raw.txt, w, encodingutf-8) as f: f.write(extract_pdf_text(数据挖掘考试题.pdf))这里有两个参数需要注意x_tolerance1表示字符之间水平距离超过1磅才认为是空格适合避免“A B C D”被拆成“A B C D”而不是原样y_tolerance3则是垂直方向的分组容差用于处理数学公式或上下标的错位。如果你的题目中公式特别多比如贝叶斯公式“P(A|B)”这两个值值得多试几组。2.2 扫描版试卷先用OCR把图片变成文字如果你拿到的文件是扫描版页面里根本没有文本层第二步就接不上。这类PDF看起来每页都是整张图片需要用OCR。我一般会先做一个判断用page.images数组的长度和文本内容对比如果page.extract_text()返回None就说明这一页是纯图片。OCR的选择上小规模试卷用Tesseract就够了但中英文混排时PaddleOCR的准确率会更好一些。下面是PaddleOCR的调用方式from paddleocr import PaddleOCR import fitz # PyMuPDF def ocr_scanned_pdf(pdf_path): ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) doc fitz.open(pdf_path) full_text [] for page in doc: pix page.get_pixmap(dpi300) img_path fpage_{page.number}.png pix.save(img_path) result ocr.ocr(img_path, clsTrue) if result and result[0]: lines [line[1][0] for line in result[0]] full_text.append(\n.join(lines)) return \n.join(full_text)注意dpi300是扫描件识别成败的关键。太低会丢失小字号中文太高会让整个流程变慢300是折中值。同时use_angle_clsTrue会先做方向分类解决扫描件旋转90度的问题但会额外耗时如果确认页面方向正确可以关闭它。2.3 表格型题目用extract_table保住对齐关系数据挖掘考试题里经常出现“对比决策树和神经网络的区别”这类表格题PDF里画了完整的网格线。pdfplumber对这类表格有专门支持with pdfplumber.open(数据挖掘考试题.pdf) as pdf: page pdf.pages[3] # 假设表格在第4页 tables page.extract_table() for row in tables: print( | .join([cell.replace(\n, ) if cell else for cell in row]))extract_table默认会以检测到的线段为边界返回一个二维列表。注意返回值中可能混入None代表空单元格。如果表格没有完整框线可以指定vertical_strategytext让算法根据文本列位置去猜测边界但这样对错位文本的容错性稍差。3. 把文本变成题目正则表达式与NLP双保险3.1 用正则切分单项选择题从PDF抽出来的文本往往长这样1. 以下哪个算法属于无监督学习 A. 决策树 B. 线性回归 C. K-Means D. 支持向量机 2. 关联规则Apriori算法的核心思想是…要把它拆成独立的题目正则表达式是第一步。我常用的模式是import re pattern re.compile(r(\d)[、.\s]*([^\n](?:\n(?!\s*[A-D][\s.、])[^\n]*)*)\n\s*([A-D][\s.、].*(?:\n\s*[A-D][\s.、].*)*), re.M) with open(exam_raw.txt, encodingutf-8) as f: content f.read() questions [] for match in pattern.finditer(content): num match.group(1) stem match.group(2).replace(\n, ) options re.split(r\s{2,}|\n, match.group(3)) questions.append({num: num, stem: stem, options: options}) print(questions[0])这个正则的核心是“否定前瞻”(?!\s*[A-D][\s.、])告诉解析器只有当下一行不是以选项标注开头时才把当前行归入题干。这样做能避免题干跨行导致的截断。但实际处理时选项有时写在一行有时每个选项占一行这就需要靠\s{2,}或换行符来切分选项。3.2 简答题和名词解释用章节标题或分值切块简答题通常没有标准选项常见排版是三、简答题每题10分 1. 简述随机森林的基本思想。 2. 解释偏差-方差权衡。这种情况我会用split按大标题分块再逐块提取小题blocks re.split(r([一二三四五六七八九十]、), content) # blocks 变成 [, 三、, 简答题..., ...]分块之后再用上面的题号正则提取每一题。这里要额外保留题目后的“分值”信息它其实是你复习优先级的权重——10分题比2分题显然更重要。3.3 用jieba给题目打语言标签题干里有很多术语比如“支持向量机”“随机森林”“过拟合”。直接用正则切分得不到这些词。配合jieba分词可以快速提取名词和热词import jieba import jieba.analyse def extract_keywords(text, topK5): return jieba.analyse.extract_tags(text, topKtopK, withWeightTrue) for q in questions: q[keywords] extract_keywords(q[stem])extract_tags默认使用TF-IDF加权也就是词频高、但在整个语料中罕见的词会排在前面。这比单纯统计词频更能反映“这个题目在考察什么”。3.4 结构化结果存成JSON最后把所有题目统一为JSON格式后面所有分析都从JSON读取[ { type: single, num: 1, stem: 以下哪个算法属于无监督学习, options: [ {label: A, content: 决策树}, {label: B, content: 线性回归}, {label: C, content: K-Means}, {label: D, content: 支持向量机} ], answer: null, score: 2, keywords: [无监督学习, K-Means] } ]建议保留answer字段初始为空由你后续去填。把题目转成JSON而不是直接存文本最大好处是后续做聚类、随机抽题、错题本时不需要再反复用正则解析。4. 考点热力图用数据挖掘的方法复习数据挖掘4.1 高频考点统计用TF-IDF找出“必考词”题目结构化后就可以把它当成一个迷你语料库来做统计。先写一个把所有题干拼接成文档集的函数再借助sklearn.feature_extraction.text.TfidfVectorizer计算每个关键词的权重。对中文需要指定分词器这里直接复用jiebafrom sklearn.feature_extraction.text import TfidfVectorizer from jieba import cut def jieba_tokenizer(text): return list(cut(text)) corpus [f{q[stem]} { .join(q[options])} for q in questions] vectorizer TfidfVectorizer(tokenizerjieba_tokenizer, max_features200) X vectorizer.fit_transform(corpus) feature_names vectorizer.get_feature_names_out() scores X.sum(axis0).A1 top sorted(zip(feature_names, scores), keylambda x: x[1], reverseTrue)[:20] for word, score in top: print(f{word}: {score:.4f})运行后你会得到一个类似“聚类、分类、决策树、Apriori、支持向量机”的Top列表。这个列表对你复习顺序的意义大于对最终成绩的直接贡献把权重最高的前几个术语作为知识主干然后把每道题归到对应主干下你看到的就不是20套零散卷子而是5个高频模块。4.2 给题目分组用KMeans发现相似考点光看权重还不够。如果两份不同年份的卷子都考了“K-Means初始中心选择”它们虽然不是同一道题但考点一样。此时可以对题目向量做一次KMeans聚类把题目自动分成若干簇再查看每个簇里共同出现的高频术语。from sklearn.cluster import KMeans n_clusters 5 # 根据你对这门课模块数量的经验设定 km KMeans(n_clustersn_clusters, n_init20, random_state42) km.fit(X) for cluster_idx in range(n_clusters): indices [i for i, c in enumerate(km.labels_) if c cluster_idx] cluster_keywords [] for i in indices[:3]: # 每簇最多看3道题 cluster_keywords.extend(questions[i][keywords]) print(f簇{cluster_idx1}: 题目{indices} 高频词: {cluster_keywords})n_init20是KMeans的一个重要参数。sklearn在旧版本中默认是10新版本要求显式输入否则未来会收到警告。它控制的是“用多少组不同初始中心去跑最后保留廓形最好的一组”。对考试题这种小规模文本20次足够稳定。random_state固定后你多次运行结果一致便于复现。4.3 根据聚类结果排出复习优先级结合题目的分值和聚类结果可以做一张复习优先级表优先级考点簇代表术语涉及题数总分值1簇3分类、决策树、熵增益8322簇1聚类、K-Means、轮廓系数6183簇2Apriori、关联规则、置信度5204簇4回归、均方误差、过拟合4125簇5降维、PCA、方差保留38优先级不是简单地按分值排序要结合“该簇题目是否有共同解法”。比如簇3里的题目虽然多但基本都在考“用信息增益选划分特征”只要吃透一个算法就能全部拿下簇1里K-Means考得少但每一题都可能结合手算迭代步骤需要花更长时间复盘。所以最终我会按“题数 × 分值 / 期望复习时长”来排而期望复习时长本身就用聚类结果粗略估算。5. 把题库变成可刷题工作流导出、自测与错题收集5.1 用pandoc把JSON转成Markdown再转PDF当你把题目整理成结构化数据后最直观的利用方式是把它们转为可读的Markdown再导成新的PDF用来刷题。转换时不需要手写模板用Python生成Markdownmd_lines [] for q in questions: md_lines.append(f### {q[num]}. {q[stem]}) for opt in q[options]: md_lines.append(f- {opt[label]}. {opt[content]}) md_lines.append() with open(题库.md, w, encodingutf-8) as f: f.write(\n.join(md_lines))然后终端里执行pandoc 题库.md -o 题库.pdf --pdf-enginexelatex -V CJKmainfontPingFang SC--pdf-enginexelatex是为了处理中文你本机需要安装LaTeX发行版。如果你不想装那么重的环境也可以在VS Code里装“Markdown PDF”扩展用浏览器内核转PDF省去LaTeX依赖。VS Code里导出PDF时经常遇到“网页PDF打印”设置只需要确保打印背景图形选项开启否则代码块底部的高亮背景会丢失。5.2 随机抽题生成模拟卷复习到后半段需要有模拟卷来验证。写一个简单脚本从JSON中按分值权重抽样随机组合一套新卷子import random def generate_exam(questions, n_single10, n_short3, seed42): random.seed(seed) singles [q for q in questions if q[type] single] shorts [q for q in questions if q[type] short] selected random.sample(singles, min(n_single, len(singles))) selected random.sample(shorts, min(n_short, len(shorts))) random.shuffle(selected) return selected mock_exam generate_exam(questions) for q in mock_exam: print(f{q[num]}. {q[stem]})随机种子seed42很重要它能保证每次运行得到的模拟卷相同方便你和同学对答案。如果把种子去掉每次抽题都不一样你会失去可复现性很难复盘“上次为什么错”。5.3 完全离线刷题用本地HTML实现点击即判最后推荐一个我最近常给助教用的方案把JSON题目导出到单个HTML文件用原生JavaScript渲染题目和选项。这个页面不依赖网络双击就能用适合在电脑和手机浏览器上快速刷题。核心脚本很短script const questions /* 从JSON复制到这里 */[]; let idx 0; function render() { document.getElementById(stem).textContent questions[idx].stem; const opts document.getElementById(options); opts.innerHTML ; questions[idx].options.forEach(o { const btn document.createElement(button); btn.textContent ${o.label}. ${o.content}; btn.onclick () { alert(你选择了 ${o.label}); }; opts.appendChild(btn); }); } document.addEventListener(DOMContentLoaded, render); /script注意这里的“点击即判”只是第一步真正的错题收集需要再加一个数组记录答案并在页面底部显示“已答数总题数”。把这个HTML生成函数和前面的JSON解析合并你就可以在拿到任意一份数据挖掘考试题PDF后半小时内生成一份可离线自测的网页。以后看到任何PDF格式的旧试卷都能重复这个流程。本文还有配套的精品资源点击获取
返回列表