Python训练脚本内存优化方案

这是什么情况啊?C:\Users\15713\Desktop\前端和项目\riiid-test-answer-prediction>python train.py Traceback (most recent call last): File "C:\Users\15713\Desktop\前端和项目\riiid-test-answer-prediction\train.py", line 17, in <module> train = pd.read_csv('train.csv', dtype=dtypes) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py", line 1026, in read_csv return _read(filepath_or_buffer, kwds) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py", line 626, in _read return parser.read(nrows) ^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py", line 1968, in read df = DataFrame( ^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\frame.py", line 778, in __init__ mgr = dict_to_mgr(data, index, columns, dtype=dtype, copy=copy, typ=manager) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\internals\construction.py", line 443, in dict_to_mgr arrays = Series(data, index=columns, dtype=object) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\series.py", line 537, in __init__ data, index = self._init_dict(data, index, dtype) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\series.py", line 651, in _init_dict s = Series(values, index=keys, dtype=dtype) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\series.py", line 584, in __init__ data = sanitize_array(data, index, dtype, copy) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\construction.py", line 651, in sanitize_array subarr = _try_cast(data, dtype, copy) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\construction.py", line 793, in _try_cast subarr = construct_1d_object_array_from_listlike(arr) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\dtypes\cast.py", line 1601, in construct_1d_object_array_from_listlike result[:] = values ~~~~~~^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\series.py", line 1031, in __array__ arr = np.asarray(values, dtype=dtype) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\arrays\masked.py", line 603, in __array__ return self.to_numpy(dtype=dtype) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Software\anaconda3\Lib\site-packages\pandas\core\arrays\masked.py", line 521, in to_numpy data = self._data.astype(dtype) ^^^^^^^^^^^^^^^^^^^^^^^^ numpy.core._exceptions._ArrayMemoryError: Unable to allocate 772. MiB for an array with shape (101230332,) and data type object,训练代码如下:import pandas as pd import numpy as np # 加载数据时指定类型节省内存 dtypes = { 'row_id': 'int64', 'timestamp': 'int64', 'user_id': 'int32', 'content_id': 'int16', 'content_type_id': 'int8', 'task_container_id': 'int16', 'user_answer': 'int8', 'answered_correctly': 'int8', 'prior_question_elapsed_time': 'float32', 'prior_question_had_explanation': 'boolean' } train = pd.read_csv('train.csv', dtype=dtypes) # 过滤掉讲座事件(只保留问题) train = train[train['content_type_id'] == 0].reset_index(drop=True) # 合并题目元数据 questions = pd.read_csv('questions.csv') train = train.merge(questions, left_on='content_id', right_on='question_id', how='left') # 处理空值 train['prior_question_elapsed_time'] = train['prior_question_elapsed_time'].fillna( train['prior_question_elapsed_time'].median()) train['prior_question_had_explanation'] = train['prior_question_had_explanation'].fillna(False) # 标签多热编码 tags_split = train['tags'].str.split(' ', expand=True) tags_dummies = pd.get_dummies(tags_split.stack()).groupby(level=0).max() train = pd.concat([train, tags_dummies], axis=1) # 用户累计正确率 user_correct = train.groupby('user_id')['answered_correctly'].agg(['mean', 'count']) user_correct.columns = ['user_hist_correct_rate', 'user_hist_question_count'] train = train.merge(user_correct, on='user_id', how='left') # 用户最近20题正确率(滑动窗口) train['user_recent_20_correct'] = train.groupby('user_id')['answered_correctly'].transform( lambda x: x.rolling(20, min_periods=1).mean() ) # 题目全局正确率 question_diff = train.groupby('question_id')['answered_correctly'].mean().reset_index() question_diff.columns = ['question_id', 'question_difficulty'] train = train.merge(question_diff, on='question_id', how='left') # 题目在用户所属分组的难度(如TOEIC part) part_diff = train.groupby('part')['answered_correctly'].mean().reset_index() part_diff.columns = ['part', 'part_avg_correct'] train = train.merge(part_diff, on='part', how='left') # 用户答题间隔时间变化率 train['time_diff_rate'] = train.groupby('user_id')['timestamp'].diff().fillna(0) / 1e3 # 转换为秒 # 用户当前任务容器与上次的时间差 train['task_container_gap'] = train.groupby('user_id')['task_container_id'].diff().fillna(0) import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit # 特征列选择 features = [ 'user_hist_correct_rate', 'user_hist_question_count', 'user_recent_20_correct', 'question_difficulty', 'part_avg_correct', 'prior_question_elapsed_time', 'prior_question_had_explanation', 'time_diff_rate', 'task_container_gap' ] + list(tags_dummies.columns) # 时间序列交叉验证 tscv = TimeSeriesSplit(n_splits=5) X = train[features] y = train['answered_correctly'] for fold, (train_idx, val_idx) in enumerate(tscv.split(X, y)): X_train, y_train = X.iloc[train_idx], y.iloc[train_idx] X_val, y_val = X.iloc[val_idx], y.iloc[val_idx] # 数据集转换 dtrain = lgb.Dataset(X_train, label=y_train) dval = lgb.Dataset(X_val, label=y_val) # 参数设置(优化NPU兼容性) # params = { # 'objective': 'binary', # 'metric': 'auc', # 'device': 'cpu', # 若使用英特尔NPU,需安装oneAPI优化版本 # 'num_leaves': 31, # 'learning_rate': 0.05, # 'feature_fraction': 0.8 # } # 定义 LightGBM 模型的参数,启用 GPU params = { 'objective': 'binary', 'metric': 'auc', 'device': 'gpu', # 启用 GPU 'gpu_device_id': 0, # 0 为 GTX 1050 的设备 ID,通常是 0 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.8 } # 训练 model = lgb.train(params, dtrain, valid_sets=[dval], callbacks=[lgb.log_evaluation(100)]) # 保存模型 model.save_model(f'lgbm_fold{fold}.txt')

