【火爐煉AI】機器學習018-專案案例:根據大樓進出人數預測是否舉辦活動

煉丹老頑童發表於2018-08-22

【火爐煉AI】機器學習018-專案案例:根據大樓進出人數預測是否舉辦活動

(本文所使用的Python庫和版本號: Python 3.5, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )

我們經常看到辦公大樓中人來人往,進進出出,在平時沒有什麼活動的時候,進出大樓的人數會非常少,而一旦舉辦有大型商業活動,則人山人海,熙熙攘攘,所以很明顯,大樓進出的人數和大樓是否舉辦活動有很明顯的關聯,那麼,是否可以構建一個模型,通過大樓進出人數來預測該大樓是否在舉辦某種活動了?

答案是肯定的,且聽煉丹老頑童娓娓道來。


1. 準備資料集

本專案案例所使用到的原始資料集來源於: UCI大學資料集,該資料集是公開的,讀者可以自行下載該資料集到自己的本地電腦上。

1.1 瞭解資料集

從該資料集的官方網站上,我們可以看到該資料集的基本介紹:

資料集的基本資訊

可以看出,該資料集一共有10080個樣本,沒有缺失資料,一共有四個基本屬性,集四個features,可以用於分類模型和時序模型,此處我們只是用來進行“是否舉辦活動”的預測,很明顯,是一個多分類問題,具體而言是一個二分類問題。

在我們下載該資料後,通過檢視比對,發現該有效資料主要存放在兩個檔案中(CalIt2.data和CalIt2.events),其中CalIt2.data中儲存了10080條資料記錄,每一條資料包含有四列,每一列的資訊說明如下表所示。而CalIt2.events包括有30條資料記錄,也包括有四列,其中的第一列是日期,第二列是活動開始時間,第三列是活動結束時間,第四列表示有活動。下是我總結的本專案案例資料集的基本資訊。

資料集各基本屬性的說明

其中表格中前面四行表示資料集的四個features,這些資訊位於CalIt2.data中,最後一行表示資料集的Label,位於CalIt2.events中。

1.2 資料規整

由於本專案的資料集位於兩個不同的檔案中,同時兩個檔案的樣本格式也不是一一對應,故而在構建模型之前,需要我們對資料進行規整,組成我們所需要的資料型別。

此處所用到的資料規整至少包括有三個方面:將同一個時間段內進出大樓的人數統一到一行,將CalIt2.events中的標記資料載入到CalIt2.data中組成完整的資料集,將資料集中的日期轉換為星期數,作為一個特徵向量。

1.2.1 進出大樓人員統一到一行

由於原始資料集CalIt2.data中,進大樓的位於一行,最前面的代號為9,出大樓的位於另外一行,最前面的代號為7,故而我們需要將這兩行資料統一到一行,下面是實現程式碼,所採用的核心思想是:用DataFrame的布林索引來獲取進出大樓的單獨一個DataFrame,然後將這兩個DF整合到一個DataFrame中。

# 將feature_set中進樓和出樓的人員統計到一行。
# 目前資料集中出樓人員(代號7)位於偶數行,進樓人員(代號9)位於奇數行。
# 可以使用for in 方式依次取出各行的人員數,但此處我更願意使用布林型索引
code_7=feature_set[0]==7
code_7_data=feature_set[code_7].iloc[:,3].reset_index(drop=True)
code_9=feature_set[0]==9
code_9_data=feature_set[code_9].reset_index(drop=True)
# print(code_9_data)# OK
feature_set2=code_9_data
feature_set2[4]=code_7_data
feature_set2.drop([0],axis=1,inplace=True) # 刪除第0列
# print(feature_set2) # col3 表示in,col4表示out,列印沒有問題
# feature_set2.to_csv('d:/feature_set2.csv') # 儲存以便檢視是否有誤
複製程式碼

列印結果可以參考我的原始程式碼(我的github),此處由於列印出來後結果太長,我沒有貼上來。

1.2.2 將label新增到Feature_set中組成完整的資料集

由於原始資料集的features特徵向量放置在CalIt2.data檔案中,且Label向量放置在CalIt2.events中,故而有必要將這兩部分整合到一起。但是,整合過程並不是簡單的將兩個DataFrame連線起來,而要考慮時間範圍。在CalIt2.events檔案中列舉了有活動的日期和起止時間,故而我們需要對Features中的日期和時間拿出來和CalIt2.events進行逐一比對,如果日期和時間落在events檔案中,則表示這個時間段有活動,需要作出特殊標記。有很多種方法可以是實現這種逐一比對,下面我還是採用DataFrame的切片和索引來完成,我認為,這種方式算是速度比較快的一種方式。

