基于LightGBM算法實現(xiàn)數(shù)據(jù)挖掘!
對于回歸問題,Datawhale已經(jīng)梳理過完整的實踐方案(可點擊),本文對多分類的數(shù)據(jù)挖掘問題做了完整的方案總結(jié)。

一、賽題數(shù)據(jù)
賽題背景
本賽題是一個多分類的數(shù)據(jù)挖掘問題。賽題以醫(yī)療數(shù)據(jù)挖掘為背景,要求選手使用提供的心跳信號傳感器數(shù)據(jù)訓(xùn)練模型并完成不同心跳信號的分類的任務(wù)。
實踐地址:https://tianchi.aliyun.com/competition/entrance/531883/information
賽題介紹
任務(wù):賽題以預(yù)測心電圖心跳信號類別為任務(wù) 數(shù)據(jù)集: 10萬條作為訓(xùn)練集; 2萬條作為測試集A; 2萬條作為測試集B; 對心跳信號類別(label)信息進行脫敏。

字段描述
id:為心跳信號分配的唯一標識 heartbeat_signals:心跳信號序列數(shù)據(jù),其中每個樣本的信號序列采樣頻次一致,長度相等(每個樣本有205條記錄)。 label:心跳信號類別(0、1、2、3)
評測標準
選手需提交4種不同心跳信號預(yù)測的概率,選手提交結(jié)果與實際心跳類型結(jié)果進行對比,求預(yù)測的概率與真實值差值的絕對值(越小越好)。
總共有n個病例,針對某一個信號,若真實值為[y1,y2,y3,y4],模型預(yù)測概率值為[a1,a2,a3,a4],那么該模型的評價指標abs-sum為 :


簡單小結(jié)
根據(jù)賽題數(shù)據(jù)可以知道,此問題為「分類問題」,且為「多分類問題」,分類算法可以考慮,如「LR」、「貝葉斯分類」、「決策樹」等等。 根據(jù)評測標準,每一個心跳樣本都要輸出4個類別下的概率值,所以可以用「邏輯回歸LR」or 「貝葉斯分類」實現(xiàn)? 由于心跳信號自帶明顯的「時序特征」(心跳參數(shù)隨時間變化),在后續(xù)的數(shù)據(jù)處理過程中要考慮「時序特征」所來來的影響? 根據(jù)評測公式,更關(guān)注的是「查準率」,即預(yù)測準確率越高,值就越?。P偷梅帜繕耍?/span>
根據(jù)初步理解,我會初步使用「邏輯回歸LR算法」,給出每個分類下的概率值。
二、數(shù)據(jù)讀取
Baseline文檔可以粗略的劃分以下幾個部分:
工具包準備
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)計信息:均值、標準差、中位數(shù)等等。
注:這里面只能統(tǒng)計ID、label列;因為heartbeat_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)為「浮點數(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é)果是為了得到隸屬于某個類別的概率,所以這里采用「one-hot編碼」。
sparse : Will return sparse matrix if set True else will return an array.(為True時返回稀疏矩陣)
模型參數(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))
#交叉驗證分數(shù)
cv_scores = []
onehot_encoder = OneHotEncoder(sparse=False)
#將訓(xùn)練集「K折」操作,i值代表第(i+1)折。每一個K折都進行「數(shù)據(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)個模型結(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, #類別個數(shù)
'num_leaves': 2 ** 5, #最大的葉子數(shù)
'feature_fraction': 0.9, #原來是0.8
'bagging_fraction': 0.9, #原來是0.8
'bagging_freq': 5, #每5次迭代,進行一次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)沒有提高,停止計算,原來為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里面,這是一個「i個模型結(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個模型輸出結(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í)賽分數(shù)
將最終的預(yù)測結(jié)果上傳到學(xué)習(xí)賽,給出結(jié)果值!

