题目参见San Francisco Crime Classification
这里简要介绍下这个题目的注意事项:
- 多分类。为每个类输出相应类的概率
- 评估的时候使用的是multi-class log loss
特征工程
无疑,当前使用的是最原始的特征。我们并未对特征进行一定的调整,而是粗暴的直接拿来使用。
目前特征分成以下几类:
- 时间类,包括月份、天、时、具体星期几
- 地址类,包括经纬度以及所在辖区
Baseline Model
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn import linear_model
from sklearn import metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
targets = ['ARSON', 'ASSAULT', 'BAD CHECKS', 'BRIBERY', 'BURGLARY',
'DISORDERLY CONDUCT', 'DRIVING UNDER THE INFLUENCE', 'DRUG/NARCOTIC',
'DRUNKENNESS', 'EMBEZZLEMENT', 'EXTORTION', 'FAMILY OFFENSES',
'FORGERY/COUNTERFEITING', 'FRAUD', 'GAMBLING', 'KIDNAPPING',
'LARCENY/THEFT', 'LIQUOR LAWS', 'LOITERING', 'MISSING PERSON',
'NON-CRIMINAL', 'OTHER OFFENSES', 'PORNOGRAPHY/OBSCENE MAT',
'PROSTITUTION', 'RECOVERED VEHICLE', 'ROBBERY', 'RUNAWAY',
'SECONDARY CODES', 'SEX OFFENSES FORCIBLE', 'SEX OFFENSES NON FORCIBLE',
'STOLEN PROPERTY', 'SUICIDE', 'SUSPICIOUS OCC', 'TREA', 'TRESPASS',
'VANDALISM', 'VEHICLE THEFT', 'WARRANTS', 'WEAPON LAWS']
def feature_engineer(data):
data["Month"] = data["Dates"].map(lambda x: int(x[5:7]))
data["Day"] = data["Dates"].map(lambda x:int(x[8:10]))
data["Hour"] = data["Dates"].map(lambda x:int(x[11:13]))
data["District_No"] = data["PdDistrict"].map(lambda x:districts[x])
data["Weekday"] = data["DayOfWeek"].map(lambda x:weekdays[x])
return data
train = pd.read_csv("/Users/maodou/Desktop/crimeclass/train.csv")
test = pd.read_csv("/Users/maodou/Desktop/crimeclass/test.csv")
districts = {c:i for i,c in enumerate(train['PdDistrict'].unique())}
weekdays = {c:i for i,c in enumerate(train['DayOfWeek'].unique())}
train = feature_engineer(train)
train["targets"] = train["Category"].map(lambda x:targets.index(x))
test = feature_engineer(test)
X_all = ["Month", "Day", "Hour", "Weekday", "District_No", "X", "Y"]
forest = RandomForestClassifier(n_estimators=100).fit(train[X_all],train['targets'])
result = forest.predict_proba(test[X_all])
submission = pd.DataFrame({targets[p] : [result[i][p] for i in range(len(result))] for p in range(len(result[0]))})
submission['Id'] = [i for i in range(len(submission))]
submission.to_csv("submission_idx_baseline_tuning.csv.gz", index=False, compression='gzip')
这个模型结果3.8分。不够好。下面将优化。
Baseline代码中的几个Tricks
- 从最终predict出来的矩阵转到submission中的multi-class的DataFrame
submission = pd.DataFrame({targets[p] : [result[i][p] for i in range(len(result))] for p in range(len(result[0]))})
一句话初始化转换。
如果使用多重for循环,先默认初始化再复制的形式,会导致运行时间大大增长。
- 单独定义feature_engineer函数,避免混乱的train和test同时变换
- 通过选择train中的指定列来定义训练数据,避免重新构造新的data frame。直接train[X_selected]即可只选择X_selected中选定的列数组
调参
RandomForest调参
RandomForest大部分参数使用默认的参数即可。涉及到调参的主要是以下几个:
- n_estimators : 森林中树的个数, 一般来说树的个数越多越好。故该参数应该设置成能设置的最大值,不需要重新搜索
- max_depth : 树的深度 一般设置为10到100之间
- min_samples_split : 向下继续分支的最小样本数
- max_features : 每颗树使用的特征数(log2, sqrt(auto), 等)
使用GridSearchCV来搜索最佳值
params = {"max_features":("log2", "sqrt")}
gsearch = GridSearchCV(estimator=RandomForestClassifier(n_estimators=100, max_depth=10, min_samples_split=100), param_grid = params, scoring = "neg_log_loss")
gsearch.fit(train[X_all], train["targets"])
print(gsearch.grid_scores_)
print("best params")
print(gsearch.best_params_)
print("best scores")
print(gsearch.best_score_)
这样逐个确认最佳参数。局部最优值不代表全局,可目前只能用这个方法啦。
最终使用参数如下:
forest = RandomForestClassifier(n_estimators=100, max_depth=10, min_samples_split=100, max_features="log2", oob_score=True).fit(train[X_all],train['targets'])
得分2.4 , 大大提高了排名。
对比
优化点 | Score |
---|---|
RF默认 | 3.84746 |
不使用街角特征 | 2.43471 |
不变换经度纬度&不使用街角特征 | 2.4061 |
不变换经度纬度&不使用街角特征&不变换Hour | 2.40072 |
使用KNN进行预测
主体代码一样,关键代码如下:
knn = KNeighborsClassifier(n_neighbors = 4, weights = 'uniform').fit(train[X_all], train["targets"])
result = knn.predict_proba(test[X_all])
最终效果,得分21.48253。
简直不可接受啊!!!!
现在进行调参。
这个n_neighbors参数很多文章说使用3到5就行了。但经试验,发现这个值会对模型结果影响特别大。
params = {"n_neighbors": range(100 , 801, 100)}
gsearch = GridSearchCV(estimator=KNeighborsClassifier( weights = 'uniform'), param_grid = params , scoring = "neg_log_loss")
gsearch.fit(train[X_all], train["targets"])
print(gsearch.grid_scores_)
print("best params")
print(gsearch.best_params_)
print("best scores")
print(gsearch.best_score_)
在该实验中,发现n_neighbors取到800能达到更好的效果。public score能到2.77分。目前该值还是越大越好。因为本人电脑性能问题,暂不对n_neighbors的最优值进行探索。
通过对KNN和RF的对比可以发现,使用不同的算法,很可能会大大影响到找到使模型快速收敛到最佳状态的参数。