嗯,用户遇到了一个内存错误,当运行他们的训练脚本时,出现了numpy.core._exceptions._ArrayMemoryError,提示无法分配772 MiB的数组。他们的代码是用来训练一个LightGBM模型的,处理Riiid测试答案预测的数据。我需要分析可能的原因并提供解决方案。

首先,错误发生在读取train.csv的时候,具体是pandas在读取数据时尝试分配一个大型数组失败。用户已经指定了dtypes来节省内存,但可能数据量太大,超出了可用内存。我需要看看他们处理数据的步骤是否有进一步优化的空间。

看一下他们的代码流程:加载数据,过滤讲座事件,合并题目元数据,处理空值,标签的多热编码,用户累计正确率,用户最近20题正确率,题目全局正确率,用户答题间隔时间等特征工程,然后进行LightGBM训练。

可能的问题点:在读取train.csv时,数据量太大,导致内存不足。即使用户指定了dtypes,如果原始数据文件train.csv本身很大,比如几GB甚至更大,加载到内存中可能会占用过多内存,尤其是在进行特征工程时,特别是多热编码部分,会生成很多新列,进一步增加内存使用。

可能的解决方案包括:

1. 使用更节省内存的数据类型,检查是否有列可以进一步优化,比如将某些int64转为更小的类型。例如,row_id可能不需要int64,可以试试int32或者更小的类型。但用户已经指定了dtypes,可能需要检查是否还有优化的空间。

2. 分块读取数据。使用pandas的chunksize参数,分批次处理数据,特别是特征工程部分,可能需要在每个块上处理,然后合并结果。但特征工程中有一些需要全局统计的操作,比如用户累计正确率,可能需要遍历所有数据,这时候分块处理可能会有挑战。

