
简介这份资源是面向准备参加微软 AI-900 认证考试的考生整理的备考资料聚焦人工智能基础概念、机器学习工作流与 Azure AI 服务相关考点适合零基础入门者以及希望系统刷题、查漏补缺的转行或在校学习者使用。压缩包内共 1 个 PDF 文件约 17.65MB内容以 ExamTopics 风格的考试题集为主逐题给出题干、选项、正确答案与社区投票分布并附带官方文档引用链接便于边做题边追溯知识点来源。题目覆盖业务价值评估、训练集与评估集划分、混淆矩阵指标如真阳性、假阴性解读、自动化机器学习中的模型可解释性与负责任 AI 原则等核心模块能让读者直观感受考试出题角度与答题逻辑。目前该资源已有 363 人学习下载可作为 AI-900 冲刺阶段的自测与复盘材料帮助快速定位薄弱环节并熟悉英文题干表述。1. AI-900 备考里ExamTopics 的 PDF 题库到底该怎么用AI-900 _ ExamTopics认证考试.pdf这个文件名本身就是一条很典型的备考路径从 ExamTopics 这类题库站点拿到 AI-900 的题目合集存成 PDF然后指望在 PDF 阅读器里从头翻到尾就能过。翻完的人大多有同一个体感——题看着都眼熟真到考场上问「哪个 Azure 服务负责关键短语提取」脑子里还是一片模糊。问题不在题源在载体。PDF 是给人眼看的线性文档不是给复习流程用的结构。AI-900 是 Azure AI Fundamentals 这一档的考试考的是 AI 工作负载与负责任 AI、机器学习基础、计算机视觉、自然语言处理、生成式 AI 这五块的判断能力选项之间差异极小靠翻页记住的是「位置感」不是「知识点」。我一般的做法是把这份 PDF 当数据源而不是读物pdf 解析成结构化题库按考试域打标签再进模拟考试与错题回流。适合已经决定考 AI-900、手上有题库 PDF、并且愿意写几十行 Python 的人。2. 用 pdfplumber 把 ExamTopics 的 AI-900 PDF 解析成结构化题库这一步的目标只有一个把一份几百页、排版不统一的 PDF变成一份每道题都有题干、选项、答案、解释字段的 JSON。做完这一步后面所有统计、抽题、错题本才有落脚点。2.1 先摸清 AI-900 题库 PDF 的排版规律再决定解析工具ExamTopics 系的题库 PDF 大致有这些固定元素页眉页脚站点名、Topic 编号、社区投票区、题号Question #12或12.、选项A./A)/ 带方框的列表、答案行Answer: A或Correct Answer: A, C多选、解释段Explanation、参考链接Reference。脏数据主要来自四类跨页断题、行尾连字符换行、选项被排成两列、页眉混进正文。工具选型上别一上来就找在线 pdf 工具箱或者 pdf 转 word那类方案零代码但不可复现处理不了几百页还要重跑的场景。常见做法是主用 pdfplumber 抽词级坐标PyMuPDF 兜底渲染扫描页。工具适合场景优势短板pdfplumber文本与表格混排、需要坐标判断分栏提供每个词的 x0/x1/top/bottom能按列重建阅读顺序速度慢大文件要分页处理PyMuPDF (fitz)大文件、需要把页渲染成图做 OCR快渲染质量高表格结构要自己重建pypdf只要纯文本、依赖越少越好安装体积小不分栏、不给坐标双列排版会串行在线 pdf 工具箱 / pdf 转 word一次性人工校对几页上手快无法批处理结果不可复现2.2 最小可跑pdfplumber 抽取全文与词级坐标先建环境确认能读通页数和词坐标再写解析逻辑。切忌一上来就写正则先看数据长什么样。python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install pdfplumber pyyaml python - PY import pdfplumber src AI-900 _ ExamTopics认证考试.pdf with pdfplumber.open(src) as pdf: print(总页数:, len(pdf.pages)) # 先确认页数顺便统计文件规模 page pdf.pages[0] words page.extract_words() # 词级对象text 坐标 for w in words[:8]: print(round(w[top], 1), round(w[x0], 1), w[text]) PYextract_words()返回的每个词都带top和x0。判断是否双列排版看同一页里x0是否明显分成两簇还原阅读顺序时按top升序、x0升序排序比直接extract_text()可靠得多。如果某页是扫描图extract_words()会返回空列表这类页要单独走 OCR 分支见第 5 章。几百页的文件按页 yield不要一次把全文拼成一个大字符串内存和调试成本都受不了。2.3 用状态机把题干、选项、答案切干净正则只负责识别行的类型切题靠状态机。每个题号开启一个新的 Question 对象之后的行按「当前处于题干段、选项段还是解释段」决定归属。import re from dataclasses import dataclass, field QUESTION_RE re.compile(r^\s*(?:Question|QUESTION)\s*#?\s*(\d{1,4})\b, re.I) OPTION_RE re.compile(r^\s*([A-F])[\.\)、]\s(.?)\s*$) ANSWER_RE re.compile(r^\s*(?:Correct\s)?Answer\s*[:]\s*([A-F](?:\s*[,/]\s*[A-F])*), re.I) NOISE_RE re.compile(r^(?:ExamTopics|Topic\s*\d|Community\s*vote|Highly\s*Voted|Reference), re.I) dataclass class Question: number: int stem: str options: dict field(default_factorydict) answers: list field(default_factorylist) explanation: str def parse_questions(lines): qs, cur, last_opt [], None, None for raw in lines: line raw.strip() if not line or NOISE_RE.match(line): # 页眉页脚先清掉 continue m QUESTION_RE.match(line) if m: if cur: qs.append(cur) cur, last_opt Question(numberint(m.group(1))), None continue if cur is None: continue m ANSWER_RE.match(line) if m: cur.answers [a.strip() for a in re.split(r[,/], m.group(1))] last_opt None # 进入解释段 continue m OPTION_RE.match(line) if m: last_opt m.group(1) cur.options[last_opt] m.group(2) continue if cur.answers: # 答案之后的行都属于解释 cur.explanation line elif last_opt: cur.options[last_opt] line # 选项的续行 else: cur.stem line # 题干的续行 if cur: qs.append(cur) return qs几个必须踩过的坑。第一ANSWER_RE一定要带行首锚点^否则解释段里出现的 the answer: read the image 会被误判成答案行。第二选项续行的归属靠last_opt判断但如果 PDF 把解释排在选项之前这个逻辑会把解释吞进最后一个选项解决办法是给状态机加一个phase字段一旦命中Answer:就锁死不再接受新选项。第三多选答案的分隔符在不同版本里可能是逗号、斜杠或空格re.split的字符集要放宽。2.4 解析完必须做的三项校验不校验就直接刷题最后记的可能是一份错位题库。三项指标卡住问题基本都能暴露。校验项判定标准不通过时先查什么题号连续性实际条数 / (max - min 1) ≥ 0.98是否有题号被页眉拆成两行导致漏题选项完整度≥ 90% 的题至少有 4 个选项双列排版是否把 B/C 两列读到同一行答案覆盖率100% 有 answers答案行是否被换行拆断Answer:与字母分处两行解释归属率≥ 80% 的题 explanation 非空解释是否被判给了最后一个选项def audit(qs): nums sorted(q.number for q in qs) span nums[-1] - nums[0] 1 if nums else 0 print(题数:, len(qs), 题号跨度:, span, 连续性:, round(len(qs) / max(span, 1), 3)) print(选项完整:, round(sum(1 for q in qs if len(q.options) 4) / max(len(qs), 1), 3)) print(答案覆盖:, round(sum(1 for q in qs if q.answers) / max(len(qs), 1), 3)) print(解释覆盖:, round(sum(1 for q in qs if q.explanation) / max(len(qs), 1), 3))连续性低于 0.98说明漏题去查页眉页脚附近那几页答案覆盖率不是 1.0基本可以断定是Answer:与字母被排版拆开把ANSWER_RE改成分两行匹配即可。3. 给 AI-900 题目打标签按考试域权重建一套可统计的题库结构化之后题库还是一盘散沙。要让它指导复习必须给每道题打上考试域标签否则你永远不知道自己的薄弱环节是计算机视觉还是 NLP。3.1 AI-900 的考试域权重与关键词信号表下面这张表里的权重区间来自公开的考试大纲考前请以官方最新 Study Guide 为准关键词列是我自己积累的信号词用来做规则打标。考试域大纲权重强信号关键词典型迷惑项AI 工作负载与负责任 AI15–20%responsible AI、fairness、inclusiveness、reliability、transparency、workload把 fairness 与 inclusiveness 混为一谈Azure 机器学习基础20–25%regression、classification、clustering、feature、label、designer、AutoML把 clustering 当成监督学习计算机视觉15–20%Custom Vision、Face、OCR、Read API、image classification、object detectionRead 与表单类服务的职责边界自然语言处理15–20%key phrase、sentiment、entity、LUIS、QnA、speech、translationLUIS 与问答服务的适用场景生成式 AI15–20%Azure OpenAI、prompt、token、grounding、DALL·E、Copilot把 Copilot 当成一个具体模型3.2 规则打标还是分类模型为什么先规则很多人第一反应是拿一个分类模型甚至大模型来打标。可行但不该是第一步。规则打标的优势在于可解释、可回归测试、零调用成本而且题库更新时你只需要往 YAML 里加几个关键词不用重新标注数据。我的顺序是先用规则覆盖 80% 以上的题把置信度低的挑出来人工过一遍人工判断的结果再写回规则文件形成「规则即标注」的循环。只有当低置信度比例长期高于 20%才值得考虑引入语义分类。3.3 打标脚本must / any 双阈值与置信度回捞把规则写成 YAML题目文本统一转小写后做匹配。must是强信号命中才给基础分any是弱信号每命中一个加一分。import yaml RULES yaml.safe_load( - domain: ml_fundamentals must: [machine learning] any: [regression, classification, clustering, feature, label, designer, automl] - domain: computer_vision must: [] any: [custom vision, face, ocr, read api, image classification, object detection] - domain: nlp must: [] any: [key phrase, sentiment, entity, luis, qna, speech, translation] - domain: generative_ai must: [] any: [openai, prompt, token, grounding, dall, copilot] - domain: ai_workload must: [] any: [responsible, fairness, inclusiveness, transparency, workload] ) def score(q, rule): text (q.stem .join(q.options.values()) q.explanation).lower() must_hit sum(1 for k in rule[must] if k in text) any_hit sum(1 for k in rule[any] if k in text) return 2.0 * must_hit any_hit # 强信号权重是弱信号的两倍 def label(q, rules): scored sorted(((score(q, r), r[domain]) for r in rules), keylambda x: -x[0]) (s1, d1), (s2, _) scored[0], scored[1] conf s1 / (s1 s2 1e-6) # 第一名占比越高越可信 return d1, round(conf, 3) def split_by_confidence(qs, rules, threshold0.6): high, low [], [] for q in qs: domain, conf label(q, rules) (high if conf threshold else low).append((q, domain, conf)) return high, lowthreshold是我最常调的参数调到 0.75 以上人工复核的题变少但漏标变多调到 0.5低置信度队列会爆炸。0.6 是个能兼顾的起点。score里把 explanation 也算进文本是因为解释里往往直接点名了服务名比题干更有信息量。3.4 用 SQL 统计各域错题率定位复习盲区题库和答题记录落到 SQLite 之后用一条 SQL 就能看出该补哪块而不是按大纲顺序从第一章背起。SELECT domain, COUNT(*) AS total, SUM(CASE WHEN wrong_count 0 THEN 1 ELSE 0 END) AS wrong_total, ROUND(1.0 * SUM(CASE WHEN wrong_count 0 THEN 1 ELSE 0 END) / COUNT(*), 3) AS wrong_rate FROM questions GROUP BY domain ORDER BY wrong_rate DESC;wrong_rate排在最前面的域就是下一次复习的起点。注意到COUNT(*)用的是题目总数而不是答题次数这样不同域的样本量差异才不会让比例失真。如果某个域的总题数不到 30比例波动会很大这时把窗口拉长到最近三轮模拟再算一次更稳。4. 从结构化题库到模拟考试抽题、计时、错题回流有了标签就可以组装一套按权重分布、可重复的模拟卷。这一步的关键不是抽题算法有多复杂而是参数要贴近真实考试并且每次结果可对比。4.1 按权重抽题的模拟考试脚本import random from collections import defaultdict WEIGHTS { ai_workload: 0.175, ml_fundamentals: 0.225, computer_vision: 0.175, nlp: 0.175, generative_ai: 0.175, } def build_paper(questions, weightsWEIGHTS, total45, seed42): rng random.Random(seed) # 固定种子同一套卷可复现 buckets defaultdict(list) for q in questions: buckets[q[domain]].append(q) paper [] for domain, w in weights.items(): n round(total * w) # 按权重换算该域题量 pool buckets[domain][:] rng.shuffle(pool) paper pool[:min(n, len(pool))] if len(paper) total: # 题量不足时从最大域回填 rest [q for q in questions if q not in paper] rng.shuffle(rest) paper rest[:total - len(paper)] rng.shuffle(paper) # 打散域顺序避免同域连出 return paper[:total]seed固定意味着每次生成的卷子完全一致方便你对比「上周做这套错了 12 题这周错了 7 题」。total对齐真实题量区间。回填逻辑是为了防止某个域题量太少导致整卷题数缩水q not in paper在题目对象不多时开销可以接受量大时换成 id 集合更合适。4.2 题量、时长、及格线、复习间隔的参数怎么定参数建议值理由单卷题量 total45贴近真实场次的题量区间太短测不出疲劳度计时时长45 分钟平均每题 1 分钟留出多选和长题干的时间及格线70%官方常见说法是 700/1000换算成百分比便于自测错题重考间隔3 / 7 / 21 天间隔重复第一轮别隔太久否则会忘光域内重考窗口7 天同一道题一周内不重复出现避免记住答案位置计时不要用手机秒表直接把开始时间写进结果文件交卷时算差值避免中途暂停造成的数据失真。4.3 错题回流 Anki 与 Markdown 输出错题只记在脑子里等于没记。导出成 Anki 卡片的 CSV比截图存相册靠谱得多。import csv def to_anki_csv(questions, pathai900_wrong.csv): # Anki 导入用制表符分隔最稳选项里的逗号不会破坏列 with open(path, w, newline, encodingutf-8) as f: writer csv.writer(f, delimiter\t) for q in questions: front q[stem] \n \n.join(f{k}. {v} for k, v in q[options].items()) back f答案{,.join(q[answers])}\n\n{q[explanation]} writer.writerow([front, back, q[domain]]) # 第三列映射为 Anki 标签 def to_markdown(questions, pathai900_wrong.md): with open(path, w, encodingutf-8) as f: for q in questions: f.write(f### Q{q[number]} · {q[domain]}\n\n{q[stem]}\n\n) for k, v in q[options].items(): f.write(f- {k}. {v}\n) f.write(f\n 答案{,.join(q[answers])}\n\n)制表符分隔是刻意的选择题干里带逗号和引号的情况极多逗号分隔的 CSV 在 Anki 导入时会大面积错列。Markdown 版本则是给打印和做 PDF 用的标题里带域标签翻页时能一眼看出这是哪一块的题。4.4 中文对照、pdf 翻译与 pdf 转 word 在复习里的正确用法非英语母语的备考者常想用 pdf 翻译工具把整份题库转成中文。我的建议是只翻解释段题干和选项保留原文。原因是 AI-900 的题干关键词本身就是考点比如 grounding、token 这类词一旦被机翻成「接地」「令牌」你在考场上看到原词反而反应不过来。如果 PDF 排版实在太乱可以先用 pdf 转 word 做一次人工校对把错位的选项调回来再回到 Python 流水线里重跑解析。这条路径比在解析脚本里硬凑正则要省时间但要记住word 只是校对中间产物最终数据源仍然是结构化的 JSON 或 SQLite别让手工修改的文档变成唯一副本。5. 进阶扫描版题库的歪斜纠偏、Markdown 转 PDF 与解析验证到这一步题库已经是可统计的数据了。剩下的问题集中在两类PDF 本身质量差以及怎么验证你的解析结果没跑偏。5.1 扫描版题库的歪斜校正纠偏与 OCR 前处理有些题库 PDF 是扫描件extract_words()返回空列表必须走 OCR。OCR 前先做歪斜校正纠偏字符错误率会明显下降。import cv2 import numpy as np img cv2.imread(page_012.png, cv2.IMREAD_GRAYSCALE) # Otsu 二值化后取前景像素用最小外接矩形估算倾斜角 _, bw cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) coords np.column_stack(np.where(bw 0)) angle cv2.minAreaRect(coords)[-1] angle -(90 angle) if angle -45 else -angle if abs(angle) 1.0: # 小角度不值得转转了反而更糊 h, w img.shape M cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0) img cv2.warpAffine(img, M, (w, h), flagscv2.INTER_CUBIC, borderModecv2.BORDER_REPLICATE) cv2.imwrite(page_012_fixed.png, img)abs(angle) 1.0这个门槛是有意加的亚像素级的倾斜 OCR 引擎自己能处理强行旋转引入的插值模糊反而会伤害识别率。borderMode用BORDER_REPLICATE是为了不让旋转后的黑边被当成文字块。5.2 用 pandoc 把错题 Markdown 转成带中文字体的 PDF第 4 章导出的 Markdown 如果想打印用 pandoc 走 XeLaTeX 通道中文字体必须显式指定否则中文会整段丢失。pandoc ai900_wrong.md -o ai900_wrong.pdf \ --pdf-enginexelatex \ -V CJKmainfontNoto Sans CJK SC \ -V geometry:margin2cm \ -V fontsize10pt习惯在编辑器里完成的用 VSCode 的 Markdown PDF 插件也能一键转区别是插件走 Chromium 渲染代码块换行和表格宽度更接近浏览器效果而 pandoc 走 LaTeX长表格会自动分页。两种都行选一种固定下来别每次换工具导致排版对比失效。5.3 解析正确率的抽样验证方法最后一步别省。随机抽 20 道题把 JSON 里的题干、选项、答案、解释四项和 PDF 原文逐条对照重点核对多选答案的字母顺序和解释的归属。这一轮抽样如果发现超过 2 道有问题说明不是偶发回头检查ANSWER_RE和选项续行归属的逻辑而不是手工改这 20 条。每次题库换版本重跑一遍第 2.4 节的audit()四项指标全部达标再进复习流程。本文还有配套的精品资源点击获取