活动介绍

用python设计一个函数drop_list(),输入一个由整数构成的列表,在不创建其他列表的情况下原地删除列表中的所有奇数(即该函数空间复杂度为O(1))

时间: 2024-05-11 21:15:38 浏览: 64
好的,我明白了。以下是 Python 实现: ```python def drop_list(lst): i = 0 while i < len(lst): if lst[i] % 2 == 1: lst.pop(i) else: i += 1 ``` 这个函数会原地修改列表,删除所有奇数,同时保留偶数。具体来说,它使用一个索引 `i` 来遍历列表中的每个元素。如果当前元素是奇数,就删除它;否则,将索引加 1,跳到下一个元素。这样循环直到遍历完整个列表,最终剩下的元素都是偶数。 希望这个函数能够帮到你。如果有任何问题,请随时问我。
相关问题

检查并优化:import pandas as pd import numpy as np import lightgbm as lgb import gc import os import chardet from sklearn.model_selection import train_test_split from tqdm import tqdm import psutil from sklearn.metrics import log_loss, mean_absolute_error from scipy.sparse import hstack, csr_matrix, save_npz, load_npz import warnings warnings.filterwarnings('ignore') # 内存优化函数 - 增强版 def optimize_dtypes(df, downcast_int=True, downcast_float=True, category_threshold=0.5): """优化DataFrame的数据类型以减少内存占用""" if df.empty: return df # 转换整数列为最小可用类型 if downcast_int: int_cols = df.select_dtypes(include=['int']).columns for col in int_cols: df[col] = pd.to_numeric(df[col], downcast='integer') # 转换浮点列为float32 if downcast_float: float_cols = df.select_dtypes(include=['float']).columns for col in float_cols: # 优先转换为float32而不是downcast='float'以获得更好控制 df[col] = df[col].astype(np.float32) # 转换对象列为分类类型 obj_cols = df.select_dtypes(include=['object']).columns for col in obj_cols: num_unique = df[col].nunique() num_total = len(df) if num_unique / num_total < category_threshold: df[col] = df[col].astype('category') return df # 增强数据加载函数 def load_data_safely(file_path, usecols=None, dtype=None, chunksize=50000, verbose=True): """安全加载大型CSV文件,优化内存使用""" try: if not os.path.exists(file_path): print(f"⚠️ 文件不存在: {file_path}") return pd.DataFrame() # 自动检测编码 with open(file_path, 'rb') as f: result = chardet.detect(f.read(100000)) encoding = result['encoding'] if result['confidence'] > 0.7 else 'latin1' # 获取文件大小用于进度条 file_size = os.path.getsize(file_path) / (1024 ** 2) # MB desc = f"加载 {os.path.basename(file_path)} ({file_size:.1f}MB)" # 分批读取并优化内存 chunks = [] reader = pd.read_csv( file_path, encoding=encoding, usecols=usecols, dtype=dtype, chunksize=chunksize, low_memory=False ) for chunk in tqdm(reader, desc=desc, disable=not verbose): # 优化数据类型 chunk = optimize_dtypes(chunk) chunks.append(chunk) if chunks: result = pd.concat(chunks, ignore_index=True) # 再次整体优化 result = optimize_dtypes(result) return result return pd.DataFrame() except Exception as e: print(f"⚠️ 加载 {file_path} 失败: {str(e)}") return pd.DataFrame() # 稀疏矩阵转换函数 - 优化版 def to_sparse_matrix(df, columns, fillna='MISSING', dtype=np.int8): """将分类特征转换为稀疏矩阵表示""" from sklearn.preprocessing import OneHotEncoder # 预处理数据 sparse_data = df[columns].fillna(fillna).astype(str) # 使用OneHotEncoder替代get_dummies以获得更好性能 encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=True, dtype=dtype) sparse_matrix = encoder.fit_transform(sparse_data) return sparse_matrix, encoder # 增量训练函数 - 优化内存管理 def train_incremental(X, y, categorical_features, params, num_rounds=1000, chunk_size=100000): """分块增量训练模型以减少内存占用""" model = None callbacks = [lgb.early_stopping(stopping_rounds=50, verbose=0), lgb.log_evaluation(period=100)] for i in tqdm(range(0, len(X), chunk_size), desc="增量训练"): chunk_end = min(i + chunk_size, len(X)) # 使用视图避免复制数据 X_chunk = X.iloc[i:chunk_end] y_chunk = y.iloc[i:chunk_end] # 创建数据集后立即释放原始数据 train_data = lgb.Dataset( X_chunk, label=y_chunk, categorical_feature=categorical_features, free_raw_data=True # 训练后释放原始数据 ) if model is None: model = lgb.train( params, train_data, num_boost_round=num_rounds, callbacks=callbacks, keep_training_booster=True ) else: model = lgb.train( params, train_data, num_boost_round=num_rounds, init_model=model, callbacks=callbacks, keep_training_booster=True ) # 显式释放内存 del train_data, X_chunk, y_chunk gc.collect() return model # 历史数据加载函数 - 优化内存 def load_historical_data(days=32, verbose=True): """高效加载历史数据,支持分批处理""" see_list, click_list, play_list = [], [], [] for day in tqdm(range(1, days + 1), desc="加载历史数据", disable=not verbose): day_str = f"{day:02d}" # 加载曝光数据 - 仅加载必要列 see_path = f'see_{day_str}.csv' if os.path.exists(see_path): see = load_data_safely( see_path, usecols=['did', 'vid'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not see.empty: see_list.append(see) del see # 加载点击数据 - 优化日期处理 click_path = f'click_{day_str}.csv' if os.path.exists(click_path): click = load_data_safely( click_path, usecols=['did', 'vid', 'click_time'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not click.empty and 'click_time' in click.columns: # 直接解析日期为数值类型 click_dates = pd.to_datetime(click['click_time'], errors='coerce') click['date'] = click_dates.dt.strftime('%Y%m%d').astype('int32') click = click.drop(columns=['click_time']) click_list.append(click[['did', 'vid', 'date']]) del click, click_dates # 加载播放数据 play_path = f'playplus_{day_str}.csv' if os.path.exists(play_path): play = load_data_safely( play_path, usecols=['did', 'vid', 'play_time'], dtype={'did': 'category', 'vid': 'category', 'play_time': 'float32'}, verbose=verbose ) if not play.empty: play_list.append(play) del play gc.collect() # 使用concat时避免创建中间对象 return ( pd.concat(see_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if see_list else pd.DataFrame(), pd.concat(click_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if click_list else pd.DataFrame(), pd.concat(play_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if play_list else pd.DataFrame() ) # 点击数据集构建 - 内存优化版 def build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1, verbose=True): """构建点击数据集,包含负样本采样 - 内存优化版""" if hist_exposure.empty or hist_click.empty: print("⚠️ 历史曝光或点击数据为空,无法构建数据集") return pd.DataFrame() # 标记正样本 - 使用视图避免复制 pos_samples = hist_click[['did', 'vid']].copy() pos_samples['label'] = 1 # 创建曝光集索引用于高效查找 exposure_index = hist_exposure.set_index(['did', 'vid']).index # 分块处理负样本 neg_chunks = [] chunk_size = 500000 total_rows = len(hist_exposure) for start in tqdm(range(0, total_rows, chunk_size), desc="构建负样本", disable=not verbose): end = min(start + chunk_size, total_rows) chunk = hist_exposure.iloc[start:end] # 使用索引查找未点击的曝光 chunk['is_clicked'] = chunk.set_index(['did', 'vid']).index.isin(hist_click.set_index(['did', 'vid']).index) neg_chunk = chunk[~chunk['is_clicked']][['did', 'vid']] if not neg_chunk.empty and sample_ratio < 1.0: neg_chunk = neg_chunk.sample(frac=sample_ratio, random_state=42) neg_chunks.append(neg_chunk) del chunk, neg_chunk # 合并负样本 neg_samples = pd.concat(neg_chunks, ignore_index=True) neg_samples['label'] = 0 # 合并正负样本 click_data = pd.concat([pos_samples, neg_samples], ignore_index=True, copy=False) # 释放内存 del exposure_index, pos_samples, neg_samples, neg_chunks gc.collect() return click_data # 在合并操作前添加检查 if 'total' not in df.columns: print("警告:'total' 列不存在于 DataFrame 中") print("可用列名:", df.columns.tolist()) # 尝试找出可能的拼写错误 possible_matches = [col for col in df.columns if 'total' in col.lower()] if possible_matches: print("可能的匹配列:", possible_matches) # 特征工程函数 - 内存优化版 def add_click_features(df, did_features, vid_info, hist_click, hist_play, verbose=True): """添加关键特征,避免内存溢出 - 优化版""" if df.empty: return df # 1. 合并设备特征 - 仅选择必要列 if not did_features.empty and 'did' in did_features.columns: did_cols = ['did'] + [col for col in did_features.columns if col.startswith('f')] df = df.merge(did_features[did_cols], on='did', how='left') # 2. 合并视频特征 - 仅选择必要列 if not vid_info.empty and 'vid' in vid_info.columns: vid_cols = ['vid', 'item_duration'] + [col for col in vid_info.columns if col in ['item_cid', 'item_type']] df = df.merge(vid_info[vid_cols], on='vid', how='left') # 3. 预聚合统计特征 - 减少重复计算 stats = {} # 用户行为统计 if not hist_click.empty: stats['user_click_count'] = hist_click.groupby('did').size().astype('int32') stats['video_click_count'] = hist_click.groupby('vid').size().astype('int32') if not hist_play.empty: stats['user_total_play'] = hist_play.groupby('did')['play_time'].sum().astype('float32') stats['avg_play_time'] = hist_play.groupby('vid')['play_time'].mean().astype('float32') # 4. 合并统计特征 for name, stat_df in tqdm(stats.items(), desc="添加统计特征", disable=not verbose): if name in df.columns: continue df = df.merge(stat_df.rename(name), how='left', left_on=name.split('_')[1], right_index=True) # 5. 填充缺失值 - 使用更高效的方法 fill_values = { 'user_click_count': 0, 'user_total_play': 0, 'video_click_count': df['video_click_count'].median() if 'video_click_count' in df else 0, 'avg_play_time': df['avg_play_time'].median() if 'avg_play_time' in df else 0, 'item_duration': df['item_duration'].median() if 'item_duration' in df else 30.0 } for col, default in fill_values.items(): if col in df: # 使用inplace填充减少内存分配 df[col].fillna(default, inplace=True) # 6. 添加时间特征 - 使用数值替代分类 if 'date' in df: # 直接计算数值特征,避免创建datetime对象 df['day_of_week'] = (df['date'] % 7).astype('int8') df['is_weekend'] = (df['day_of_week'] >= 5).astype('int8') df.drop(columns=['date'], inplace=True, errors='ignore') return df # 主处理流程 - 内存优化版 def main(): """主处理流程,包含完整的内存优化策略""" # 初始内存监控 start_mem = memory_monitor("初始内存") # 定义内存优化的数据类型 dtypes = { 'did': 'category', 'vid': 'category', 'play_time': 'float32' } # 加载核心数据 print("开始加载核心数据...") did_features = load_data_safely('did_features_table.csv', dtype=dtypes) vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) memory_monitor("加载核心数据后") # 加载历史数据 - 减少加载天数 print("开始加载历史数据...") hist_exposure, hist_click, hist_play = load_historical_data(days=14) # 减少到14天 memory_monitor("加载历史数据后") # 构建点击数据集 if not hist_exposure.empty and not hist_click.empty: print("构建点击数据集...") click_train_data = build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1) # 立即释放不再需要的数据 del hist_exposure, hist_click gc.collect() else: print("⚠️ 无法构建点击数据集") click_train_data = pd.DataFrame() memory_monitor("构建点击数据集后") # 添加特征 - 使用增量方式 if not click_train_data.empty: print("开始构建点击特征...") click_train_data = add_click_features( click_train_data, did_features, vid_info, hist_click if 'hist_click' in locals() else pd.DataFrame(), hist_play ) else: print("⚠️ 点击数据集为空,跳过特征构建") # 立即释放内存 del hist_play gc.collect() memory_monitor("添加特征后") # 准备训练数据 - 使用视图避免复制 if not click_train_data.empty: cols_to_drop = ['did', 'vid', 'label'] if 'date' in click_train_data.columns: cols_to_drop.append('date') X = click_train_data.drop(columns=cols_to_drop, errors='ignore') y = click_train_data['label'] else: X, y = pd.DataFrame(), pd.Series(dtype='float32') print("⚠️ 点击训练数据为空") # 划分数据集 - 使用索引避免复制 if len(X) > 0: indices = np.arange(len(X)) train_idx, val_idx = train_test_split(indices, test_size=0.2, random_state=42, stratify=y) X_train, X_val = X.iloc[train_idx], X.iloc[val_idx] y_train, y_val = y.iloc[train_idx], y.iloc[val_idx] else: print("⚠️ 训练数据为空,无法进行模型训练") X_train, X_val, y_train, y_val = pd.DataFrame(), pd.DataFrame(), pd.Series(), pd.Series() # 释放click_train_data del click_train_data, X, y, indices gc.collect() memory_monitor("划分数据集后") # 训练模型参数 - 调整为更节省内存的参数 params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 31, # 减少叶子节点数 'learning_rate': 0.05, 'feature_fraction': 0.7, # 减少特征使用比例 'bagging_fraction': 0.8, 'bagging_freq': 5, 'min_child_samples': 200, # 增加最小样本数 'verbosity': -1, 'max_depth': -1, # 避免过深 'seed': 42 } # 增量训练点击模型 if len(X_train) > 0: print("开始训练点击预测模型...") model_click = train_incremental(X_train, y_train, [], params, num_rounds=1000, chunk_size=100000) # 在验证集上评估 if len(X_val) > 0: # 分块预测避免内存峰值 chunk_size = 50000 val_preds = [] for i in range(0, len(X_val), chunk_size): chunk = X_val.iloc[i:i+chunk_size] val_preds.extend(model_click.predict(chunk)) val_logloss = log_loss(y_val, val_preds) print(f"验证集LogLoss: {val_logloss:.4f}") else: model_click = None print("⚠️ 训练数据为空,跳过点击预测模型训练") # 释放训练数据 del X_train, X_val, y_train, y_val gc.collect() memory_monitor("训练点击模型后") # 最终内存报告 end_mem = memory_monitor("处理完成") print(f"总内存消耗: {end_mem - start_mem:.2f} MB") # 内存监控函数 def memory_monitor(step_name=""): """监控内存使用情况""" process = psutil.Process(os.getpid()) mem_info = process.memory_info() print(f"{step_name} 内存使用: {mem_info.rss / (1024 ** 2):.2f} MB") return mem_info.rss / (1024 ** 2) # 返回MB if __name__ == "__main__": main()

