并不想花太多精力去拼比赛拿名次,毕竟又工作又带娃,时间并不多。但比较喜欢看比赛里的技术论坛。工作中的内容相对要单一很多,很容易陷入狭窄的思维中,而比赛中,大家的思维还是很有营养的。偶尔遇到合胃口的数据,下一份,玩一玩还是不错。
之前的糖尿病大赛,看到的时候离初赛结束只有几天了,周末紧急下载数据,搞了一天弄出结果,然而没有办法提交,后来仔细看了下赛制才发现,初赛最后两天会换数据,之前没提交过的,最后两天没办法提交。遗憾呀,搞了一天,结果都没能验证一下测试集的loss。
4月末看到双高预测比赛,看着还有几天,忍不住又下了数据,结果五一果断玩去了,得到5月5号换数据前一天,又开始加急处理,名次什么,无所谓的,目标是提交一次。最终经历曲折的7个小时,终于提交了两次(实际方法一样,迭代次数稍微不同),结果loss为0.0367,好意外的结果。
这7个小时中,干了些什么呢?请看笨妞的流水账。
1. 合并数据
合并数据和滤去太少的列,这个完全照搬技术圈里面的程序,帖子的地址,只用了其中一部分,后面提特征的觉得不太能用,自己玩了。
import pandas as pd
import numpy as np
pd.set_option('max_colwidth',512)
data_part1 = pd.read_csv("dataset/meinian_round1_data_part1_20180408.txt", sep="$")
data_part2 =pd.read_csv("dataset/meinian_round1_data_part2_20180408.txt", sep='$')
data = pd.concat([data_part1, data_part2])
print(data.shape)
# (8104368, 3)
# 把table_id相同的体检项,全部保留;堆叠为同一行保存,以 “;” 分隔
data_keep_all = data.groupby(['vid','table_id'],as_index=False).apply(lambda x:";".join(map(str, x['field_results'])))
data_keep_all = pd.DataFrame(data_keep_all,columns=['field_results'])
print(data_keep_all.shape)
# (7820997,)
# 转化为 行列格式
data_fmt_all = data_keep_all.unstack(fill_value=None)
data_fmt_all.columns = data_fmt_all.columns.droplevel(level=0)
print(data_fmt_all.shape)
# (57298, 2795) 共有2795个特征值
# 缺失值统计
null_count = data_fmt_all.isnull().sum()
print(len(null_count[null_count<50000]))
# 256 缺失值少于50000的特征只有256个..
# 删除缺失值过多的数据
data_keep_50000 = data_fmt_all .drop(labels=null_count [null_count >=50000].index,axis=1)
data_keep_50000 .to_csv("tmp/data_keep_50000.csv")
经过这段程序处理后,两个数据文件中每个受检查者的所有检查项目合并到一行中,原本的2795项检查结果,通过丢掉缺失值大于50000个的特征处理后,剩下256个。
2. 特征进一步过滤
这一步实际上花了比较长时间,因为都是手动看数据、分析每一项可能是哪一类检查。剩下的256项检查结果,里面包含外科基本检查、内科基本检查、医生问诊、口腔检查、CT、超声、心电图、眼科检查、妇科检查、各类生化检查等。其中很多检查结果都是自然语言,这些特征我直接放弃了。原因很简单,除了脑部CT、动脉超声对高血压和高血脂有诊断帮助,其他超声、CT,心电图对双高基本没用;眼科、口腔、妇科、尿检等跟双高也基本不搭边;外科基本检查、内科基本检查大家体检都会做的,按按肚子,看看腿,更不沾边了。还有一个致命的原因,我根本没有时间做复杂的NLP啊。所以,该扔就扔。这一扔就只剩下28维特征了。酷!
这28维特征里面除了数值类型的,还有字符类型的,大概的扫了一遍,把字符类型的按照规则转化为数值,特征基本定型。
import codecs
import pandas as pd
def isnumber(aString):
try:
float(aString)
return True
except:
return False
f = codecs.open('tmp/data_keep_50000_part1.csv', 'r', 'gbk')
df_col = ['id']
for i in range(28):
df_col.append('f' + str(i))
print(df_col)
f_df = pd.DataFrame(columns =df_col)
line = f.readline()
line = f.readline()
j = 0
normal_exp = ['阴性', '正常', 'normal', '-', '阴性(+)', '阴性(+)', '- 0mmol/L', '0(-)', '- 0umol/L',
'- 0g/L', '- 0CELL/uL', '- 10CELL/uL', '--', '- 10CELL/uL']
positive_exp = ['阳性', '+', '阳性(+)']
pattern = '[0-9]+\.[0-9]+'
prog = re.compile(pattern)
while line:
words = line.strip().split(',')
idx = words[0]
f_list = []
null_num = 0
for i in range(28):
feature = words[i + 1]
if ';' in feature:
feature = feature.split(';')[1]
if feature == '':
f_list.append(None)
null_num += 1
elif isnumber(feature) or feature.isdigit():
f_list.append(float(feature))
elif feature in normal_exp:
f_list.append(float(0))
elif feature in positive_exp:
f_list.append(float(1))
elif '+' in feature:
n = 0.0
for c in feature:
if c == '+':
n += 1.0
f_list.append(float(n))
elif feature == '>=1.030':
f_list.append(1.030)
else:
a = re.search( pattern, feature)
try:
f_list.append(float(a.group()))
except:
print(feature)
f_list.append(None)
tmp_dict = {'id': idx}
for i in range(28):
tmp_dict[df_col[i + 1]] = f_list[i]
f_df.loc[j] = tmp_dict
#print(tmp_dict)
line = f.readline()
j += 1
if j%1000 == 0 :
print(j)
f_df.to_csv('tmp/feature.csv')
f.close()
print(len(f_df))
特征规模变为57298x28.
3. 筛除个数小于20000的特征项,并补全缺失值,做归一化(这段代码因为fillna函数不知为什么,老容易罢工,结果用了无比笨重的方式)
print(len(null_count[null_count<20000]))
f_df_chosen = f_df.drop(labels='f26',axis=1)
f_df_chosen.to_csv('tmp/feature_chosen.csv')
#“阴”“阳”性数据补全
f_df_filled = f_df_chosen.fillna({'f0':0})
#数值数据补全
f_df_filled = f_df_filled.fillna({'f1':f_df_filled['f1'].mean(),
'f2':f_df_filled['f2'].mean(),
'f3':f_df_filled['f3'].mean(),
'f4':f_df_filled['f4'].mean(),
'f5':f_df_filled['f5'].mean(),
'f6':f_df_filled['f6'].mean(),
'f7':f_df_filled['f7'].mean(),
'f8':f_df_filled['f8'].mean(),
'f9':f_df_filled['f9'].mean(),
'f10':f_df_filled['f10'].mean(),
'f11':f_df_filled['f11'].mean(),
'f12':f_df_filled['f12'].mean(),
'f13':f_df_filled['f13'].mean(),
'f14':f_df_filled['f14'].mean(),
'f15':f_df_filled['f15'].mean(),
'f17':f_df_filled['f17'].mean(),
'f18':f_df_filled['f18'].mean(),
'f16':f_df_filled['f16'].mean(),
'f19':f_df_filled['f19'].mean(),
'f20':f_df_filled['f20'].mean(),
'f21':f_df_filled['f21'].mean(),
'f22':f_df_filled['f22'].mean(),
'f23':f_df_filled['f23'].mean(),
'f24':f_df_filled['f24'].mean(),
'f25':f_df_filled['f25'].mean(),
'f27':f_df_filled['f27'].mean()})
#数值型数据归一化
f_df_filled.set_index(['id'], inplace = True)
f_df_norm = (f_df_filled - f_df_filled.min()) / (f_df_filled.max() - f_df_filled.min())
f_df_filled.info()
f_df_filled.to_csv('tmp/feature_filled.csv')
4. 获取训练数据
这个赛题中,训练和测试的基本数据都在前面处理的两部分数据中,训练文件只包含受检人的vid和5项指标的值,测试文件只包含要测试的vid。在整合训练数据和测试数据时,需要将基本文件和训练文件按照vid联合获取训练数据,整合测试数据需要基本数据和测试vid联合获取测试特征数据。(因为没有事先好好观察数据,这里还走了弯路。原本在第2步筛除数据时,将特征少于10维的vid丢掉,那时以为处理的数据就是训练数据,这样避免某些vid数据缺失太多,预测不准。结果这样一来,一共9538个测试vid,在基本数据中只有7000多个匹配到特征了)
label_df = pd.read_csv('dataset/meinian_round1_train_20180408.csv', index_col='vid')
label_df = label_df.astype(float)
train_df = pd.concat([f_df_norm, label_df], axis=1, join='inner')
train_df = train_df.astype(float)
train_df.info()
5. 获取测试数据
test_vid_df = pd.read_csv('dataset/meinian_round1_test_a_20180409.csv', index_col='vid')
test_df = pd.concat([f_df_norm, test_vid_df], axis=1, join='inner')
test_df.info()
6. 训练标签数据清洗
特征数据清洗整理完之后,还有标签数据也得清洗,这个比赛里面有些舒张压比收缩压还大,有些数据为负,这些都需要清理掉。负值数据直接通过手动删除了,收缩压和舒张压清理如下:
train_df = train_df.drop(labels=train_df[train_df['收缩压']<=train_df['舒张压']].index, axis=0)
7. 训练
选择xgboost算法来学习这个题目。本人实际上还并不懂xgboost的基本原理,参加这个比赛很重要的目的就是玩玩xgboost,但是为了先提交,暂时没有时间详细学习xgboost的基本原理了。安装之后,看了官方使用文档,然后直接上。
原则上应该要先cv调参的,但是,依然由于中间犯了个低级错误,浪费了1个小时,没有时间调参了。
def trainandTest(X_train, y_train, X_test, vid_list, n):
# XGBoost训练过程
print('开始训练...')
model = xgb.XGBRegressor(learning_rate=0.05, n_estimators=800, max_depth=5, min_child_weight=5, seed=0,
subsample=0.7, colsample_bytree=0.7, gamma=0.1, reg_alpha=1, reg_lambda=1)
model.fit(X_train, y_train)
# 对测试集进行预测
print('开始测试...')
print(len(X_test))
ans = model.predict(X_test)
print(ans[0:10])
ans_len = len(ans)
pd_data = pd.DataFrame(columns=['vid', 'y'])
for i in range(0, ans_len):
tmp_dict = {'vid': vid_list[i], 'y': ans[i]}
pd_data.loc[i] = tmp_dict
pd_data.to_csv('submit_800_' + str(n) + '.csv', index=None)
print('完成')
return pd_data
print('读训练数据...')
#X_train, y_train = featureSet(train_df, '收缩压')
train_x_df = train_df.drop(labels=['收缩压', '舒张压', '血清甘油三酯', '血清高密度脂蛋白', '血清低密度脂蛋白'],axis=1)
print(train_x_df.info())
train_arr = np.array(train_x_df)
X_train = train_arr.tolist()
vid_list = test_df.index
train_y_1 = train_df['收缩压'].tolist()
print(train_y_1[0:10])
train_y_2 = train_df['舒张压'].tolist()
train_y_3 = train_df['血清甘油三酯'].tolist()
train_y_4 = train_df['血清高密度脂蛋白'].tolist()
train_y_5 = train_df['血清低密度脂蛋白'].tolist()
print('读测试数据...')
test_x_df = test_df.drop(labels=[ '收缩压', '舒张压', '血清甘油三酯', '血清高密度脂蛋白', '血清低密度脂蛋白'],axis=1)
test_arr = np.array(test_x_df)
X_test = test_arr.tolist()
# 预测最终的结果
pd_1 = trainandTest(X_train, train_y_1, X_test, vid_list, 1)
pd_1.set_index(['vid'], inplace = True)
pd_1 = pd_1.astype(int)
pd_2 = trainandTest(X_train, train_y_2, X_test, vid_list, 2)
pd_2.set_index(['vid'], inplace = True)
pd_2 = pd_2.astype(int)
#一直跑出来结果为NAN的原因是,label里面有一个缺失值
pd_3 = trainandTest(X_train, train_y_3, X_test, vid_list, 3)
pd_3.set_index(['vid'], inplace = True)
pd_4 = trainandTest(X_train, train_y_4, X_test, vid_list, 4)
pd_4.set_index(['vid'], inplace = True)
pd_5 = trainandTest(X_train, train_y_5, X_test, vid_list, 5)
pd_5.set_index(['vid'], inplace = True)
分5次训练和预测,略显尴尬。
8. 合并数据并提交
#合并数据
pd_1.rename(columns={'y':'收缩压'}, inplace = True)
pd_2.rename(columns={'y':'舒张压'}, inplace = True)
pd_3.rename(columns={'y':'血清甘油三酯'}, inplace = True)
pd_4.rename(columns={'y':'血清高密度脂蛋白'}, inplace = True)
pd_5.rename(columns={'y':'血清低密度脂蛋白'}, inplace = True)
pd_result = pd.concat([pd_1, pd_2], axis=1, join='inner')
pd_result = pd.concat([pd_result, pd_3], axis=1, join='inner')
pd_result = pd.concat([pd_result, pd_4], axis=1, join='inner')
pd_result = pd.concat([pd_result, pd_5], axis=1, join='inner')
pd_result = pd_result.reset_index(drop=False)
pd_result.to_csv('result_800.csv', index=None)
9. 犯过的低级错误
训练文件中,5项label中“血清甘油三脂”有一个缺失值,但是我在前面查看info的时候没有发现,后面再训练这一项的时候,怎么调节参数,预测值都是nan,后来无语了,把算法换成lr,想着是不是因为这一项的最大值和最小值差异较大xgboost不适合。结果lr自觉的报“输入值包含nan”才回去查找缺失值。各种调参、换模型,浪费了接近1个小时。
查找缺失值
#确定nan的位置
np.where(np.isnan(train_df))