挖掘?qū)崙?zhàn),基于 LightGBM 算法搞定多分類競賽
本文對的多分類數(shù)據(jù)挖掘問題做了完整的方案總結(jié),基于LightGBM算法實(shí)現(xiàn)數(shù)據(jù)挖掘。

一、賽題數(shù)據(jù)
賽題背景
本賽題是一個(gè)多分類的數(shù)據(jù)挖掘問題。賽題以醫(yī)療數(shù)據(jù)挖掘?yàn)楸尘?,要求選手使用提供的心跳信號傳感器數(shù)據(jù)訓(xùn)練模型并完成不同心跳信號的分類的任務(wù)。
實(shí)踐地址:https://tianchi.aliyun.com/competition/entrance/531883/information

賽題介紹

字段描述
heartbeat_signals:心跳信號序列數(shù)據(jù),其中每個(gè)樣本的信號序列采樣頻次一致,長度相等(每個(gè)樣本有205條記錄)。
評測標(biāo)準(zhǔn)
選手需提交4種不同心跳信號預(yù)測的概率,選手提交結(jié)果與實(shí)際心跳類型結(jié)果進(jìn)行對比,求預(yù)測的概率與真實(shí)值差值的絕對值(越小越好)。
總共有n個(gè)病例,針對某一個(gè)信號,若真實(shí)值為[y1,y2,y3,y4],模型預(yù)測概率值為[a1,a2,a3,a4],那么該模型的評價(jià)指標(biāo)abs-sum為 :
根據(jù)賽題數(shù)據(jù)可以知道,此問題為「分類問題」,且為「多分類問題」,分類算法可以考慮,如「LR」、「貝葉斯分類」、「決策樹」等等。
根據(jù)評測標(biāo)準(zhǔn),每一個(gè)心跳樣本都要輸出4個(gè)類別下的概率值,所以可以用「邏輯回歸LR」or 「貝葉斯分類」實(shí)現(xiàn)?
由于心跳信號自帶明顯的「時(shí)序特征」(心跳參數(shù)隨時(shí)間變化),在后續(xù)的數(shù)據(jù)處理過程中要考慮「時(shí)序特征」所來來的影響?
根據(jù)評測公式,更關(guān)注的是「查準(zhǔn)率」,即預(yù)測準(zhǔn)確率越高,值就越小(模型得分目標(biāo))
根據(jù)初步理解,我會初步使用「邏輯回歸LR算法」,給出每個(gè)分類下的概率值。
二、數(shù)據(jù)讀取
Baseline文檔可以粗略的劃分以下幾個(gè)部分:
工具包準(zhǔn)備
import?os
import?gc
import?math
import?pandas?as?pd
import?numpy?as?np
import?lightgbm?as?lgb
#?import?xgboost?as?xgb
from?catboost?import?CatBoostRegressor
from?sklearn.linear_model?import?SGDRegressor,?LinearRegression,?Ridge
from?sklearn.preprocessing?import?MinMaxScaler
from?sklearn.model_selection?import?StratifiedKFold,?KFold
from?sklearn.metrics?import?log_loss
from?sklearn.model_selection?import?train_test_split
from?sklearn.preprocessing?import?OneHotEncoder
from?tqdm?import?tqdm
import?matplotlib.pyplot?as?plt
import?time
import?warnings
warnings.filterwarnings('ignore')
工具包導(dǎo)入:pandas、numpy、sklearn、lightgbm等。
數(shù)據(jù)讀取
path?=?'/Users/huangyulong/Desktop/心跳信號分類預(yù)測'
train_csv?=?'/train.csv'?
testA_csv?=?'/testA.csv'
train?=?pd.read_csv(path?+?train_csv)
test?=?pd.read_csv(path?+?testA_csv)
查看數(shù)據(jù)集與測試集
train.head()

test.head()

4種心跳信號特征:
signal_values?=?[]
for?i?in?range(4):
????temp?=?train[train['label']==i].iloc[0,?1].split(',')
????temp?=?list(map(float,?temp))
????signal_values.append(temp)
????
signal_values?=?np.array(signal_values)
color?=?['red',?'green',?'yellow',?'blue']
label?=?['label_0',?'label_1'?,'label_2'?,'label_3']
plt.figure(figsize=(8,?4))
for?i?in?range(4):
????plt.plot(signal_values[i],?color=color[i],?label=label[i])
????plt.legend()?
????
plt.show()