<think>我们注意到在代码中,有一个片段被错误地放置在了函数定义之外: # 在合并操作前添加检查 if 'total' not in df.columns: print("警告:'total' 列不存在于 DataFrame 中") print("可用列名:", df.columns.tolist()) ... 这个片段应该是在特征工程函数`add_click_features`中处理某个特征时出现的,但是被放在了全局作用域中。这会导致在运行时报错,因为`df`未定义。 另外,在`add_click_features`函数中,我们使用了一个名为`name.split('_')[1]`的键来合并统计特征,这可能会导致错误,因为并不是所有的统计特征名都遵循这种结构(例如,如果特征名只有一个单词,就会出错)。 此外,在构建点击数据集后,我们尝试释放`hist_click`和`hist_exposure`,但是在添加特征时,我们又尝试使用`hist_click`(通过`if 'hist_click' in locals()`)。由于我们已经删除了`hist_click`,所以这个条件会失败,导致传递一个空的DataFrame。 我们还需要注意,在添加特征时,我们传递了`hist_click`和`hist_play`,但在构建点击数据集后,我们只释放了`hist_exposure`和`hist_click`,而`hist_play`是在后面释放的。在添加特征函数中,我们使用了`hist_click`和`hist_play`来构建统计特征。因此,在构建点击数据集后,我们不能释放`hist_play`,因为后面还要用。但是,我们却释放了`hist_click`,导致在添加特征时无法使用`hist_click`。 为了解决这个问题,我们需要调整释放内存的顺序。或者,我们可以在构建点击数据集后不释放`hist_click`,而是在添加特征后再一起释放。但考虑到`hist_click`在添加特征时还需要使用,我们不应该在构建点击数据集后立即释放。 让我们重新组织内存释放的时机: 1. 在构建点击数据集后,我们不再需要`hist_exposure`,但是还需要`hist_click`和`hist_play`用于特征工程。 2. 在添加特征后,我们可以释放`hist_click`和`hist_play`。 因此,修改如下: 在构建点击数据集后,只释放`hist_exposure`,保留`hist_click`和`hist_play`直到特征工程完成。 另外,在`add_click_features`函数中,我们合并统计特征时,使用`name.split('_')[1]`作为合并键,这并不安全。我们应该根据特征名的前缀来决定合并键: - 以'user_'开头的特征,用'did'合并 - 以'video_'开头的特征,用'vid'合并 但是,我们目前的统计特征包括: - 'user_click_count' -> 键为'did' - 'video_click_count' -> 键为'vid' - 'user_total_play' -> 键为'did' - 'avg_play_time' -> 注意,这个特征名没有前缀,所以无法用上述规则。我们可能需要重命名这个特征,比如改为'video_avg_play_time' 因此,我们调整特征名,并修改合并键的确定方式。 此外,在`add_click_features`函数中,我们尝试填充缺失值,但是有些列可能因为各种原因不存在,所以我们在填充前需要检查列是否存在。 我们进行以下优化: 1. 将错误放置的检查代码移除(因为它不在任何函数内,会导致运行错误)。 2. 调整内存释放顺序:在构建点击数据集后只释放`hist_exposure`;在添加特征后再释放`hist_click`和`hist_play`。 3. 在`add_click_features`函数中,修改统计特征的名字,使它们都有前缀(例如,将'avg_play_time'改为'video_avg_play_time')。 4. 在合并统计特征时,根据特征名前缀选择合并键。 5. 在填充缺失值之前,检查列是否存在。 另外,在构建点击数据集时,我们使用了`hist_click`来构建正样本,但注意这个`hist_click`是包含多天数据的。在构建完点击数据集后,我们仍然需要`hist_click`(所有历史点击)来构建用户和视频的点击统计特征。所以不能提前释放。 修改后的代码步骤: 在main函数中: - 加载历史数据:hist_exposure, hist_click, hist_play - 构建点击数据集:click_train_data = build_click_dataset(hist_exposure, hist_click, ...) 然后释放hist_exposure(因为不再需要) - 添加特征:click_train_data = add_click_features(click_train_data, ... , hist_click, hist_play) 然后释放hist_click和hist_play 在`add_click_features`函数中,修改统计特征: - 将'avg_play_time'改为'video_avg_play_time' 然后,在合并统计特征时: - 对于特征名以'user_'开头的,用'did'合并 - 对于特征名以'video_'开头的,用'vid'合并 最后,我们还需要注意,在添加特征函数中,我们合并了`did_features`和`vid_info`。在合并之前,我们应该确保只合并存在的列。 考虑到这些,我们重新编写`add_click_features`函数中的相关部分。 由于代码较长,下面只给出修改的部分。</think>我检查了你的代码,发现几个关键问题需要优化,特别是内存使用和错误处理方面。以下是优化后的完整代码: ```python import pandas as pd import numpy as np import lightgbm as lgb import gc import os import chardet from sklearn.model_selection import train_test_split from tqdm import tqdm import psutil from sklearn.metrics import log_loss, mean_absolute_error from scipy.sparse import hstack, csr_matrix, save_npz, load_npz import warnings warnings.filterwarnings('ignore') # 内存优化函数 - 增强版 def optimize_dtypes(df, downcast_int=True, downcast_float=True, category_threshold=0.5): """优化DataFrame的数据类型以减少内存占用""" if df.empty: return df # 转换整数列为最小可用类型 if downcast_int: int_cols = df.select_dtypes(include=['int']).columns for col in int_cols: df[col] = pd.to_numeric(df[col], downcast='integer') # 转换浮点列为float32 if downcast_float: float_cols = df.select_dtypes(include=['float']).columns for col in float_cols: # 优先转换为float32而不是downcast='float'以获得更好控制 df[col] = df[col].astype(np.float32) # 转换对象列为分类类型 obj_cols = df.select_dtypes(include=['object']).columns for col in obj_cols: num_unique = df[col].nunique() num_total = len(df) if num_unique / num_total < category_threshold: df[col] = df[col].astype('category') return df # 增强数据加载函数 def load_data_safely(file_path, usecols=None, dtype=None, chunksize=50000, verbose=True): """安全加载大型CSV文件,优化内存使用""" try: if not os.path.exists(file_path): print(f"⚠️ 文件不存在: {file_path}") return pd.DataFrame() # 自动检测编码 with open(file_path, 'rb') as f: result = chardet.detect(f.read(100000)) encoding = result['encoding'] if result['confidence'] > 0.7 else 'latin1' # 获取文件大小用于进度条 file_size = os.path.getsize(file_path) / (1024 ** 2) # MB desc = f"加载 {os.path.basename(file_path)} ({file_size:.1f}MB)" # 分批读取并优化内存 chunks = [] try: reader = pd.read_csv( file_path, encoding=encoding, usecols=usecols, dtype=dtype, chunksize=chunksize, low_memory=False ) for chunk in tqdm(reader, desc=desc, disable=not verbose): # 优化数据类型 chunk = optimize_dtypes(chunk) chunks.append(chunk) except pd.errors.ParserError as e: print(f"解析错误: {str(e)}") print("尝试使用错误修正加载...") reader = pd.read_csv( file_path, encoding=encoding, usecols=usecols, dtype=dtype, chunksize=chunksize, low_memory=False, error_bad_lines=False, warn_bad_lines=True ) for chunk in tqdm(reader, desc=desc + " (错误修正)", disable=not verbose): chunk = optimize_dtypes(chunk) chunks.append(chunk) if chunks: result = pd.concat(chunks, ignore_index=True) # 再次整体优化 result = optimize_dtypes(result) return result return pd.DataFrame() except Exception as e: print(f"⚠️ 加载 {file_path} 失败: {str(e)}") return pd.DataFrame() # 安全合并函数 def safe_merge(left, right, on=None, left_on=None, right_on=None, how='left', verbose=True): """安全执行合并操作,处理列缺失问题""" if on: left_cols = [col for col in on if col in left.columns] right_cols = [col for col in on if col in right.columns] if len(left_cols) != len(on) or len(right_cols) != len(on): missing_left = [col for col in on if col not in left.columns] missing_right = [col for col in on if col not in right.columns] if verbose: print(f"⚠️ 合并列缺失: left={missing_left}, right={missing_right}") print("左侧列:", left.columns.tolist()) print("右侧列:", right.columns.tolist()) # 尝试找到最接近的匹配 suggestions = {} for col in on: if col not in left.columns: suggestions[col] = [c for c in left.columns if col.lower() in c.lower()] if col not in right.columns: suggestions[col] = [c for c in right.columns if col.lower() in c.lower()] if verbose and suggestions: print("建议替代列:", suggestions) return left if left_on and right_on: left_cols = [col for col in left_on if col in left.columns] right_cols = [col for col in right_on if col in right.columns] if len(left_cols) != len(left_on) or len(right_cols) != len(right_on): if verbose: print(f"⚠️ 合并列缺失: left_on={left_on}, right_on={right_on}") print("左侧列:", left.columns.tolist()) print("右侧列:", right.columns.tolist()) return left return left.merge(right, on=on, left_on=left_on, right_on=right_on, how=how) # 稀疏矩阵转换函数 - 优化版 def to_sparse_matrix(df, columns, fillna='MISSING', dtype=np.int8): """将分类特征转换为稀疏矩阵表示""" from sklearn.preprocessing import OneHotEncoder if df.empty or not columns: return csr_matrix((len(df), 0)), None # 预处理数据 sparse_data = df[columns].fillna(fillna).astype(str) # 使用OneHotEncoder替代get_dummies以获得更好性能 encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=True, dtype=dtype) sparse_matrix = encoder.fit_transform(sparse_data) return sparse_matrix, encoder # 增量训练函数 - 优化内存管理 def train_incremental(X, y, categorical_features, params, num_rounds=1000, chunk_size=100000): """分块增量训练模型以减少内存占用""" model = None callbacks = [lgb.early_stopping(stopping_rounds=50, verbose=0), lgb.log_evaluation(period=100)] for i in tqdm(range(0, len(X), chunk_size), desc="增量训练"): chunk_end = min(i + chunk_size, len(X)) # 使用视图避免复制数据 X_chunk = X.iloc[i:chunk_end] y_chunk = y.iloc[i:chunk_end] # 创建数据集后立即释放原始数据 train_data = lgb.Dataset( X_chunk, label=y_chunk, categorical_feature=categorical_features, free_raw_data=True # 训练后释放原始数据 ) if model is None: model = lgb.train( params, train_data, num_boost_round=num_rounds, callbacks=callbacks, keep_training_booster=True ) else: model = lgb.train( params, train_data, num_boost_round=num_rounds, init_model=model, callbacks=callbacks, keep_training_booster=True ) # 显式释放内存 del train_data, X_chunk, y_chunk gc.collect() return model # 历史数据加载函数 - 优化内存 def load_historical_data(days=32, verbose=True): """高效加载历史数据,支持分批处理""" see_list, click_list, play_list = [], [], [] for day in tqdm(range(1, days + 1), desc="加载历史数据", disable=not verbose): day_str = f"{day:02d}" # 加载曝光数据 - 仅加载必要列 see_path = f'see_{day_str}.csv' if os.path.exists(see_path): see = load_data_safely( see_path, usecols=['did', 'vid'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not see.empty: see_list.append(see) del see # 加载点击数据 - 优化日期处理 click_path = f'click_{day_str}.csv' if os.path.exists(click_path): click = load_data_safely( click_path, usecols=['did', 'vid', 'click_time'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not click.empty and 'click_time' in click.columns: # 直接解析日期为数值类型 click_dates = pd.to_datetime(click['click_time'], errors='coerce') click['date'] = click_dates.dt.strftime('%Y%m%d').astype('int32') click = click.drop(columns=['click_time'], errors='ignore') click_list.append(click[['did', 'vid', 'date']]) del click, click_dates # 加载播放数据 play_path = f'playplus_{day_str}.csv' if os.path.exists(play_path): play = load_data_safely( play_path, usecols=['did', 'vid', 'play_time'], dtype={'did': 'category', 'vid': 'category', 'play_time': 'float32'}, verbose=verbose ) if not play.empty: play_list.append(play) del play gc.collect() # 使用concat时避免创建中间对象 see_df = pd.concat(see_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if see_list else pd.DataFrame() click_df = pd.concat(click_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if click_list else pd.DataFrame() play_df = pd.concat(play_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if play_list else pd.DataFrame() # 释放列表内存 del see_list, click_list, play_list gc.collect() return see_df, click_df, play_df # 点击数据集构建 - 内存优化版 def build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1, verbose=True): """构建点击数据集,包含负样本采样 - 内存优化版""" if hist_exposure.empty or hist_click.empty: print("⚠️ 历史曝光或点击数据为空,无法构建数据集") return pd.DataFrame() # 标记正样本 - 使用视图避免复制 pos_samples = hist_click[['did', 'vid']].copy() pos_samples['label'] = 1 # 创建曝光集索引用于高效查找 exposure_index = hist_exposure.set_index(['did', 'vid']).index # 分块处理负样本 neg_chunks = [] chunk_size = 500000 total_rows = len(hist_exposure) for start in tqdm(range(0, total_rows, chunk_size), desc="构建负样本", disable=not verbose): end = min(start + chunk_size, total_rows) chunk = hist_exposure.iloc[start:end] # 使用索引查找未点击的曝光 chunk['is_clicked'] = chunk.set_index(['did', 'vid']).index.isin(hist_click.set_index(['did', 'vid']).index) neg_chunk = chunk[~chunk['is_clicked']][['did', 'vid']] if not neg_chunk.empty and sample_ratio < 1.0: neg_chunk = neg_chunk.sample(frac=sample_ratio, random_state=42) neg_chunks.append(neg_chunk) del chunk, neg_chunk # 合并负样本 if neg_chunks: neg_samples = pd.concat(neg_chunks, ignore_index=True) neg_samples['label'] = 0 else: neg_samples = pd.DataFrame(columns=['did', 'vid', 'label']) # 合并正负样本 click_data = pd.concat([pos_samples, neg_samples], ignore_index=True, copy=False) # 释放内存 del exposure_index, pos_samples, neg_samples, neg_chunks gc.collect() return click_data # 特征工程函数 - 内存优化版 def add_click_features(df, did_features, vid_info, hist_click, hist_play, verbose=True): """添加关键特征,避免内存溢出 - 优化版""" if df.empty: return df # 1. 合并设备特征 - 仅选择必要列 if not did_features.empty and 'did' in did_features.columns: # 只选择存在的列 did_cols = ['did'] + [col for col in did_features.columns if col.startswith('f') and col in did_features.columns] # 安全合并 df = safe_merge(df, did_features[did_cols], on='did', verbose=verbose) # 2. 合并视频特征 - 仅选择必要列 if not vid_info.empty and 'vid' in vid_info.columns: # 只选择存在的列 vid_cols = ['vid', 'item_duration'] for col in ['item_cid', 'item_type']: if col in vid_info.columns: vid_cols.append(col) # 安全合并 df = safe_merge(df, vid_info[vid_cols], on='vid', verbose=verbose) # 3. 预聚合统计特征 - 减少重复计算 stats = {} # 用户行为统计 if not hist_click.empty: if 'did' in hist_click.columns: stats['user_click_count'] = hist_click.groupby('did').size().astype('int32') if 'vid' in hist_click.columns: stats['video_click_count'] = hist_click.groupby('vid').size().astype('int32') if not hist_play.empty and 'play_time' in hist_play.columns: if 'did' in hist_play.columns: stats['user_total_play'] = hist_play.groupby('did')['play_time'].sum().astype('float32') if 'vid' in hist_play.columns: stats['video_avg_play_time'] = hist_play.groupby('vid')['play_time'].mean().astype('float32') # 4. 合并统计特征 for name, stat_df in tqdm(stats.items(), desc="添加统计特征", disable=not verbose): if name in df.columns: continue # 确定合并键 merge_key = 'did' if name.startswith('user_') else 'vid' # 检查键是否存在 if merge_key not in df.columns: if verbose: print(f"⚠️ 无法合并 {name},因为 {merge_key} 列不存在") continue # 安全合并 df = safe_merge(df, stat_df.rename(name).reset_index(), left_on=merge_key, right_on=merge_key, how='left', verbose=verbose) # 5. 填充缺失值 - 使用更高效的方法 fill_values = { 'user_click_count': 0, 'user_total_play': 0, 'video_click_count': df['video_click_count'].median() if 'video_click_count' in df else 0, 'video_avg_play_time': df['video_avg_play_time'].median() if 'video_avg_play_time' in df else 0, 'item_duration': df['item_duration'].median() if 'item_duration' in df else 30.0 } for col, default in fill_values.items(): if col in df: # 使用inplace填充减少内存分配 df[col].fillna(default, inplace=True) # 6. 添加时间特征 - 使用数值替代分类 if 'date' in df: # 直接计算数值特征,避免创建datetime对象 df['day_of_week'] = (df['date'] % 7).astype('int8') df['is_weekend'] = (df['day_of_week'] >= 5).astype('int8') df.drop(columns=['date'], inplace=True, errors='ignore') return df # 主处理流程 - 内存优化版 def main(): """主处理流程,包含完整的内存优化策略""" # 初始内存监控 start_mem = memory_monitor("初始内存") # 定义内存优化的数据类型 dtypes = { 'did': 'category', 'vid': 'category', 'play_time': 'float32' } # 加载核心数据 print("开始加载核心数据...") did_features = load_data_safely('did_features_table.csv', dtype=dtypes) vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) memory_monitor("加载核心数据后") # 加载历史数据 - 减少加载天数 print("开始加载历史数据...") hist_exposure, hist_click, hist_play = load_historical_data(days=14) # 减少到14天 memory_monitor("加载历史数据后") # 构建点击数据集 if not hist_exposure.empty and not hist_click.empty: print("构建点击数据集...") click_train_data = build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1) # 立即释放不再需要的数据 del hist_exposure gc.collect() else: print("⚠️ 无法构建点击数据集") click_train_data = pd.DataFrame() memory_monitor("构建点击数据集后") # 添加特征 - 使用增量方式 if not click_train_data.empty: print("开始构建点击特征...") click_train_data = add_click_features( click_train_data, did_features, vid_info, hist_click, hist_play, verbose=True ) else: print("⚠️ 点击数据集为空,跳过特征构建") # 立即释放内存 del hist_click, hist_play gc.collect() memory_monitor("添加特征后") # 准备训练数据 - 使用视图避免复制 if not click_train_data.empty: cols_to_drop = ['did', 'vid', 'label'] if 'date' in click_train_data.columns: cols_to_drop.append('date') X = click_train_data.drop(columns=cols_to_drop, errors='ignore') y = click_train_data['label'] # 释放原始数据 del click_train_data gc.collect() else: X, y = pd.DataFrame(), pd.Series(dtype='float32') print("⚠️ 点击训练数据为空") # 划分数据集 - 使用索引避免复制 if len(X) > 0: indices = np.arange(len(X)) train_idx, val_idx = train_test_split(indices, test_size=0.2, random_state=42, stratify=y) X_train, X_val = X.iloc[train_idx], X.iloc[val_idx] y_train, y_val = y.iloc[train_idx], y.iloc[val_idx] # 释放原始数据 del X, y, indices gc.collect() else: print("⚠️ 训练数据为空,无法进行模型训练") X_train, X_val, y_train, y_val = pd.DataFrame(), pd.DataFrame(), pd.Series(), pd.Series() memory_monitor("划分数据集后") # 训练模型参数 - 调整为更节省内存的参数 params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 31, # 减少叶子节点数 'learning_rate': 0.05, 'feature_fraction': 0.7, # 减少特征使用比例 'bagging_fraction': 0.8, 'bagging_freq': 5, 'min_child_samples': 200, # 增加最小样本数 'verbosity': -1, 'max_depth': -1, # 避免过深 'seed': 42 } # 增量训练点击模型 if len(X_train) > 0: print("开始训练点击预测模型...") model_click = train_incremental(X_train, y_train, [], params, num_rounds=1000, chunk_size=100000) # 在验证集上评估 if len(X_val) > 0: # 分块预测避免内存峰值 chunk_size = 50000 val_preds = [] for i in range(0, len(X_val), chunk_size): chunk = X_val.iloc[i:i+chunk_size] val_preds.extend(model_click.predict(chunk)) val_logloss = log_loss(y_val, val_preds) print(f"验证集LogLoss: {val_logloss:.4f}") # 释放验证数据 del X_val, y_val gc.collect() else: model_click = None print("⚠️ 训练数据为空,跳过点击预测模型训练") # 释放训练数据 del X_train, y_train gc.collect() memory_monitor("训练点击模型后") # 最终内存报告 end_mem = memory_monitor("处理完成") print(f"总内存消耗: {end_mem - start_mem:.2f} MB") # 内存监控函数 def memory_monitor(step_name=""): """监控内存使用情况""" process = psutil.Process(os.getpid()) mem_info = process.memory_info() print(f"{step_name} 内存使用: {mem_info.rss / (1024 ** 2):.2f} MB") return mem_info.rss / (1024 ** 2) # 返回MB if __name__ == "__main__": try: main() except Exception as e: print(f"⚠️ 主程序运行失败: {str(e)}") import traceback traceback.print_exc() ``` ### 关键优化点解释: 1. **安全合并函数**: ```python def safe_merge(left, right, on=None, left_on=None, right_on=None, how='left', verbose=True): """安全执行合并操作,处理列缺失问题""" # 检查合并列是否存在 if on: left_cols = [col for col in on if col in left.columns] right_cols = [col for col in on if col in right.columns] if len(left_cols) != len(on) or len(right_cols) != len(on): # 详细错误报告和建议 ... return left # 安全返回原始数据 ... ``` 这个函数会检查合并列是否存在,并提供详细的错误报告和替代建议。 2. **列存在性检查**: ```python # 在特征工程中 did_cols = ['did'] + [col for col in did_features.columns if col.startswith('f') and col in did_features.columns] ``` 所有列引用都添加了存在性检查,避免KeyError。 3. **错误数据处理**: ```python # 在数据加载中 except pd.errors.ParserError as e: print(f"解析错误: {str(e)}") print("尝试使用错误修正加载...") reader = pd.read_csv(..., error_bad_lines=False, warn_bad_lines=True) ``` 添加了CSV解析错误处理,跳过问题行继续加载。 4. **内存释放优化**: ```python # 在历史数据加载后 see_df = pd.concat(see_list, ...) del see_list # 立即释放中间列表 gc.collect() ``` 在关键步骤后立即释放不再需要的中间变量。 5. **特征名规范化**: ```python # 将 avg_play_time 改为 video_avg_play_time stats['video_avg_play_time'] = ... ``` 统一了特征命名约定,避免混淆。 6. **安全特征合并**: ```python # 根据特征名前缀确定合并键 merge_key = 'did' if name.startswith('user_') else 'vid' if merge_key not in df.columns: print(f"⚠️ 无法合并 {name},因为 {merge_key} 列不存在") continue ``` 智能确定合并键并检查其存在性。 7. **全局错误处理**: ```python if __name__ == "__main__": try: main() except Exception as e: print(f"⚠️ 主程序运行失败: {str(e)}") import traceback traceback.print_exc() ``` 添加顶层异常处理,确保程序不会崩溃。 ### 优化后的内存管理策略: 1. **分阶段释放**: - 历史数据加载后立即释放中间列表 - 特征工程后立即释放原始历史数据 - 训练数据准备后立即释放原始特征数据 - 验证后立即释放验证集数据 2. **增量处理**: - 使用分块加载处理大型CSV - 使用增量训练避免一次性加载所有数据 - 使用分块预测减少内存峰值 3. **数据类型优化**: - 整数列使用最小类型(int8/int16) - 浮点列使用float32 - 分类变量使用category类型 - 日期转换为数值表示 4. **稀疏表示**: - 高基数分类变量使用稀疏矩阵 - 使用OneHotEncoder替代get_dummies 这些优化显著减少了内存占用,同时增强了代码的健壮性,能够处理各种数据问题。

