赞
踩
本次案例是中国人民大学“人工智能与机器学习(2022年秋季)”课程的课堂竞赛。
比赛是根据有关电影的各种信息来预测电影的受欢迎程度,包括演员、工作人员、情节关键字、预算、收入、海报、上映日期、语言、制作公司、国家、TMDB 投票计数、平均投票等。
比赛是在kaggle上进行的,需要这代码演示数据的同学可以参考:数据
由于原始数据特征变量基本都是文本,本次案例最大价值在于特征工程的构建,即怎么把文本变为数值型变量。
导入常用包
- #导入数据分析常用包
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sns
-
- %matplotlib inline
- plt.rcParams['font.sans-serif'] = ['KaiTi'] #中文
- plt.rcParams['axes.unicode_minus'] = False #负号
读取训练集和测试集
- data=pd.read_csv('movies_train.csv')
- data2=pd.read_csv('movies_test.csv')
查看数据前五行
data.head()
有点多就不展示完了
查看训练集和测试集数据基础信息
- data=data.infer_objects()
- data2=data2.infer_objects()
- data.info() ,data2.info()
可以看到大部分变量都不是数值型,需要进行处理
id- 电影ID。
title- 电影名称 文本变量
homepage- 电影主页 文本变量
genres- 电影类型 分类型变量
overview- 电影概述 文本变量
poster_path- 电影海报的位置 图片文本
tagline- 电影标语 文本变量
runtime- 电影的运行时间 数值型变量
spoken_languages- 电影口语 分类型变量
original_language- 电影原文 分类型变量
original_title- 电影原名 文本变量
production_companies- 电影制作公司 分类型变量
production_countries- 电影的制作国家 分类型变量
release_date- 电影上映日期 时间变量
budget- 电影预算 数值型变量
revenue- 电影收入 数值型变量
status- 电影状态 分类型变量
vote_count- 电影票数 数值型变量
vote_average- 电影的平均票数 数值型变量
keywords- 电影关键词 文本变量
cast- 电影演员 字典变量
crew- 电影剧组 字典变量
popularity- 电影的人气评分 目标变量,数值型
由于数据的文本型变量较多,较难处理。将一些没用的文本变量和难以提取信息的文本特征选择删除
这里先选择删除电影ID,电影主页,电影概述,电影海报的位置,电影标语,电影关键词,电影制作公司,电影的制作国家
- #删除的变量
- col_drop=['id','homepage','overview','poster_path','tagline','keywords','production_companies','production_countries']
- #测试集ID留着后面提交
- ID=data2['id']
- data.drop(col_drop,axis=1,inplace=True)
- data2.drop(col_drop,axis=1,inplace=True)
剩余的文本变量,一一进行处理,进行新的特征工程的构建。
首先对电影名称和电影的原始名称进行一个匹配,相同返回1,不相同返回0,从而构建一个新特征
- data=data.assign(name_change=lambda d: (d.title==d.original_title)*1)
- data2=data2.assign(name_change=lambda d: (d.title==d.original_title)*1)
- def check_languages(txt):
- txt=eval(txt)
- if 'en'in txt:
- languages=1
- else:
- languages=0
- return languages
- data['spoken']=data['spoken_languages'].apply(check_languages)
- data2['spoken']=data2['spoken_languages'].apply(check_languages)
电影原文也是一样的处理
- def check_languages2(txt):
- if txt=='en':
- languages=1
- else:
- languages=0
- return languages
- data['original']=data['original_language'].apply(check_languages2)
- data2['original']=data2['original_language'].apply(check_languages2)
通过对发行日期计算,得到该影片的年龄, 缺失值采用均值填充
- data['movie_age']=(2022-pd.to_datetime(data['release_date']).dt.year).fillna((2022-pd.to_datetime(data['release_date']).dt.year).mean()).astype('int')
- data2['movie_age']=(2022-pd.to_datetime(data2['release_date']).dt.year).fillna((2022-pd.to_datetime(data2['release_date']).dt.year).mean()).astype('int')
对电影演员、电影剧组的字典变量进行简单处理,计算它们的个数,构建一个新的特征
- def check(d):
- return len(d)
- data['cast_num']=data['cast'].apply(check)
- data2['cast_num']=data2['cast'].apply(check)
-
- data['crew_num']=data['crew'].apply(check)
- data2['crew_num']=data2['crew'].apply(check)
对于电影类别,进行虚拟变量处理,由于一个电影可能属于多个类别,不能直接独立热编码,需要进行处理。
首先得到所有类别的名称列表
- all_kind=[]
- for a in [eval(i)for i in data['genres'].unique()]:
- for a1 in a:
- all_kind.append(a1)
- set_kind=list(set(all_kind))
定义处理函数,生成虚拟变量
- def check2(txt):
- txt=eval(txt)
- dummys=[]
- for k in set_kind:
- if k in txt:
- dummys.append(1)
- else:
- dummys.append(0)
- return np.array(dummys)
- def check3(col,data):
- all_kind=[]
- for a in [eval(i)for i in data[col].unique()]:
- for a1 in a:
- all_kind.append(a1)
- set_kind=list(set(all_kind))
- print(f'{col}特征里面有{len(set_kind)}个类别,生成{len(set_kind)}个虚拟变量')
- dummys_max=np.array([np.array(arr) for arr in data[col].apply(check2).to_numpy()])
- for i,kind in enumerate(set_kind):
- data[f'{col}_{kind}']=dummys_max[:,i]