# 下面是如何將feature_set2和label_set整合到一個DataFrame中來
# 要判斷時間,如果feature_set2中的日期和時間都落在了label_set對應的時間內,
# 則表示有event發生,用1表示,如果沒有,用0表示。
# 比較日期時間的方法有很多,此處我採用比較簡單的方法
feature_set2[5]=0 # 表示是否有event的列都初始化為0

def calc_mins(time_str):
    nums=time_str.split(':')
    return 60*int(nums[0])+int(nums[1]) # 將時間轉換為分鐘數,此處不用考慮秒

for row_id,date in enumerate(label_set[0]): # 先取出label中的日期
    temp_df=feature_set2[feature_set2[1]==date]
    if temp_df is None:
        continue
    
    start_min=calc_mins(label_set.iloc[row_id,1])
    stop_min=calc_mins(label_set.iloc[row_id,2])
    for row in temp_df[2]: # 在逐一判斷時間是否位於label中時間之間
        feature_min=calc_mins(row)
        if feature_min>=start_min and feature_min<=stop_min: 
            feature_row=temp_df[temp_df[2]==row].index.tolist()
            feature_set2.loc[feature_row,5]=1 
        
# feature_set2.to_csv('d:/feature_set2_withLabel.csv') # 儲存後列印檢視沒有問題   
複製程式碼

1.2.3 將日期轉換為星期數

這個相對於前面兩個資料規整方面,要簡單得多,直接貼程式碼。

# 進一步處理,由於日期在以後的日子裡不可重複,作為feature並不合適,而可以用星期數來代替,
feature_set2[0]=pd.to_datetime(feature_set2[1])
# print(feature_set2.tail())
feature_set2[0]=feature_set2[0].map(lambda x: x.strftime('%w')) # 將日期轉換為星期數
feature_set2=feature_set2.reindex(columns=range(6))
print(feature_set2.tail()) # 檢視轉換沒有問題
feature_set2.to_csv('E:\PyProjects\DataSet\BuildingInOut/Sorted_Set.txt') # 將整理好的資料集儲存,下次可以直接讀取
複製程式碼

-----------------------輸---------出--------------------------------

0 1 2 3 4 5 5035 6 11/05/05 21:30:00 0 0 0 5036 6 11/05/05 22:00:00 0 3 0 5037 6 11/05/05 22:30:00 0 0 0 5038 6 11/05/05 23:00:00 0 0 0 5039 6 11/05/05 23:30:00 0 1 0

-------------------------完-------------------------------------

處理完成之後的feature_set2可以直接儲存一下,這樣下次可以直接呼叫這個資料規整之後的檔案,讀取裡面的資料集來訓練和測試即可。

1.3 資料編碼

很明顯,從上面的資料集中可以看到第1列是日期,不適合做特徵向量,需要刪除,而第2列是字串型別,沒法用於機器學習,故而我們需要對第2列進行編碼,如下程式碼:

# 由於第1列只是包含日期,作為特徵向量並不合適,故而需要刪除
feature_set2.drop([1],axis=1,inplace=True)
# 而第2列明顯是字串型別,裡面的內容對機器學習而言如同天書,故需要編碼
from sklearn import preprocessing
time_encoder=preprocessing.LabelEncoder()
feature_set2[2]=time_encoder.fit_transform(feature_set2[2])
print(feature_set2.tail())
複製程式碼

------------------輸---------出--------------------------------

0 2 3 4 5 5035 6 43 0 0 0 5036 6 44 0 3 0 5037 6 45 0 0 0 5038 6 46 0 0 0 5039 6 47 0 1 0

---------------------完-------------------------------------

所以,可以看到,第2列已經變成了數字編碼,這部分內容在我以前的文章中用到過很多次。如【火爐煉AI】機器學習013-用樸素貝葉斯分類器估算個人收入階層

########################小**********結#################

1. 在網路上,我找了好久都沒有找到如何處理這個資料集,好多都是直接給出處理後的結果,所以此處我就自己對這個資料集進行了規整,並將規整的程式碼放出來。

2. 資料集的規整和預處理往往會花掉機器學習的大部分時間,此處因為已經有了現成資料,僅僅是對資料進行規整,故而耗時相對較少。