3. 减少内存使用的步骤,比如多热编码部分。如果tags_split产生很多列,可能会导致内存爆炸。例如,如果tags是很多不同的标签,每个标签都生成一个虚拟变量,那么tags_dummies的列数可能非常多,占用大量内存。这时候可能需要限制标签的数量,或者使用更高效的方法,如稀疏矩阵。

4. 使用更高效的数据格式,比如将数据转换为parquet格式,或者使用feather,可能会减少加载时的内存占用,但用户可能需要先转换数据,这可能对当前情况不太直接。

5. 使用Dask或者Vaex这样的库来处理大数据,但需要修改代码结构,可能会比较复杂。

6. 增加系统的虚拟内存,或者关闭其他占用内存的程序,释放更多内存。

具体来看用户的代码,在标签多热编码部分:

tags_split = train['tags'].str.split(' ', expand=True)

tags_dummies = pd.get_dummies(tags_split.stack()).groupby(level=0).max()

train = pd.concat([train, tags_dummies], axis=1)

这里,假设每个tags字段有多个标签,split之后每一行可能有多个标签,然后进行stack和get_dummies,这会生成很多列。例如,如果有成千上万个不同的标签,那么tags_dummies的列数会非常大,导致内存不足。例如,假设每个标签是一个整数,如果有1000个不同的标签,那么这里会增加1000列,每列是布尔类型。这会导致内存使用量激增。

这时候可以考虑是否有必要使用多热编码,或者是否有其他方式处理,比如将标签转换为数值特征,或者使用稀疏矩阵来存储。例如,使用scipy的稀疏矩阵,而不是pandas的DataFrame,可以节省内存。

另外,在用户累计正确率和题目全局正确率的计算中,可能也需要遍历整个数据集,如果数据太大,这些groupby操作也会占用大量内存。这时候可以考虑分块处理,或者使用更高效的计算方式。

另一个方面,用户可能在特征工程过程中生成了很多中间变量,比如合并后的questions,用户统计特征等。这些步骤可能会增加内存的使用,尤其是在合并后没有删除原始不需要的列的情况下。

可能的优化步骤:

1. 检查tags的数量,如果太多,考虑减少或分桶。或者使用稀疏格式存储多热编码的结果。例如,使用scipy.sparse的csr_matrix,然后转换为稀疏DataFrame(如果pandas支持的话,但pandas的稀疏DataFrame可能不如普通DataFrame节省内存)。或者,使用更高效的数据类型,比如uint8,如果每个dummy变量只有0和1,那么使用uint8会比bool更节省空间?不过pandas的get_dummies默认生成的是uint8类型,可能已经优化了。

或者,对于多热编码,如果标签数量太多,可能需要降维,比如使用PCA或者哈希编码,但可能影响模型效果。

2. 在读取数据时,使用更高效的方法。例如,使用usecols参数只加载需要的列,但用户可能需要所有列。或者,在读取时进行过滤,比如提前过滤掉content_type_id为0的行,这样在读取时就减少数据量。例如,在pd.read_csv中使用chunksize,逐块读取并过滤,然后合并。这样可以减少加载到内存中的数据量。

例如:

chunks = pd.read_csv('train.csv', dtype=dtypes, chunksize=100000)

filtered_chunks = []

for chunk in chunks:

filtered_chunk = chunk[chunk['content_type_id'] == 0]

filtered_chunks.append(filtered_chunk)

train = pd.concat(filtered_chunks, ignore_index=True)

这样在读取时逐块过滤,可以避免一次性加载全部数据到内存,减少内存峰值。

3. 对于特征工程中的groupby操作,比如用户累计正确率等,可以考虑使用更节省内存的方式,或者使用分块计算。例如,先对用户进行分组,分块处理每个用户的数据,累计统计。

4. 在每一步操作后,及时删除不再需要的中间变量,并用gc.collect()回收内存。例如,在合并questions后,如果不需要原始的questions DataFrame,可以删除它,或者使用inplace参数。

5. 将某些列转换为分类类型(category dtype),特别是像tags这样的列,如果有重复的字符串,转换为category可以节省内存。但需要注意,如果tags是空格分隔的多个标签,可能需要先拆分处理。

