利用数据构建非深度的传统机器学习模型¶
下面是我们要做的事-
- 我们将实现两种不同的模型
- 我们将做一个性能的度量,即一个定量的方法,以确定两种模型具体的差距如何
- 讨论一些两种模型的不同之处,比如他们的优点,缺陷等等
前文已经说过,要实现起来需要做许多决策。比如在特性管理、参数调优、模型选择以及您希望模型的可解释性之间(参考:Bayesian vs.-Bayesian方法),有许多问题需要决策。例如,下面是一些可能的模型:
- 广义线性模型
- 支持向量机
- 浅层神经网络
- 随机森林
- Boosting算法
- 决策树
或者是叶贝斯相关的:
- 朴素叶贝斯
- 线性判别分析
- 叶贝斯层次模型
这样的列表能一直列下去,但是并不是所有的模型都适用于你自定义框架的问题。你应该想想到底哪个模型最适合你。
对于我们的问题,我将分别从上述两个类别中个选择一个简单的模型作为样例-
1.支持向量机
2.多项式朴素叶贝斯
下面是接下来部分的概览:
- 一些特征管理
- 两种不同的模型
- 评估矩阵的选择
- 模型对比
让我们从一些特征管理开始¶
设计正确的特性取决于两个关键思想。首先,你想解决什么问题?例如,如果你想猜我的音乐喜好,并且你试图训练一个超级棒的模型,同时给出我的身高作为输入特征,你将没有运气。另一方面,给它我的Spotify播放列表那么任何模型都能解决问题。因此,问题的内容扮演了一个角色。
其次,您只能根据手头的数据来表示。也就是说,如果你没有访问我的Spotify播放列表,但是访问我的Facebook状态——你知道我所有的关于哈佛的状态可能都没有用。但如果你将我Facebook状态中发的Youtube链接作为输入,那么这也可以解决这个问题。因此,手头数据的可用性是第二个因素。
考虑这个问题的一个好方法是,首先考虑手头的问题,但设计特性受可用数据的限制。如果你有很多独立特征,每个特征都与类相关联,那么学习很容易。另一方面,如果类是一个非常复杂的功能特性,您可能无法学习它。
在这个问题的背景下,我们想预测一部电影的体裁。我们可以查看的是-电影概述,这是电影情节的文本描述。这个假设是有道理的,概括是对故事的简短描述,故事在给电影分配类型方面显然很重要。
因此,让我们通过利用电影概述中的单词来改进我们的特征。回到我们前面讨论的一个有趣的方法-TF-IDF。最初我们用它来过滤单词,但是我们也可以将tf-idf值作为“importance”字段的值赋给单词,而不是认为每个单词权值相等。TF-IDF只是试图给单词组中每个单词赋予一个权重。
同样,它的工作方式是-大多数电影描述都有“The的”这个词。显然,它并没有告诉你任何特别的事情。因此,权重应该与描述单词的电影数量成反比。这是属于IDF的部分。
另一方面,对于电影《星际穿越》来说,如果概述中单词“Space”出现了5次,而单词“wormhole”出现了2次,那么很可能它更多是讲述关于“Space”而不是“wormhole”。因此单词“space”应该有更高的权重。这是属于TF的部分。
我们只是使用TF-IDF给单词组中的每个单词分配权重。这很有道理,是吧?:)
from sklearn.feature_extraction.text import TfidfTransformer
tfidf_transformer = TfidfTransformer()
X_tfidf = tfidf_transformer.fit_transform(X)
X_tfidf.shape
(1595, 1365)
让我们把我们的 X 矩阵和 Y 矩阵分成训练部分和测试部分。我们在训练部分中训练模型,然后在训练部分中测出性能。你可以把这个类比成你在做习题集和在考试。当然,他们都是(假设是)来自相同的问题范围。在习题集中取得好成绩是一个很好的指标,表明你在考试中会取得好成绩,但实际上,你必须在你声称你了解这个科目之前进行测试。
msk = np.random.rand(X_tfidf.shape[0]) < 0.8
X_train_tfidf=X_tfidf[msk]
X_test_tfidf=X_tfidf[~msk]
Y_train=Y[msk]
Y_test=Y[~msk]
positions=range(len(movies_with_overviews))
# print positions
test_movies=np.asarray(positions)[~msk]
# test_movies
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import f1_score
from sklearn.metrics import make_scorer
from sklearn.metrics import classification_report
parameters = {'kernel':['linear'], 'C':[0.01, 0.1, 1.0]}
gridCV = GridSearchCV(SVC(class_weight='balanced'), parameters, scoring=make_scorer(f1_score, average='micro'))
classif = OneVsRestClassifier(gridCV)
classif.fit(X_train_tfidf, Y_train)
OneVsRestClassifier(estimator=GridSearchCV(cv=None, error_score=’raise’,estimator=SVC(C=1.0, cache_size=200, class_weight=’balanced’, coef0=0.0,decision_function_shape=None, degree=3, gamma=’auto’, kernel=’rbf’,max_iter=-1, probability=False, random_state=None, shrinking=True,tol=0.001, verbose=False),…refit=True, return_train_score=True,scoring=make_scorer(f1_score, average=micro), verbose=0),n_jobs=1)
predstfidf=classif.predict(X_test_tfidf)
print classification_report(Y_test, predstfidf, target_names=genre_names)
| class | precision | recall | f1-score | support |
| Adventure | 0.42 | 0.57 | 0.48 | 56 |
| Fantasy | 0.46 | 0.67 | 0.55 | 45 |
| Animation | 0.27 | 0.42 | 0.33 | 31 |
| Drama | 0.60 | 0.57 | 0.58 | 132 |
| Horror | 0.00 | 0.00 | 0.00 | 41 |
| Action | 0.49 | 0.67 | 0.57 | 70 |
| Comedy | 0.40 | 0.53 | 0.46 | 77 |
| History | 0.40 | 0.37 | 0.38 | 27 |
| Western | 0.33 | 0.14 | 0.20 | 7 |
| Thriller | 0.26 | 1.00 | 0.41 | 76 |
| Crime | 0.46 | 0.48 | 0.47 | 46 |
| Documentary | 0.61 | 0.67 | 0.64 | 21 |
| Science Fiction | 0.12 | 1.00 | 0.22 | 36 |
| Mystery | 0.23 | 0.37 | 0.29 | 35 |
| Music | 0.95 | 0.59 | 0.73 | 34 |
| Romance | 0.35 | 0.50 | 0.41 | 46 |
| Family | 0.32 | 0.38 | 0.35 | 42 |
| War | 0.27 | 0.44 | 0.33 | 9 |
| Foreign | 0.00 | 0.00 | 0.00 | 4 |
| TV Movie | 0.00 | 0.00 | 0.00 | 5 |
| avg / total | 0.40 | 0.56 | 0.44 | 840 |
正如你所看到的,对于像战争和动画这样的表现较少的电影来说,表演总体上更差,而对于像戏剧这样的类别,表现更好。
撇开数字不谈,让我们看看我们的模型对测试集中的一小部分电影的预测。
genre_list=sorted(list(Genre_ID_to_name.keys()))
predictions=[]
for i in range(X_test_tfidf.shape[0]):
pred_genres=[]
movie_label_scores=predstfidf[i]
#print movie_label_scores
for j in range(20):
#print j
if movie_label_scores[j]!=0:
genre=Genre_ID_to_name[genre_list[j]]
pred_genres.append(genre)
predictions.append(pred_genres)
import pickle
f=open('classifer_svc','wb')
pickle.dump(classif,f)
f.close()
for i in range(X_test_tfidf.shape[0]):
if i%50==0 and i!=0:
print 'MOVIE: ',movies_with_overviews[i]['title'],'\tPREDICTION: ',','.join(predictions[i])
MOVIE: The Walk PREDICTION: Adventure,Fantasy,Animation,Action,Thriller,Science Fiction
MOVIE: Cinderella PREDICTION: Adventure,Fantasy,Action,Thriller,Science Fiction
MOVIE: Liza, the Fox-Fairy PREDICTION: Drama,Thriller,Science Fiction,Romance,War
MOVIE: The Polar Express PREDICTION: Adventure,Action,Thriller,Science Fiction,Family
MOVIE: Patema Inverted PREDICTION: Thriller,Science Fiction,Music
让我们看看我们的第二个模型怎么样?朴素叶贝斯模型。
from sklearn.naive_bayes import MultinomialNB
classifnb = OneVsRestClassifier(MultinomialNB())
classifnb.fit(X[msk].toarray(), Y_train)
predsnb=classifnb.predict(X[~msk].toarray())
import pickle
f2=open('classifer_nb','wb')
pickle.dump(classifnb,f2)
f2.close()
predictionsnb=[]
for i in range(X_test_tfidf.shape[0]):
pred_genres=[]
movie_label_scores=predsnb[i]
for j in range(20):
#print j
if movie_label_scores[j]!=0:
genre=Genre_ID_to_name[genre_list[j]]
pred_genres.append(genre)
predictionsnb.append(pred_genres)
for i in range(X_test_tfidf.shape[0]):
if i%50==0 and i!=0:
print 'MOVIE: ',movies_with_overviews[i]['title'],'\tPREDICTION: ',','.join(predictionsnb[i])
MOVIE: The Walk PREDICTION: Adventure,Fantasy,Animation,Science Fiction
MOVIE: Cinderella PREDICTION: Adventure,Action,Science Fiction
MOVIE: Liza, the Fox-Fairy PREDICTION: Drama,Romance
MOVIE: The Polar Express PREDICTION: Science Fiction
MOVIE: Patema Inverted PREDICTION: Documentary,Music
正如上面所看到的,结果看起来很有希望,但是我们如何真正比较这两个模型呢?我们需要量化我们的表现,以便我们可以说哪一个更好。让我们回到我们刚开始讨论的内容.——我们正在学习一个函数g,它可以近似原始未知函数f。对于XI的一些值,预测肯定是错误的,我们想要最小化它。
对于多标签系统,我们经常使用“精准度”和“召回率”来评估性能。这些是标准度量,如果您对这些术语不熟悉,可以通过google阅读更多关于它们的信息。
评估矩阵¶
我们将使用标准的 精准度-召回率 矩阵来评估我们的系统。
def precision_recall(gt,preds):
TP=0
FP=0
FN=0
for t in gt:
if t in preds:
TP+=1
else:
FN+=1
for p in preds:
if p not in gt:
FP+=1
if TP+FP==0:
precision=0
else:
precision=TP/float(TP+FP)
if TP+FN==0:
recall=0
else:
recall=TP/float(TP+FN)
return precision,recall
precs=[]
recs=[]
for i in range(len(test_movies)):
if i%1==0:
pos=test_movies[i]
test_movie=movies_with_overviews[pos]
gtids=test_movie['genre_ids']
gt=[]
for g in gtids:
g_name=Genre_ID_to_name[g]
gt.append(g_name)
#print predictions[i],movies_with_overviews[i]['title'],gt
a,b=precision_recall(gt,predictions[i])
precs.append(a)
recs.append(b)
print np.mean(np.asarray(precs)),np.mean(np.asarray(recs))
0.33085149314 0.570960451977
precs=[]
recs=[]
for i in range(len(test_movies)):
if i%1==0:
pos=test_movies[i]
test_movie=movies_with_overviews[pos]
gtids=test_movie['genre_ids']
gt=[]
for g in gtids:
g_name=Genre_ID_to_name[g]
gt.append(g_name)
#print predictions[i],movies_with_overviews[i]['title'],gt
a,b=precision_recall(gt,predictionsnb[i])
precs.append(a)
recs.append(b)
print np.mean(np.asarray(precs)),np.mean(np.asarray(recs))
0.48893866021 0.549604519774
我们样本的平均精准度和召回率评分相当不错!模型似乎起作用了!另外,我们可以看到朴素叶贝斯的性能优于支持向量机。我强烈建议你阅读一下Multinomial叶贝斯,思考一下它为和非常适用于“文档分类”问题,这与我们的问题十分相似,因为每部电影的概览都可以被看成是需要我们分配标签的文档。