zoukankan      html  css  js  c++  java
  • 【火炉炼AI】机器学习018-项目案例:根据大楼进出人数预测是否举办活动

    【火炉炼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:PyProjectsDataSetBuildingInOut/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著,陶俊杰,陈小莉译

  • 相关阅读:
    leetcode Convert Sorted List to Binary Search Tree
    leetcode Convert Sorted Array to Binary Search Tree
    leetcode Binary Tree Level Order Traversal II
    leetcode Construct Binary Tree from Preorder and Inorder Traversal
    leetcode[105] Construct Binary Tree from Inorder and Postorder Traversal
    证明中序遍历O(n)
    leetcode Maximum Depth of Binary Tree
    限制 button 在 3 秒内不可重复点击
    HTML 和 CSS 画三角形和画多边行基本原理及实践
    在线前端 JS 或 HTML 或 CSS 编写 Demo 处 JSbin 与 jsFiddle 比较
  • 原文地址:https://www.cnblogs.com/RayDean/p/9765702.html
Copyright © 2011-2022 走看看