6. 使用更小的数值类型。例如,检查各个列的最大最小值,看是否可以将某些int32转为int16或者int8。例如,用户可能已经做了很多优化,但row_id可能被设置为int64,如果不需要这么大的范围,可以转成更小的类型。比如,如果row_id的最大值小于2^31,可以用int32。

7. 使用modin或pandas-on-ray等支持并行处理的库,但可能对内存帮助不大,反而可能增加开销。

回到错误信息,错误出现在读取train.csv的时候,用户可能内存不足以加载整个文件。即使指定了dtype,如果文件太大,比如超过可用内存,就会报错。这时候需要减少加载时的内存占用。

可能的解决方案顺序:

1. 在读取数据时进行过滤,使用chunksize参数,逐块读取并过滤掉content_type_id != 0的行,这样可以减少加载到内存中的数据量。例如:

chunks = pd.read_csv('train.csv', dtype=dtypes, chunksize=100000)

train_chunks = []

for chunk in chunks:

chunk = chunk[chunk['content_type_id'] == 0]

train_chunks.append(chunk)

train = pd.concat(train_chunks, ignore_index=True)

这样,在读取时逐块处理,可以避免一次性加载全部数据,特别是当原始数据包含大量content_type_id不为0的行时,过滤后数据量可能减少很多。

2. 检查tags的处理。如果tags的数量很多,生成的多热编码会导致内存问题。可以考虑将tags转换为更节省内存的方式。例如,如果tags是数值型的,可以分桶或者统计每个tag的出现次数,而不是进行多热编码。或者,使用稀疏矩阵:

from scipy.sparse import csr_matrix

tags_split = train['tags'].str.split(' ', expand=True)

stacked = tags_split.stack()

dummies = pd.get_dummies(stacked, sparse=True)

tags_dummies = dummies.groupby(level=0).max()

tags_sparse = csr_matrix(tags_dummies.values)

# 然后,将tags_sparse与其他特征合并可能需要转换其他特征为稀疏格式,这可能比较复杂。

或者,使用category类型:

tags_list = train['tags'].str.split(' ')

unique_tags = set(tag for sublist in tags_list.dropna() for tag in sublist)

for tag in unique_tags:

train[f'tag_{tag}'] = train['tags'].apply(lambda x: 1 if str(tag) in x.split() else 0)

但这种方法可能同样占用较多内存,如果tag数量很大。

另一种办法是统计每个用户的tag出现频率,或者每个问题的tag组合作为一个特征,但可能需要更复杂的处理。

3. 减少数据量。如果数据量实在太大,可以考虑对数据进行采样,比如随机采样部分用户或部分记录进行训练,虽然可能影响模型效果,但可以解决内存问题。

4. 使用Dask来处理数据。Dask可以处理超过内存的数据集,通过并行和分块处理。例如:

import dask.dataframe as dd

train = dd.read_csv('train.csv', dtype=dtypes)

train = train[train['content_type_id'] == 0].compute()

但需要足够的磁盘空间和适当的分块设置,否则可能在compute()时仍然需要大量内存。

5. 优化其他特征工程步骤的内存使用:

- 在合并questions后,删除原始的questions DataFrame。

- 将不需要的列及时删除,例如在合并后,如果不需要某些列,使用drop方法。

- 在分组统计时,使用更高效的方法,例如transform可能生成副本,考虑是否可以用更高效的方式计算。

- 将某些对象类型的列转换为category类型,比如'prior_question_had_explanation'已经被指定为boolean,可能已经是优化过的。

6. 检查系统内存是否足够。用户是否在内存较小的机器上运行?例如,如果可用内存只有8GB,而数据需要10GB,那么需要扩展内存或使用其他方法。

总结,最直接的解决方案可能是在读取数据时进行分块处理,并在读取时过滤掉不需要的行。这样可以有效减少内存使用。同时,检查tags的处理,避免生成过多的列,导致内存爆炸。如果必须处理大量标签,考虑使用稀疏矩阵或其他降维方法。