數(shù)據(jù)整體信息:數(shù)據(jù)類型、是否有缺失值等
train.info()
<class?'pandas.core.frame.DataFrame'>
RangeIndex:?100000?entries,?0?to?99999
Data?columns?(total?3?columns):
?#???Column?????????????Non-Null?Count???Dtype??
---??------?????????????--------------???-----??
?0???id?????????????????100000?non-null??int64??
?1???heartbeat_signals??100000?non-null??object?
?2???label??????????????100000?non-null??float64
dtypes:?float64(1),?int64(1),?object(1)
memory?usage:?2.3+?MB
數(shù)據(jù)統(tǒng)計(jì)信息:均值、標(biāo)準(zhǔn)差、中位數(shù)等等。
注:這里面只能統(tǒng)計(jì)ID、label列;因?yàn)閔eartbeat_signals數(shù)據(jù)不符合格式。
train.describe()

4種心跳信號類別在數(shù)據(jù)集中占比情況:
train['label'].value_counts()
0.0????64327
3.0????17912
2.0????14199
1.0?????3562
Name:?label,?dtype:?int64

三、數(shù)據(jù)預(yù)處理
由于原始數(shù)據(jù)中,heartbeat_signals 列存儲了205條信息,所以要把這一列數(shù)據(jù)轉(zhuǎn)化成方便讀取、易于使用的格式:比如構(gòu)建205列。
train_list?=?[]
for?items?in?train.values:
????train_list.append([items[0]]?+?[float(i)?for?i?in?items[1].split(',')]?+?[items[2]])
train1?=?pd.DataFrame(np.array(train_list))
train1.columns?=?['id']?+?['s_'+str(i)?for?i?in?range(len(train_list[0])-2)]?+?['label']
train1