应用函数
- check3('genres',data)
- check3('genres',data2)
这样每个电影对应20个类别特征,如果它属于这个类别,取值为1,不属于取值为0。
将构建完的旧特征进行删除
- #删除的变量
- col_drop2=['original_title','title','release_date','cast','crew','genres','spoken_languages','original_language']
- data.drop(col_drop2,axis=1,inplace=True)
- data2.drop(col_drop2,axis=1,inplace=True)
剩下的变量status是典型的分类变量,可以直接进行虚拟变量独热处理
- data=pd.get_dummies(data)
- data2=pd.get_dummies(data2)
再次查看所有变量的信息
- data.info()
- data2.info()
可以看到所有的特征变量都是数值型,可以进行模型运算了。
但是电影时间一列还有缺失值,需要填充,采用均值进行填充。
- data['runtime']=data['runtime'].fillna(data['runtime'].mean())
- data2['runtime']=data2['runtime'].fillna(data2['runtime'].mean())
status这个变量测试集独热出来多了一列,由于训练集的status没有status_Canceled这个情况,我们选择进行删除这个虚拟变量特征
data2.drop(columns=['status_Canceled'],inplace=True)
最后我们将训练集的y——popularity作为响应变量提取出来,完成特征工程的构建。
取出y
- y=data['popularity']
- data.drop(columns=['popularity'],inplace=True)
取出X
- X=data.copy()
- X2=data2[data.columns]
查看训练集,测试集,y的形状
print(X.shape,y.shape,X2.shape)
可以看到最终训练集和测试集都是36个变量,训练集31801条,测试集13629条,下面开始数据探索分析机器学习的模型构建。
- #查看特征变量的箱线图分布
- columns = data.columns.tolist() # 列表头
- dis_cols = 6 #一行几个
- dis_rows = len(columns)
- plt.figure(figsize=(4 * dis_cols, 4 * dis_rows))
-
- for i in range(len(columns)):
- plt.subplot(dis_rows,dis_cols,i+1)
- sns.boxplot(data=data[columns[i]], orient="v",width=0.5)
- plt.xlabel(columns[i],fontsize = 20)
- plt.tight_layout()
- #plt.savefig('特征变量箱线图.jpg',dpi=512)
- plt.show()
可以看到分类型的虚拟变量较多,数值型变量——budget,revenue,runtime的极大值较多
#画密度图,训练集和测试集对比
- dis_cols = 6 #一行几个
- dis_rows = len(columns)
- plt.figure(figsize=(4 * dis_cols, 4 * dis_rows))
-
- for i in range(len(columns)):
- ax = plt.subplot(dis_rows, dis_cols, i+1)
- ax = sns.kdeplot(data[columns[i]], color="Red" ,shade=True)
- ax = sns.kdeplot(data2[columns[i]], color="Blue",warn_singular=False,shade=True)
- ax.set_xlabel(columns[i],fontsize = 20)
- ax.set_ylabel("Frequency",fontsize = 18)
- ax = ax.legend(["train", "test"])
- plt.tight_layout()
- #plt.savefig('训练测试特征变量核密度图.jpg',dpi=500)
- plt.show()
训练集和测试集数据的分布还是较为一致
y是数值型变量,画其箱线图直方图密度图
- # 查看y的分布
- #回归问题
- plt.figure(figsize=(6,2),dpi=128)
- plt.subplot(1,3,1)
- y.plot.box(title='响应变量箱线图')
- plt.subplot(1,3,2)
- y.plot.hist(title='响应变量直方图')
- plt.subplot(1,3,3)
- y.plot.kde(title='响应变量核密度图')
- #sns.kdeplot(y, color='Red', shade=True)
- #plt.savefig('处理前响应变量.png')
- plt.tight_layout()
- plt.show()
可以看到y有很严重的异常值,要筛掉,将y大于50的样本都筛掉
- #处理y的异常值
- y=y[y <= 50]
- plt.figure(figsize=(6,2),dpi=128)
- plt.subplot(1,3,1)
- y.plot.box(title='响应变量箱线图')
- plt.subplot(1,3,2)
- y.plot.hist(title='响应变量直方图')
- plt.subplot(1,3,3)
- y.plot.kde(title='响应变量核密度图')
- #sns.kdeplot(y, color='Red', shade=True)
- #plt.savefig('处理后响应变量.png')
- plt.tight_layout()
- plt.show()
可以看到极端值情况好了一些,然后将筛出来的样本赋值给x
- #筛选给x
- X=X.iloc[y.index,:]
- X.shape
31801数据变成了31771条。
#X异常值处理,先标准化
- from sklearn.preprocessing import StandardScaler
- scaler = StandardScaler()
- X_s = scaler.fit_transform(X)
- X2_s = scaler.fit_transform(X2)
#然后画图查看
- plt.figure(figsize=(20,8))
- plt.boxplot(x=X_s,labels=data.columns)
- plt.hlines([-20,20],0,len(columns))
- plt.xticks(rotation=40)
- #plt.savefig('特征变量标准化箱线图.png',dpi=256)
- plt.show()
可以看到budget,revenue,runtime,vote_count,genres_Family,status_In Production,status_Planned这几个特征都有严重的异常值,超过了20倍的方差,需要进行筛除。
#异常值多的列进行处理
- def deal_outline(data,col,n): #数据,要处理的列名,几倍的方差
- for c in col:
- mean=data[c].mean()
- std=data[c].std()
- data=data[(data[c]>mean-n*std)&(data[c]<mean+n*std)]
- #print(data.shape)
- return data
超过10倍方差进行删除
- X=deal_outline(X,['budget','revenue','runtime','vote_count','genres_Family','status_In Production','status_Planned'],10)
- y=y[X.index]
- X.shape,y.shape
还剩31536个样本
- corr = plt.subplots(figsize = (18,16),dpi=128)
- corr= sns.heatmap(data.assign(Y=y).corr(method='spearman'),annot=True,square=True)
- #plt.savefig('训练集特征热力图.png',dpi=512)
特征有点多,可能不是很清楚
可以看到y与budget,revenue,cast_num,crew_num,vote_count这几个变量的相关性高,说明这几个变量对于y的影响较大。
划分训练集和验证集,80%训练,20%进行验证
- from sklearn.model_selection import train_test_split
- X_train,X_val,y_train,y_val=train_test_split(X,y,test_size=0.2,random_state=0)
数据标准化
- from sklearn.preprocessing import StandardScaler
- scaler = StandardScaler()
- scaler.fit(X_train)
- X_train_s = scaler.transform(X_train)
- X_val_s = scaler.transform(X_val)
- X2_s=scaler.transform(X2)
- print('训练数据形状:')
- print(X_train_s.shape,y_train.shape)
- print('验证测试数据形状:')
- (X_val_s.shape,y_val.shape,X2_s.shape)
采用十种模型,对比验证集精度
- from sklearn.linear_model import LinearRegression
- from sklearn.linear_model import ElasticNet
- from sklearn.neighbors import KNeighborsRegressor
- from sklearn.tree import DecisionTreeRegressor
- from sklearn.ensemble import RandomForestRegressor
- from sklearn.ensemble import GradientBoostingRegressor
- from xgboost.sklearn import XGBRegressor
- from lightgbm import LGBMRegressor
- from sklearn.svm import SVR
- from sklearn.neural_network import MLPRegressor
定义评估函数
- from sklearn.metrics import mean_absolute_error
- from sklearn.metrics import mean_squared_error,r2_score
-
- def evaluation(y_test, y_predict):
- mae = mean_absolute_error(y_test, y_predict)
- mse = mean_squared_error(y_test, y_predict)
- rmse = np.sqrt(mean_squared_error(y_test, y_predict))
- #mape=(abs(y_predict -y_test)/ y_test).mean()
- r_2=r2_score(y_test, y_predict)
- return mae, rmse, r_2 #mse
模型实例化
- #线性回归
- model1 = LinearRegression()
-
- #弹性网回归
- model2 = ElasticNet(alpha=0.05, l1_ratio=0.5)
-
- #K近邻
- model3 = KNeighborsRegressor(n_neighbors=10)
-
- #决策树
- model4 = DecisionTreeRegressor(random_state=77)
-
- #随机森林
- model5= RandomForestRegressor(n_estimators=500, max_features=int(X_train.shape[1]/3) , random_state=0)
-
- #梯度提升
- model6 = GradientBoostingRegressor(n_estimators=500,random_state=123)
-
- #极端梯度提升
- model7 = XGBRegressor(objective='reg:squarederror', n_estimators=1000, random_state=0)
-
- #轻量梯度提升
- model8 = LGBMRegressor(n_estimators=1000,objective='regression', # 默认是二分类
- random_state=0)
-
- #支持向量机
- model9 = SVR(kernel="rbf")
-
- #神经网络
- model10 = MLPRegressor(hidden_layer_sizes=(16,8), random_state=77, max_iter=10000)
-
- model_list=[model1,model2,model3,model4,model5,model6,model7,model8,model9,model10]
- model_name=['线性回归','惩罚回归','K近邻','决策树','随机森林','梯度提升','极端梯度提升','轻量梯度提升','支持向量机','神经网络']

