首次試水天池數據大賽——7個小時玩了把美年健康AI大賽

並不想花太多精力去拼比賽拿名次,畢竟又工作又帶娃,時間並不多。但比較喜歡看比賽裏的技術論壇。工作中的內容相對要單一很多,很容易陷入狹窄的思維中,而比賽中,大家的思維還是很有營養的。偶爾遇到合胃口的數據,下一份,玩一玩還是不錯。

之前的糖尿病大賽,看到的時候離初賽結束只有幾天了,週末緊急下載數據,搞了一天弄出結果,然而沒有辦法提交,後來仔細看了下賽制才發現,初賽最後兩天會換數據,之前沒提交過的,最後兩天沒辦法提交。遺憾呀,搞了一天,結果都沒能驗證一下測試集的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))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章