
今天是“大模型学习”的第9天按惯例先记录一下进度前面8天我从大模型的基本概念、Transformer结构、Prompt设计一路学到微调的基础流程但越往后越发现一个绕不开的坎——Python基础不够扎实。很多示例代码能看懂一旦要自己动手改数据处理逻辑、调试训练脚本就卡在语法和数据结构上。所以第9天我停掉新知识回头恶补Python的数据结构用两道练习题来检验这几天的积累。先说结论数据结构这块儿不是单独学完就完事它像盖房子的钢筋骨架大模型的数据清洗、Token处理、Batch拼接、评估指标计算处处都在用。今天这两道练习题一道是列表去重与排序一道是词频统计都是大模型相关任务里最常碰到的底层操作。把这个搞熟后面跑数据预处理脚本时你会省下大量时间也能少掉很多头发。1. 内容整体设计与思路拆解1.1 为什么学大模型要先练Python数据结构大模型领域的日常工作经常是“两头忙”一头是模型训练、推理框架的调用另一头是大量的数据清洗与预处理。坦白说模型框架本身封装得很好调用起来反而简单真正容易出问题的是数据准备阶段。举个例子你从多个渠道爬了一批语料格式可能千奇百怪有的每行是一段文本有的是JSON结构有的夹带HTML标签和重复段落。这时候你需要用Python写脚本把这些数据清洗成标准格式喂给训练脚本。在这个过程中列表、字典、集合、元组这些数据结构就是你最基础的工具。那和“数据结构”这门课有什么关系数据结构本质上研究的是“数据怎么组织、怎么操作效率更高”。比如列表追加元素和插入元素底层实现不同性能差别就很大集合查重为什么比列表快因为底层是哈希表字典按键取值为什么是O(1)复杂度也是哈希表的功劳。这些知识点写大模型数据处理脚本时都会遇到。如果我当时直接上手看数据处理代码可能会被各种操作绕晕但提前用练习题把这些结构吃透后面看代码、写代码都会轻松很多。1.2 两道练习题的选题思路今天的练习题我刻意选了能覆盖核心数据结构的题目练习题1列表去重与排序练习题2英文句子词频统计选这两个题理由很简单列表去重涉及列表、集合的相互转换排序涉及排序函数的自定义规则这两块是处理文本数据时的高频场景。词频统计则涵盖了字符串分割、字典更新、排序输出几乎是所有文本统计类任务的标准模板。另外这两道题都适合用朴素方法和进阶方法对比着做能直观感受到不同数据结构带来的性能差异。这种“对比感”很重要它能帮你建立“算法复杂度”的直觉而不是死记硬背。下面我把两道题的具体解法、思路和踩过的坑都展开写一遍。2. 核心细节解析与实操要点2.1 练习题1列表去重与排序题目本身不复杂给定一个包含重复元素的整数列表要求去掉重复元素对去重后的列表进行排序输出最终结果看起来简单但展开来可以写好几个版本每个版本背后的数据结构思维都不一样。方法一集合去重再排序这是最直白的写法numbers [5, 3, 8, 3, 1, 5, 8, 9, 2, 1] unique_numbers list(set(numbers)) unique_numbers.sort() print(unique_numbers)第一行代码的set(numbers)是核心。集合的特点是不允许重复元素而且底层用哈希表实现去重的时间复杂度是O(n)。转换成列表后调用sort()默认按升序排列。这个方法最简单但它有个隐蔽的坑集合是无序的如果你需要保持原列表里元素的首次出现顺序直接转集合再转列表顺序就可能被打乱。举例来说words [apple, banana, apple, cherry, banana] unique_words list(set(words)) print(unique_words)输出顺序是不确定的每次运行可能都不一样。如果业务场景是“需要保留原始顺序”这个方法就不适用了。方法二保留原始顺序的去重经典循环如果需要保持顺序可以用列表加集合配合def deduplicate(items): seen set() result [] for item in items: if item not in seen: seen.add(item) result.append(item) return result numbers [5, 3, 8, 3, 1, 5, 8, 9, 2, 1] result deduplicate(numbers) print(result)这个写法在业务代码里出现频率非常高。它利用了集合的快速查找特性item not in seen是O(1)同时用列表保住原始顺序。我在写大模型数据清洗脚本时经常用它来去掉重复的文本行同时保住语料的原始顺序。方法三一行代码去重加排序的进阶写法Python里还可以用sorted配合set一行搞定numbers [5, 3, 8, 3, 1, 5, 8, 9, 2, 1] result sorted(set(numbers)) print(result)这个写法的效果是“先去重再返回一个新的排序列表”。注意sorted返回新列表而sort()是在原列表上修改。如果后续还用到原列表这两个方法的区别就很关键。这里顺带说一下sorted(set(numbers))的时间复杂度是O(n log n)因为排序算法最快也就是这个级别。去重的O(n)加上排序的O(n log n)整体就是O(n log n)。数据量小看不出差别要是处理几十万条大规模语料选择合适的数据结构和方法执行时间可能差好几倍。2.2 练习题1的扩展自定义排序规则排序看起来简单但真实场景里往往有不简单的排序需求。大模型任务里经常要按照文本长度排序、按照得分排序、按照优先级排序等。这时候就用到了sorted的关键参数key。texts [神经网络, 深度学习, 大语言模型, Transformer, AI] # 按字符串长度排序 sorted_by_length sorted(texts, keylen) print(sorted_by_length) # 按字符串长度降序排序 sorted_by_length_desc sorted(texts, keylen, reverseTrue) print(sorted_by_length_desc)再复杂一点比如按元组第二个值排序data [(apple, 5), (banana, 2), (cherry, 8), (date, 3)] sorted_data sorted(data, keylambda x: x[1], reverseTrue) print(sorted_data)这里的lambda x: x[1]是匿名函数意思是“取元素的第二个字段作为排序依据”。这种写法在处理大模型评估结果、按分数排序候选答案时很常用。2.3 练习题2英文句子词频统计第二道题是给定一段英文文本统计每个单词出现的次数按频次从高到低输出。这个题目在自然语言处理领域太常见了。大模型训练前的语料分析、评估生成结果的词频分布、关键词提取等场景都是类似的套路。核心知识点包括字符串清洗、大小写转换、字符串分割、字典统计、排序输出。基础解法text Hello world hello Python python AI Hello # 1. 统一转小写避免大小写造成的统计误差 text_lower text.lower() # 2. 分割单词 words text_lower.split() # 3. 用字典统计词频 word_count {} for word in words: if word in word_count: word_count[word] 1 else: word_count[word] 1 # 4. 按词频降序输出 sorted_words sorted(word_count.items(), keylambda x: x[1], reverseTrue) for word, count in sorted_words: print(f{word}: {count})运行结果hello: 3 python: 2 world: 1 ai: 1这道题的关键点有三个。第一统一转小写或者不转小写要根据业务需求决定第二split()默认按空白字符分割如果文本里有标点符号还需要额外清洗第三字典统计时if word in word_count的判断是最直观的写法但还可以用collections.defaultdict和collections.Counter简化。进阶用collections模块简化from collections import Counter text Hello world hello Python python AI Hello text_lower text.lower() words text_lower.split() word_count Counter(words) for word, count in word_count.most_common(): print(f{word}: {count})Counter是Python内置的一个计数器工具它本身就是dict的子类专门用来做统计类任务。most_common()方法直接按频次从高到低排序返回一行代码代替了手动排序的步骤。我第一次用Counter的时候感觉很惊艳因为它把“统计加排序”这个高频需求封装得太好了。不过我也踩过一个坑如果单词数量特别多most_common()默认对所有词排序如果想只取前10个高频词要传参数比如word_count.most_common(10)。不传参数时它会对全部词排序那部分性能开销可能是多余的。进阶加入标点清洗真实文本里会有逗号、句号、引号等标点直接split()会把hello,和hello当成两个词。清洗方法是用字符串的strip()或正则表达式去掉标点import re from collections import Counter text Hello, world! Hello Python. Python, AI! Hello? # 提取所有字母和数字去掉标点符号 words re.findall(r\b\w\b, text.lower()) word_count Counter(words) for word, count in word_count.most_common(): print(f{word}: {count})\b\w\b是正则表达式里的一个经典模式含义是“匹配由字母、数字组成的完整单词”标点符号会被自动忽略。这个写法在词频统计里几乎是标准答案。2.4 两道题背后的大模型应用场景光做题可能会觉得“这跟大模型有什么关系”我举两个实际场景你就明白了。第一个场景数据去重漏斗。在大模型训练前企业通常会有海量原始语料这些语料往往存在大量重复内容比如网络爬虫抓取的页面就有很高的重复率。去掉重复文本能减少训练成本也能避免模型因为重复数据过多而产生记忆偏差。爬虫采集的文本就是放在列表里去重用到的就是集合或者“集合加列表”的技巧。海量数据下还会用更复杂的MinHash等算法做近似去重但理解列表、集合的差异是入门第一步。第二个场景训练数据Token分布分析。你在准备大模型微调数据时需要快速了解语料里哪些词出现频率高、哪些低频词是稀疏的这直接关系到词表的构建和训练策略的调整。统计词频用的就是字典或Counter。我实际跑过一个小型语料统计几万行文本用基础解法几秒钟出结果但用Counter写法更简洁不容易出 bug。所以说数据结构不是孤立的考试题它是大模型工作流里那些“不起眼但必不可少”的基础设施。3. 实操过程与核心环节实现3.1 环境准备与调试工具在做题之前先把环境准备好。我目前使用的是Python 3.10版本编辑器是VSCode配合Python插件。如果你还没装Python建议直接去官网下载最新稳定版安装时勾选“Add Python to PATH”否则命令行用不了python命令。VSCode调试Python代码时我习惯在代码里写几个print来打点先肉眼确认中间结果对不对再继续往下写。今天这两道题都有中间步骤适合练习“分段验证”的调试习惯。3.2 练习题1的完整实操记录我先把题目写完整方便你复制运行# 题目列表去重与排序 # 输入示例 numbers [5, 3, 8, 3, 1, 5, 8, 9, 2, 1] # 解法1集合去重排序输出 result1 sorted(set(numbers)) print(解法1结果:, result1) # 解法2保留原顺序去重 seen set() result2 [] for num in numbers: if num not in seen: seen.add(num) result2.append(num) print(解法2结果:, result2) # 解法3自定义排序规则示例按数字的绝对值排序 numbers_with_negative [-5, 3, -8, 3, -1, 5, 8, -9, 2, 1] result3 sorted(set(numbers_with_negative), keyabs) print(解法3结果:, result3)运行结果解法1结果: [1, 2, 3, 5, 8, 9] 解法2结果: [5, 3, 8, 1, 9, 2] 解法3结果: [1, 2, -1, 3, 5, -5, -8, 8, -9]解法3用了keyabs意思是对每个元素先取绝对值再按绝对值排序所以负数不会按原值排在最前而是按它的绝对值排。这个技巧在处理带符号的评分数据、排序距离值时很实用。3.3 练习题2的完整实操记录我增加了一点难度用一段带标点、带大小写的文本from collections import Counter import re text Machine learning is a subset of artificial intelligence. It focuses on building systems that learn from data. Deep learning is a subset of machine learning. Artificial intelligence and machine learning are changing the world. # 1. 文本清洗小写 提取单词 words re.findall(r\b\w\b, text.lower()) # 2. 基础字典统计 word_count {} for word in words: if word in word_count: word_count[word] 1 else: word_count[word] 1 # 3. 使用 Counter 统计和上面的字典结果一致 counter_result Counter(words) print(字典统计结果:) for word, count in sorted(word_count.items(), keylambda x: x[1], reverseTrue): print(f {word}: {count}) print(\nCounter 统计结果(前5个):) for word, count in counter_result.most_common(5): print(f {word}: {count})运行结果字典统计结果: learning: 4 is: 3 machine: 3 a: 3 of: 3 data: 2 subset: 2 intelligence: 1 ... Counter 统计结果(前5个): learning: 4 is: 3 machine: 3 a: 3 of: 3这个题目帮你建立了“文本统计”的标准模板清洗 - 分割 - 统计 - 排序。后面做大模型相关的任何文本分析基本都在这个框架里打转。3.4 动手试一下扩展练习题做完了我建议你再扩展练习一下这样能加深对数据结构的理解把练习题2的文本换成中文用jieba库分词后再统计词频把练习题1的列表换成包含元组的列表按元组多个字段排序用defaultdict重写练习题2的字典统计部分对比代码简洁度这三个扩展练习每个都对应真实业务里的常见需求。特别是第一个大模型处理中文语料时分词是必经之路提前熟悉中文分词工具后面做数据清洗会顺手很多。4. 常见问题与排查技巧实录4.1 集合去重后顺序变了这个问题我前面提过但它是新手最容易踩的坑值得再强调一次。set的本质是哈希表它天生无序。如果你写代码时依赖“去重后顺序还跟原列表一样”结果顺序是乱的那就要改用“集合加列表”的双重结构方案。一个小经验先确定需求是“只要去重”还是“去重且保序”。如果是后者直接写循环别绕路。4.2 字典统计时 KeyError 的隐患在字典统计的循环里如果先判断if word in word_count再更新就没问题。但如果不判断直接写word_count[word] 1当word第一次出现时字典里没有这个键就会抛出KeyError。这是字典操作最经典的错误。解决方案有三种用if word in word_count判断后更新用dict.get()方法带默认值用collections.defaultdict第三种写法最优雅from collections import defaultdict word_count defaultdict(int) for word in words: word_count[word] 1defaultdict在键不存在时会自动调用int()生成默认值0然后执行加1操作。这样省掉了显式判断的代码。我在处理大规模语料时经常用这个写法减少判断分支代码也更清晰。4.3 sorted 是返回新列表sort 是原地修改这个坑我踩过一次。写脚本时我定义了一个列表先用sorted()排序但没把返回值赋给新变量接着对原列表操作结果发现原列表根本没变。原因就是sorted()不修改原列表它返回一个排序后的新列表而list.sort()是原地修改。如果后续流程还要用原始顺序的数据用sorted()如果原始顺序无所谓用sort()更省内存。具体用哪个要看业务上下文。4.4 词频统计时标点符号导致的误差用text.split()统计词频时hello,和hello会被当成两个不同的单词。这个问题我已经在前面用正则表达式re.findall(r\b\w\b, text.lower())解决了。这里提醒一句正则表达式里的\b是单词边界\w表示字母、数字、下划线。如果文本中包含连字符比如state-of-the-art这个模式会把整个短语切成三个词。具体业务里怎么切要根据需求调整模式。4.5 大文本处理时的性能注意事项当文本量特别大时比如几GB的语料把整个文本读进内存再做统计可能会内存溢出。这时候建议分块读取文件逐块统计最后合并结果。这里有个小技巧Counter对象可以直接相加所以分块统计后能直接合并。from collections import Counter total_count Counter() for chunk in read_file_by_chunks(large_corpus.txt): words re.findall(r\b\w\b, chunk.lower()) total_count Counter(words)total_count Counter(words)就是利用了Counter的加法合并能力。这个技巧在真实大数据场景下非常实用面试里也算加分项。4.6 快速判断列表里所有元素是否唯一这虽然不是今天的题但实际工作里也经常遇到。一行代码就能判断def has_duplicates(items): return len(items) ! len(set(items)) print(has_duplicates([1, 2, 3, 4])) # False print(has_duplicates([1, 2, 3, 3])) # True原理是列表里的重复元素在集合里只剩一个所以长度会变小。这个写法时间复杂度是O(n)比双重循环判断快得多。5. 学习大模型的节奏建议与心得在第9天这个节点回顾一下整个学习安排前8天接触的是大模型的高层概念和基础实操现在退回到Python数据结构补基础。很多人会问这样是不是绕远了其实不是。数据结构就是地基地基打得越扎实后面建楼才越稳。我现在最大的体会是大模型相关的代码框架往往封装得很高级但你迟早要深入到数据处理那一层。那一层代码不会像教程代码那么干净它满是列表推导式、字典操作、集合去重、排序、状态标记。如果数据结构不熟练光调试就能磨掉半天的激情。今天这两道题我反复做了三遍第一遍是直接写第二遍是尝试用不同的数据结构实现第三遍是把代码执行过程用注释拆解给自己听。三遍下来列表和字典的底层逻辑终于有了更直观的感觉。后面我打算继续把排序、栈、队列、链表这些基础数据结构都过一遍然后再回到大模型的代码实战边用边巩固。如果你也在学大模型我建议基础阶段别急着赶路花点时间把Python这块磨扎实这个时间花得很值。