TensorFlow多标签分类:NBA球员位置预测命中任意标签即正确
TensorFlow多标签NBA位置预测TypeError修复方案
问题场景
- 目标:基于球员数据预测场上位置,数据集标注的是球员生涯出任过的所有位置组合,而非单一固定位置
- 最初方案:将23种位置组合直接映射为1-23的整数值做单标签分类,代码可运行但不符合需求:
posdict = {'G-F' : 1, 'SG' : 2, 'SF' : 3, 'F' : 4, 'G' : 5, 'F-C' : 6, 'PG' : 7, 'F-G' : 8, 'C' : 9, 'PF' : 10, 'C-F' : 11, 'PF-C' : 12, 'SF-SG' : 13, 'C-PF' : 14, 'SG-SF' : 15, 'PF-SF' : 16, 'SF-PF' : 17, 'SG-PG' : 18, 'SF-PG' : 19, 'C-SF' : 20, 'PG-SG' : 21, 'PG-SF' : 22, 'SG-PF' : 23}
- 实际需求:将所有位置归为5个基础类别(PG:1, SG:2, SF:3, PF:4, C:5),打过多个位置的球员对应多个有效标签,只要预测结果落在球员真实出任过的位置集合内就算预测正确。重构后的映射字典如下,替换后运行触发TypeError:
posdict = {'G-F' : [1,2,3,4], 'SG' : [2], 'SF' : [3], 'F' : [3,4], 'G' : [1,2], 'F-C' : [3,4,5], 'PG' : [1], 'F-G' : [1,2,3,4], 'C' : [5], 'PF' : [4], 'C-F' : [3,4,5], 'PF-C' : [4,5], 'SF-SG' : [2,3], 'C-PF' : [4,5], 'SG-SF' : [2,3], 'PF-SF' : [3,4], 'SF-PF' : [3,4], 'SG-PG' : [1,2], 'SF-PG' : [1,3], 'C-SF' : [3,5], 'PG-SG' : [1,2], 'PG-SF' : [1,3], 'SG-PF' : [2,4]}
- 触发报错的原始代码(基于Estimator的单标签分类实现):
dftrain[('Pos')] = [posdict[item] for item in dftrain['Pos']] dfeval[('Pos')] = [posdict[item] for item in dfeval['Pos']] y_train = dftrain.pop('Pos') y_eval = dfeval.pop('Pos') NUMERICAL_COLUMS = ['G', 'PER', 'TS%', 'FTr', 'USG%', 'FG', 'FGA', 'FG%', '3P', '3PA', 'AST%', 'STL%', 'BLK%', 'TOV%', '2P', '2PA', 'OBPM', 'DBPM', 'BPM', '2P%', 'eFG%', 'FT', 'FTA', 'FT%', 'PTS', 'ORB%', 'DRB%', 'TRB%', 'OWS', 'DWS', 'WS'] feature_colums= [] for feature_name in NUMERICAL_COLUMS: feature_colums.append(tf.feature_column.numeric_column(feature_name, dtype=tf.float64)) def make_input_fn(data_df, label_df, num_epochs=10, shuffle=True, batch_size=32): def input_function(): ds = tf.data.Dataset.from_tensor_slices((dict(data_df), label_df)) if shuffle: ds = ds.shuffle(1000) ds = ds.batch(batch_size).repeat(num_epochs) return ds return input_function train_input_fn = make_input_fn(dftrain, y_train) eval_input_fn = make_input_fn(dfeval, y_eval, num_epochs=1, shuffle=False) linear_est = tf.estimator.LinearClassifier(feature_columns=feature_colums, n_classes=24) linear_est.train(train_input_fn) result = linear_est.evaluate(eval_input_fn) print(result['accuracy'])
报错根因
- 你用的
tf.estimator.LinearClassifier是单标签多分类模型,只接受单个整数形式的标签输入,直接传入位置编号列表会触发类型错误 - 代码里设置的
n_classes=24是为之前23种位置组合的单分类任务配置的,和当前5个基础位置的多标签任务完全不匹配 - 模型内置的accuracy指标仅支持“预测值与真实值完全相等”的判定逻辑,无法实现“预测值落在真实位置集合即算正确”的自定义规则
修复步骤
1. 标签转换为multi-hot格式
多标签任务不能直接传编号列表,要转为固定长度的multi-hot向量:向量长度为6(索引0留空,对齐你1-5的位置编号规则),球员打过的位置对应位设为1,其余设为0。比如同时打PG、SG的球员标签为[0,1,1,0,0,0]。
import numpy as np def pos_to_multihot(pos_list): label = np.zeros(6, dtype=np.int32) for pos in pos_list: label[pos] = 1 return label dftrain['Pos'] = [pos_to_multihot(posdict[item]) for item in dftrain['Pos']] dfeval['Pos'] = [pos_to_multihot(posdict[item]) for item in dfeval['Pos']] y_train = np.array(dftrain.pop('Pos').tolist()) y_eval = np.array(dfeval.pop('Pos').tolist())
2. 替换为支持多标签的模型结构
LinearClassifier不支持多标签任务,新手更推荐用tf.keras实现,结构更简单易改:最后一层用5个sigmoid激活的神经元,每个神经元独立输出对应位置的出战概率。
import tensorflow as tf from tensorflow.keras import layers # 构建数值特征输入层 inputs = {col: layers.Input(name=col, shape=(), dtype=tf.float64) for col in NUMERICAL_COLUMS} x = layers.concatenate(list(inputs.values())) # 多标签输出层 outputs = layers.Dense(5, activation='sigmoid')(x) model = tf.keras.Model(inputs=inputs, outputs=outputs)
3. 自定义符合规则的准确率指标
不用内置的准确率,自己实现判定逻辑:取每个样本预测概率最高的位置,判断该位置是否在球员的真实位置集合中。
def custom_accuracy(y_true, y_pred): # 取预测概率最高的位置,+1对齐1-5的编号 pred_pos = tf.argmax(y_pred, axis=1) + 1 # 索引对应到multi-hot的位置,判断该位是否为1(即是否是真实打过的位置) pred_idx = tf.stack([tf.range(tf.shape(y_true)[0]), tf.cast(pred_pos, tf.int32)], axis=1) correct = tf.gather_nd(y_true, pred_idx) return tf.reduce_mean(tf.cast(correct, tf.float32)) model.compile( optimizer='adam', loss='binary_crossentropy', metrics=[custom_accuracy] )
4. 调整数据输入逻辑
不用原来的Estimator输入函数,直接用tf.data构造训练/验证集即可:
train_ds = tf.data.Dataset.from_tensor_slices((dict(dftrain), y_train)).shuffle(1000).batch(32).repeat(10) eval_ds = tf.data.Dataset.from_tensor_slices((dict(dfeval), y_eval)).batch(32) model.fit(train_ds, validation_data=eval_ds) result = model.evaluate(eval_ds, return_dict=True) print(result['custom_accuracy'])
后续如果需要输出球员所有可能的位置,只需要设置概率阈值(比如0.5),把概率超过阈值的位置都作为预测结果即可,自定义准确率的逻辑也可以对应调整为“预测结果和真实位置集合有交集就算正确”。
内容的提问来源于stack exchange,提问作者Doom Lord
相关产品推荐
相关产品推荐