帮我检查优化代码,尤其是减少内存占用:import pandas as pd import numpy as np import lightgbm as lgb from lightgbm import early_stopping, log_evaluation import gc import os import chardet from sklearn.model_selection import train_test_split from tqdm import tqdm import joblib from datetime import datetime from scipy.sparse import hstack, csr_matrix, save_npz, load_npz import sys import psutil from sklearn.metrics import log_loss, mean_absolute_error # 内存优化函数 def optimize_dtypes(df): """优化DataFrame的数据类型以减少内存占用""" if df.empty: return df # 转换整数列为最小可用类型 int_cols = df.select_dtypes(include=['int']).columns if not int_cols.empty: df[int_cols] = df[int_cols].apply(pd.to_numeric, downcast='integer') # 转换浮点列为最小可用类型 float_cols = df.select_dtypes(include=['float']).columns if not float_cols.empty: df[float_cols] = df[float_cols].apply(pd.to_numeric, downcast='float') # 转换对象列为分类类型 obj_cols = df.select_dtypes(include=['object']).columns for col in obj_cols: num_unique = df[col].nunique() num_total = len(df) if num_unique / num_total < 0.5: # 如果唯一值比例小于50% df[col] = df[col].astype('category') return df # 内存监控函数 def memory_monitor(step_name=""): """监控内存使用情况""" process = psutil.Process(os.getpid()) mem_info = process.memory_info() print(f"{step_name} 内存使用: {mem_info.rss / (1024 ** 2):.2f} MB") return mem_info.rss / (1024 ** 2) # 返回MB # 增强数据加载函数 def load_data_safely(file_path, usecols=None, dtype=None, chunksize=100000): """安全加载大型CSV文件,优化内存使用""" try: if not os.path.exists(file_path): print(f"⚠️ 文件不存在: {file_path}") return pd.DataFrame() # 自动检测编码 with open(file_path, 'rb') as f: result = chardet.detect(f.read(100000)) encoding = result['encoding'] if result['confidence'] > 0.7 else 'latin1' # 分批读取并优化内存 chunks = [] reader = pd.read_csv( file_path, encoding=encoding, usecols=usecols, dtype=dtype, chunksize=chunksize, low_memory=False ) for chunk in tqdm(reader, desc=f"加载 {os.path.basename(file_path)}"): # 优化分类列内存 for col in chunk.columns: if dtype and col in dtype and dtype[col] == 'category': chunk[col] = chunk[col].astype('category').cat.as_ordered() # 优化数据类型 chunk = optimize_dtypes(chunk) chunks.append(chunk) if chunks: result = pd.concat(chunks, ignore_index=True) # 再次整体优化 result = optimize_dtypes(result) return result return pd.DataFrame() except Exception as e: print(f"⚠️ 加载 {file_path} 失败: {str(e)}") return pd.DataFrame() # 稀疏矩阵转换函数 def to_sparse_matrix(df, columns): """将分类特征转换为稀疏矩阵表示""" sparse_matrices = [] for col in columns: if col in df.columns: # 处理NaN值 df[col] = df[col].fillna('MISSING') # 创建稀疏矩阵 sparse_mat = csr_matrix(pd.get_dummies(df[col], sparse=True).values) sparse_matrices.append(sparse_mat) # 水平堆叠所有稀疏矩阵 if sparse_matrices: return hstack(sparse_matrices) return None # 增量训练函数 def train_incremental(X, y, categorical_features, params, num_rounds=1000, chunk_size=100000): """分块增量训练模型以减少内存占用""" model = None for i in tqdm(range(0, len(X), chunk_size), desc="增量训练"): chunk_end = min(i + chunk_size, len(X)) X_chunk = X.iloc[i:chunk_end] y_chunk = y.iloc[i:chunk_end] train_data = lgb.Dataset( X_chunk, label=y_chunk, categorical_feature=categorical_features ) if model is None: model = lgb.train( params, train_data, num_boost_round=num_rounds, keep_training_booster=True ) else: model = lgb.train( params, train_data, num_boost_round=num_rounds, init_model=model, keep_training_booster=True ) return model # 主处理流程 def main(): """主处理流程,包含完整的内存优化策略""" # 初始内存监控 start_mem = memory_monitor("初始内存") # 定义内存优化的数据类型 dtypes = { 'did': 'category', 'vid': 'category', 'play_time': 'float32' } # 可选特征 optional_features = { 'item_cid': 'category', 'item_type': 'category', 'item_assetSource': 'category', 'item_classify': 'category', 'item_isIntact': 'category', 'sid': 'category', 'stype': 'category' } # 添加特征字段 for i in range(88): dtypes[f'f{i}'] = 'float32' # 加载核心数据 print("开始加载核心数据...") did_features = load_data_safely('did_features_table.csv', dtype=dtypes) vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) memory_monitor("加载核心数据后") # 添加可选特征到dtypes for feature, dtype in optional_features.items(): if not vid_info.empty and feature in vid_info.columns: dtypes[feature] = dtype # 重新加载数据以确保所有列使用正确的数据类型 if os.path.exists('did_features_table.csv'): did_features = load_data_safely('did_features_table.csv', dtype=dtypes) else: print("⚠️ did_features_table.csv 不存在") did_features = pd.DataFrame() if os.path.exists('vid_info_table.csv'): vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) else: print("⚠️ vid_info_table.csv 不存在") vid_info = pd.DataFrame() memory_monitor("重新加载数据后") # 加载历史数据 print("开始加载历史数据...") hist_exposure, hist_click, hist_play = load_historical_data(days=32) memory_monitor("加载历史数据后") # 构建点击数据集 if not hist_exposure.empty and not hist_click.empty: print("构建点击数据集...") click_train_data = build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1) else: print("⚠️ 无法构建点击数据集") click_train_data = pd.DataFrame() memory_monitor("构建点击数据集后") # 添加特征 if not click_train_data.empty: print("开始构建点击特征...") click_train_data = add_click_features( click_train_data, did_features, vid_info, hist_click, hist_play ) else: print("⚠️ 点击数据集为空,跳过特征构建") memory_monitor("添加特征后") # 准备训练数据 if not click_train_data.empty: if 'date' in click_train_data.columns: X = click_train_data.drop(columns=['did', 'vid', 'label', 'date'], errors='ignore') else: X = click_train_data.drop(columns=['did', 'vid', 'label'], errors='ignore') y = click_train_data['label'] else: X, y = pd.DataFrame(), pd.Series() print("⚠️ 点击训练数据为空") # 划分数据集 if not X.empty and not y.empty: X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) else: print("⚠️ 训练数据为空,无法进行模型训练") X_train, X_val, y_train, y_val = pd.DataFrame(), pd.DataFrame(), pd.Series(), pd.Series() memory_monitor("划分数据集后") # 训练模型参数 params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 63, 'learning_rate': 0.05, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'min_child_samples': 100, 'verbosity': -1 } # 增量训练点击模型 if not X_train.empty: print("开始训练点击预测模型...") model_click = train_incremental(X_train, y_train, categorical_features, params, num_rounds=1500, chunk_size=100000) # 在验证集上评估 val_preds = model_click.predict(X_val) val_logloss = log_loss(y_val, val_preds) print(f"验证集LogLoss: {val_logloss:.4f}") else: model_click = None print("⚠️ 训练数据为空,跳过点击预测模型训练") memory_monitor("训练点击模型后") # 构建完播率数据集 print("开始构建完播率数据集...") play_train_data = build_play_dataset(hist_play, vid_info, did_features, hist_click) memory_monitor("构建完播率数据集后") # 训练完播率模型 if not play_train_data.empty: X_play = play_train_data.drop(columns=['did', 'vid', 'play_time', 'item_duration', 'completion_rate'], errors='ignore') y_play = play_train_data['completion_rate'] else: X_play, y_play = pd.DataFrame(), pd.Series() print("⚠️ 完播率训练数据为空") if not X_play.empty and not y_play.empty: X_train_play, X_val_play, y_train_play, y_val_play = train_test_split( X_play, y_play, test_size=0.2, random_state=42 ) else: print("⚠️ 完播率训练数据为空,无法进行模型训练") X_train_play, X_val_play, y_train_play, y_val_play = pd.DataFrame(), pd.DataFrame(), pd.Series(), pd.Series() # 训练参数 params_reg = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 63, 'learning_rate': 0.03, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'lambda_l1': 0.1, 'lambda_l2': 0.1, 'min_data_in_leaf': 50, 'verbosity': -1 } # 增量训练完播率模型 if not X_train_play.empty: print("开始训练完播率模型...") model_play = train_incremental(X_train_play, y_train_play, play_categorical_features, params_reg, num_rounds=2000, chunk_size=100000) # 在验证集上评估 val_preds = model_play.predict(X_val_play) val_mae = mean_absolute_error(y_val_play, val_preds) print(f"验证集MAE: {val_mae:.4f}") else: model_play = None print("⚠️ 训练数据为空,跳过完播率模型训练") memory_monitor("训练完播率模型后") # 保存模型 if model_click: model_click.save_model('click_model.txt') print("点击预测模型已保存") if model_play: model_play.save_model('play_model.txt') print("完播率预测模型已保存") # 预测流程 print("开始加载预测数据...") to_predict_users = load_data_safely('testA_pred_did.csv', dtype={'did': 'category'}) to_predict_exposure = load_data_safely('testA_did_show.csv', dtype={'did': 'category', 'vid': 'category'}) # 执行预测 if not to_predict_users.empty and not to_predict_exposure.empty: print("开始生成预测结果...") submission = predict_for_test_data(to_predict_users, to_predict_exposure, did_features, vid_info) # 保存结果 if not submission.empty: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_file = f'submission_{timestamp}.csv' submission.to_csv(output_file, index=False) print(f"预测结果已保存至: {output_file}") else: print("⚠️ 预测结果为空,未保存文件") else: print("⚠️ 预测数据加载失败,无法生成结果") # 最终内存报告 end_mem = memory_monitor("处理完成") print(f"总内存消耗: {end_mem - start_mem:.2f} MB") # 历史数据加载函数 def load_historical_data(days=32): """高效加载历史数据,支持分批处理""" see_list, click_list, play_list = [], [], [] for day in tqdm(range(1, days + 1), desc="加载历史数据"): day_str = f"{day:02d}" # 加载曝光数据 see_path = f'see_{day_str}.csv' if os.path.exists(see_path): see = load_data_safely(see_path, usecols=['did', 'vid'], dtype={'did': 'category', 'vid': 'category'}) if not see.empty and 'did' in see.columns and 'vid' in see.columns: see_list.append(see) del see gc.collect() # 加载点击数据 click_path = f'click_{day_str}.csv' if os.path.exists(click_path): click = load_data_safely(click_path, usecols=['did', 'vid', 'click_time'], dtype={'did': 'category', 'vid': 'category'}) if not click.empty and 'click_time' in click.columns and 'did' in click.columns and 'vid' in click.columns: # 优化日期处理 click['date'] = pd.to_datetime(click['click_time'], errors='coerce').dt.date click = click.drop(columns=['click_time'], errors='ignore') click_list.append(click[['did', 'vid', 'date']]) del click gc.collect() # 加载播放数据 play_path = f'playplus_{day_str}.csv' if os.path.exists(play_path): play = load_data_safely(play_path, usecols=['did', 'vid', 'play_time'], dtype={'did': 'category', 'vid': 'category'}) if not play.empty and 'play_time' in play.columns and 'did' in play.columns and 'vid' in play.columns: play_list.append(play) del play gc.collect() gc.collect() # 确保返回三个DataFrame return ( pd.concat(see_list).drop_duplicates(['did', 'vid']) if see_list else pd.DataFrame(), pd.concat(click_list).drop_duplicates(['did', 'vid']) if click_list else pd.DataFrame(), pd.concat(play_list).drop_duplicates(['did', 'vid']) if play_list else pd.DataFrame() ) # 点击数据集构建 def build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1): """构建点击数据集,包含负样本采样""" if hist_exposure.empty or hist_click.empty: print("⚠️ 历史曝光或点击数据为空,无法构建数据集") return pd.DataFrame() # 标记正样本 hist_click = hist_click.copy() hist_click['label'] = 1 # 高效标记负样本 exposure_set = set(zip(hist_exposure['did'], hist_exposure['vid'])) click_set = set(zip(hist_click['did'], hist_click['vid'])) # 找出未点击的曝光 negative_set = exposure_set - click_set # 创建负样本DataFrame if negative_set: negative_dids, negative_vids = zip(*negative_set) negative_samples = pd.DataFrame({ 'did': list(negative_dids), 'vid': list(negative_vids), 'label': 0 }) # 采样负样本 if sample_ratio < 1.0: negative_samples = negative_samples.sample(frac=sample_ratio, random_state=42) else: negative_samples = pd.DataFrame(columns=['did', 'vid', 'label']) # 合并数据集 click_data = pd.concat([ hist_click[['did', 'vid', 'label']], negative_samples ], ignore_index=True) # 释放内存 del exposure_set, click_set, negative_set, negative_samples gc.collect() return click_data # 特征工程函数 def add_click_features(df, did_features, vid_info, hist_click, hist_play): """添加关键特征,避免内存溢出""" if df.empty: return df # 基础特征 if not did_features.empty and 'did' in did_features.columns: # 只取需要的列 did_cols = [col for col in did_features.columns if col not in ['did'] or col == 'did'] df = df.merge(did_features[did_cols], on='did', how='left') if not vid_info.empty and 'vid' in vid_info.columns: vid_cols = [col for col in vid_info.columns if col not in ['vid'] or col == 'vid'] df = df.merge(vid_info[vid_cols], on='vid', how='left') # 用户行为统计 if not hist_click.empty and 'did' in hist_click.columns: user_click_count = hist_click.groupby('did').size().rename('user_click_count') df = df.merge(user_click_count, on='did', how='left') else: df['user_click_count'] = 0 if not hist_play.empty and 'did' in hist_play.columns and 'play_time' in hist_play.columns: user_total_play = hist_play.groupby('did')['play_time'].sum().rename('user_total_play') df = df.merge(user_total_play, on='did', how='left') else: df['user_total_play'] = 0 if not hist_click.empty and 'vid' in hist_click.columns: video_click_count = hist_click.groupby('vid').size().rename('video_click_count') df = df.merge(video_click_count, on='vid', how='left') else: df['video_click_count'] = 0 if not hist_play.empty and 'vid' in hist_play.columns and 'play_time' in hist_play.columns: avg_play_time = hist_play.groupby('vid')['play_time'].mean().rename('avg_play_time') df = df.merge(avg_play_time, on='vid', how='left') else: df['avg_play_time'] = 0 # 填充缺失值 fill_values = { 'user_click_count': 0, 'user_total_play': 0, 'video_click_count': df['video_click_count'].median() if 'video_click_count' in df else 0, 'avg_play_time': df['avg_play_time'].median() if 'avg_play_time' in df else 0 } for col, value in fill_values.items(): if col in df: df[col] = df[col].fillna(value) # 添加时间相关特征 if 'date' in df: df['day_of_week'] = pd.to_datetime(df['date']).dt.dayofweek.astype('int8') df['hour'] = pd.to_datetime(df['date']).dt.hour.astype('int8') return df # 预测函数 def predict_for_test_data(test_users, test_exposure, did_features, vid_info): """为测试数据生成预测结果""" if test_users.empty or test_exposure.empty: print("⚠️ 测试数据为空,无法进行预测") return pd.DataFrame() # 合并测试数据 test_data = test_exposure.merge(test_users, on='did', how='left') # 添加特征 test_data = add_click_features( test_data, did_features, vid_info, pd.DataFrame(), # 无历史点击 pd.DataFrame() # 无历史播放 ) # 预测点击率 X_test = test_data.drop(columns=['did', 'vid', 'date'], errors='ignore') click_probs = [] if model_click and not X_test.empty: # 分块预测避免内存问题 click_probs = [] chunk_size = 100000 for i in range(0, len(X_test), chunk_size): chunk = X_test.iloc[i:i+chunk_size] click_probs.extend(model_click.predict(chunk)) else: click_probs = [0.5] * len(test_data) # 默认值 # 预测完播率 completion_rates = [] if model_play and not X_test.empty: # 添加视频时长信息 if not vid_info.empty and 'vid' in vid_info.columns and 'item_duration' in vid_info.columns: test_data = test_data.merge(vid_info[['vid', 'item_duration']], on='vid', how='left') else: test_data['item_duration'] = 1.0 # 分块预测 completion_rates = [] for i in range(0, len(X_test), chunk_size): chunk = X_test.iloc[i:i+chunk_size] completion_rates.extend(model_play.predict(chunk)) else: completion_rates = [0.7] * len(test_data) # 默认值 # 计算综合得分 test_data['click_prob'] = click_probs test_data['completion_rate'] = completion_rates test_data['score'] = test_data['click_prob'] * test_data['completion_rate'] # 为每个用户选择得分最高的视频 submission = test_data.sort_values('score', ascending=False).groupby('did').head(1) # 选择需要的列 submission = submission[['did', 'vid', 'completion_rate']].copy() # 重命名列 submission.columns = ['did', 'vid', 'completion_rate'] # 确保数据格式正确 submission['did'] = submission['did'].astype(str) submission['vid'] = submission['vid'].astype(str) submission['completion_rate'] = submission['completion_rate'].round(4) return submission # 主程序入口 if __name__ == "__main__": main()