根据错误信息,你的程序在读取训练数据时因内存不足而崩溃。以下是分步解决方案:

1. 分块读取并过滤数据

python

复制

# 修改数据读取部分为分块读取
chunk_size = 100000  # 根据内存情况调整
chunks = pd.read_csv('train.csv', dtype=dtypes, chunksize=chunk_size)
filtered_chunks = []

for chunk in chunks:
    # 立即过滤不需要的行
    chunk = chunk[chunk['content_type_id'] == 0]
    # 立即删除原始列释放内存
    chunk.drop(columns=['content_type_id'], inplace=True)
    filtered_chunks.append(chunk)

train = pd.concat(filtered_chunks, ignore_index=True)

2. 优化标签处理(内存消耗最大的部分)

python

复制

# 使用稀疏矩阵处理标签
from scipy.sparse import csr_matrix

# 预处理tags列
tags_series = train['tags'].astype(str).str.split()
unique_tags = set(tag for sublist in tags_series for tag in sublist)

# 创建稀疏矩阵
row_indices = []
col_indices = []
for i, tags in enumerate(tags_series):
    for tag in tags:
        col_idx = list(unique_tags).index(tag)
        row_indices.append(i)
        col_indices.append(col_idx)

tags_sparse = csr_matrix(([1]*len(row_indices), (row_indices, col_indices))

# 将稀疏矩阵转换为DataFrame(可选)
tags_df = pd.DataFrame.sparse.from_spmatrix(tags_sparse, columns=unique_tags)
train = pd.concat([train, tags_df], axis=1)

3. 优化内存使用的其他技巧

python

复制

# 在每一步操作后释放内存
import gc

# 合并题目元数据后
del questions
gc.collect()

# 处理空值后
train['prior_question_elapsed_time'] = train['prior_question_elapsed_time'].astype('float32')
train['prior_question_had_explanation'] = train['prior_question_had_explanation'].astype('bool')

# 删除中间变量
del tags_series, tags_sparse, tags_df
gc.collect()

4. 特征工程优化

python

复制

# 使用更高效的分组统计方法
# 用户累计正确率(使用transform避免创建中间对象)
train['user_hist_correct_rate'] = train.groupby('user_id')['answered_correctly'].transform('mean')
train['user_hist_question_count'] = train.groupby('user_id')['answered_correctly'].transform('count')

# 使用更高效的时间差计算
train['task_container_gap'] = train.groupby('user_id')['task_container_id'].diff().astype('int16')

5. 最终内存优化

python

复制

# 转换所有可能列的数值类型
float_cols = train.select_dtypes(include=['float64']).columns
train[float_cols] = train[float_cols].astype('float32')

int_cols = train.select_dtypes(include=['int64']).columns
train[int_cols] = train[int_cols].astype('int32')

# 删除原始标签列
train.drop(columns=['tags'], inplace=True)

补充建议:

  1. 使用memory_profiler分析内存使用

  2. 尝试将数据转换为parquet格式后再读取

  3. 如果GPU显存不足,改回CPU训练:

python

复制

params = {
    'objective': 'binary',
    'metric': 'auc',
    'device': 'cpu',  # 使用CPU
    'num_leaves': 31,
    'learning_rate': 0.05,
    'feature_fraction': 0.8
}
  1. 考虑使用Dask处理超大数据集:

python

复制

import dask.dataframe as dd

train = dd.read_csv('train.csv', dtype=dtypes)
train = train[train['content_type_id'] == 0]
train = train.compute()  # 转换为pandas DataFrame

这些修改应该能显著降低内存使用量。如果仍然遇到内存问题,可以考虑:

  1. 使用更强大的云服务器

  2. 对数据进行降采样

  3. 使用特征选择减少特征数量

  4. 使用更紧凑的模型架构(如神经网络嵌入)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值