这个数据科学项目已经被用作Meta(Facebook)招聘过程中的一项外带作业。在这个外带作业中,我们将探索烂番茄如何将电影标记为“烂”、“新鲜”或“认证新鲜”。
这个数据科学项目的链接:https://platform.stratascratch.com/data-projects/rotten-tomatoes-movies-rating-prediction
为了做到这一点,我们将开发两种不同的方法。
在我们的探索过程中,我们将讨论数据预处理、各种分类器以及提高模型性能的潜在改进方法。
在本文的最后,你将了解到如何利用机器学习来预测电影的成功,并且了解这些知识如何应用于娱乐行业。
但在深入研究之前,让我们先了解一下我们将要处理的数据。
第二种方法:基于评论情感预测电影状态
在第二种方法中,我们计划通过评估评论的情感来预测电影的成功。我们将特别应用情感分析来评估评论的整体情感,并根据这种情感将电影分类为“新鲜”或“烂”。
然而,在开始情感分析之前,我们必须先准备好我们的数据集。与前面的策略不同,这个策略涉及到处理文本数据(评论),而不是数值和分类变量。对于这个挑战,我们将继续使用随机森林模型。让我们在继续之前仔细看看我们的数据。
首先,让我们读取数据。
以下是代码。
df_critics = pd.read_csv('rotten_tomatoes_critic_reviews_50k.csv')
df_critics.head()
以下是输出结果。
很好,让我们从数据预处理开始。
数据预处理
在这个数据集中,我们没有电影名称和对应的状态。对于这个数据集,我们有review_content和review_type变量。
这就是为什么我们将这个数据集与我们之前的数据集合并,根据rotten_tomatoes_link选择必要的特征,使用索引括号进行选择。
以下是代码:
df_merged = df_critics.merge(df_movie, how='inner', on=['rotten_tomatoes_link'])
df_merged = df_merged[['rotten_tomatoes_link', 'movie_title', 'review_content', 'review_type', 'tomatometer_status']]
df_merged.head()
以下是输出结果。
在这个方法中,我们只使用review_content列作为输入特征,review_type作为真实标签。
为了确保数据可用,我们需要过滤掉review_content列中的任何缺失值,因为空的评论不能用于情感分析。
df_merged = df_merged.dropna(subset=['review_content'])
在过滤掉缺失值后,我们将可视化review_type的分布,以更好地了解数据的分布情况。
# 绘制评论分布图
ax = df_merged.review_type.value_counts().plot(kind='bar', figsize=(12,9))
ax.bar_label(ax.containers[0])
这个可视化将帮助我们确定数据中是否存在类别不平衡,并指导我们选择适当的模型评估指标。
以下是全部代码:
df_merged = df_merged.dropna(subset=['review_content'])
# 绘制评论分布图
ax = df_merged.review_type.value_counts().plot(kind='bar', figsize=(12,9))
ax.bar_label(ax.containers[0])
这是输出结果。
看起来我们的特征之间存在一个不平衡的问题。
而且,我们的数据点太多了,这可能会降低我们的速度。
所以,我们首先从原始数据集中选择5000个条目。
df_sub = df_merged[0:5000]
然后我们将进行序数编码。
review_type = pd.DataFrame(df_sub.review_type.replace(['Rotten','Fresh'],[0,1]))
最后,我们将通过使用Python中的concat()方法创建一个包含编码标签和评论内容的数据框,并使用head()方法查看前5行。
df_feature_critics = pd.concat([df_sub[['review_content']]
,review_type], axis=1).dropna()
df_feature_critics.head()
这是整个代码。
# 从原始数据集中只选择5000个条目
df_sub = df_merged[0:5000]
# 对标签进行编码
review_type = pd.DataFrame(df_sub.review_type.replace(['Rotten','Fresh'],[0,1]))
# 构建最终的数据框
df_feature_critics = pd.concat([df_sub[['review_content']]
,review_type], axis=1).dropna()
df_feature_critics.head()
这是输出结果。
太好了,现在作为本节的最后一步,让我们将数据集分割为训练集和测试集。
X_train, X_test, y_train, y_test = train_test_split( df_feature_critics['review_content'], df_feature_critics['review_type'], test_size=0.2, random_state=42)
默认随机森林
为了使用DataFrame中的文本评论进行机器学习方法,我们必须将它们转换成可以处理的格式。在自然语言处理中,这被称为标记化,即将文本或单词转换为n维向量,然后将这些向量表示作为我们的机器学习算法的训练数据。
为此,我们将使用scikit-learn的CountVectorizer类将文本评论转换为令牌计数矩阵。我们首先根据输入文本创建一个唯一术语的字典。
例如,基于两个评论“这部电影很好”和“这部电影很糟糕”,算法将创建一个唯一短语的字典,如:
然后,根据输入文本,我们计算字典中每个单词出现的次数。
[“this”, “movie”, “is”, “a”, “good”, “the”, “bad”].
例如,输入“这部电影很好”将得到一个向量[1, 2, 1, 1, 1, 0, 0]
最后,我们将生成的向量输入到我们的随机森林模型中。
通过训练我们的随机森林分类器对向量化的文本数据进行情感预测,并将电影分类为“新鲜”的或“烂”的。
以下代码实例化了一个CountVectorizer类,将文本数据转换为数值向量,并指定一个词必须在至少一个文档中出现才能包含在词汇表中。
# 实例化向量化器类
vectorizer = CountVectorizer(min_df=1)
接下来,我们将使用实例化的CountVectorizer对象将训练数据转换为向量。
# 将我们的文本数据转换为向量
X_train_vec = vectorizer.fit_transform(X_train).toarray()
然后,我们使用指定的随机状态实例化一个RandomForestClassifier对象,并使用训练数据拟合随机森林模型。
# 初始化随机森林并训练
rf = RandomForestClassifier(random_state=2)
rf.fit(X_train_vec, y_train)
现在是使用训练模型和转换后的测试数据进行预测的时候了。
然后我们将打印出包含精确度、召回率和F1值等评估指标的分类报告。
# 预测并输出分类报告
y_predicted = rf.predict(vectorizer.transform(X_test).toarray())
print(classification_report(y_test, y_predicted))
最后,让我们创建一个指定大小的新图来绘制混淆矩阵。
fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax
这是整个代码。
# 实例化向量化器类
vectorizer = CountVectorizer(min_df=1)
# 将文本数据转换为向量
X_train_vec = vectorizer.fit_transform(X_train).toarray()
# 初始化随机森林并训练
rf = RandomForestClassifier(random_state=2)
rf.fit(X_train_vec, y_train)
# 预测并输出分类报告
y_predicted = rf.predict(vectorizer.transform(X_test).toarray())
print(classification_report(y_test, y_predicted))
fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax
这是输出结果。
加权随机森林
从最新的混淆矩阵中可以看出,我们模型的性能还不够好。
然而,这可能是由于工作数据点数量有限(仅为5000,而非100000)所致。
让我们看看是否可以通过解决类别不平衡问题来提高性能。
以下是代码。
class_weight = compute_class_weight(class_weight='balanced', classes=np.unique(df_feature_critics.review_type),
y=df_feature_critics.review_type.values)
class_weight_dict = dict(zip(range(len(class_weight.tolist())), class_weight.tolist()))
class_weight_dict
这是输出结果。
现在,我们使用向量化的文本输入来训练随机森林分类器,但这次包括类别权重信息以提高评估指标。
首先,我们创建CountVectorizer类,并像之前一样将文本输入转换为向量。
然后,我们定义一个带有计算的类别权重的随机森林并进行训练。
vectorizer = CountVectorizer(min_df=1)
X_train_vec = vectorizer.fit_transform(X_train).toarray()
现在,我们进行预测,使用测试数据并打印分类报告。
# 预测并输出分类报告
y_predicted = rf_weighted.predict(vectorizer.transform(X_test).toarray())
print(classification_report(y_test, y_predicted))
最后一步,我们设置图的大小并绘制混淆矩阵。
fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf_weighted, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax)
这是整个代码。
# 实例化向量化器类
vectorizer = CountVectorizer(min_df=1)
# 将文本数据转换为向量
X_train_vec = vectorizer.fit_transform(X_train).toarray()
# 初始化随机森林并训练
rf_weighted = RandomForestClassifier(random_state=2, class_weight=class_weight_dict)
rf_weighted.fit(X_train_vec, y_train)
# 预测并输出分类报告
y_predicted = rf_weighted.predict(vectorizer.transform(X_test).toarray())
print(classification_report(y_test, y_predicted))
fig, ax = plt.subplots(figsize=(12, 9))
plot_confusion_matrix(rf_weighted, vectorizer.transform(X_test).toarray(), y_test, cmap ='cividis', ax=ax)
这是输出结果。
现在我们的模型的准确率略高于没有使用类别权重的模型。
此外,因为类别0(’Rotten’)的权重大于类别1(’Fresh’),所以模型在预测’腐烂’影评时表现更好,但在预测’新鲜’影评时表现更差。
这是因为模型更加关注被标记为’腐烂’的数据。
电影状态预测
现在我们使用随机森林模型来预测电影的状态,因为我们已经训练它来预测电影评论的情感。我们将通过以下步骤确定电影的状态:
- 收集某部电影的所有评论。
- 利用我们的随机森林模型估计每个评论的状态(例如,’新鲜’或’腐烂’)。
- 根据Rotten Tomatoes网站上给出的基于规则的方法,对电影的最终状态进行分类。
在下面的代码中,我们首先创建一个名为predict_movie_status的函数,它以一个预测值作为参数。
然后,根据positive_percentage值,我们确定电影的状态,将’新鲜’或’腐烂’分配给预测变量。
最后,它将输出积极评论的百分比和电影的状态。
以下是代码。
def predict_movie_status(prediction):
"""根据预测结果分配标签(新鲜/腐烂)"""
positive_percentage = (prediction == 1).sum()/len(prediction)*100
prediction = '新鲜' if positive_percentage >= 60 else '腐烂'
print(f'积极评论:{positive_percentage:.2f}%')
print(f'电影状态:{prediction}')
在这个例子中,我们将预测三部电影的状态:《谎言的身体》(Body of Lies)、《天使之心》(Angel Heart)和《公爵夫人》(The Duchess)。让我们从《谎言的身体》开始。
《谎言的身体》的预测
现在根据上面所述,首先让我们收集《谎言的身体》电影的所有评论。
以下是代码。
# 收集《谎言的身体》电影的所有评论
df_bol = df_merged.loc[df_merged['movie_title'] == 'Body of Lies']
df_bol.head()
以下是输出。
很好,在这个阶段让我们应用加权随机森林算法来预测状态。然后我们将其用于之前定义的自定义函数中,该函数以预测值作为参数。
以下是代码。
y_predicted_bol = rf_weighted.predict(vectorizer.transform(df_bol['review_content']).toarray())
predict_movie_status(y_predicted_bol)
以下是输出。
这是我们的结果,让我们通过与实际状态进行比较来检查结果是否有效。
以下是代码。
df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'Body of Lies'].unique()
以下是输出。
看起来我们的预测是相当准确的,因为该电影的状态与我们预测的一致。
《天使之心》的预测
在这里我们将重复所有步骤。
- 收集所有评论
- 进行预测
- 进行比较
让我们首先收集《安娜·卡列尼娜》电影的所有评论。
以下是代码。
df_ah = df_merged.loc[df_merged['movie_title'] == 'Angel Heart']
df_ah.head()
以下是输出。
现在是使用随机森林和我们自定义函数进行预测的时候了。
以下是代码。
y_predicted_ah = rf_weighted.predict(vectorizer.transform(df_ah['review_content']).toarray())
predict_movie_status(y_predicted_ah)
以下是输出结果。
让我们进行比较。
以下是代码。
df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'Angel Heart'].unique()
以下是输出结果。
我们的模型再次预测正确。
现在我们再试一次。
‘The Duchess’ 预测
首先让我们收集所有的评论。
以下是代码。
df_duchess = df_merged.loc[df_merged['movie_title'] == 'The Duchess']
df_duchess.head()
以下是输出结果。
然后现在是进行预测的时候了。
以下是代码。
y_predicted_duchess = rf_weighted.predict(vectorizer.transform(df_duchess['review_content']).toarray())
predict_movie_status(y_predicted_duchess)
以下是输出结果。
让我们将我们的预测与真实情况进行比较。
以下是代码。
df_merged['tomatometer_status'].loc[df_merged['movie_title'] == 'The Duchess'].unique()
以下是输出结果。
而这部电影的真实标签是’Fresh’,表明我们模型的预测是不正确的。
然而,可以注意到我们模型的预测非常接近60%的阈值,这意味着对模型进行微调可能会将其预测从’Rotten’更改为’Fresh’。
显然,我们上面训练的随机森林模型并不是最好的模型,因为仍然有改进的潜力。在下一部分中,我们将提供许多改进我们模型性能的建议。
改进性能的建议
- 增加数据量。
- 设置不同的随机森林模型超参数。
- 应用不同的机器学习模型以找到最佳模型。
- 调整用于表示文本数据的方法。
结论
在本文中,我们探讨了基于数值和分类特征预测电影状态的两种不同方法。
我们首先进行了数据预处理,然后应用了决策树分类器和随机森林分类器来训练我们的模型。
我们还尝试了特征选择和加权随机森林分类器。
在第二种方法中,我们使用默认的随机森林和加权随机森林来预测三部不同电影的状态。
我们提供了改进模型性能的建议。希望本文对您有所帮助。
如果您想要一些初学者级别的项目,请查看我们的帖子“初学者的数据科学项目创意”。Nate Rosidi 是一位数据科学家和产品战略家。他还是一位兼职教授,教授分析学,并且是StrataScratch的创始人,这是一个帮助数据科学家准备面试的平台,提供来自顶级公司的真实面试题。您可以通过Twitter:StrataScratch或LinkedIn与他联系。