<think>我们有一个大型的机器学习训练和预测流程,主要涉及内存优化。代码中已经采取了一些优化措施,如分批读取、数据类型优化、稀疏矩阵转换等。但仍有进一步优化的空间,尤其是在内存占用方面。 优化建议: 1. 使用更高效的数据结构:例如,在构建点击数据集时,使用集合操作可能会消耗大量内存,可以考虑使用分块处理或更高效的方法。 2. 避免不必要的复制:在函数中,尽量避免创建不必要的中间DataFrame,使用inplace操作或直接修改现有DataFrame。 3. 更早地释放内存:在不再需要某些大对象时,显式删除并调用gc.collect()。 4. 使用稀疏矩阵:对于高维分类变量,使用稀疏矩阵可以显著减少内存占用。但注意,LightGBM支持稀疏矩阵输入,我们可以利用这一点。 5. 减少数据类型转换次数:在`load_data_safely`函数中,我们分批读取并优化数据类型,但在主流程中又重新加载,这可能导致两次优化。我们可以考虑只优化一次。 6. 使用更小的数据类型:例如,对于分类特征,使用更小的整数类型(如int8, int16)作为类别编码,而不是使用字符串或category类型(注意:LightGBM可以直接处理category类型,但内部会转换为整数,所以我们可以提前转换为整数编码并指定为更小的数据类型)。 7. 避免在特征工程中创建大型临时对象:例如,在`add_click_features`中,每次合并都会创建一个新的DataFrame,可以考虑使用索引或更高效的方法。 具体优化点: 1. 在`build_click_dataset`函数中,使用集合操作(exposure_set, click_set)可能会在数据量很大时占用大量内存。我们可以考虑分块处理,或者使用DataFrame的合并操作(但合并操作也可能很耗内存)。另一种方法是使用位图(bitmap)或布隆过滤器,但实现复杂。这里我们可以尝试使用分块标记负样本。 2. 在`add_click_features`函数中,我们进行了多次合并(merge)操作,每次合并都会创建一个新的DataFrame。我们可以尝试将多个特征统计合并到一个临时DataFrame,然后只进行一次合并。 3. 在`load_historical_data`函数中,我们按天加载数据并分别存储到列表中,最后再合并。这可能导致内存中同时存在多天的数据。我们可以改为逐天处理并合并到单个DataFrame,然后立即释放当天的数据。 4. 使用稀疏矩阵表示特征:在训练模型时,我们可以将高维的分类特征转换为稀疏矩阵,然后与其他数值特征拼接。这样,整个训练数据可以保存在稀疏矩阵中,减少内存占用。但是,LightGBM可以直接从稀疏矩阵中读取数据,所以我们可以修改训练部分。 5. 在`optimize_dtypes`函数中,我们转换整数列为最小类型,浮点数为float32。但我们可以更激进一些,例如将int8用于小范围的整数(如0-100)。 6. 在`to_sparse_matrix`函数中,我们使用`pd.get_dummies`,这可能会创建非常大的临时DataFrame。我们可以使用`scipy.sparse`中的`OneHotEncoder`来直接创建稀疏矩阵,避免中间DataFrame。 7. 在增量训练函数`train_incremental`中,我们每次迭代都使用整个chunk创建Dataset,但LightGBM支持从稀疏矩阵中构建Dataset,我们可以尝试将每个chunk转换为稀疏矩阵,然后创建Dataset。 8. 在预测部分,我们为每个用户选择最高得分的视频时,使用了排序和groupby,这可能会消耗内存。我们可以使用更高效的方法,例如使用`idxmax`。 由于代码较长,我们不可能重写所有部分,但我们可以针对关键部分进行修改。 下面我们针对几个关键函数进行优化: 优化1:`build_click_dataset` 负样本采样 原方法:将曝光和点击数据转换为集合,然后求差集。当数据量很大时,集合可能占用大量内存。 替代方法:我们可以使用分块的方式标记负样本。具体步骤: - 首先,为曝光数据添加一个标记列(例如1),表示曝光。 - 然后,将点击数据(正样本)与曝光数据合并,通过合并的方式标记哪些是点击(正样本)。 - 最后,从曝光数据中抽取未点击的样本作为负样本。 优化2:在`add_click_features`中,将多个groupby操作合并,减少合并次数。 优化3:在`load_historical_data`中,我们改为逐天加载并合并到主DataFrame后立即删除当天的数据。 优化4:在`optimize_dtypes`中,我们增加对整数列的范围检查,使用更小的整数类型(如int8, int16)。 优化5:在`to_sparse_matrix`中,使用`OneHotEncoder`替代`pd.get_dummies`。 由于时间关系,我们选择几个重点进行优化,并提供修改后的代码。 我们将修改以下函数: 1. `build_click_dataset`:避免使用集合,改用合并标记的方式。 2. `optimize_dtypes`:增加对整数列范围的检查,使用更小的整数类型。 3. 在特征工程中,避免多次合并,尝试一次合并多个特征。 另外,我们注意到在`main`函数中,我们重新加载了数据,这可能是多余的。我们可以移除重新加载的步骤,因为`load_data_safely`已经返回了优化后的数据。 由于代码较长,我们只给出修改后的函数代码,并指出修改点。 修改1:`build_click_dataset`函数 原方法: exposure_set = set(zip(hist_exposure['did'], hist_exposure['vid'])) click_set = set(zip(hist_click['did'], hist_click['vid'])) negative_set = exposure_set - click_set 新方法:使用合并操作标记正样本,然后抽取负样本。注意:合并操作可能会慢,但内存占用更可控。 修改2:`optimize_dtypes`函数,增加对整数范围的判断,使用更小的整数类型。 修改3:`add_click_features`函数,将多个groupby操作合并为一个临时DataFrame,然后一次性合并到主DataFrame。 此外,我们还可以在函数中更早地删除不再需要的变量。 由于代码量很大,我们只展示修改后的函数,并给出解释。 注意:由于无法运行完整代码,这些修改需要在实际环境中测试。 下面是修改后的函数代码: 1. 修改`build_click_dataset`:</think>我将帮助你优化代码以减少内存占用,重点关注关键改进点并提供优化后的代码。以下是针对内存优化的主要修改: ### 优化点分析: 1. **避免数据重复加载**:移除不必要的重新加载操作 2. **稀疏矩阵优化**:更高效处理分类特征 3. **内存释放策略**:显式释放不再需要的对象 4. **增量特征工程**:避免创建大型中间DataFrame 5. **数据类型优化**:更精确控制数据类型 6. **预测流程优化**:减少内存峰值 ### 优化后的代码: ```python import pandas as pd import numpy as np import lightgbm as lgb import gc import os import chardet from sklearn.model_selection import train_test_split from tqdm import tqdm import psutil from sklearn.metrics import log_loss, mean_absolute_error from scipy.sparse import hstack, csr_matrix, save_npz, load_npz import warnings warnings.filterwarnings('ignore') # 内存优化函数 - 增强版 def optimize_dtypes(df, downcast_int=True, downcast_float=True, category_threshold=0.5): """优化DataFrame的数据类型以减少内存占用""" if df.empty: return df # 转换整数列为最小可用类型 if downcast_int: int_cols = df.select_dtypes(include=['int']).columns for col in int_cols: df[col] = pd.to_numeric(df[col], downcast='integer') # 转换浮点列为float32 if downcast_float: float_cols = df.select_dtypes(include=['float']).columns for col in float_cols: # 优先转换为float32而不是downcast='float'以获得更好控制 df[col] = df[col].astype(np.float32) # 转换对象列为分类类型 obj_cols = df.select_dtypes(include=['object']).columns for col in obj_cols: num_unique = df[col].nunique() num_total = len(df) if num_unique / num_total < category_threshold: df[col] = df[col].astype('category') return df # 增强数据加载函数 def load_data_safely(file_path, usecols=None, dtype=None, chunksize=50000, verbose=True): """安全加载大型CSV文件,优化内存使用""" try: if not os.path.exists(file_path): print(f"⚠️ 文件不存在: {file_path}") return pd.DataFrame() # 自动检测编码 with open(file_path, 'rb') as f: result = chardet.detect(f.read(100000)) encoding = result['encoding'] if result['confidence'] > 0.7 else 'latin1' # 获取文件大小用于进度条 file_size = os.path.getsize(file_path) / (1024 ** 2) # MB desc = f"加载 {os.path.basename(file_path)} ({file_size:.1f}MB)" # 分批读取并优化内存 chunks = [] reader = pd.read_csv( file_path, encoding=encoding, usecols=usecols, dtype=dtype, chunksize=chunksize, low_memory=False ) for chunk in tqdm(reader, desc=desc, disable=not verbose): # 优化数据类型 chunk = optimize_dtypes(chunk) chunks.append(chunk) if chunks: result = pd.concat(chunks, ignore_index=True) # 再次整体优化 result = optimize_dtypes(result) return result return pd.DataFrame() except Exception as e: print(f"⚠️ 加载 {file_path} 失败: {str(e)}") return pd.DataFrame() # 稀疏矩阵转换函数 - 优化版 def to_sparse_matrix(df, columns, fillna='MISSING', dtype=np.int8): """将分类特征转换为稀疏矩阵表示""" from sklearn.preprocessing import OneHotEncoder # 预处理数据 sparse_data = df[columns].fillna(fillna).astype(str) # 使用OneHotEncoder替代get_dummies以获得更好性能 encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=True, dtype=dtype) sparse_matrix = encoder.fit_transform(sparse_data) return sparse_matrix, encoder # 增量训练函数 - 优化内存管理 def train_incremental(X, y, categorical_features, params, num_rounds=1000, chunk_size=100000): """分块增量训练模型以减少内存占用""" model = None callbacks = [lgb.early_stopping(stopping_rounds=50, verbose=0), lgb.log_evaluation(period=100)] for i in tqdm(range(0, len(X), chunk_size), desc="增量训练"): chunk_end = min(i + chunk_size, len(X)) # 使用视图避免复制数据 X_chunk = X.iloc[i:chunk_end] y_chunk = y.iloc[i:chunk_end] # 创建数据集后立即释放原始数据 train_data = lgb.Dataset( X_chunk, label=y_chunk, categorical_feature=categorical_features, free_raw_data=True # 训练后释放原始数据 ) if model is None: model = lgb.train( params, train_data, num_boost_round=num_rounds, callbacks=callbacks, keep_training_booster=True ) else: model = lgb.train( params, train_data, num_boost_round=num_rounds, init_model=model, callbacks=callbacks, keep_training_booster=True ) # 显式释放内存 del train_data, X_chunk, y_chunk gc.collect() return model # 历史数据加载函数 - 优化内存 def load_historical_data(days=32, verbose=True): """高效加载历史数据,支持分批处理""" see_list, click_list, play_list = [], [], [] for day in tqdm(range(1, days + 1), desc="加载历史数据", disable=not verbose): day_str = f"{day:02d}" # 加载曝光数据 - 仅加载必要列 see_path = f'see_{day_str}.csv' if os.path.exists(see_path): see = load_data_safely( see_path, usecols=['did', 'vid'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not see.empty: see_list.append(see) del see # 加载点击数据 - 优化日期处理 click_path = f'click_{day_str}.csv' if os.path.exists(click_path): click = load_data_safely( click_path, usecols=['did', 'vid', 'click_time'], dtype={'did': 'category', 'vid': 'category'}, verbose=verbose ) if not click.empty and 'click_time' in click.columns: # 直接解析日期为数值类型 click_dates = pd.to_datetime(click['click_time'], errors='coerce') click['date'] = click_dates.dt.strftime('%Y%m%d').astype('int32') click = click.drop(columns=['click_time']) click_list.append(click[['did', 'vid', 'date']]) del click, click_dates # 加载播放数据 play_path = f'playplus_{day_str}.csv' if os.path.exists(play_path): play = load_data_safely( play_path, usecols=['did', 'vid', 'play_time'], dtype={'did': 'category', 'vid': 'category', 'play_time': 'float32'}, verbose=verbose ) if not play.empty: play_list.append(play) del play gc.collect() # 使用concat时避免创建中间对象 return ( pd.concat(see_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if see_list else pd.DataFrame(), pd.concat(click_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if click_list else pd.DataFrame(), pd.concat(play_list, ignore_index=True, copy=False).drop_duplicates(['did', 'vid']) if play_list else pd.DataFrame() ) # 点击数据集构建 - 内存优化版 def build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1, verbose=True): """构建点击数据集,包含负样本采样 - 内存优化版""" if hist_exposure.empty or hist_click.empty: print("⚠️ 历史曝光或点击数据为空,无法构建数据集") return pd.DataFrame() # 标记正样本 - 使用视图避免复制 pos_samples = hist_click[['did', 'vid']].copy() pos_samples['label'] = 1 # 创建曝光集索引用于高效查找 exposure_index = hist_exposure.set_index(['did', 'vid']).index # 分块处理负样本 neg_chunks = [] chunk_size = 500000 total_rows = len(hist_exposure) for start in tqdm(range(0, total_rows, chunk_size), desc="构建负样本", disable=not verbose): end = min(start + chunk_size, total_rows) chunk = hist_exposure.iloc[start:end] # 使用索引查找未点击的曝光 chunk['is_clicked'] = chunk.set_index(['did', 'vid']).index.isin(hist_click.set_index(['did', 'vid']).index) neg_chunk = chunk[~chunk['is_clicked']][['did', 'vid']] if not neg_chunk.empty and sample_ratio < 1.0: neg_chunk = neg_chunk.sample(frac=sample_ratio, random_state=42) neg_chunks.append(neg_chunk) del chunk, neg_chunk # 合并负样本 neg_samples = pd.concat(neg_chunks, ignore_index=True) neg_samples['label'] = 0 # 合并正负样本 click_data = pd.concat([pos_samples, neg_samples], ignore_index=True, copy=False) # 释放内存 del exposure_index, pos_samples, neg_samples, neg_chunks gc.collect() return click_data # 特征工程函数 - 内存优化版 def add_click_features(df, did_features, vid_info, hist_click, hist_play, verbose=True): """添加关键特征,避免内存溢出 - 优化版""" if df.empty: return df # 1. 合并设备特征 - 仅选择必要列 if not did_features.empty and 'did' in did_features.columns: did_cols = ['did'] + [col for col in did_features.columns if col.startswith('f')] df = df.merge(did_features[did_cols], on='did', how='left') # 2. 合并视频特征 - 仅选择必要列 if not vid_info.empty and 'vid' in vid_info.columns: vid_cols = ['vid', 'item_duration'] + [col for col in vid_info.columns if col in ['item_cid', 'item_type']] df = df.merge(vid_info[vid_cols], on='vid', how='left') # 3. 预聚合统计特征 - 减少重复计算 stats = {} # 用户行为统计 if not hist_click.empty: stats['user_click_count'] = hist_click.groupby('did').size().astype('int32') stats['video_click_count'] = hist_click.groupby('vid').size().astype('int32') if not hist_play.empty: stats['user_total_play'] = hist_play.groupby('did')['play_time'].sum().astype('float32') stats['avg_play_time'] = hist_play.groupby('vid')['play_time'].mean().astype('float32') # 4. 合并统计特征 for name, stat_df in tqdm(stats.items(), desc="添加统计特征", disable=not verbose): if name in df.columns: continue df = df.merge(stat_df.rename(name), how='left', left_on=name.split('_')[1], right_index=True) # 5. 填充缺失值 - 使用更高效的方法 fill_values = { 'user_click_count': 0, 'user_total_play': 0, 'video_click_count': df['video_click_count'].median() if 'video_click_count' in df else 0, 'avg_play_time': df['avg_play_time'].median() if 'avg_play_time' in df else 0, 'item_duration': df['item_duration'].median() if 'item_duration' in df else 30.0 } for col, default in fill_values.items(): if col in df: # 使用inplace填充减少内存分配 df[col].fillna(default, inplace=True) # 6. 添加时间特征 - 使用数值替代分类 if 'date' in df: # 直接计算数值特征,避免创建datetime对象 df['day_of_week'] = (df['date'] % 7).astype('int8') df['is_weekend'] = (df['day_of_week'] >= 5).astype('int8') df.drop(columns=['date'], inplace=True, errors='ignore') return df # 主处理流程 - 内存优化版 def main(): """主处理流程,包含完整的内存优化策略""" # 初始内存监控 start_mem = memory_monitor("初始内存") # 定义内存优化的数据类型 dtypes = { 'did': 'category', 'vid': 'category', 'play_time': 'float32' } # 加载核心数据 print("开始加载核心数据...") did_features = load_data_safely('did_features_table.csv', dtype=dtypes) vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) memory_monitor("加载核心数据后") # 加载历史数据 - 减少加载天数 print("开始加载历史数据...") hist_exposure, hist_click, hist_play = load_historical_data(days=14) # 减少到14天 memory_monitor("加载历史数据后") # 构建点击数据集 if not hist_exposure.empty and not hist_click.empty: print("构建点击数据集...") click_train_data = build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1) # 立即释放不再需要的数据 del hist_exposure, hist_click gc.collect() else: print("⚠️ 无法构建点击数据集") click_train_data = pd.DataFrame() memory_monitor("构建点击数据集后") # 添加特征 - 使用增量方式 if not click_train_data.empty: print("开始构建点击特征...") click_train_data = add_click_features( click_train_data, did_features, vid_info, hist_click if 'hist_click' in locals() else pd.DataFrame(), hist_play ) else: print("⚠️ 点击数据集为空,跳过特征构建") # 立即释放内存 del hist_play gc.collect() memory_monitor("添加特征后") # 准备训练数据 - 使用视图避免复制 if not click_train_data.empty: cols_to_drop = ['did', 'vid', 'label'] if 'date' in click_train_data.columns: cols_to_drop.append('date') X = click_train_data.drop(columns=cols_to_drop, errors='ignore') y = click_train_data['label'] else: X, y = pd.DataFrame(), pd.Series(dtype='float32') print("⚠️ 点击训练数据为空") # 划分数据集 - 使用索引避免复制 if len(X) > 0: indices = np.arange(len(X)) train_idx, val_idx = train_test_split(indices, test_size=0.2, random_state=42, stratify=y) X_train, X_val = X.iloc[train_idx], X.iloc[val_idx] y_train, y_val = y.iloc[train_idx], y.iloc[val_idx] else: print("⚠️ 训练数据为空,无法进行模型训练") X_train, X_val, y_train, y_val = pd.DataFrame(), pd.DataFrame(), pd.Series(), pd.Series() # 释放click_train_data del click_train_data, X, y, indices gc.collect() memory_monitor("划分数据集后") # 训练模型参数 - 调整为更节省内存的参数 params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 31, # 减少叶子节点数 'learning_rate': 0.05, 'feature_fraction': 0.7, # 减少特征使用比例 'bagging_fraction': 0.8, 'bagging_freq': 5, 'min_child_samples': 200, # 增加最小样本数 'verbosity': -1, 'max_depth': -1, # 避免过深 'seed': 42 } # 增量训练点击模型 if len(X_train) > 0: print("开始训练点击预测模型...") model_click = train_incremental(X_train, y_train, [], params, num_rounds=1000, chunk_size=100000) # 在验证集上评估 if len(X_val) > 0: # 分块预测避免内存峰值 chunk_size = 50000 val_preds = [] for i in range(0, len(X_val), chunk_size): chunk = X_val.iloc[i:i+chunk_size] val_preds.extend(model_click.predict(chunk)) val_logloss = log_loss(y_val, val_preds) print(f"验证集LogLoss: {val_logloss:.4f}") else: model_click = None print("⚠️ 训练数据为空,跳过点击预测模型训练") # 释放训练数据 del X_train, X_val, y_train, y_val gc.collect() memory_monitor("训练点击模型后") # 最终内存报告 end_mem = memory_monitor("处理完成") print(f"总内存消耗: {end_mem - start_mem:.2f} MB") # 内存监控函数 def memory_monitor(step_name=""): """监控内存使用情况""" process = psutil.Process(os.getpid()) mem_info = process.memory_info() print(f"{step_name} 内存使用: {mem_info.rss / (1024 ** 2):.2f} MB") return mem_info.rss / (1024 ** 2) # 返回MB if __name__ == "__main__": main() ``` ### 主要优化解释: 1. **数据加载优化**: - 使用更小的块大小(50,000行) - 增强数据类型优化,优先使用float32 - 显示文件大小在进度条中 2. **稀疏矩阵处理**: ```python def to_sparse_matrix(df, columns, fillna='MISSING', dtype=np.int8): # 使用OneHotEncoder替代get_dummies encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=True, dtype=dtype) return encoder.fit_transform(df[columns].fillna(fillna)) ``` 3. **增量训练优化**: ```python # 设置free_raw_data=True释放原始数据 train_data = lgb.Dataset(X_chunk, label=y_chunk, free_raw_data=True) # 添加早停和日志回调 callbacks = [lgb.early_stopping(stopping_rounds=50), lgb.log_evaluation(period=100)] ``` 4. **历史数据处理优化**: ```python # 直接处理为数值日期 click['date'] = pd.to_datetime(click['click_time']).dt.strftime('%Y%m%d').astype('int32') ``` 5. **特征工程优化**: ```python # 预聚合统计特征减少重复计算 stats = {} stats['user_click_count'] = hist_click.groupby('did').size().astype('int32') df = df.merge(stats['user_click_count'].rename('user_click_count'), ...) ``` 6. **内存敏感操作**: ```python # 使用concat时设置copy=False pd.concat(..., copy=False) # 使用视图而非副本 X = click_train_data.drop(columns=cols_to_drop) ``` 7. **预测优化**: ```python # 分块预测避免内存峰值 val_preds = [] for i in range(0, len(X_val), chunk_size): chunk = X_val.iloc[i:i+chunk_size] val_preds.extend(model.predict(chunk)) ``` ### 内存优化策略: 1. **及时释放内存**: - 显式删除不再需要的对象 - 在关键步骤后调用gc.collect() - 使用`del`语句移除大对象引用 2. **减少数据复制**: - 使用DataFrame视图而非副本 - 设置`copy=False`在合并操作中 - 避免创建中间DataFrame 3. **数据类型优化**: - 优先使用category类型处理分类变量 - 使用float32替代float64 - 使用int8/int16替代int64 4. **分块处理**: - 所有大数据操作分块进行 - 训练、预测、特征工程均支持分块 - 合理设置块大小平衡内存和I/O 5. **稀疏表示**: - 高基数字段使用稀疏矩阵 - 使用OneHotEncoder替代get_dummies - LightGBM原生支持稀疏矩阵输入 这些优化能显著降低内存占用,特别是在处理大型数据集时效果更明显。关键是在数据加载、特征工程和模型训练各阶段都实施内存敏感的操作策略。
阅读全文

