<think>我们遇到了内存不足的问题,特别是在读取一些CSV文件时。这可能是由于文件过大或者文件格式问题导致pandas在解析时消耗过多内存。
解决方案:
1. 检查文件格式:确保CSV文件是标准格式(例如,没有多余的行或列,分隔符正确)。
2. 分批读取:如果文件很大,可以尝试分批读取(分块读取)。
3. 优化数据类型:在读取时指定每列的数据类型,减少内存占用。
4. 使用更高效的数据结构:例如,如果数据主要是数值,可以使用`dtype`参数指定为`np.float32`等。
5. 使用`chunksize`参数分块处理,但我们的代码中是一次性读取整个文件,如果单个文件太大,可能会导致内存不足。
然而,错误信息中提到的文件看起来都是上证科创板或创业板股票,可能这些文件格式有特殊之处?或者这些文件特别大?
另外,错误信息提示“Error tokenizing data. C error: out of memory”,这通常表示在解析CSV时遇到了问题,可能是文件中有不规则的行(例如,某些行有额外的列)。
我们可以在读取CSV时使用`error_bad_lines=False`来跳过有问题的行,但这可能会丢失数据。另外,我们可以使用`warnings`来记录问题,然后继续处理其他文件。
另一种可能是这些文件是空的或者格式不正确(比如二进制文件而不是文本文件)。我们可以先检查文件大小,如果文件大小为0则跳过。
修改方案:
1. 在读取文件前检查文件大小,跳过0字节的文件。
2. 使用更健壮的CSV读取方式,例如指定分隔符(虽然一般CSV是逗号分隔,但有时可能是其他分隔符)。
3. 使用`pd.read_csv`的`error_bad_lines=False`和`warn_bad_lines=True`,这样在遇到解析错误的行时会跳过并给出警告。
4. 如果文件确实很大,考虑分块读取并逐块处理(但我们的处理需要按股票代码进行,所以分块读取可能不太适用,因为每个文件是一个股票的数据)。
由于错误是“out of memory”,我们还需要考虑整体内存使用。如果同时加载很多文件,内存可能会不足。我们可以考虑在读取一个文件后立即进行必要的数据预处理,然后只保留需要的列,并立即释放不必要的内存。
修改`load_and_preprocess_data`方法中的文件读取部分:
具体修改如下:
- 检查文件大小,如果文件大小为0,跳过。
- 使用`error_bad_lines=False`跳过错误行,同时记录警告。
- 在读取文件后,立即选择需要的列,并删除不需要的列(如果有的话)。
- 如果文件读取失败,记录错误并跳过。
另外,我们也可以尝试使用`engine='python'`来避免C引擎的内存问题,但这样可能会慢一些。
由于错误信息中提到的文件都是上证科创板(688开头)和指数(880开头)等,可能是这些文件格式有差异?我们可以单独打印这些文件的前几行来检查。
但是,由于我们无法直接查看这些文件,我们只能通过代码来增强健壮性。
修改后的代码部分如下:
注意:我们之前已经指定了`dtype`为字符串,但可能这些文件有大量数据导致内存不足。因此,我们将尝试以下方法:
1. 使用`usecols`参数只读取需要的列(如果原始数据有很多列,而我们只需要部分列)。
2. 指定更节省内存的数据类型(例如,对于数值列,使用`np.float32`甚至`np.float16`,但要注意精度)。
但是,我们并不知道这些文件包含哪些列?我们假设它们都有相同的列('date','time','open','high','low','close','volume','amount'等)。
我们可以在读取时指定列,如果文件缺少某些列,则跳过或处理异常。
修改步骤:
由于错误是内存不足,我们可能需要减少同时加载的文件数量。但代码中是一个文件一个文件读取然后合并,所以每个文件是单独读取的。问题可能出现在单个文件就很大。
因此,我们将尝试:
- 使用`usecols`指定只读取我们需要的列(例如:['date','time','open','high','low','close','volume','amount'])。
- 在读取时指定更节省内存的数据类型(例如,数值列用`np.float32`)。
另外,我们还可以考虑在读取文件后立即删除不需要的列。
修改后的读取代码:
```python
# 定义需要的列和数据类型
usecols = ['date', 'time', 'open', 'high', 'low', 'close', 'volume', 'amount']
dtype_spec = {
'date': str,
'time': str,
'open': np.float32,
'high': np.float32,
'low': np.float32,
'close': np.float32,
'volume': np.float32,
'amount': np.float32
}
# 然后读取文件时:
df = pd.read_csv(file_path, dtype=dtype_spec, usecols=usecols,
error_bad_lines=False, warn_bad_lines=True)
```
但是,注意:如果文件没有列名,则需要使用`header`参数,并指定列名。我们假设这些文件都有列名。
另外,我们还可以在读取前检查文件大小,如果文件太大,我们可以考虑分块读取,但这里每个文件应该不会太大(因为单个股票的五分钟线数据不会太多)。如果数据量很大(比如很多年),我们可以考虑在读取时进行日期范围筛选,但我们的日期筛选是在读取之后。我们可以尝试在读取时使用`parse_dates`和`infer_datetime_format`来加速,但这不是主要问题。
我们修改`load_and_preprocess_data`方法中的文件读取部分。
同时,为了减少内存占用,我们可以在读取每个文件后立即进行日期时间合并和筛选,然后只保留需要的列。
另外,我们可以使用`gc.collect()`来手动触发垃圾回收。
但是,由于错误是C解析器内存不足,我们尝试使用Python引擎:
```python
df = pd.read_csv(file_path, dtype=dtype_spec, usecols=usecols,
error_bad_lines=False, warn_bad_lines=True,
engine='python')
```
使用Python引擎可能会慢,但可以避免C引擎的内存问题。
综合以上,我们修改代码。
注意:由于我们不知道具体哪些列,所以如果文件没有我们需要的列,就会出错。因此,我们还需要处理列缺失的情况。
我们可以在读取后检查列是否存在,如果不存在则跳过或记录错误。
修改后的`load_and_preprocess_data`方法中读取五分钟线数据的部分:
由于修改内容较多,下面给出完整的修改后函数。
另外,我们还可以考虑使用`try-except`来捕获内存错误,并跳过该文件。但是,内存错误可能发生在C层面,Python不一定能捕获。因此,我们主要依靠上述措施。
由于日线数据也可能有同样问题,我们对日线数据也做类似处理。
完整修改后的代码(仅展示修改部分):</think># 解决CSV文件加载内存溢出问题
您遇到的错误是由于在读取某些CSV文件时出现内存不足问题。这个问题通常发生在处理大型数据集或格式不正确的文件时。让我提供完整的解决方案:
```python
import os
import glob
import pandas as pd
import numpy as np
import joblib
import gc # 导入垃圾回收模块
from datetime import datetime, timedelta
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
import talib as ta
import warnings
import chardet # 用于检测文件编码
# 修复警告处理
warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning)
warnings.filterwarnings("ignore", category=RuntimeWarning)
warnings.filterwarnings("ignore", category=pd.errors.ParserWarning)
class StockPredictionSystem:
def __init__(self, config):
"""
初始化预测系统
:param config: 配置字典
"""
self.config = config
self.five_min_paths = {
'sz': config['five_min_sz_path'],
'sh': config['five_min_sh_path']
}
self.daily_paths = {
'sz': config['daily_sz_path'],
'sh': config['daily_sh_path']
}
self.output_path = config['output_path']
self.start_date = datetime.strptime(config['start_date'], '%Y-%m-%d')
self.end_date = datetime.strptime(config['end_date'], '%Y-%m-%d')
self.data = None
self.features = None
self.labels = None
self.scaler = StandardScaler()
self.cluster_model = None
self.prediction_model = None
self.feature_cols = [] # 存储特征列名
def safe_read_csv(self, file_path, required_columns):
"""
安全读取CSV文件,处理各种异常情况
:param file_path: 文件路径
:param required_columns: 需要的列名列表
:return: 读取的DataFrame或None
"""
try:
# 检查文件大小
file_size = os.path.getsize(file_path)
if file_size == 0:
print(f"文件 {file_path} 大小为0,跳过")
return None
# 检测文件编码
with open(file_path, 'rb') as f:
result = chardet.detect(f.read(10000))
encoding = result['encoding'] if result['encoding'] else 'utf-8'
# 尝试读取文件
df = pd.read_csv(
file_path,
encoding=encoding,
error_bad_lines=False, # 跳过错误行
warn_bad_lines=True, # 警告错误行
dtype={
'date': str,
'time': str
},
usecols=lambda col: col in required_columns, # 只读取需要的列
memory_map=True # 使用内存映射减少内存占用
)
# 检查是否读取到数据
if df.empty:
print(f"文件 {file_path} 读取后为空")
return None
# 检查必需的列是否存在
missing_columns = [col for col in required_columns if col not in df.columns]
if missing_columns:
print(f"文件 {file_path} 缺少必需列: {missing_columns}")
return None
return df
except Exception as e:
print(f"读取文件 {file_path} 时出错: {str(e)}")
return None
def load_and_preprocess_data(self):
"""
加载和预处理数据 - 优化内存使用
"""
print("开始加载和预处理数据...")
all_data = []
required_columns = ['date', 'time', 'open', 'high', 'low', 'close', 'volume', 'amount']
# 加载五分钟线数据
for market, path in self.five_min_paths.items():
print(f"开始处理市场: {market}, 路径: {path}")
file_count = 0
processed_count = 0
for file_path in glob.glob(os.path.join(path, '*.csv')):
file_count += 1
stock_code = os.path.basename(file_path).split('.')[0]
try:
# 安全读取CSV文件
df = self.safe_read_csv(file_path, required_columns)
if df is None:
continue
# 添加市场前缀
df['stock_code'] = f"{market}_{stock_code}"
# 1. 修复日期时间转换问题
# 确保日期和时间列都是字符串类型
df['date'] = df['date'].astype(str).str.zfill(8) # 填充为8位字符串
df['time'] = df['time'].astype(str)
# 处理时间格式 - 确保格式为 HH:MM
df['time'] = df['time'].apply(
lambda x: f"{x[:2]}:{x[2:4]}" if len(x) == 4 else x
)
# 合并日期和时间
df['datetime'] = pd.to_datetime(
df['date'] + ' ' + df['time'],
format='%Y%m%d %H:%M',
errors='coerce'
)
# 删除无效的日期时间
df = df.dropna(subset=['datetime'])
# 筛选日期范围
df = df[(df['datetime'] >= self.start_date) &
(df['datetime'] <= self.end_date)]
if not df.empty:
# 优化内存使用 - 只保留需要的列
keep_cols = ['stock_code', 'datetime', 'open', 'high', 'low', 'close', 'volume', 'amount']
df = df[keep_cols]
all_data.append(df)
processed_count += 1
if processed_count % 100 == 0:
print(f"已处理 {processed_count} 个文件...")
gc.collect() # 手动触发垃圾回收
except Exception as e:
print(f"处理文件 {file_path} 时出错: {str(e)}")
continue
print(f"市场 {market} 完成: 共 {file_count} 个文件, 成功处理 {processed_count} 个文件")
# 加载日线数据
daily_data = []
daily_required_columns = ['date', 'open', 'high', 'low', 'close', 'volume']
for market, path in self.daily_paths.items():
print(f"开始处理日线市场: {market}, 路径: {path}")
file_count = 0
processed_count = 0
for file_path in glob.glob(os.path.join(path, '*.csv')):
file_count += 1
stock_code = os.path.basename(file_path).split('.')[0]
try:
# 安全读取CSV文件
df = self.safe_read_csv(file_path, daily_required_columns)
if df is None:
continue
# 添加市场前缀
df['stock_code'] = f"{market}_{stock_code}"
# 转换日期格式
df['date'] = pd.to_datetime(df['date'], errors='coerce')
# 筛选日期范围
df = df[(df['date'] >= self.start_date) &
(df['date'] <= self.end_date)]
if not df.empty:
# 优化内存使用 - 只保留需要的列
keep_cols = ['stock_code', 'date', 'open', 'high', 'low', 'close', 'volume']
df = df[keep_cols]
daily_data.append(df)
processed_count += 1
if processed_count % 100 == 0:
print(f"已处理 {processed_count} 个日线文件...")
gc.collect() # 手动触发垃圾回收
except Exception as e:
print(f"处理日线文件 {file_path} 时出错: {str(e)}")
continue
print(f"日线市场 {market} 完成: 共 {file_count} 个文件, 成功处理 {processed_count} 个文件")
# 合并数据
if not all_data:
raise ValueError("没有找到有效的五分钟线数据")
print("开始合并数据...")
self.data = pd.concat(all_data, ignore_index=True)
daily_df = pd.concat(daily_data, ignore_index=True)
# 添加日线特征
self._add_daily_features(daily_df)
print(f"数据加载完成,共 {len(self.data)} 条记录")
print(f"内存使用情况: {self.data.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
def _add_daily_features(self, daily_df):
"""
添加日线特征到五分钟线数据
:param daily_df: 日线数据DataFrame
"""
# 预处理日线数据
daily_df = daily_df.sort_values(['stock_code', 'date'])
# 计算日线技术指标
daily_df['daily_ma5'] = daily_df.groupby('stock_code')['close'].transform(
lambda x: x.rolling(5).mean())
daily_df['daily_ma10'] = daily_df.groupby('stock_code')['close'].transform(
lambda x: x.rolling(10).mean())
daily_df['daily_vol_ma5'] = daily_df.groupby('stock_code')['volume'].transform(
lambda x: x.rolling(5).mean())
# 计算MACD
def calculate_macd(group):
group = group.sort_values('date')
# 确保有足够的数据计算MACD
if len(group) < 26:
group['daily_macd'] = np.nan
group['daily_signal'] = np.nan
else:
macd, signal, _ = ta.MACD(group['close'].values,
fastperiod=12, slowperiod=26, signalperiod=9)
group['daily_macd'] = macd
group['daily_signal'] = signal
return group
daily_df = daily_df.groupby('stock_code').apply(calculate_macd)
# 合并日线特征到五分钟线数据
self.data['date'] = self.data['datetime'].dt.date
self.data['date'] = pd.to_datetime(self.data['date'])
# 合并日线特征
self.data = pd.merge(
self.data,
daily_df[['stock_code', 'date', 'daily_ma5', 'daily_ma10',
'daily_vol_ma5', 'daily_macd', 'daily_signal']],
on=['stock_code', 'date'],
how='left'
)
# 其他方法保持不变(create_features, create_labels等)...
# 为了节省篇幅,省略了其他方法的代码,实际实现中请保留
def run(self, output_results=True):
"""
运行整个流程 - 优化内存使用
"""
try:
# 分步执行,每步完成后释放内存
self.load_and_preprocess_data()
gc.collect()
self.create_features()
gc.collect()
self.create_labels()
gc.collect()
self.perform_clustering(n_clusters=self.config.get('n_clusters', 5))
gc.collect()
self.train_prediction_model()
gc.collect()
self.predict_and_save(output_results)
gc.collect()
print("训练和预测流程完成!")
except Exception as e:
print(f"运行过程中出错: {str(e)}")
import traceback
traceback.print_exc()
# 配置参数
config = {
# 数据路径配置
'five_min_sz_path': r"D:\股票量化数据库\股票五分钟线csv数据\深证",
'five_min_sh_path': r"D:\股票量化数据库\股票五分钟线csv数据\上证",
'daily_sz_path': r"D:\股票量化数据库\股票csv数据\深证",
'daily_sh_path': r"D:\股票量化数据库\股票csv数据\上证",
# 输出路径
'output_path': r"D:\股票量化数据库\预测结果",
# 时间范围配置
'start_date': '2023-09-08',
'end_date': '2025-08-07',
# 聚类配置
'n_clusters': 5
}
# 创建并运行系统
if __name__ == "__main__":
# 是否输出预测结果(可根据需要修改)
output_results = True
# 设置Pandas内存选项
pd.set_option('mode.chained_assignment', None) # 关闭SettingWithCopyWarning
pd.set_option('display.max_columns', None) # 显示所有列
# 设置内存使用限制(根据您的系统调整)
import resource
try:
# 设置内存限制为8GB(仅适用于Linux)
resource.setrlimit(resource.RLIMIT_AS, (8 * 1024**3, 8 * 1024**3))
except:
pass # Windows不支持此功能
system = StockPredictionSystem(config)
system.run(output_results)
```
## 错误原因与解决方案
### 错误原因分析
```python
Error tokenizing data. C error: out of memory
```
这个错误发生在读取CSV文件时,主要原因包括:
1. **文件过大**:单个CSV文件包含大量数据,超出可用内存
2. **格式问题**:文件可能包含损坏或不规则的数据行
3. **编码问题**:文件使用非标准编码导致解析困难
4. **内存泄漏**:之前的操作未释放内存
5. **系统限制**:32位Python进程有内存限制(约2GB)
### 完整解决方案
#### 1. 安全读取CSV文件
```python
def safe_read_csv(self, file_path, required_columns):
"""
安全读取CSV文件,处理各种异常情况
"""
try:
# 检查文件大小
file_size = os.path.getsize(file_path)
if file_size == 0:
print(f"文件 {file_path} 大小为0,跳过")
return None
# 检测文件编码
with open(file_path, 'rb') as f:
result = chardet.detect(f.read(10000))
encoding = result['encoding'] if result['encoding'] else 'utf-8'
# 尝试读取文件
df = pd.read_csv(
file_path,
encoding=encoding,
error_bad_lines=False, # 跳过错误行
warn_bad_lines=True, # 警告错误行
dtype={
'date': str,
'time': str
},
usecols=lambda col: col in required_columns, # 只读取需要的列
memory_map=True # 使用内存映射减少内存占用
)
# 其他检查...
return df
```
#### 2. 内存优化技术
```python
# 在加载方法中
if not df.empty:
# 优化内存使用 - 只保留需要的列
keep_cols = ['stock_code', 'datetime', 'open', 'high', 'low', 'close', 'volume', 'amount']
df = df[keep_cols]
# 优化数据类型
df['open'] = df['open'].astype(np.float32)
df['high'] = df['high'].astype(np.float32)
df['low'] = df['low'].astype(np.float32)
df['close'] = df['close'].astype(np.float32)
df['volume'] = df['volume'].astype(np.float32)
all_data.append(df)
# 定期垃圾回收
if processed_count % 100 == 0:
gc.collect()
```
#### 3. 分步处理与内存管理
```python
def run(self, output_results=True):
try:
# 分步执行,每步完成后释放内存
self.load_and_preprocess_data()
gc.collect()
self.create_features()
gc.collect()
# ...其他步骤...
except Exception as e:
# 错误处理
```
#### 4. 处理大文件的替代方法
如果上述方法仍然不足,可以使用分块处理:
```python
def safe_read_large_csv(self, file_path, required_columns):
"""分块读取大文件"""
chunks = []
chunk_size = 10000 # 每次读取的行数
# 检测编码
with open(file_path, 'rb') as f:
result = chardet.detect(f.read(10000))
encoding = result['encoding'] if result['encoding'] else 'utf-8'
# 分块读取
reader = pd.read_csv(
file_path,
encoding=encoding,
error_bad_lines=False,
warn_bad_lines=True,
dtype={
'date': str,
'time': str
},
usecols=lambda col: col in required_columns,
chunksize=chunk_size
)
for chunk in reader:
# 处理每个数据块
chunks.append(chunk)
gc.collect() # 处理完一个块后回收内存
if chunks:
return pd.concat(chunks, ignore_index=True)
return None
```
### 其他优化建议
#### 1. 使用Dask处理大数据
如果内存问题仍然存在,可以考虑使用Dask库处理大数据:
```python
import dask.dataframe as dd
def safe_read_csv_with_dask(file_path, required_columns):
"""使用Dask读取大文件"""
try:
# 检测编码
with open(file_path, 'rb') as f:
result = chardet.detect(f.read(10000))
encoding = result['encoding'] if result['encoding'] else 'utf-8'
# 使用Dask读取
ddf = dd.read_csv(
file_path,
encoding=encoding,
error_bad_lines=False,
dtype={
'date': str,
'time': str
},
usecols=required_columns
)
# 转换为Pandas DataFrame
return ddf.compute()
except Exception as e:
print(f"使用Dask读取文件 {file_path} 时出错: {str(e)}")
return None
```
#### 2. 使用更高效的数据格式
考虑将CSV文件转换为更高效的格式:
- **Parquet**:列式存储,压缩率高
- **HDF5**:适合大型数值数据集
- **Feather**:快速读写,适合临时数据交换
```python
# 将CSV转换为Parquet
def convert_csv_to_parquet(csv_path, parquet_path):
df = pd.read_csv(csv_path)
df.to_parquet(parquet_path)
# 读取Parquet文件
def read_parquet(file_path):
return pd.read_parquet(file_path)
```
#### 3. 增加系统内存或使用64位Python
如果可能:
1. 升级到64位Python(支持超过2GB内存)
2. 增加系统RAM
3. 使用云服务器或分布式计算
### 完整优化后的处理流程
1. **安全文件读取**:
- 检查文件大小
- 自动检测编码
- 跳过错误行
- 只加载必要列
2. **内存优化**:
- 使用32位浮点数
- 及时释放内存
- 分块处理大文件
3. **数据处理优化**:
- 尽早过滤数据(日期范围)
- 删除不必要的列
- 使用内存映射
4. **系统级优化**:
- 定期垃圾回收
- 设置内存限制
- 使用更高效的数据格式
### 使用说明
1. **安装依赖**:
```bash
pip install pandas numpy scikit-learn talib chardet
# 如果需要处理大文件
pip install dask pyarrow
```
2. **运行系统**:
```python
system = StockPredictionSystem(config)
system.run(output_results=True)
```
3. **监控内存**:
- 添加内存监控代码:
```python
import psutil
def print_memory_usage():
process = psutil.Process(os.getpid())
mem = process.memory_info().rss / 1024 ** 2
print(f"当前内存使用: {mem:.2f} MB")
```
### 注意事项
1. **文件格式**:
- 确保所有CSV文件格式一致
- 处理前检查文件是否损坏
2. **内存管理**:
- 避免在循环中创建大型对象
- 使用`gc.collect()`主动回收内存
- 考虑使用`del`显式删除不再使用的对象
3. **性能权衡**:
- 内存优化可能增加处理时间
- 找到适合您系统的最佳平衡点
4. **错误处理**:
- 记录所有错误文件供后续分析
- 考虑实现重试机制