使用Pulp求解器时Jupyter Notebook内核持续崩溃问题求助
问题描述
运行Jupyter Notebook中的LP(线性规划)求解器脚本时,执行最后一行代码出现错误:The kernel appears to have died. It will restart automatically.,在普通Python脚本中运行也会出现相同问题。使用的数据集dfs_proj为240行、5列。
代码如下:
import pandas as pd from pulp import * from pulp import LpMaximize dfs_proj = pd.read_csv("4for4_dfs_projections_120321.csv") dfs_proj['count'] = 1 cols = ['Player', 'Pos', 'FFPts', 'DK ($)', 'count'] dfs_proj = dfs_proj[cols] dfs_proj = dfs_proj[(dfs_proj['DK ($)'] >= 4000) | (dfs_proj['Pos'] == "DEF") | (dfs_proj['Pos'] == "TE")] player_dict = dict(zip(dfs_proj['Player'], dfs_proj['count'])) # create a helper function to return the number of players assigned each position def get_position_sum(player_vars, df, position): return pulp.lpSum([player_vars[i] * (position in df['Pos'].iloc[i]) for i in range(len(df))]) def get_optimals(site, data, num_lineups, optimize_on='FFPts'): """ Generates x number of optimal lineups, based on the column to designate as the one to optimize on. :param str site: DK or FD. Used for salary constraints :param pd.DataFrame data: Pandas dataframe containing projections. :param int num_lineups: Number of lineups to generate. :param str optimize_on: Name of column in dataframe to use when optimizing """ #global lineups lineups = [] player_dict = dict(zip(data['Player'], data['count'])) for i in range(1, num_lineups+1): prob = pulp.LpProblem('DK_NFL_weekly', pulp.const.LpMaximize) player_vars = [] for row in data.itertuples(): var = pulp.LpVariable(f'{row.Player}', cat='Binary') player_vars.append((row.Player, var)) # total assigned players constraint prob += pulp.lpSum(player_var for player_var in player_vars) == 9 # total salary constraint prob += pulp.lpSum(data['DK ($)'].iloc[i] * player_vars[i][1] for i in range(len(data))) <= 50000 # for QB and DST, require 1 of each in the lineup prob += get_position_sum(player_vars, df, 'QB') == 1 prob += get_position_sum(player_vars, df, 'DEF') == 1 # to account for the FLEX position, we allow additional selections of the 3 FLEX-eligible positions: RB, WR, TE prob += get_position_sum(player_vars, df, 'RB') >= 2 prob += get_position_sum(player_vars, df, 'WR') >= 3 prob += get_position_sum(player_vars, df, 'TE') >= 1 if i > 1: if optimize_on == 'Optimal Frequency': prob += pulp.lpSum([data['FFPts'].iloc[i] * player_vars[i][1] for i in range(len(data))]) <= (optimal - 0.001) else: prob += pulp.lpSum([data['FFPts'].iloc[i] * player_vars[i][1] for i in range(len(data))]) <= (optimal - 0.01) prob += pulp.lpSum([data['FFPts'].iloc[i] * player_vars[i][1] for i in range(len(data))]) # solve and print the status prob.solve(PULP_CBC_CMD(msg=False)) optimal = prob.objective.value() count = 1 lineup = {} for i in range(len(data)): if player_vars[i][1].value() == 1: row = data.iloc[i] lineup[f'G{count}'] = row['Player'] count += 1 lineup['Total Points'] = optimal lineups.append(lineup) players = list(lineup.values()) for i in range(0, len(players)): if type(players[i]) == str: player_dict[players[i]] += 1 if player_dict[players[i]] == 45: data = data[data['Player'] != players[i]] return lineups lineups = get_optimals(dfs_proj, 20, 'FFPts')
问题分析
内核崩溃的核心原因是代码存在逻辑错误,同时内存使用效率低下:
- 参数顺序不匹配:调用
get_optimals时参数顺序与函数定义不符,导致site参数被传入数据集,后续处理类型错误。 - 未定义变量:
get_position_sum调用中使用了未定义的df变量,应该使用函数传入的data参数。 - 循环变量冲突:外层循环的
i与内层遍历数据的i重名,导致薪资约束计算错误,进而引发求解器异常。 - 内存浪费:每次循环重复创建大量变量,且变量命名可能重复(球员名重复时),未及时回收资源,导致内存占用过高。
修复方案
以下是修正并优化后的代码:
import pandas as pd from pulp import LpProblem, LpMaximize, LpVariable, lpSum, PULP_CBC_CMD import psutil import gc # 内存监控函数 def get_memory_usage(): process = psutil.Process() return f"{process.memory_info().rss / 1024 ** 2:.2f} MB" dfs_proj = pd.read_csv("4for4_dfs_projections_120321.csv") dfs_proj['count'] = 1 cols = ['Player', 'Pos', 'FFPts', 'DK ($)', 'count'] dfs_proj = dfs_proj[cols] dfs_proj = dfs_proj[(dfs_proj['DK ($)'] >= 4000) | (dfs_proj['Pos'] == "DEF") | (dfs_proj['Pos'] == "TE")] # 预处理位置标志列,避免重复计算 dfs_proj['is_QB'] = dfs_proj['Pos'].apply(lambda x: 1 if 'QB' in x else 0) dfs_proj['is_DEF'] = dfs_proj['Pos'].apply(lambda x: 1 if 'DEF' in x else 0) dfs_proj['is_RB'] = dfs_proj['Pos'].apply(lambda x: 1 if 'RB' in x else 0) dfs_proj['is_WR'] = dfs_proj['Pos'].apply(lambda x: 1 if 'WR' in x else 0) dfs_proj['is_TE'] = dfs_proj['Pos'].apply(lambda x: 1 if 'TE' in x else 0) def get_optimals(site, data, num_lineups, optimize_on='FFPts'): lineups = [] # 复制工作数据集,避免修改原数据 working_data = data.copy().reset_index(drop=True) player_dict = dict(zip(working_data['Player'], working_data['count'])) for lineup_num in range(1, num_lineups+1): print(f"Generating lineup {lineup_num}, current memory: {get_memory_usage()}") prob = LpProblem('DK_NFL_weekly', LpMaximize) # 用索引+球员名作为变量名,确保唯一性 player_vars = { idx: LpVariable(f'Player_{idx}_{row.Player.replace(" ", "_")}', cat='Binary') for idx, row in working_data.iterrows() } # 总人数约束 prob += lpSum(player_vars.values()) == 9 # 薪资约束 salary_col = 'DK ($)' if site == 'DK' else 'FD ($)' prob += lpSum(working_data[salary_col].iloc[idx] * player_vars[idx] for idx in player_vars) <= 50000 # 位置约束 prob += lpSum(working_data['is_QB'].iloc[idx] * player_vars[idx] for idx in player_vars) == 1 prob += lpSum(working_data['is_DEF'].iloc[idx] * player_vars[idx] for idx in player_vars) == 1 prob += lpSum(working_data['is_RB'].iloc[idx] * player_vars[idx] for idx in player_vars) >= 2 prob += lpSum(working_data['is_WR'].iloc[idx] * player_vars[idx] for idx in player_vars) >= 3 prob += lpSum(working_data['is_TE'].iloc[idx] * player_vars[idx] for idx in player_vars) >= 1 # 次优解约束 if lineup_num > 1: prev_optimal = lineups[-1]['Total Points'] if optimize_on == 'Optimal Frequency': prob += lpSum(working_data[optimize_on].iloc[idx] * player_vars[idx] for idx in player_vars) <= (prev_optimal - 0.001) else: prob += lpSum(working_data[optimize_on].iloc[idx] * player_vars[idx] for idx in player_vars) <= (prev_optimal - 0.01) # 目标函数 prob += lpSum(working_data[optimize_on].iloc[idx] * player_vars[idx] for idx in player_vars) # 求解 prob.solve(PULP_CBC_CMD(msg=False)) current_optimal = prob.objective.value() # 提取阵容 lineup = {'Total Points': current_optimal} count = 1 for idx in player_vars: if player_vars[idx].value() == 1: row = working_data.iloc[idx] lineup[f'G{count}'] = row['Player'] count += 1 lineups.append(lineup) # 更新球员出场次数并移除达到上限的球员 for player_key in [k for k in lineup if k.startswith('G')]: player = lineup[player_key] player_dict[player] += 1 if player_dict[player] == 45: working_data = working_data[working_data['Player'] != player].reset_index(drop=True) player_dict.pop(player) # 回收内存 del prob, player_vars gc.collect() return lineups # 正确调用函数:参数顺序匹配定义 lineups = get_optimals('DK', dfs_proj, 20, 'FFPts')
关键优化点
- 修正参数顺序:确保函数调用时参数与定义一致。
- 预处理位置列:提前生成位置二进制标志,减少重复计算,提升效率。
- 唯一变量命名:用索引+球员名(替换空格)作为变量名,避免冲突。
- 内存监控与回收:添加内存监控函数,每次循环后删除无用对象并触发垃圾回收,降低内存占用。
- 修复循环变量冲突:将外层循环变量改为
lineup_num,避免与内层索引变量冲突。
内容的提问来源于stack exchange,提问作者Sam Hoppen
相关产品推荐
相关产品推荐