相关推荐

请帮我检查并完善代码:#步骤1:数据读取与预处理,如果内存不足,可以考虑分批处理或使用Dask等工具。 import pandas as pd import numpy as np import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, mean_absolute_error # 读取数据 did_features = pd.read_csv('did_features_table.csv') # 用户特征 vid_info = pd.read_csv('vid_info_table.csv') # 视频信息 # 历史32天曝光数据see_list(用于构建负样本) # 历史32天点击数据click_list(正样本) # 历史32天播放数据play_list(用于训练完播率模型) def detect_encoding(file_path): with open(file_path, 'rb') as f: result = chardet.detect(f.read(10000)) return result['encoding'], result['confidence'] def load_all_data(days=32): see_list, click_list, play_list = [], [], [] dtypes = {'did': 'category', 'vid': 'category'} for i in range(1, days + 1): day = f"{i:02d}" # 检查文件是否存在 for file_type in [f'see_{day}.csv', f'click_{day}.csv', f'playplus_{day}.csv']: if not os.path.exists(file_type): print(f"⚠️ 警告: 文件 {file_type} 不存在,跳过该天数据") continue try: # 加载 see 数据 see = pd.read_csv(f'see_{day}.csv', encoding='latin1', dtype=dtypes) if 'did' not in see.columns or 'vid' not in see.columns: print(f"⚠️ 警告: see_{day}.csv 缺少必要字段,跳过该天数据") continue see['day'] = day see_list.append(see) del see gc.collect() # 加载 click 数据 click = pd.read_csv( f'click_{day}.csv', encoding='ISO-8859-1', on_bad_lines='skip', dtype=dtypes ) if 'click_time' not in click.columns: print(f"⚠️ 警告: click_{day}.csv 缺少 click_time 字段,跳过该天数据") continue click['date'] = pd.to_datetime(click['click_time'], errors='coerce').dt.date click_list.append(click[['did', 'vid', 'date']]) del click gc.collect() # 加载 play 数据 play = pd.read_csv( f'playplus_{day}.csv', engine='python', encoding_errors='ignore', dtype=dtypes ) if 'play_time' not in play.columns: print(f"⚠️ 警告: playplus_{day}.csv 缺少 play_time 字段,跳过该天数据") continue play_list.append(play[['did', 'vid', 'play_time']]) del play gc.collect() except Exception as e: print(f"⚠️ 加载第 {day} 天数据时出错: {str(e)}") continue # 处理所有天都没有数据的情况 if not see_list: raise ValueError("错误: 未找到任何有效数据,请检查输入文件") all_see = pd.concat(see_list).drop_duplicates(['did', 'vid']) all_click = pd.concat(click_list).drop_duplicates(['did', 'vid']) to_predict_users = pd.read_csv('testA_pred_did.csv') # 待预测用户 to_predict_exposure = pd.read_csv('testA_did_show.csv') # 待预测用户的曝光视频 # 注意:弹幕数据暂不使用 #步骤2:构建点击预测模型的训练集,如果内存不足,可以考虑分批处理或使用Dask等工具。 # 正样本:历史点击数据,标记为1 positive_samples = hist_click[['did', 'vid']].copy() positive_samples['label'] = 1 # 负样本:从历史曝光数据中,排除出现在历史点击数据中的(did, vid) # 注意:同一个用户可能曝光了多个视频,但只点击了部分,所以未点击的就是负样本 # 合并同一个用户的所有曝光和点击,然后取差集 # 方法:对每个用户,曝光视频中不在点击视频列表中的作为负样本 # 注意:数据量可能很大,需要分组操作 # 先获取每个用户点击了哪些视频(did, vid集合) user_clicked_vids = hist_click.groupby('did')['vid'].apply(set).reset_index(name='clicked_set') # 将历史曝光数据与user_clicked_vids合并 hist_exposure_with_click = hist_exposure.merge(user_clicked_vids, on='did', how='left') # 对于每个曝光记录,如果vid不在clicked_set中,则作为负样本 # 注意:如果用户没有点击记录,则clicked_set为NaN,使用空集 hist_exposure_with_click['clicked_set'] = hist_exposure_with_click['clicked_set'].apply(lambda x: x if isinstance(x, set) else set()) hist_exposure_with_click['is_clicked'] = hist_exposure_with_click.apply(lambda row: row['vid'] in row['clicked_set'], axis=1) # 负样本:未点击的曝光记录 negative_samples = hist_exposure_with_click[~hist_exposure_with_click['is_clicked']][['did', 'vid']] negative_samples['label'] = 0 # 合并正负样本 click_train_data = pd.concat([positive_samples, negative_samples], axis=0, ignore_index=True) # 合并用户特征和视频特征 click_train_data = click_train_data.merge(did_features, on='did', how='left') click_train_data = click_train_data.merge(vid_info, on='vid', how='left') # 注意:这里可能会有缺失值(比如视频信息表中没有某个视频的信息),需要填充 # 填充策略:对于数值特征,用中位数或均值;对于类别特征,用众数或特殊值(如-1) #步骤3:特征工程(点击预测模型) video_click_count = hist_click.groupby('vid').size().reset_index(name='video_click_count') click_train_data = click_train_data.merge(video_click_count, on='vid', how='left') click_train_data['video_click_count'].fillna(0, inplace=True) # 对于新视频,用0填充 #步骤4:训练点击预测模型 # 划分训练集和验证集 X = click_train_data.drop(columns=['did', 'vid', 'label']) y = click_train_data['label'] # 将类别特征转换为类别类型(LightGBM可以处理类别特征) categorical_features = ['item_cid', 'item_type', 'item_assetSource', 'item_classify', 'item_isIntact', 'sid', 'stype'] for col in categorical_features: if col in X.columns: X[col] = X[col].astype('category') X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) # 训练LightGBM分类器 params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': 0 } train_data = lgb.Dataset(X_train, label=y_train, categorical_feature=categorical_features, free_raw_data=False) val_data = lgb.Dataset(X_val, label=y_val, categorical_feature=categorical_features, free_raw_data=False) model_click = lgb.train(params, train_data, valid_sets=[train_data, val_data], num_boost_round=1000, early_stopping_rounds=50, verbose_eval=10) # 保存模型 model_click.save_model('click_model.txt') #步骤5:构建完播率预测模型的训练集 # 使用历史播放数据(有播放时长),需要合并视频信息表获取视频时长,然后计算完播率 # 注意:播放时长可能大于视频时长,所以完播率最大为1 play_data = hist_play.merge(vid_info[['vid', 'item_duration']], on='vid', how='left') play_data['completion_rate'] = play_data['play_time'] / play_data['item_duration'] play_data['completion_rate'] = play_data['completion_rate'].clip(upper=1.0) # 超过1的设为1 # 合并用户特征和视频特征 play_train_data = play_data.merge(did_features, on='did', how='left') play_train_data = play_train_data.merge(vid_info.drop(columns=['item_duration']), on='vid', how='left') # 同样,构造统计特征(如用户平均完播率、视频平均完播率等) # 示例:用户平均完播率 user_avg_completion = play_train_data.groupby('did')['completion_rate'].mean().reset_index(name='user_avg_completion') play_train_data = play_train_data.merge(user_avg_completion, on='did', how='left') # 视频平均完播率 video_avg_completion = play_train_data.groupby('vid')['completion_rate'].mean().reset_index(name='video_avg_completion') play_train_data = play_train_data.merge(video_avg_completion, on='vid', how='left') # 填充缺失值 # ... # 特征矩阵 X_play = play_train_data.drop(columns=['did', 'vid', 'play_time', 'item_duration', 'completion_rate']) y_play = play_train_data['completion_rate'] #步骤6:训练完播率预测模型 # 划分训练集和验证集 X_train_play, X_val_play, y_train_play, y_val_play = train_test_split(X_play, y_play, test_size=0.2, random_state=42) # 训练LightGBM回归模型 params_reg = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': 0 } train_data_play = lgb.Dataset(X_train_play, label=y_train_play, categorical_feature=categorical_features, free_raw_data=False) val_data_play = lgb.Dataset(X_val_play, label=y_val_play, categorical_feature=categorical_features, free_raw_data=False) model_play = lgb.train(params_reg, train_data_play, valid_sets=[train_data_play, val_data_play], num_boost_round=1000, early_stopping_rounds=50, verbose_eval=10) # 保存模型 model_play.save_model('play_model.txt')

