在做机器翻译中,语料库很大,每次都要遍历文件,效率极低。正确的做法是预先将文件全部读入内存,但这对大语料不现实。

DeepSeek提供有三种思路来实现

  • 方法A(推荐):使用线性访问。确保你的数据集文件是按行顺序存储的,这样操作系统会有缓存,速度会快很多。但shuffle时还是会跳转。

  • 方法B(最佳):将语料库预处理成多个小文件(例如,每100000个句子一个文件),然后你的Dataset根据idx计算出在哪个小文件的哪一行,这样加载更快。或者预处理成内存映射(mmap)格式或数据库(如SQLite)。

  • 方法C(通用):在__init__中,只把每一行的起始偏移量(file pointer offset)记录到一个列表里。在__getitem__中,使用seek(offset)直接定位到行,然后读取一行。这避免了遍历,效率很高。

我是通过方法C来实现的,具体代码如下:

# 偏移读取
class EfficientTranslationDataset(torch.utils.data.Dataset):
    def __init__(self, file_path):
        self.file_path = file_path
        self.file_offsets = self._build_offset_index(file_path)
        self.num_samples = len(self.file_offsets)
        with open(file_path, 'r', encoding='utf-8') as f:
            self.num_samples = sum(1 for _ in f)

    def __len__(self):
        return self.num_samples


    def __getitem__(self, idx):
        en_words_list = []
        zh_words_list = []
        line = self._read_line_by_offset(self.file_path, self.file_offsets[idx])
        line_arr = line.strip().split('\t')
        if len(line_arr) >= 2:
            line_en = line_arr[0].strip()

            for word in line_en.split(' '):
                word = word.replace('.', '').replace(',', '').lower()
                if word:
                    add_word = remove_punctuation_at_begin_and_end(word)
                    if contains_only_lowercase_english(add_word):
                        if not (is_all_same_characters(add_word) and len(add_word) != 1):
                            if len(add_word) != 0:
                                en_words_list.append(add_word)

            line_zh = line_arr[1].strip()
            chinese_words = list(jieba.lcut(line_zh.strip()))
            zh_words_list = [word for word in chinese_words if is_chinese_only(word) and word.strip()]
        en_len = len(en_words_list)
        zh_len = len(zh_words_list)
        return en_words_list, en_len, zh_words_list,  zh_len, idx

    def _build_offset_index(self, file_path):
        """
        构建文件偏移量索引列表。
        返回一个列表,其中每个元素是文件中一行的起始字节偏移量。
        """
        offsets = []
        with open(file_path, 'rb') as f: # 用二进制模式打开以正确计算字节偏移
            offsets.append(0) # 第一行的偏移量总是0
            while True:
                chunk = f.read(8192) # 每次读取8KB的块,提高效率
                if not chunk:
                    break
                # 在块中查找换行符的位置
                # 注意:换行符在不同系统中可能不同,这里用\n
                # 对于Windows生成的\r\n文件,可能需要调整
                start = 0
                while True:
                    pos = chunk.find(b'\n', start)
                    if pos == -1:
                        break
                    # 计算绝对偏移量:文件当前位置 - 块长度 + 换行符在块中的位置 + 1
                    # +1 是为了指向下一行的开始
                    offsets.append(f.tell() - len(chunk) + pos + 1)
                    start = pos + 1
        return offsets

    def _read_line_by_offset(self, file_path, offset):
        """
        根据给定的字节偏移量,从文件中读取一行。
        """
        with open(file_path, 'r', encoding='utf-8') as f:
            f.seek(offset) # 跳到指定偏移量
            line = f.readline().strip() # 读取一行
            return line

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