拟合训练模型,计算模型误差指标
- df_eval=pd.DataFrame(columns=['MAE','RMSE','R2'])
- for i in range(10):
- model_C=model_list[i]
- name=model_name[i]
- model_C.fit(X_train_s, y_train)
- pred=model_C.predict(X_val_s)
- s=evaluation(y_val,pred)
- df_eval.loc[name,:]=list(s)
查看不同模型的评价指标
df_eval
画图查看
- bar_width = 0.4
- colors=['c', 'b', 'g', 'tomato', 'm', 'y', 'lime', 'k','orange','pink','grey','tan']
- fig, ax = plt.subplots(3,1,figsize=(6,12))
- for i,col in enumerate(df_eval.columns):
- n=int(str('31')+str(i+1))
- plt.subplot(n)
- df_col=df_eval[col]
- m =np.arange(len(df_col))
-
- #hatch=['-','/','+','x'],
- plt.bar(x=m,height=df_col.to_numpy(),width=bar_width,color=colors)
-
- #plt.xlabel('Methods',fontsize=12)
- names=df_col.index
- plt.xticks(range(len(df_col)),names,fontsize=14)
- plt.xticks(rotation=40)
-
- if col=='R2':
- plt.ylabel(r'$R^{2}$',fontsize=14)
- else:
- plt.ylabel(col,fontsize=14)
- plt.tight_layout()
- #plt.savefig('柱状图.jpg',dpi=512)
- plt.show()