import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dense, Dropout, Layer from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau from tensorflow.keras.optimizers import Adam from sklearn.model_selection import TimeSeriesSplit import joblib import tensorflow as tf from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dense, Dropout, Layer from tensorflow.keras.optimizers import Adam import warnings import tensorflow as tf warnings.filterwarnings('ignore') # ===== 自定义注意力层 ===== class AttentionLayer(Layer): def __init__(self, **kwargs): super(AttentionLayer, self).__init__(**kwargs) def build(self, input_shape): self.time_steps = input_shape[1] self.feature_dim = input_shape[2] # 创建可训练权重 self.W = self.add_weight( name='att_weight', shape=(self.feature_dim, self.feature_dim), initializer='glorot_uniform', trainable=True ) self.b = self.add_weight( name='att_bias', shape=(self.feature_dim,), initializer='zeros', trainable=True ) self.V = self.add_weight( name='att_v', shape=(self.feature_dim, 1), initializer='glorot_uniform', trainable=True ) super(AttentionLayer, self).build(input_shape) def call(self, inputs): # 计算注意力分数 score = tf.tanh(tf.matmul(inputs, self.W) + self.b) # (batch, time_steps, feature_dim) score = tf.matmul(score, self.V) # (batch, time_steps, 1) score = tf.squeeze(score, axis=-1) # (batch, time_steps) # 应用softmax获取注意力权重 alpha = tf.nn.softmax(score, axis=-1) # (batch, time_steps) alpha = tf.expand_dims(alpha, axis=-1) # (batch, time_steps, 1) # 加权求和 context = tf.reduce_sum(alpha * inputs, axis=1) # (batch, feature_dim) return context def compute_output_shape(self, input_shape): return (input_shape[0], input_shape[2]) # (batch_size, feature_dim) # ===== 模型构建函数 ===== def build_attention_lstm_model(input_shape): time_steps, num_features = input_shape inputs = Input(shape=input_shape) # LSTM层 lstm_out = LSTM(128, return_sequences=True)(inputs) lstm_out = Dropout(0.3)(lstm_out) # 使用自定义注意力层 attention_out = AttentionLayer()(lstm_out) # 红球分支 red_branch = Dense(64, activation='relu')(attention_out) red_branch = Dropout(0.2)(red_branch) red_output = Dense(33, activation='sigmoid', name='red_output')(red_branch) # 蓝球分支 blue_branch = Dense(32, activation='relu')(attention_out) blue_branch = Dropout(0.2)(blue_branch) blue_output = Dense(16, activation='sigmoid', name='blue_output')(blue_branch) model = Model(inputs=inputs, outputs=[red_output, blue_output]) optimizer = Adam(learning_rate=0.001) model.compile( optimizer=optimizer, loss={'red_output': 'binary_crossentropy', 'blue_output': 'binary_crossentropy'}, metrics={'red_output': 'binary_accuracy', 'blue_output': 'binary_accuracy'}, # 关键修改:使用字典形式指定损失权重 loss_weights={'red_output': 0.7, 'blue_output': 0.3} ) model.summary() return model # ===== 数据预处理函数 ===== def step1_format_data(): """格式化原始数据""" print("===== 步骤1: 格式化原始数据 =====") df = pd.read_excel('01hand.xlsx', sheet_name='Sheet1', header=None) # 提取A列和C列数据 new_df = pd.DataFrame({ 'A': pd.to_numeric(df.iloc[:, 0], errors='coerce'), 'B': pd.to_numeric(df.iloc[:, 2], errors='coerce') }).dropna() # 保存新文件 new_df.to_excel('01hand2.xlsx', index=False, header=False) print(f"新表格 '01hand2.xlsx' 创建成功! 包含 {len(new_df)} 行数据") def step2_process_data(): """数据去重和排序""" print("\n===== 步骤2: 数据去重和排序 =====") input_file = "01hand2.xlsx" output_file1 = "02resultA.xlsx" # 降序输出 output_file2 = "02resultB.xlsx" # 升序输出 # 读取数据并转换为长格式 df = pd.read_excel(input_file, header=None) all_values = df.stack().dropna().astype(str).tolist() # 确保数据长度是8的倍数 valid_length = len(all_values) - (len(all_values) % 8) if len(all_values) != valid_length: print(f"警告: 数据总量 {len(all_values)} 不符合8的倍数, 截断至 {valid_length} 个元素") all_values = all_values[:valid_length] # 转换数据格式 new_data = [] for i in range(0, len(all_values), 8): group = all_values[i:i+8] try: # 转换日期和数字 date = int(group[0]) numbers = [int(float(num)) if '.' in num else int(num) for num in group[1:]] new_data.append([date] + numbers) except: continue # 创建DataFrame并去重 columns = ['日期', '数字1', '数字2', '数字3', '数字4', '数字5', '数字6', '数字7'] df = pd.DataFrame(new_data, columns=columns) df = df.drop_duplicates(subset='日期').dropna() # 保存降序文件 df.sort_values('日期', ascending=False).to_excel(output_file1, index=False) print(f"降序文件保存至: {output_file1}") # 保存升序文件 df.sort_values('日期', ascending=True).to_excel(output_file2, index=False) print(f"升序文件保存至: {output_file2}") print(f"最终数据维度: {df.shape}") return df # ===== 特征工程函数 ===== def create_features(df, save_features=True): """创建模型特征并保存特征处理器""" print("\n===== 步骤3: 特征工程 =====") features = df[['日期']].copy() red_cols = ['数字1', '数字2', '数字3', '数字4', '数字5', '数字6'] # 基础特征 features['红球和值'] = df[red_cols].sum(axis=1) features['蓝球值'] = df['数字7'] features['奇偶比'] = df[red_cols].applymap(lambda x: x % 2).sum(axis=1) features['大小比'] = df[red_cols].applymap(lambda x: 1 if x > 16 else 0).sum(axis=1) # 窗口特征 (窗口大小10) window_size = 10 for col in ['红球和值', '奇偶比', '大小比']: features[f'{col}_MA{window_size}'] = features[col].rolling(window=window_size).mean() features[f'{col}_STD{window_size}'] = features[col].rolling(window=window_size).std() # 滞后特征 (滞后1-9期) for lag in range(1, 10): for col in red_cols + ['数字7']: features[f'{col}_lag{lag}'] = df[col].shift(lag) # 目标变量 (下一期开奖结果) red_targets = [] blue_targets = [] for i in range(len(df) - 1): next_row = df.iloc[i + 1] # 红球目标 (33选6) red_target = [1 if num in next_row[red_cols].values else 0 for num in range(1, 34)] # 蓝球目标 (16选1) blue_target = [1 if i == next_row['数字7'] else 0 for i in range(1, 17)] red_targets.append(red_target) blue_targets.append(blue_target) # 转换为numpy数组 red_targets = np.array(red_targets) blue_targets = np.array(blue_targets) # 移除无效数据 (前window_size行和最后一行) features = features.iloc[window_size:-1].reset_index(drop=True) red_targets = red_targets[window_size-1:-1] # 对齐索引 blue_targets = blue_targets[window_size-1:-1] # 保存特征处理器 feature_columns = features.drop(columns=['日期']).columns.tolist() joblib.dump(feature_columns, 'feature_columns.pkl') print(f"特征列名已保存: {len(feature_columns)}个特征") if save_features: features.to_excel('04_features.xlsx', index=False) print(f"特征工程完成, 维度: {features.shape}") return features, red_targets, blue_targets # ===== 模型构建函数 ===== def prepare_data(features, red_targets, blue_targets): """准备训练数据并保存数据处理器""" print("\n===== 步骤4: 数据准备 =====") scaler_X = MinMaxScaler() X_scaled = scaler_X.fit_transform(features.drop(columns=['日期'])) # 保存特征处理器 joblib.dump(scaler_X, 'scaler_X.save') print("特征缩放器已保存") # 创建时间序列数据 time_steps = 10 X_seq, y_red_seq, y_blue_seq = [], [], [] for i in range(time_steps, len(X_scaled)): X_seq.append(X_scaled[i-time_steps:i, :]) y_red_seq.append(red_targets[i-1]) # 使用当前时间步的目标 y_blue_seq.append(blue_targets[i-1]) X_seq = np.array(X_seq) y_red_seq = np.array(y_red_seq) y_blue_seq = np.array(y_blue_seq) print(f"时间序列数据形状: X={X_seq.shape}, y_red={y_red_seq.shape}, y_blue={y_blue_seq.shape}") # 保存历史数据用于预测 joblib.dump(X_scaled[-10:], 'historical_data.pkl') print("历史数据已保存用于预测") return X_seq, y_red_seq, y_blue_seq, scaler_X # ===== 模型训练函数 ===== def train_model(X, y_red, y_blue): """训练模型""" print("\n===== 步骤5: 模型训练 =====") best_models = [] tscv = TimeSeriesSplit(n_splits=3) for fold, (train_index, val_index) in enumerate(tscv.split(X)): print(f"\n===== 训练 Fold {fold+1}/3 =====") X_train, X_val = X[train_index], X[val_index] y_red_train, y_red_val = y_red[train_index], y_red[val_index] y_blue_train, y_blue_val = y_blue[train_index], y_blue[val_index] model = build_attention_lstm_model((X_train.shape[1], X_train.shape[2])) callbacks = [ EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1), ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=7, min_lr=1e-6, verbose=1) ] history = model.fit( X_train, {'red_output': y_red_train, 'blue_output': y_blue_train}, epochs=100, batch_size=32, validation_data=(X_val, {'red_output': y_red_val, 'blue_output': y_blue_val}), callbacks=callbacks, verbose=1 ) model.save(f'best_model_fold{fold+1}.h5') best_models.append(model) # 保存训练历史图 plot_training_history(history, fold+1) return best_models def plot_training_history(history, fold): """绘制训练历史图表""" plt.figure(figsize=(15, 10)) # 损失曲线 plt.subplot(2, 2, 1) plt.plot(history.history['loss'], label='训练损失') plt.plot(history.history['val_loss'], label='验证损失') plt.title(f'Fold {fold} - 总损失曲线') plt.ylabel('损失') plt.xlabel('Epoch') plt.legend() # 红球准确率 plt.subplot(2, 2, 2) plt.plot(history.history['red_output_binary_accuracy'], label='红球训练准确率') plt.plot(history.history['val_red_output_binary_accuracy'], label='红球验证准确率') plt.title(f'Fold {fold} - 红球准确率') plt.ylabel('准确率') plt.xlabel('Epoch') plt.legend() # 蓝球准确率 plt.subplot(2, 2, 3) plt.plot(history.history['blue_output_binary_accuracy'], label='蓝球训练准确率') plt.plot(history.history['val_blue_output_binary_accuracy'], label='蓝球验证准确率') plt.title(f'Fold {fold} - 蓝球准确率') plt.ylabel('准确率') plt.xlabel('Epoch') plt.legend() # 学习率 plt.subplot(2, 2, 4) if 'lr' in history.history: plt.plot(history.history['lr'], label='学习率') plt.title(f'Fold {fold} - 学习率变化') plt.ylabel('学习率') plt.xlabel('Epoch') plt.legend() plt.tight_layout() plt.savefig(f'training_history_fold{fold}.png') plt.close() # ===== 预测准备函数 ===== def prepare_prediction_input(df, features, scaler_X): """准备预测输入,确保特征一致性""" print("\n===== 准备预测输入 =====") # 加载特征列名 feature_columns = joblib.load('feature_columns.pkl') print(f"预期特征数量: {len(feature_columns)}") # 创建空DataFrame prediction_features = pd.DataFrame(columns=feature_columns) # 获取最后10行有效数据 last_10 = features.iloc[-10:] # 填充基础特征 red_cols = ['数字1', '数字2', '数字3', '数字4', '数字5', '数字6'] current_row = df.iloc[-1] prediction_features.loc[0, '红球和值'] = current_row[red_cols].sum() prediction_features.loc[0, '蓝球值'] = current_row['数字7'] prediction_features.loc[0, '奇偶比'] = current_row[red_cols].apply(lambda x: x % 2).sum() prediction_features.loc[0, '大小比'] = current_row[red_cols].apply(lambda x: 1 if x > 16 else 0).sum() # 填充窗口特征 window_size = 10 for col in ['红球和值', '奇偶比', '大小比']: col_values = features[col].iloc[-window_size:] prediction_features.loc[0, f'{col}_MA{window_size}'] = col_values.mean() prediction_features.loc[0, f'{col}_STD{window_size}'] = col_values.std() # 填充滞后特征 - 修正逻辑 for lag in range(1, 10): # 确保滞后索引有效 lag_index = -lag - 1 # 从当前行向前追溯 for col in red_cols + ['数字7']: feature_name = f'{col}_lag{lag}' if feature_name in feature_columns: if len(df) > lag: prediction_features.loc[0, feature_name] = df[col].iloc[lag_index] else: # 数据不足时使用平均值 prediction_features.loc[0, feature_name] = df[col].mean() # 处理缺失特征 missing_cols = set(feature_columns) - set(prediction_features.columns) for col in missing_cols: prediction_features[col] = 0 # 默认填充0 # 确保顺序一致 prediction_features = prediction_features[feature_columns] # 标准化 X_pred = scaler_X.transform(prediction_features) print(f"预测输入形状: {X_pred.shape}") return X_pred # ===== 预测函数 ===== def predict_next_period(models): """预测下一期开奖结果""" print("\n===== 步骤6: 预测 =====") # 加载历史数据和缩放器 scaler_X = joblib.load('scaler_X.save') historical_data = joblib.load('historical_data.pkl') # 获取特征列名 feature_columns = joblib.load('feature_columns.pkl') n_features = len(feature_columns) # 构建时间序列输入 time_steps = 10 if len(historical_data) >= time_steps: # 使用完整的历史数据 X_seq = historical_data[-time_steps:].reshape(1, time_steps, n_features) else: # 数据不足时复制最后一个时间点 X_seq = np.repeat(historical_data[-1:], time_steps, axis=0).reshape(1, time_steps, n_features) print(f"模型输入序列形状: {X_seq.shape}") # 集成模型预测 red_probs = np.zeros((1, 33)) blue_probs = np.zeros((1, 16)) total_weight = 0 for i, model in enumerate(models): r_prob, b_prob = model.predict(X_seq, verbose=0) weight = 0.3 + i * 0.2 # 加权集成 red_probs += r_prob * weight blue_probs += b_prob * weight total_weight += weight # 归一化概率 red_probs /= total_weight blue_probs /= total_weight # 获取预测结果 red_indices = np.argsort(red_probs[0])[::-1][:6] blue_indices = np.argsort(blue_probs[0])[::-1][:3] return ( [i+1 for i in red_indices], [red_probs[0][i] for i in red_indices], [i+1 for i in blue_indices], [blue_probs[0][i] for i in blue_indices] ) # ===== 主函数 ===== def main(): # 执行数据处理步骤 step1_format_data() df = step2_process_data() # 特征工程 features, red_targets, blue_targets = create_features(df) # 准备训练数据 X, y_red, y_blue, scaler_X = prepare_data(features, red_targets, blue_targets) # 训练模型 models = train_model(X, y_red, y_blue) # 预测 red_nums, red_probs, blue_nums, blue_probs = predict_next_period(models) # 打印结果 print("\n" + "="*50) print("双色球下一期预测结果") print("="*50) print("\n红球预测 (前6个):") for num, prob in zip(red_nums, red_probs): print(f"号码 {num:2d} : 概率 {prob:.4f}") print("\n蓝球预测 (前3个):") for num, prob in zip(blue_nums, blue_probs): print(f"号码 {num:2d} : 概率 {prob:.4f}") # 保存结果 result_df = pd.DataFrame({ '红球预测': red_nums, '红球概率': red_probs, '蓝球预测': blue_nums, '蓝球概率': blue_probs }) result_df.to_excel('prediction_results.xlsx', index=False) print("\n预测结果已保存至: prediction_results.xlsx") if __name__ == "__main__": main() # main.py import tensorflow as tf from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dense, Dropout, Layer from tensorflow.keras.optimizers import Adam # 内联定义自定义层(避免导入问题) class AttentionLayer(Layer): """自定义注意力层""" def __init__(self, **kwargs): super(AttentionLayer, self).__init__(**kwargs) def build(self, input_shape): self.time_steps = input_shape[1] self.feature_dim = input_shape[2] self.W = self.add_weight( name='att_weight', shape=(self.feature_dim, self.feature_dim), initializer='glorot_uniform', trainable=True ) self.b = self.add_weight( name='att_bias', shape=(self.feature_dim,), initializer='zeros', trainable=True ) self.V = self.add_weight( name='att_v', shape=(self.feature_dim, 1), initializer='glorot_uniform', trainable=True ) super(AttentionLayer, self).build(input_shape) def call(self, inputs): score = tf.tanh(tf.matmul(inputs, self.W) + self.b) score = tf.matmul(score, self.V) score = tf.squeeze(score, axis=-1) alpha = tf.nn.softmax(score, axis=-1) alpha = tf.expand_dims(alpha, axis=-1) context = tf.reduce_sum(alpha * inputs, axis=1) return context def compute_output_shape(self, input_shape): return (input_shape[0], input_shape[2]) # 模型构建函数 def build_attention_lstm_model(input_shape): inputs = Input(shape=input_shape) # LSTM层 lstm_out = LSTM(128, return_sequences=True)(inputs) lstm_out = Dropout(0.3)(lstm_out) # 使用自定义层 attention_out = AttentionLayer()(lstm_out) # 输出分支 red_branch = Dense(64, activation='relu')(attention_out) red_output = Dense(33, activation='sigmoid', name='red_output')(red_branch) blue_branch = Dense(32, activation='relu')(attention_out) blue_output = Dense(16, activation='sigmoid', name='blue_output')(blue_branch) model = Model(inputs=inputs, outputs=[red_output, blue_output]) model.compile( optimizer=Adam(0.001), loss={'red_output': 'binary_crossentropy', 'blue_output': 'binary_crossentropy'}, # 这里也需要修改为字典形式 loss_weights={'red_output': 0.7, 'blue_output': 0.3} ) return model # 测试模型构建 if __name__ == "__main__": model = build_attention_lstm_model(input_shape=(10, 20)) model.summary() print("模型构建成功!") #请优化代码,当前报错:All arrays must be of the same length

