赞
踩
并不想花太多精力去拼比赛拿名次,毕竟又工作又带娃,时间并不多。但比较喜欢看比赛里的技术论坛。工作中的内容相对要单一很多,很容易陷入狭窄的思维中,而比赛中,大家的思维还是很有营养的。偶尔遇到合胃口的数据,下一份,玩一玩还是不错。
之前的糖尿病大赛,看到的时候离初赛结束只有几天了,周末紧急下载数据,搞了一天弄出结果,然而没有办法提交,后来仔细看了下赛制才发现,初赛最后两天会换数据,之前没提交过的,最后两天没办法提交。遗憾呀,搞了一天,结果都没能验证一下测试集的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))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。