設(shè)置數(shù)值類型
設(shè)置每列數(shù)值的「數(shù)值類型」:由每列的最大值和最小值來確定。
def?reduce_mem_usage(df):
????start_mem?=?df.memory_usage().sum()?/?1024**2?
????print('Memory?usage?of?dataframe?is?{:.2f}?MB'.format(start_mem))
????
????for?col?in?df.columns:
????????col_type?=?df[col].dtype
????????
????????if?col_type?!=?object:
????????????c_min?=?df[col].min()
????????????c_max?=?df[col].max()
????????????if?str(col_type)[:3]?==?'int':
????????????????if?c_min?>?np.iinfo(np.int8).min?and?c_max?<?np.iinfo(np.int8).max:
????????????????????df[col]?=?df[col].astype(np.int8)
????????????????elif?c_min?>?np.iinfo(np.int16).min?and?c_max?<?np.iinfo(np.int16).max:
????????????????????df[col]?=?df[col].astype(np.int16)
????????????????elif?c_min?>?np.iinfo(np.int32).min?and?c_max?<?np.iinfo(np.int32).max:
????????????????????df[col]?=?df[col].astype(np.int32)
????????????????elif?c_min?>?np.iinfo(np.int64).min?and?c_max?<?np.iinfo(np.int64).max:
????????????????????df[col]?=?df[col].astype(np.int64)??
????????????else:
????????????????if?c_min?>?np.finfo(np.float16).min?and?c_max?<?np.finfo(np.float16).max:
????????????????????df[col]?=?df[col].astype(np.float16)
????????????????elif?c_min?>?np.finfo(np.float32).min?and?c_max?<?np.finfo(np.float32).max:
????????????????????df[col]?=?df[col].astype(np.float32)
????????????????else:
????????????????????df[col]?=?df[col].astype(np.float64)
????????else:
????????????df[col]?=?df[col].astype('category')
????end_mem?=?df.memory_usage().sum()?/?1024**2?
????print('Memory?usage?after?optimization?is:?{:.2f}?MB'.format(end_mem))
????print('Decreased?by?{:.1f}%'.format(100?*?(start_mem?-?end_mem)?/?start_mem))
????
????return?df
轉(zhuǎn)換數(shù)據(jù)格式
將「字符串」轉(zhuǎn)為「浮點(diǎn)數(shù)」
train_list = []
for items in train.values:
? ?train_list.append([items[0]] + [float(i) for i in items[1].split(',')] + [items[2]])
train2 = pd.DataFrame(np.array(train_list))
train2.columns = ['id'] + ['s_'+str(i) for i in range(len(train_list[0])-2)] + ['label']
train2 = reduce_mem_usage(train2)
test_list=[]
for items in test.values:
? ?test_list.append([items[0]] + [float(i) for i in items[1].split(',')])
test2 = pd.DataFrame(np.array(test_list))
test2.columns = ['id'] + ['s_'+str(i) for i in range(len(test_list[0])-1)]
test2 = reduce_mem_usage(test2)
數(shù)據(jù)樣本處理
訓(xùn)練數(shù)據(jù)樣本與測試數(shù)據(jù)樣本
#訓(xùn)練樣本
x_train = train2.drop(['id','label'], axis=1)
y_train = train2['label']
#測試樣本
x_test = test2.drop(['id'], axis=1)
四、模型訓(xùn)練
1、評估函數(shù)
評測公式(損失函數(shù)):
def?abs_sum(y_pre,y_tru):
????y_pre=np.array(y_pre)
????y_tru=np.array(y_tru)
????loss=sum(sum(abs(y_pre-y_tru)))
????return?loss
2、模型參數(shù)設(shè)置
n_splits : int, default=3
shuffle : Whether to shuffle the data before splitting into batches.
random_state : When shuffle=True, pseudo-random number generator state used for shuffling. If None, use default numpy RNG for shuffling.
3、one-hot編碼
而我們的分類結(jié)果是為了得到隸屬于某個(gè)類別的概率,所以這里采用「one-hot編碼」。
sparse : Will return sparse matrix if set True else will return an array.(為True時(shí)返回稀疏矩陣)
模型參數(shù)設(shè)置
def?cv_model(clf,?train_x,?train_y,?test_x,?clf_name):
????folds?=?100
????seed?=?2021
????kf?=?KFold(n_splits=folds,?shuffle=True,?random_state=seed)
????
????#設(shè)置測試集,輸出矩陣。每一組數(shù)據(jù)輸出:[0,0,0,0]以概率值填入
????test?=?np.zeros((test_x.shape[0],4))
????
????#交叉驗(yàn)證分?jǐn)?shù)
????cv_scores?=?[]
????onehot_encoder?=?OneHotEncoder(sparse=False)
????
????#將訓(xùn)練集「K折」操作,i值代表第(i+1)折。每一個(gè)K折都進(jìn)行「數(shù)據(jù)混亂:隨機(jī)」操作
????#train_index:用于訓(xùn)練的(K-1)的樣本索引值
????#valid_index:剩下1折樣本索引值,用于給出「訓(xùn)練誤差」
????for?i,?(train_index,?valid_index)?in?enumerate(kf.split(train_x,?train_y)):
????????if?i?<?7:
????????????#打印第(i+1)個(gè)模型結(jié)果
????????????print('************************************?{}?************************************'.format(str(i+1)))
????????????
????????????#將訓(xùn)練集分為:真正訓(xùn)練的數(shù)據(jù)(K-1折),和?訓(xùn)練集中的測試數(shù)據(jù)(1折)
????????????trn_x,?trn_y,?val_x,?val_y?=?train_x.iloc[train_index],?train_y[train_index],?train_x.iloc[valid_index],?train_y[valid_index]
????????????
????????????
????????????#LGB模型
????????????if?clf_name?==?"lgb":
????????????????
????????????????#訓(xùn)練樣本
????????????????train_matrix?=?clf.Dataset(trn_x,?label=trn_y)
????????????????#訓(xùn)練集中測試樣本
????????????????valid_matrix?=?clf.Dataset(val_x,?label=val_y)
????????????????
????????????????#參數(shù)設(shè)置
????????????????params?=?{
????????????????????'boosting_type':?'gbdt',??????????#boosting方式
????????????????????'objective':?'multiclass',????????#任務(wù)類型為「多分類」
????????????????????'num_class':?4,???????????????????#類別個(gè)數(shù)
????????????????????'num_leaves':?2?**?5,?????????????#最大的葉子數(shù)
????????????????????'feature_fraction':?0.9,??????????#原來是0.8
????????????????????'bagging_fraction':?0.9,??????????#原來是0.8
????????????????????'bagging_freq':?5,????????????????#每5次迭代,進(jìn)行一次bagging
????????????????????'learning_rate':?0.05,????????????#學(xué)習(xí)效率:原來是0.1
????????????????????'seed':?seed,?????????????????????#seed值,保證模型復(fù)現(xiàn)
????????????????????'nthread':?28,????????????????????#
????????????????????'n_jobs':24,??????????????????????#多線程
????????????????????'verbose':?1,
????????????????????'lambda_l1':?0.4,?????????????????#新添加?L1
????????????????????'lambda_l2':?0.5,?????????????????#新添加?L2
????????????????????'min_data_in_leaf':100,???????????#葉子可能具有的最小記錄數(shù)
????????????????}
????????????????
????????????????#模型
????????????????model?=?clf.train(params,?
??????????????????????????train_set=train_matrix,?????#訓(xùn)練樣本
??????????????????????????valid_sets=valid_matrix,????#測試樣本?
??????????????????????????num_boost_round=10000,??????#迭代次數(shù),原來為2000
??????????????????????????verbose_eval=100,???????????#
??????????????????????????early_stopping_rounds=500)??#如果數(shù)據(jù)在500次內(nèi)沒有提高,停止計(jì)算,原來為200
????????????????val_pred?=?model.predict(val_x,?num_iteration=model.best_iteration)
????????????????test_pred?=?model.predict(test_x,?num_iteration=model.best_iteration)?
????????????val_y?=?np.array(val_y).reshape(-1,?1)
????????????val_y?=?onehot_encoder.fit_transform(val_y)
????????????print('預(yù)測的概率矩陣為:')
????????????print(test_pred)
????????????
????????????#將預(yù)測結(jié)果填入到test里面,這是一個(gè)「i個(gè)模型結(jié)果累加過程」
????????????test?+=?test_pred
????????????#評測公式
????????????score?=?abs_sum(val_y,?val_pred)
????????????cv_scores.append(score)
????????????print(cv_scores)
????????
????print("%s_scotrainre_list:"?%?clf_name,?cv_scores)
????print("%s_score_mean:"?%?clf_name,?np.mean(cv_scores))
????print("%s_score_std:"?%?clf_name,?np.std(cv_scores))
????
????#下面公式是什么含義呢?為啥要除以「K折數(shù)」?:i個(gè)模型輸出結(jié)果的平均值。
????test?=?test?/?7
????return?test
調(diào)用模型
def?lgb_model(x_train,?y_train,?x_test):
????lgb_test?=?cv_model(lgb,?x_train,?y_train,?x_test,?"lgb")
????return?lgb_test
訓(xùn)練模型
lgb_test?=?lgb_model(x_train,?y_train,?x_test)
預(yù)測結(jié)果
temp = pd.DataFrame(lgb_test)
result=pd.read_csv('sample_submit.csv')
result['label_0']=temp[0]
result['label_1']=temp[1]
result['label_2']=temp[2]
result['label_3']=temp[3]
result.to_csv('submit1.csv',index=False)
第一次天池學(xué)習(xí)賽分?jǐn)?shù)
將最終的預(yù)測結(jié)果上傳到學(xué)習(xí)賽,給出結(jié)果值!

五、思考
1、Baseline代碼是基于LGBM算法實(shí)現(xiàn)的,其中一些參數(shù)具體含義需要后續(xù)學(xué)習(xí)。
2、在原始代碼上,我修改了一下學(xué)習(xí)率,增加了迭代次數(shù),嘗試配置了約束項(xiàng)。輸出的訓(xùn)練誤差很?。╯core25左右),但是在測試集上的誤差就很大,說明模型過擬合了,需要后續(xù)研究是哪些參數(shù)的作用。
3、Baseline里面只是對原始數(shù)據(jù)做了學(xué)習(xí),根據(jù)4中心跳信號分類圖可以知道,不同類型的信號「時(shí)序特征」有著明顯的區(qū)別,如何在模型中體現(xiàn)?
4、是否需要考慮類別數(shù)量不均衡的問題?