""" 第一步读取数据,取出我们想要的指标 服务器地址:localhost 数据库:bdpd_rmv 账户名:root 密码:123456 端口:3306 字符编码:'utf8mb4' 表名:RMV_KDSQ_MODEL_INDEX_INFO 提取指定字段:SCORE,BAD_FLAG """ import pandas as pd from sqlalchemy import create_engine import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.font_manager as fm import numpy as np def calKs(): # 数据库连接配置 db_config = { 'host': 'localhost', # 服务器地址 'port': 3306, # 端口 'user': 'root', # 账户名 'password': '123456', # 密码 'database': 'bdpd_rmv', # 数据库名 'charset': 'utf8mb4' # 字符编码(默认使用utf8mb4) } # 步骤1: 创建数据库连接 # 使用SQLAlchemy创建MySQL连接引擎 # 格式: mysql+pymysql://用户名:密码@主机:端口/数据库名 engine = create_engine( f"mysql+pymysql://{db_config['user']}:{db_config['password']}@{db_config['host']}:{db_config['port']}/{db_config['database']}?charset={db_config['charset']}" ) # 步骤2: 读取数据并提取指定字段 query = "SELECT * FROM RMV_KDSQ_MODEL_INDEX_INFO" # SQL查询语句,获取所有列 data = pd.read_sql(query, engine) # 执行查询并读取到DataFrame # 将列名转换为大写(确保字段名一致性) data.columns = data.columns.str.upper() # 检查数据 print(f"成功读取 {len(data)} 条记录") print(data.head()) # 打印前5行数据(用于调试) # 修复步骤1: 确保所有列使用正确数据类型 # 将SCORE转换为浮点数(无法转换的设为NaN) data['SCORE'] = pd.to_numeric(data['SCORE'], errors='coerce') # 将BAD_FLAG转换为整数(0/1) # 处理非数值数据:无法转换的设为NaN,然后用0填充,最后转换为整数 data['BAD_FLAG'] = pd.to_numeric(data['BAD_FLAG'], errors='coerce').fillna(0).astype(int) # 定义一个新的分组,按照FLAG和MONITOR_DT做分组 cols = ['FLAG', 'MONITOR_DT'] # 分组依据的列 # 按FLAG和MONITOR_DT分组,取每组第一条记录(实际只需要分组标识) modelInfo = data[cols].groupby(by=cols).first().reset_index() # 获取分组标识列表 flag_list = modelInfo['FLAG'].to_list() monitor_list = modelInfo['MONITOR_DT'].to_list() print(flag_list, monitor_list) # 打印分组标识(调试用) # 获取分组数量 modelInfo_len = len(flag_list) # 遍历每个分组 for i in range(modelInfo_len): print(flag_list[i], monitor_list[i]) # 打印当前分组信息 # 筛选当前分组的数据(三种方法) # 方法1: 使用where(不推荐,会保留所有行,不符合条件的设为NaN) # df = data.where((data['FLAG'] == flag_list[i]) & (data['MONITOR_DT'] == monitor_list[i])) # 方法2: 布尔索引(推荐) # df = data[(data['FLAG'] == flag_list[i]) & (data['MONITOR_DT'] == monitor_list[i])] # 方法3: 使用query(当前代码使用的方法) # 注意:字符串类型的值需要加引号,但这里flag_list[i]可能是数值类型,可能导致错误 df = data.query(f"FLAG == '{flag_list[i]}' and MONITOR_DT == '{monitor_list[i]}'") # 选择需要的列(SCORE和BAD_FLAG) df = df[['SCORE', 'BAD_FLAG']] # 步骤2: 按SCORE升序排序(排序后重置索引) df.sort_values('SCORE', ascending=True, inplace=True) df.reset_index(drop=True, inplace=True) # 重置索引,丢弃原索引 # 添加行号(从1开始) df['row_number'] = range(1, len(df) + 1) # 步骤3: 计算基本统计量 total_samples = len(df) # 总样本数 total_bad = df['BAD_FLAG'].sum() # 坏样本总数 total_good = total_samples - total_bad # 好样本总数 # 步骤4: 计算累计值 df['cum_bad'] = df['BAD_FLAG'].cumsum() # 累计坏样本数 df['GOOD_FLAG'] = 1 - df['BAD_FLAG'] # 创建好样本标志(1-BAD_FLAG) df['cum_good'] = df['GOOD_FLAG'].cumsum() # 累计好样本数 # 计算累计占比(避免除零错误) df['cum_bad_pct'] = df['cum_bad'] / total_bad if total_bad > 0 else 0 df['cum_good_pct'] = df['cum_good'] / total_good if total_good > 0 else 0 # 步骤5: 计算KS值(好坏占比差值的最大绝对值) df['cum_diff'] = abs(df['cum_bad_pct'] - df['cum_good_pct']) # 计算每个点的差值 # 找到最大差值(即KS值) ks_value = df['cum_diff'].max() # 找到KS值对应的分数点(使用idxmax获取最大差值的索引) print(i, '找到KS值对应的分数点:', df['cum_diff'].idxmax()) # ks_point = df.loc[df['cum_diff'].idxmax(), 'SCORE'] # ks_point=df.query(f'cum_diff=={ks_value}')['row_number'].to_list()[0] ks_point=np.argmax(df['cum_diff'])+1 # 输出结果 print(f"\nKS值计算结果:") print(f"最大KS值: {ks_value:.4f}") print(f"KS值对应的分数点: {ks_point}") print(df) # 绘图(传入当前分组的相关数据) ks_plot(ks=ks_value, x=df['row_number'], y1=df['cum_bad_pct'], y2=df['cum_good_pct'], ks_point=ks_point, num=i) def ks_plot(ks, x, y1, y2, ks_point, num): """绘制KS曲线图""" # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'KaiTi', 'SimSun'] # 常用中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 创建图形 plt.figure(figsize=(10, 6)) # 绘制两条曲线 plt.plot(x, y1, label='累计坏客户占比') # 坏客户累计占比曲线 plt.plot(x, y2, label='累计好客户占比') # 好客户累计占比曲线 # 注释:当前代码中KS点标记被注释掉了 plt.axvline(x=ks_point, color='r', linestyle='--', label=f'KS点(row_number={ks_point})') # 设置图表标题和标签 plt.title(f'KS曲线 (KS值={ks:.4f})') plt.xlabel('样本数') # x轴标签 plt.ylabel('累计占比') # y轴标签 # 添加图例和网格 plt.legend() plt.grid(True) # 保存和显示图像 # plt.savefig(f'ks_{num}.png') # 保存为图片(文件名包含分组编号) plt.show() # 展示图片 if __name__ == '__main__': # 程序入口 calKs() 给我逐行注释

import numpy as np from tqdm import tqdm import tensorflow as tf import pandas as pd import torch import re from sklearn.model_selection import train_test_split CHEM_FORMULA_SIZE = "([A-Z][a-z]*)([0-9]*)" VALID_ELEMENTS = [ "C", "N", "P", "O", "S", "Si", "I", "H", "Cl", "F", "Br", "B", "Se", "Fe", "Co", "As", "K", "Na", ] ELEMENT_VECTORS = np.eye(len(VALID_ELEMENTS)) element_to_position = dict(zip(VALID_ELEMENTS, ELEMENT_VECTORS)) def formula_to_dense(chem_formula: str) -> np.ndarray: total_onehot = [] for (chem_symbol, num) in re.findall(CHEM_FORMULA_SIZE, chem_formula): num = 1 if num == "" else int(num) one_hot = element_to_position[chem_symbol].reshape(1, -1) one_hot_repeats = np.repeat(one_hot, repeats=num, axis=0) total_onehot.append(one_hot_repeats) if len(total_onehot) == 0: dense_vec = np.zeros(len(element_to_position)) else: dense_vec = np.vstack(total_onehot).sum(0) return dense_vec def sine_embed(v, max_count=256): num_freqs = int(np.ceil(np.log2(max_count))) freqs = 0.5 ** torch.arange(num_freqs, dtype=torch.float32) * np.pi v_tensor = torch.tensor(v, dtype=torch.float32)[:, None] embedded = torch.sin(v_tensor * freqs[None, :]) return torch.abs(embedded).numpy() def encode_formula(formula: str): candidate_features = formula_to_dense(formula) # 将单个化学式转为特征向量 sine_embeddings = sine_embed(candidate_features) return sine_embeddings.flatten() def positional_encoding(max_position, d_model, min_freq=1e-6): position = np.arange(max_position) freqs = min_freq**(2*(np.arange(d_model)//2)/d_model) pos_enc = position.reshape(-1,1)*freqs.reshape(1,-1) pos_enc[:, ::2] = np.cos(pos_enc[:, ::2]) pos_enc[:, 1::2] = np.sin(pos_enc[:, 1::2]) return pos_enc P=positional_encoding(2000000,256, min_freq=1e2) dimn=256 def encoding(rag_tensor,P,dimn): to_pad=[] for sample in rag_tensor: all_dim=[sample[0].numpy().tolist()] pos_enc=[P[int(i)-1] for i in sample[1].numpy().tolist()] for dim in range(dimn): dim_n=[i[dim] for i in pos_enc] all_dim.append(dim_n) to_pad.append(all_dim) to_pad=[tf.keras.preprocessing.sequence.pad_sequences(i,maxlen=501,dtype='float32',padding='post',truncating='post',value=10) for i in to_pad] to_pad=np.stack((to_pad)) to_pad=np.swapaxes(to_pad, 1, -1) return to_pad def trun_n_d(n,d): return ( n if not n.find('.')+1 else n[:n.find('.')+d+1] ) def prepro_specs_train(df): df = df.reset_index(drop=True) valid = [] mz_intensity = df['Spectrum'].to_list() def process_line(line): pairs = line.split() mz_list = [] intensity_list = [] for pair in pairs: mz, intensity = pair.split(':') mz_list.append(float(mz)) intensity_list.append(float(intensity)) return mz_list, intensity_list for idx, intensities in tqdm(enumerate(mz_intensity)): mz_list, intensity_list = process_line(intensities) mz_list.append(float(df.at[idx, 'Total Exact Mass'])) round_mz_list = [round(float(mz), 2) for mz in mz_list] round_intensity_list = [round(float(intensity), 2) for intensity in intensity_list] valid.append([round_mz_list, round_intensity_list]) return tf.ragged.constant(valid) import json import torch from typing import Dict, List from torch.utils.data import Dataset import transformers from peft import LoraConfig, TaskType, get_peft_model from torch.utils.data import DataLoader, SequentialSampler from transformers import Trainer, TrainingArguments from lora_plus import LoraPlusTrainer from torch.utils.data import RandomSampler def infer_seqlen(source_len: int, target_len: int, cutoff_len: int) -> tuple[int, int]: if target_len * 2 < cutoff_len: # truncate source max_target_len = cutoff_len elif source_len * 2 < cutoff_len: # truncate target max_target_len = cutoff_len - source_len else: # truncate both max_target_len = int(cutoff_len * (target_len / (source_len + target_len))) new_target_len = min(max_target_len , target_len) max_source_len = max(cutoff_len - new_target_len, 0) new_source_len = min(max_source_len, source_len) return new_source_len, new_target_len class SupervisedDataset(Dataset): """Dataset for supervised fine-tuning.""" def __init__( self, data_path, tokenizer, model_max_length, user_tokens=[151644], assistant_tokens=[151645], ): super(SupervisedDataset, self).__init__() self.data = json.load(open(data_path)) self.tokenizer = tokenizer self.model_max_length = model_max_length self.user_tokens = user_tokens self.assistant_tokens = assistant_tokens self.ignore_index = -100 # 测试第一条数据是否正确处理 item = self.preprocessing(self.data[0]) print("input:", self.tokenizer.decode(item["input_ids"])) labels = [id_ for id_ in item["labels"] if id_ != -100] # 过滤 -100 的标签 def __len__(self): return len(self.data) def preprocessing(self, example): input_ids = [] labels = [] # 将用户和助手的内容配对 messages = example["conversations"] pairs = [] current_user_encoded = None # 将 user 和 assistant 配对,并将其打包成编码后的 pairs for message in messages: if message["role"] == "user": # 编码用户消息 current_user_encoded = [self.tokenizer.bos_token_id] + self.user_tokens + self.tokenizer.encode( message["content"], add_special_tokens=False ) elif message["role"] == "assistant" and current_user_encoded is not None: # 编码助手消息 assistant_encoded = self.assistant_tokens + self.tokenizer.encode( message["content"], add_special_tokens=False ) # 配对形成一个 (source_ids, target_ids) pairs.append((current_user_encoded, assistant_encoded)) current_user_encoded = None total_length = 0 # 初始化总长度 # 逐对处理编码后的 (source_ids, target_ids) for turn_idx, (source_ids, target_ids) in enumerate(pairs): # 检查是否超出最大长度,若超出则停止处理 if total_length >= self.model_max_length: print("Exceeded max length, stopping processing further turns.") break # 动态截断长度 source_len, target_len = infer_seqlen( len(source_ids), len(target_ids), self.model_max_length - total_length ) source_ids = source_ids[:source_len] target_ids = target_ids[:target_len] # 更新总长度 total_length += source_len + target_len source_label = [self.tokenizer.bos_token_id] + [self.ignore_index] * (source_len-1) target_label = target_ids # 数据拼接 input_ids += source_ids + target_ids labels += source_label + target_label # 添加 EOS 标记 input_ids += [self.tokenizer.eos_token_id] labels += [self.tokenizer.eos_token_id] input_ids += [self.tokenizer.pad_token_id] * ( self.model_max_length - len(input_ids) ) labels += [self.ignore_index] * (self.model_max_length - len(labels)) # 转换为 Tensor input_ids = torch.LongTensor(input_ids) labels = torch.LongTensor(labels) # 构造 attention_mask attention_mask = attention_mask = input_ids.ne(self.tokenizer.pad_token_id) return { "input_ids": input_ids, "labels": labels, "attention_mask": attention_mask, } def __getitem__(self, idx) -> Dict[str, torch.Tensor]: return self.preprocessing(self.data[idx])修改代码,改为读取csv文件,提取csv文件的Molecular Formula、Total Exact Mass、Spectrum以及SELFIES列数据,对Molecular Formula使用formula_to_dense函数获取形状为(batch,18)的输入,对Total Exact Mass和Spectrum列使用prepro_specs_train和encoding函数获取形状为(batch,501,257)的输入,然后SELFIES使用tokenrizer编码,使用cls_token和sep_token作为开始和结束标记,填充标记为pad_token

--------------------------------------------------------------------------- KeyError Traceback (most recent call last) /var/folders/ft/w9s_5yts2410m9cnr40wmf000000gn/T/ipykernel_26172/3475412424.py in ?() 21 # 创建基础时间框架 22 future = model.make_future_dataframe(periods=len(future_weather), freq='15T') 23 24 # 关键修正:合并未来天气特征数据 ---> 25 future = future.merge( 26 combined_df[feature_cols].reset_index(), # 包含标准化后的特征 27 on='ds', 28 how='left' ~/miniconda3/lib/python3.10/site-packages/pandas/core/frame.py in ?(self, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate) 10828 validate: MergeValidate | None = None, 10829 ) -> DataFrame: 10830 from pandas.core.reshape.merge import merge 10831 > 10832 return merge( 10833 self, 10834 right, 10835 how=how, ~/miniconda3/lib/python3.10/site-packages/pandas/core/reshape/merge.py in ?(left, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate) 166 validate=validate, 167 copy=copy, 168 ) 169 else: --> 170 op = _MergeOperation( 171 left_df, 172 right_df, 173 how=how, ~/miniconda3/lib/python3.10/site-packages/pandas/core/reshape/merge.py in ?(self, left, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, indicator, validate) 790 self.right_join_keys, 791 self.join_names, 792 left_drop, 793 right_drop, --> 794 ) = self._get_merge_keys() 795 796 if left_drop: 797 self.left = self.left._drop_labels_or_levels(left_drop) ~/miniconda3/lib/python3.10/site-packages/pandas/core/reshape/merge.py in ?(self) 1293 # Then we're either

条件组合MySQL数据库密码是220603,我提供一个excel文档,我可以将这个文档进行导入到我的HTML页面里面的导入按钮,端口号为5008 导入功能:店铺管理(添加,修改,删除店铺的功能),通过输入店铺码和店铺名称来添加,添加完成之后会有一个进行导入店铺数据功能,这个就通过一个选择文件(如excel和csv),我认为在MySQL数据库里面创建表太麻烦了,只想创建一个数据库名字为shop_order,然后根据导入文件的第一行的所有表名字作为关键字创建所对应的MySQL表放在shop——oeder下面(如将Paid Time自动识别为DATETIME类型),切记这个关键字就是导入文档表的第一行,并且下面都是该关键字所对应的所有数据,由于我所做的系统需要导入三个文档,所以要设置三个导入数据的按钮,分别为All order,in come,creater order这三个表所对应了所有订单数据,收入订单数据和达人订单数据(也就是通过达人带货卖出去的订单)。 查询功能:在导入需要的数据之后,我们需要进行数据查询,目前先通过店铺码和店铺名称查询店铺,查询成功后进入店铺,这个时候我们需要再查询店铺的数据,这个时候就需要通过查询我们目前需要的数据,有最开始得到的关键字:Order ID,Paid Time(付款时间),还要给这个数据排列,也就是在这两个查询数据前面还需要建立一个Shop ID作为编号如01,02,03等等,查询后显示的完整数据的是[Shop ID:Order ID,Paid Time],实现上面功能还不够,我希望还可以增加一个时间查询也就是可以输入某个时间段到某个时间段来进行查询这段时间所有订单的数据,列如Start time[年月日]——End timr[年月日],这个时间查询是通过最开始得到的关键字里面的Paid Time所对应的时间数据("10/07/2025 10:24:31 " )来决定,因为不是每个订单号Order ID都会被进行付款(paid_time),我们只需要提前这个付款时间不需要其他时间,所以只需要查询有paid_time的Order ID并且提取出来即可,若没有paid_time数据就直接跳过放弃,继续查询有paid_time的数据订单即可,根据以上要求给出这个项目的开发教程,要求保姆级教程,再说一遍,在里面必须有可以导入excel和csv文档的功能,并且是点击导入数据就可以在我的电脑里面查找选择文档。要注意下面几个问题,第一:日期时间列识别问题:系统错误地将非日期列(如 Order_Status、Quantity)识别为日期列,日期转换失败率高(如 Created_Time 只有33.4%的转换成功率),错误提示:列 XXX 保持原样: 日期格式识别率低 (0.0%)原因是:仅通过列名关键词(如包含"time")判断是否为日期列,缺乏数据内容验证,未排除明确非日期列的字段(如状态、数量、金额等),未处理多种日期格式(如 10/07/2023 10:24:31 和 2023-07-10T15:45:00),一定要增强列识别逻辑,还有多格式日期解析和分阶段验证。 第二:数据类型推断错误:避免数值列(如 Quantity)被误识别为日期,数据库插入时报错:Incorrect datetime value: '579567909575820736' for column 'Order ID',原因是因为类型推断仅基于列名,未结合数据内容,未处理大整数和特殊格式数字。所以要设置一个优先级调整和安全转换。 第三:数据库交互问题,特别是RuntimeError: The session is unavailable because no secret key was set,事务回滚失败导致表残留,数据类型不匹配(如DATETIME和VARCHAR冲突)原因是Flask会话密钥未配置未正确处理MySQL错误代码(如1292=日期格式错误)和表结构创建与数据插入逻辑分离。所以要做到基础配置和事务管理和原子性操作这三个方面。 第四:前端模板渲染问题: 问题表现: jinja2.exceptions.TemplateNotFound jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'id' JavaScript中 const report = {{ date_report|tojson|safe }} 报错 根本原因: 模板文件未放在正确目录(templates/),变量未定义或为None时直接调用方法,未处理JSON序列化中的None值,解决方案:设置目录结构规范和安全变量访问以及默认值处理。 第五: 文件上传与数据处理问题: 问题表现: 上传的Excel/CSV文件解析失败 临时文件未清理 列名含空格导致SQL错误 根本原因: 未验证文件内容格式 未处理文件编码问题(如UTF-8 vs GBK) 未规范化列名(如 Paid Time → Paid_Time) 解决方案:设置文件预处理和资源清理和编码指定。 第六:分页与性能问题: 问题表现: 大数据量查询时内存溢出 分页逻辑错误(LIMIT/OFFSET计算) 解决方案:设置流式查询和分页优化。 最后做一些优化比如:系统架构改进建议 日志监控: python代码: app.logger.addHandler(logging.FileHandler('import_errors.log')) 配置分离: python代码: class DevelopmentConfig(Config): DEBUG = True UPLOAD_FOLDER = '/tmp/uploads' 单元测试: python代码: def test_datetime_detection(): assert is_potential_datetime_column("Paid_Time", pd.Series(["2023-01-01"])) is True 根据上面所有要求和问题还有解决方法制定一个Python电商数据分析系统教程,要求保姆级教程!!!请仔细分析我给出的所有问题并且一一解决生成一个完整教程,要求准确并且代码质量高可以运行

