机器学习常需处理的数据集规模庞大,无法一次性载入可用内存。若尝试整批载入,可能导致MemoryError异常或因内存交换造成严重的性能下降。此时,Python的生成器便能发挥作用。它们提供了一种惰性求值的机制,按需逐项处理数据,从而大幅降低内存消耗。您可能已经熟悉使用yield关键字的基本生成器函数。生成器函数并非返回一个完整的列表,而是每次生成一个值,并在两次生成之间暂停其执行状态。# 示例:惰性读取大文件 def read_large_file(file_path): """逐行从文件中生成内容。""" try: with open(file_path, 'r') as f: for line in f: yield line.strip() # 处理并生成一行 except FileNotFoundError: print(f"Error: File not found at {file_path}") # 内存中不创建大型列表 # 用法: # 假设 'large_dataset.csv' 存在 # lines_generator = read_large_file('large_dataset.csv') # print(next(lines_generator)) # 获取第一行 # print(next(lines_generator)) # 获取第二行 # ... 处理剩余行这种方法对于处理机器学习中常见的大文件(如CSV文件、日志文件或文本语料库)非常重要。无论文件大小如何,内存占用都保持在最低水平,因为在任何给定时刻,内存中通常只保留一行(或一小块缓冲区)。生成器表达式对于更简单的情况,生成器表达式提供了一种更简洁的语法,类似于列表推导式,但使用圆括号而非方括号。# 列表推导式(将所有内容加载到内存中) squares_list = [x*x for x in range(1000000)] # 可能出现 MemoryError # 生成器表达式(内存高效) squares_gen = (x*x for x in range(1000000)) # 创建一个生成器对象 # print(sum(squares_gen)) # 惰性地消费生成器生成器表达式会创建迭代器对象,这些对象按需生成值,与生成器函数一样。当您需要一个可迭代对象但又不想首先实例化整个序列时,它们在函数调用中尤其有用。链式生成器用于构建数据流机器学习数据流的真正优势在于链式连接生成器。数据处理数据流中的每一步都可以作为一个生成器来实现,它从前一个生成器获取数据,进行处理,然后生成结果。这创建了一个内存高效的流式数据流。考虑一个简单的文本预处理数据流:读取行、转换为小写、分词。import re def read_lines(file_path): """从文件中读取行的生成器。""" with open(file_path, 'r') as f: for line in f: yield line def lowercase_lines(lines_gen): """将行转换为小写的生成器。""" for line in lines_gen: yield line.lower() def tokenize_lines(lines_gen): """将行分词的生成器。""" for line in lines_gen: # 基于非字母数字字符的简单分词 yield re.findall(r'\b\w+\b', line) # --- 构建数据流 --- # 假设 'my_text_data.txt' 存在 file_path = 'my_text_data.txt' lines = read_lines(file_path) lower_lines = lowercase_lines(lines) tokenized_stream = tokenize_lines(lower_lines) # --- 消费数据流 --- # 处理前5行分词后的内容 count = 0 for tokens in tokenized_stream: print(tokens) count += 1 if count >= 5: break # 随着数据流经数据流,内存使用量保持在较低水平。在这个示例中,整个文件从未一次性载入。一行数据被读取、转换为小写、分词,然后处理下一行。数据像水流过管道一样流经整个数据流,无需大型存储器(内存)来保存中间结果。使用 yield from 委派当处理嵌套的可迭代对象或构建复杂的生成器逻辑时,yield from表达式(Python 3.3引入)能简化代码。它将迭代从当前生成器委派给子生成器或可迭代对象。def process_batch(batch_data): """处理单个数据批次(例如,解析)。""" # 示例:模拟处理,生成单个项 for item in batch_data: processed_item = f"processed_{item}" yield processed_item def batch_generator(data_source, batch_size): """按批次生成数据。""" batch = [] for item in data_source: batch.append(item) if len(batch) == batch_size: yield batch batch = [] if batch: # 生成任何剩余的项 yield batch def process_all_items(data_source, batch_size): """使用批处理和 yield from 处理所有项。""" batches = batch_generator(data_source, batch_size) for batch in batches: # 不使用嵌套循环: # for item in process_batch(batch): # yield item # 使用 yield from: yield from process_batch(batch) # 委派给 process_batch # 使用示例 source_data = range(15) # 模拟数据源 processed_stream = process_all_items(source_data, batch_size=5) for processed_item in processed_stream: print(processed_item)yield from有效地扁平化了迭代过程,使得process_all_items生成器能够直接从process_batch生成项,而无需显式嵌套循环。这提高了复杂数据流的可读性和可维护性,在这种数据流中,一个生成器需要生成由另一个生成器产生的所有值。生成器作为协程(简要概述)尽管我们在此主要侧重于内存高效的数据生成和处理,但值得注意的是,生成器也可以使用send()方法接收数据。当以这种方式使用时,它们充当协程。这允许创建更复杂的数据流,其中生成器的行为可以由外部输入进行修改。def data_consumer(): """一个接收数据的简单协程。""" print("消费者准备接收...") while True: data = yield # 在此处暂停,等待 send() 调用 print(f"Received: {data}") consumer = data_consumer() next(consumer) # 启动协程(推进到第一个 yield) consumer.send("First item") consumer.send("Second item") # consumer.close() # 关闭协程虽然功能强大,但构建完整的基于协程的数据流通常涉及更多复杂性(例如,错误处理、关闭)。对于许多机器学习数据预处理任务,链式生成器模式提供了足够的能力且开销较低。我们将在第5章学习基于这些思路构建的asyncio异步编程。总结高级生成器技术是编写内存高效的机器学习Python代码的重要工具。通过凭借惰性求值、生成器表达式、链式连接和yield from,您可以构建复杂的数据处理数据流,能够处理大量数据集而不耗尽系统内存。这在机器学习工作流的初始阶段尤其重要,包括数据加载、清洗和特征提取。请记住,生成器一次只生成一个值,这使得它们非常适合将数据流式传输通过一系列处理步骤。