3. Pandas和Numpy基本上是資料預處理,資料清洗,資料規整的有力神器,一定要熟練掌握。

#####################################################


2. 使用SVM構建分類器

SVM分類器的構建和其他專案案例中並沒有太大差別,可以直接參考我的其他文章:【火爐煉AI】機器學習014-用SVM構建非線性分類模型.下面直接貼出程式碼。

# 下面是使用SVM構建分類器
from sklearn.svm import SVC
classifier=SVC(kernel='rbf',probability=True,class_weight='balanced')
classifier.fit(train_X,train_y)
複製程式碼

-------------------輸---------出--------------------------------

SVC(C=1.0, cache_size=200, class_weight='balanced', coef0=0.0, decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf', max_iter=-1, probability=True, random_state=None, shrinking=True, tol=0.001, verbose=False)

-------------------------完-------------------------------------

# 用交叉驗證來檢驗模型的準確性,只是在test set上驗證準確性
from sklearn.cross_validation import cross_val_score
num_validations=5
accuracy=cross_val_score(classifier,test_X,test_y,
                         scoring='accuracy',cv=num_validations)
print('準確率:{:.2f}%'.format(accuracy.mean()*100))
precision=cross_val_score(classifier,test_X,test_y,
                         scoring='precision_weighted',cv=num_validations)
print('精確度:{:.2f}%'.format(precision.mean()*100))
recall=cross_val_score(classifier,test_X,test_y,
                         scoring='recall_weighted',cv=num_validations)
print('召回率:{:.2f}%'.format(recall.mean()*100))
f1=cross_val_score(classifier,test_X,test_y,
                         scoring='f1_weighted',cv=num_validations)
print('F1  值:{:.2f}%'.format(f1.mean()*100))
複製程式碼

-----------------------輸---------出--------------------------------

準確率:93.78%
精確度:92.96%
召回率:93.78%
F1 值:92.96%

----------------------------完-------------------------------------

從交叉驗證的結果可以看出,在測試集上,該SVM分類器仍然可以得到各項指標都達到90%以上的效果,可以認為,該SVM分類器的效果比較好。當然,如果還想繼續提升該分類效果,可以使用GridSearch方法來搜尋最佳引數組合,GridSearch的使用方法可以參考我的文章【火爐煉AI】機器學習017-使用GridSearch搜尋最佳引數組合


3. 使用該分類器對新樣本進行預測

首先我們需要新樣本資料,此處我自己構建了一些新樣本,並不一定準確,但是可以用於預測。對新樣本的預測也很簡單,直接使用classifier.predict()函式即可。

# 看起來該模型的預測效果很不錯
# 那麼用它來對新樣本資料進行預測,會是什麼樣了?
new_samples=np.array([[2,'09:30:00',20,12], # 即某個星期二,上午9點-9點半時間段,進大樓20人,出大樓12人
             [2,'11:30:00',26,9],
             [6,'12:30:00',4,22],
             [0,'05:00:00',1,0]])
transformed=time_encoder.transform(new_samples[:,1])
# print(transformed) # 檢查OK
new_samples[:,1]=transformed
print(new_samples)

# 使用classifier進行預測
output_class = classifier.predict(new_samples)
print('has events? {}'.format(output_class))
複製程式碼

---------------------輸---------出--------------------------------

[['2' '19' '20' '12']
['2' '23' '26' '9']
['6' '25' '4' '22']
['0' '10' '1' '0']]
has events? [0 1 0 0]

----------------------------完-------------------------------------

從輸出結果可以看出,只有第二個樣本有活動,其他樣本都沒有活動,可以想象的出來,第一個樣本9點-9點半有很多人進樓,但是少量人出樓,會不會是正常的上班族?而第二個樣本11點到11點半之間有很多人進來,卻很少有了出去,這個時間段應該就是會議期間正常的人員流動,而第三個樣本12點到12點半,有很多人出樓,這大概是人家出去吃午飯吧。而第四個樣本,週日的凌晨五點,估計只有鬼才能在大樓門口晃悠了。。。。

########################小**********結#################

1. 這個專案的難點不在於SVM模型的構建和分類訓練上,而在於前期資料集的規整上。

2. 後面使用SVM進行模型構建和訓練,對新樣本進行預測,在我前面的文章中講了很多次了,至此已經沒有什麼新意了。

#####################################################


注:本部分程式碼已經全部上傳到(我的github)上,歡迎下載。

參考資料:

1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯

相關文章