最新推荐

recommend-type

Python计算IV值的示例讲解

【Python 计算 IV 值】...生成列表通常使用列表推导式,如创建一个由1到10的整数列表: ```python [num for num in range(1, 11)] ``` 这些基础知识在Python编程中非常基础且实用,对于理解和编写高效的代码至关重要。
recommend-type

机器学习数据中类别变量(categorical variable)的处理方法

在实际应用中,除了以上方法,还有其他处理类别变量的方式,如目标编码(Target Encoding)和有序独热编码(Ordinal One-Hot Encoding),这些方法更复杂,但可能在特定情况下能提高模型性能。处理类别变量时,应...
recommend-type

掌握XFireSpring整合技术:HELLOworld原代码使用教程

标题:“xfirespring整合使用原代码”中提到的“xfirespring”是指将XFire和Spring框架进行整合使用。XFire是一个基于SOAP的Web服务框架,而Spring是一个轻量级的Java/Java EE全功能栈的应用程序框架。在Web服务开发中,将XFire与Spring整合能够发挥两者的优势,例如Spring的依赖注入、事务管理等特性,与XFire的简洁的Web服务开发模型相结合。 描述:“xfirespring整合使用HELLOworld原代码”说明了在这个整合过程中实现了一个非常基本的Web服务示例,即“HELLOworld”。这通常意味着创建了一个能够返回"HELLO world"字符串作为响应的Web服务方法。这个简单的例子用来展示如何设置环境、编写服务类、定义Web服务接口以及部署和测试整合后的应用程序。 标签:“xfirespring”表明文档、代码示例或者讨论集中于XFire和Spring的整合技术。 文件列表中的“index.jsp”通常是一个Web应用程序的入口点,它可能用于提供一个用户界面,通过这个界面调用Web服务或者展示Web服务的调用结果。“WEB-INF”是Java Web应用中的一个特殊目录,它存放了应用服务器加载的Servlet类文件和相关的配置文件,例如web.xml。web.xml文件中定义了Web应用程序的配置信息,如Servlet映射、初始化参数、安全约束等。“META-INF”目录包含了元数据信息,这些信息通常由部署工具使用,用于描述应用的元数据,如manifest文件,它记录了归档文件中的包信息以及相关的依赖关系。 整合XFire和Spring框架,具体知识点可以分为以下几个部分: 1. XFire框架概述 XFire是一个开源的Web服务框架,它是基于SOAP协议的,提供了一种简化的方式来创建、部署和调用Web服务。XFire支持多种数据绑定,包括XML、JSON和Java数据对象等。开发人员可以使用注解或者基于XML的配置来定义服务接口和服务实现。 2. Spring框架概述 Spring是一个全面的企业应用开发框架,它提供了丰富的功能,包括但不限于依赖注入、面向切面编程(AOP)、数据访问/集成、消息传递、事务管理等。Spring的核心特性是依赖注入,通过依赖注入能够将应用程序的组件解耦合,从而提高应用程序的灵活性和可测试性。 3. XFire和Spring整合的目的 整合这两个框架的目的是为了利用各自的优势。XFire可以用来创建Web服务,而Spring可以管理这些Web服务的生命周期,提供企业级服务,如事务管理、安全性、数据访问等。整合后,开发者可以享受Spring的依赖注入、事务管理等企业级功能,同时利用XFire的简洁的Web服务开发模型。 4. XFire与Spring整合的基本步骤 整合的基本步骤可能包括添加必要的依赖到项目中,配置Spring的applicationContext.xml,以包括XFire特定的bean配置。比如,需要配置XFire的ServiceExporter和ServicePublisher beans,使得Spring可以管理XFire的Web服务。同时,需要定义服务接口以及服务实现类,并通过注解或者XML配置将其关联起来。 5. Web服务实现示例:“HELLOworld” 实现一个Web服务通常涉及到定义服务接口和服务实现类。服务接口定义了服务的方法,而服务实现类则提供了这些方法的具体实现。在XFire和Spring整合的上下文中,“HELLOworld”示例可能包含一个接口定义,比如`HelloWorldService`,和一个实现类`HelloWorldServiceImpl`,该类有一个`sayHello`方法返回"HELLO world"字符串。 6. 部署和测试 部署Web服务时,需要将应用程序打包成WAR文件,并部署到支持Servlet 2.3及以上版本的Web应用服务器上。部署后,可以通过客户端或浏览器测试Web服务的功能,例如通过访问XFire提供的服务描述页面(WSDL)来了解如何调用服务。 7. JSP与Web服务交互 如果在应用程序中使用了JSP页面,那么JSP可以用来作为用户与Web服务交互的界面。例如,JSP可以包含JavaScript代码来发送异步的AJAX请求到Web服务,并展示返回的结果给用户。在这个过程中,JSP页面可能使用XMLHttpRequest对象或者现代的Fetch API与Web服务进行通信。 8. 项目配置文件说明 项目配置文件如web.xml和applicationContext.xml分别在Web应用和服务配置中扮演关键角色。web.xml负责定义Web组件,比如Servlet、过滤器和监听器,而applicationContext.xml则负责定义Spring容器中的bean,包括数据源、事务管理器、业务逻辑组件和服务访问器等。 总之,通过上述整合使用原代码的知识点,可以深入理解XFire与Spring框架的结合使用,以及如何开发和部署基本的Web服务。这些技术知识有助于进行更高层次的Web服务开发,以及在复杂的IT环境中灵活运用各种框架和工具。
recommend-type

【Unity2018汉化大揭秘】:一步到位优化中文用户体验

# 摘要 本论文详细介绍了Unity2018汉化项目的实施过程,从理论准备到实践步骤,再到后期的测试与优化,最终分享了汉化经验和心得。首先,强调了汉化项目对于用户体验提升和产品国际化的重要性,并分析了汉化资源的分类、识别与管理方法。接着,详细阐述了汉化工具的选择、脚本编写、资源手动调整等实践步骤,以及汉化质量测试、体验优化和项目维护更新的重要性。论文最后通过案例研究,分析了汉化中的挑战和应对策略,并预测了汉化技术未来的发展方向。整个汉化过程不仅涉及技术层面的操作,还包含了深入的理论研究和实践心得分享,对提升汉化项目的效率和质量具有指导意义。 # 关键字 Unity汉化;用户体验;国际化;资源
recommend-type

iPhone

<think>我们注意到用户意图不明确,但提到了“照片提取”和“其他功能帮助”。因此,我们需要通过搜索来获取关于iPhone照片提取的常见方法以及其他可能的功能帮助。由于用户问题比较宽泛,我们将重点放在照片提取上,因为这是明确提到的关键词。同时,我们也会考虑一些其他常用功能的帮助。首先,针对照片提取,可能涉及从iPhone导出照片、从备份中提取照片、或者从损坏的设备中恢复照片等。我们将搜索这些方面的信息。其次,关于其他功能帮助,我们可以提供一些常见问题的快速指南,如电池优化、屏幕时间管理等。根据要求,我们需要将答案组织为多个方法或步骤,并在每个步骤间换行。同时,避免使用第一人称和步骤词汇。由于
recommend-type

驾校一点通软件:提升驾驶证考试通过率

标题“驾校一点通”指向的是一款专门为学员考取驾驶证提供帮助的软件,该软件强调其辅助性质,旨在为学员提供便捷的学习方式和复习资料。从描述中可以推断出,“驾校一点通”是一个与驾驶考试相关的应用软件,这类软件一般包含驾驶理论学习、模拟考试、交通法规解释等内容。 文件标题中的“2007”这个年份标签很可能意味着软件的最初发布时间或版本更新年份,这说明了软件具有一定的历史背景和可能经过了多次更新,以适应不断变化的驾驶考试要求。 压缩包子文件的文件名称列表中,有以下几个文件类型值得关注: 1. images.dat:这个文件名表明,这是一个包含图像数据的文件,很可能包含了用于软件界面展示的图片,如各种标志、道路场景等图形。在驾照学习软件中,这类图片通常用于帮助用户认识和记忆不同交通标志、信号灯以及驾驶过程中需要注意的各种道路情况。 2. library.dat:这个文件名暗示它是一个包含了大量信息的库文件,可能包含了法规、驾驶知识、考试题库等数据。这类文件是提供给用户学习驾驶理论知识和准备科目一理论考试的重要资源。 3. 驾校一点通小型汽车专用.exe:这是一个可执行文件,是软件的主要安装程序。根据标题推测,这款软件主要是针对小型汽车驾照考试的学员设计的。通常,小型汽车(C1类驾照)需要学习包括车辆构造、基础驾驶技能、安全行车常识、交通法规等内容。 4. 使用说明.html:这个文件是软件使用说明的文档,通常以网页格式存在,用户可以通过浏览器阅读。使用说明应该会详细介绍软件的安装流程、功能介绍、如何使用软件的各种模块以及如何通过软件来帮助自己更好地准备考试。 综合以上信息,我们可以挖掘出以下几个相关知识点: - 软件类型:辅助学习软件,专门针对驾驶考试设计。 - 应用领域:主要用于帮助驾考学员准备理论和实践考试。 - 文件类型:包括图片文件(images.dat)、库文件(library.dat)、可执行文件(.exe)和网页格式的说明文件(.html)。 - 功能内容:可能包含交通法规知识学习、交通标志识别、驾驶理论学习、模拟考试、考试题库练习等功能。 - 版本信息:软件很可能最早发布于2007年,后续可能有多个版本更新。 - 用户群体:主要面向小型汽车驾照考生,即C1类驾照学员。 - 使用方式:用户需要将.exe安装文件进行安装,然后根据.html格式的使用说明来熟悉软件操作,从而利用images.dat和library.dat中的资源来辅助学习。 以上知识点为从给定文件信息中提炼出来的重点,这些内容对于了解“驾校一点通”这款软件的功能、作用、使用方法以及它的发展历史都有重要的指导意义。
recommend-type

【DFLauncher自动化教程】:简化游戏启动流程,让游戏体验更流畅

# 摘要 DFLauncher是一个功能丰富的游戏启动和管理平台,本论文将介绍其安装、基础使用、高级设置、社区互动以及插件开发等方面。通过对配置文件的解析、界面定制、自动化功能的实现、高级配置选项、安全性和性能监控的详细讨论,本文阐述了DFLauncher如何帮助用户更高效地管理和优化游戏环境。此外,本文还探讨了DFLauncher社区的资源分享、教育教程和插件开发等内容,
recommend-type

自适应卡尔曼滤波是什么意思

<think>嗯,用户这次想深入了解自适应卡尔曼滤波(AKF)的概念和原理。从对话历史看,用户之前研究过相机自动对焦系统的降噪技术,其中提到过自适应卡尔曼滤波的应用场景。用户可能是个工程师或研究者,正在探索信号处理在具体场景的实现细节。 用户提供的三篇参考文献很有价值:[1]是基础理论综述,[2]聚焦多传感器场景,[3]讨论噪声协方差自适应方法。需要特别注意相机AF系统的特殊需求——实时性要求高(每秒数十次对焦计算)、噪声环境复杂(机械振动/弱光干扰),这些在解释原理时要结合具体案例。 技术要点需要分层解析:先明确标准卡尔曼滤波的局限(固定噪声参数),再展开自适应机制。对于相机AF场景,重
recommend-type

EIA-CEA 861B标准深入解析:时间与EDID技术

EIA-CEA 861B标准是美国电子工业联盟(Electronic Industries Alliance, EIA)和消费电子协会(Consumer Electronics Association, CEA)联合制定的一个技术规范,该规范详细规定了视频显示设备和系统之间的通信协议,特别是关于视频显示设备的时间信息(timing)和扩展显示识别数据(Extended Display Identification Data,简称EDID)的结构与内容。 在视频显示技术领域,确保不同品牌、不同型号的显示设备之间能够正确交换信息是至关重要的,而这正是EIA-CEA 861B标准所解决的问题。它为制造商提供了一个统一的标准,以便设备能够互相识别和兼容。该标准对于确保设备能够正确配置分辨率、刷新率等参数至关重要。 ### 知识点详解 #### EIA-CEA 861B标准的历史和重要性 EIA-CEA 861B标准是随着数字视频接口(Digital Visual Interface,DVI)和后来的高带宽数字内容保护(High-bandwidth Digital Content Protection,HDCP)等技术的发展而出现的。该标准之所以重要,是因为它定义了电视、显示器和其他显示设备之间如何交互时间参数和显示能力信息。这有助于避免兼容性问题,并确保消费者能有较好的体验。 #### Timing信息 Timing信息指的是关于视频信号时序的信息,包括分辨率、水平频率、垂直频率、像素时钟频率等。这些参数决定了视频信号的同步性和刷新率。正确配置这些参数对于视频播放的稳定性和清晰度至关重要。EIA-CEA 861B标准规定了多种推荐的视频模式(如VESA标准模式)和特定的时序信息格式,使得设备制造商可以参照这些标准来设计产品。 #### EDID EDID是显示设备向计算机或其他视频源发送的数据结构,包含了关于显示设备能力的信息,如制造商、型号、支持的分辨率列表、支持的视频格式、屏幕尺寸等。这种信息交流机制允许视频源设备能够“了解”连接的显示设备,并自动设置最佳的输出分辨率和刷新率,实现即插即用(plug and play)功能。 EDID的结构包含了一系列的块(block),其中定义了包括基本显示参数、色彩特性、名称和序列号等在内的信息。该标准确保了这些信息能以一种标准的方式被传输和解释,从而简化了显示设置的过程。 #### EIA-CEA 861B标准的应用 EIA-CEA 861B标准不仅适用于DVI接口,还适用于HDMI(High-Definition Multimedia Interface)和DisplayPort等数字视频接口。这些接口技术都必须遵循EDID的通信协议,以保证设备间正确交换信息。由于标准的广泛采用,它已经成为现代视频信号传输和显示设备设计的基础。 #### EIA-CEA 861B标准的更新 随着技术的进步,EIA-CEA 861B标准也在不断地更新和修订。例如,随着4K分辨率和更高刷新率的显示技术的发展,该标准已经扩展以包括支持这些新技术的时序和EDID信息。任何显示设备制造商在设计新产品时,都必须考虑最新的EIA-CEA 861B标准,以确保兼容性。 #### 结论 EIA-CEA 861B标准是电子显示领域的一个重要规范,它详细定义了视频显示设备在通信时所使用的信号时序和设备信息的格式。该标准的存在,使得不同厂商生产的显示设备可以无缝连接和集成,极大地增强了用户体验。对于IT专业人士而言,了解和遵守EIA-CEA 861B标准是进行视频系统设计、故障诊断及设备兼容性测试的重要基础。
recommend-type

【DFLauncher应用实战】:如何将DFLauncher融入矮人要塞并提升效率

# 摘要 DFLauncher是一款功能全面的游戏管理工具,旨在简化游戏安装、启动和维护过程。本文介绍了DFLauncher的基本使用方法,详细解析了其核心功能,包括游戏库管理、游戏配置优化、更新机制等。同时,文章探讨了DFLauncher在特定游戏“矮人要塞”中的集成应用,以及通过插件和脚本进行的高级定制。故障诊断与系统优化章节提供了实用的诊断方法和性能提升技巧。最后,本文展望了DFLauncher的未来发展方向,并鼓励社区贡献和用户反馈,以满足不断变化的用户需求。 # 关键字 DFLauncher;游戏管理工具;安装配置;性能优化;故障诊断;社区贡献;定制化扩展;网络功能集成 参考资源