我们采用三种最优的模型进一步搜索最优超参数:随机森林,梯度提升,轻量梯度,然后进行预测和存储。
- #利用K折交叉验证搜索最优超参数
- from sklearn.model_selection import KFold, StratifiedKFold
- from sklearn.model_selection import GridSearchCV,RandomizedSearchCV
- # Choose best hyperparameters by RandomizedSearchCV
- #随机搜索决策树的参数
- param_distributions = {'max_depth': range(4, 10), 'subsample':np.linspace(0.5,1,5 ),'num_leaves': [15, 31, 63, 127],
- 'colsample_bytree': [0.6, 0.7, 0.8, 1.0]}
- # 'min_child_weight':np.linspace(0,0.1,2 ),
- kfold = KFold(n_splits=3, shuffle=True, random_state=1)
- model =RandomizedSearchCV(estimator= LGBMRegressor(objective='regression',random_state=0),
- param_distributions=param_distributions, n_iter=200)
- model.fit(X_train_s, y_train)
- #查看最优参数
- model.best_params_
最优参数赋值给模型,然后拟合评价
- model = model.best_estimator_
- model.score(X_val_s, y_val)
可以看到拟合优度上升了一点
#利用找出来的最优超参数在所有的训练集上训练,然后预测
- model=LGBMRegressor(objective='regression',subsample=0.625,learning_rate= 0.01,n_estimators= 1000,num_leaves=15,
- max_depth= 4,colsample_bytree=1.0,random_state=0)
- model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])
- print(model.score(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val]))
- pred=model.predict(X2_s)
储存预测结果
- df=pd.DataFrame(ID)
- df['popularity']=pred
- df.to_csv('LGBM预测结果.csv',index=False)
#梯度提升和随机森林也是一样搜索超参数,然后训练和预测
- #梯度提升
- param_distributions = {'max_depth': range(4, 10), 'subsample':np.linspace(0.5,1,5 ),'learning_rate': np.linspace(0.05,0.3,6 ), 'n_estimators':[100,500,1000,1500, 2000]}
- # 'min_child_weight':np.linspace(0,0.1,2 ),
- kfold = KFold(n_splits=3, shuffle=True, random_state=1)
- model =RandomizedSearchCV(estimator= GradientBoostingRegressor(n_estimators=500,random_state=123),param_distributions=param_distributions, n_iter=5)
- model.fit(X_train_s, y_train)
- model = model.best_estimator_
- model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])
- print(model.score(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val]))
- pred=model.predict(X2_s)
- df['popularity']=pred
- df.to_csv('梯度提升预测结果.csv',index=False)
- #随机森林
- param_distributions = {'max_depth': range(4, 10), 'n_estimators':[100,500,1000,1500, 2000]}
- kfold = KFold(n_splits=3, shuffle=True, random_state=1)
- model =RandomizedSearchCV(estimator=RandomForestRegressor(n_estimators=500, max_features=int(X_train.shape[1]/3) , random_state=0),param_distributions=param_distributions, n_iter=5)
- model.fit(X_train_s, y_train)
- model = model.best_estimator_
- model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])
- print(model.score(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val]))
- pred=model.predict(X2_s)
- df['popularity']=pred
- df.to_csv('随机森林提升预测结果.csv',index=False)
下面就可以将这三个预测结果题kaggle提交了!!!
以LGBM为例,画出每个特征变量对响应变量影响程度的图。
- model=LGBMRegressor(objective='regression',subsample=0.5,learning_rate= 0.01,n_estimators= 1000,num_leaves=127,
- max_depth= 4,colsample_bytree=1.0,random_state=0)
- model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])
- plt.figure(figsize=(4,8))
- sorted_index = model.feature_importances_.argsort()
- plt.barh(range(data.shape[1]), model.feature_importances_[sorted_index])
- plt.yticks(np.arange(data.shape[1]), data.columns[sorted_index])
- plt.xlabel('Feature Importance')
- plt.ylabel('Feature')
- plt.show()
可以看到影响y变量最重要的是vote_count,movie_age,cast_num,crew_num等变量,
movie_age,cast_num,crew_num变量是自己构建的变量,说明这几个特征还是很有效的。
目前在kaggle上能得到最好的预测结果的最好的模型参数。
- model=LGBMRegressor(objective='regression',subsample=0.65,learning_rate= 0.01,n_estimators= 800,num_leaves=127,
- max_depth= 5,colsample_bytree=0.75,random_state=10)
- model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])
- print(model.score(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val]))
- pred=model.predict(X2_s)
- df['popularity']=pred
- df.to_csv('LGBM2.csv',index=False)
创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制代码可私信)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。