前言
本系列记录《机器学习》课的实验部分。实验所有资料在此下载:百度网盘 提取码:8848
线性回归——音乐年代预测
目录
一. 概述:音乐年代预测与回归问题
二. 数据集的加载与展示
三. 特征筛选与预处理
四. 线性回归
五. 多项式回归
六. 岭回归
一. 概述:音乐年代预测与回归问题
1. 音乐年代预测问题
根据一首音乐的音频特征,推测这首音乐的发行年份。
问题的输入:音乐的特征
问题的输出:发行年份(是一个实数)
音乐年代预测问题是一个比较典型的回归问题 。
2. 回归问题(regression)
和分类问题一样,回归问题也是以数据的特征为输入,以数据的标签为输出。
回归与分类的区别
分类模型的输出是离散的、孤立的类别,比如鸢尾花的种类。
回归模型的输出是连续的数值 ,比如这里的年份可以近似看作是连续的。
分类问题是提供定性的输出,回归问题是提供定量的输出。
生活中的回归问题
房屋价格预测
输入一个房屋的地理位置、面积等特征
输出这个房屋的价格
气温预测
接下来,实验正式开始。
我们需要首先安装PrettyTable
模组,它可以帮我们打印好看的表格(如果已安装可忽略)。
接着我们导入本课程代码所需要的python
库(不能在后文的代码中自行import)。
1 2 ! pip install PrettyTable
Requirement already satisfied: PrettyTable in d:\application\anaconda\lib\site-packages (3.0.0)
Requirement already satisfied: wcwidth in d:\application\anaconda\lib\site-packages (from PrettyTable) (0.2.5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as snsimport sklearnfrom sklearn import linear_modelfrom sklearn import preprocessingfrom sklearn import metricsfrom sklearn.utils import shufflefrom io import StringIOimport sysimport hdf5_getters import prettytable as ptsns.set_style('whitegrid' ) %matplotlib inline print ('导入成功!' )
导入成功!
二. 数据集的加载与展示
1. 百万歌曲数据集
本课程采用的数据集来自于“百万歌曲数据集(Million Song Dataset, MSD)”。
百万歌曲数据集包括了1,000,000首当代流行音乐的音频特征和元数据。
音频特征是对歌曲的音频的数据描述,包括响度、音色、音调等。
歌曲的元数据 指歌曲名称、演唱者、词曲作者、发行公司、发行年份等信息,它们不能在歌曲的音频中直接体现。
每首歌用一个.h5
文件储存其特征,所有的文件在一个目录树中分布。
更多关于百万歌曲数据集的介绍、示例、下载等,可以参考官网:http://millionsongdataset.com/
下面,我们展示其中一首歌的特征。
我们读取一个数据文件,用数据集提供的hdf5_getters
模组来获取其中包含的特征。
我们首先看一下一个数据文件中包含多少种特征,然后用表格的形式展示出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 getter_list = list (filter (lambda x: x[:3 ] == 'get' , hdf5_getters.__dict__.keys())) number_of_features = len (getter_list) print (u'一首歌曲的特征数:' , number_of_features)print ('文件TRAAABD128F429CF47.h5中的所有特征:' )file_path = 'TRAAABD128F429CF47.h5' file = hdf5_getters.open_h5_file_read(file_path) table = pt.PrettyTable() table.field_names = ["序号" , "特征名" , "特征类型" , "值/数组形状" ] for cnt, key in zip (range (1 , number_of_features+1 ), getter_list): func = hdf5_getters.__dict__[key] if isinstance (func(file), np.ndarray): table.add_row([cnt, key[4 :], str (type (func(file)))[14 :-2 ], func(file).shape]) else : table.add_row([cnt, key[4 :], str (type (func(file)))[14 :-2 ], func(file)]) print (table)file.close()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 文件TRAAABD128F429CF47.h5中的所有特征: +------+----------------------------+----------+-----------------------------------------+ | 序号 | 特征名 | 特征类型 | 值/数组形状 | +------+----------------------------+----------+-----------------------------------------+ | 1 | num_songs | int64 | 1 | | 2 | artist_familiarity | float64 | 0.6306300375898077 | | 3 | artist_hotttnesss | float64 | 0.4174996449709784 | | 4 | artist_id | bytes_ | b'ARMJAGH1187FB546F3' | | 5 | artist_mbid | bytes_ | b'1c78ab62-db33-4433-8d0b-7c8dcf1849c2' | | 6 | artist_playmeid | int32 | 22066 | | 7 | artist_7digitalid | int32 | 1998 | | 8 | artist_latitude | float64 | 35.14968 | | 9 | artist_longitude | float64 | -90.04892 | | 10 | artist_location | bytes_ | b'Memphis, TN' | | 11 | artist_name | bytes_ | b'The Box Tops' | | 12 | release | bytes_ | b'Dimensions' | | 13 | release_7digitalid | int32 | 300822 | | 14 | song_id | bytes_ | b'SOCIWDW12A8C13D406' | | 15 | song_hotttnesss | float64 | nan | | 16 | title | bytes_ | b'Soul Deep' | | 17 | track_7digitalid | int32 | 3400270 | | 18 | similar_artists | ndarray | (100,) | | 19 | artist_terms | ndarray | (38,) | | 20 | artist_terms_freq | ndarray | (38,) | | 21 | artist_terms_weight | ndarray | (38,) | | 22 | analysis_sample_rate | int32 | 22050 | | 23 | audio_md5 | bytes_ | b'bb9771eeef3d5b204a3c55e690f52a91' | | 24 | danceability | float64 | 0.0 | | 25 | duration | float64 | 148.03546 | | 26 | end_of_fade_in | float64 | 0.148 | | 27 | energy | float64 | 0.0 | | 28 | key | int32 | 6 | | 29 | key_confidence | float64 | 0.169 | | 30 | loudness | float64 | -9.843 | | 31 | mode | int32 | 0 | | 32 | mode_confidence | float64 | 0.43 | | 33 | start_of_fade_out | float64 | 137.915 | | 34 | tempo | float64 | 121.274 | | 35 | time_signature | int32 | 4 | | 36 | time_signature_confidence | float64 | 0.384 | | 37 | track_id | bytes_ | b'TRAAABD128F429CF47' | | 38 | segments_start | ndarray | (550,) | | 39 | segments_confidence | ndarray | (550,) | | 40 | segments_pitches | ndarray | (550, 12) | | 41 | segments_timbre | ndarray | (550, 12) | | 42 | segments_loudness_max | ndarray | (550,) | | 43 | segments_loudness_max_time | ndarray | (550,) | | 44 | segments_loudness_start | ndarray | (550,) | | 45 | sections_start | ndarray | (9,) | | 46 | sections_confidence | ndarray | (9,) | | 47 | beats_start | ndarray | (296,) | | 48 | beats_confidence | ndarray | (296,) | | 49 | bars_start | ndarray | (73,) | | 50 | bars_confidence | ndarray | (73,) | | 51 | tatums_start | ndarray | (591,) | | 52 | tatums_confidence | ndarray | (591,) | | 53 | artist_mbtags | ndarray | (1,) | | 54 | artist_mbtags_count | ndarray | (1,) | | 55 | year | int32 | 1969 | +------+----------------------------+----------+-----------------------------------------+
从上面的结果可以看出:
每个样本都有55种特征。
从数据类型看,有的特征值是一个实数或字符串,有的特征值是一个array。
有的样本的部分特征值缺失了。(比如这里的song_hotttness,即歌曲热度,是nan,意为无效数据。)
2. 数据集的加载
在本实验中,我们要使用到百万歌曲数据集中的音频特征和发行年份。我们使用的是YearPredictionMSD Data Set 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 data = pd.read_csv('year_prediction.csv' ) X = data.iloc[:,1 :] Y = data.label assert X.shape[0 ] == Y.size print ('读取成功!' )print ('数据集中的样本数:' , X.shape[0 ])print ('每个样本的特征数:' , X.shape[1 ])data.head(5 )
读取成功!
数据集中的样本数: 515345
每个样本的特征数: 12
label
TimbreAvg1
TimbreAvg2
TimbreAvg3
TimbreAvg4
TimbreAvg5
TimbreAvg6
TimbreAvg7
TimbreAvg8
TimbreAvg9
TimbreAvg10
TimbreAvg11
TimbreAvg12
0
2001
49.94357
21.47114
73.07750
8.74861
-17.40628
-13.09905
-25.01202
-12.23257
7.83089
-2.46783
3.32136
-2.31521
1
2001
48.73215
18.42930
70.32679
12.94636
-10.32437
-24.83777
8.76630
-0.92019
18.76548
4.59210
2.21920
0.34006
2
2001
50.95714
31.85602
55.81851
13.41693
-6.57898
-18.54940
-3.27872
-2.35035
16.07017
1.39518
2.73553
0.82804
3
2001
48.24750
-1.89837
36.29772
2.58776
0.97170
-26.21683
5.05097
-10.34124
3.55005
-6.36304
6.63016
-3.35142
4
2001
50.97020
42.20998
67.09964
8.46791
-15.85279
-16.81409
-12.48207
-9.37636
12.63699
0.93609
1.60923
2.19223
可见,数据集中有515345个样本,每个样本除了预测目标year以外,有12个特征。
这些特征包括12个平均值(TimbreAvg1~12)。
每一个样本包含每首歌都被分为12个片段(segment),每个片段用一个12维的向量来表示它的音色(timbre)。对每个12维的向量求平均值,那么一首歌可以得到12个平均值。
3. 数据集的展示
我们首先来以10年为一个单位,展示数据集中歌曲年代的分布。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 data['decade' ] = [i//10 *10 for i in Y] sns.countplot(y=data['decade' ]) plt.title('Distribution of Release Year' ,fontsize=20 ) plt.xlabel('Count of Songs' ,fontsize=14 ) plt.ylabel('Decade' ,fontsize=14 )
Text(0, 0.5, ‘Decade’)
我们的任务,就是使用这些样本进行训练。
我们想要建立起一个回归模型,即对于一个未知的样本:
输入:由12个特征值组成的向量
输出:音乐的发行年代year
三. 特征筛选与预处理
1. 特征筛选
根据第二部分的结果,我们可以发现,在百万歌曲数据集中:
不是所有特征都是数字,能方便地进行数学计算;
不是所有特征都与我们的目标(预测年代)密切相关;
因此,需要对这些特征进行特征筛选 ,即:保留部分特征作为后续分析的输入。
我们采用的数据集YearPredictionMSD Data Set,已对百万歌曲数据集进行了数据筛选。
只保留了和音色timbre有关的特征,然后进行计算,得到平均值和协方差。
2. 特征的数值分布展示
我们观察一下样本中各维度的数值分布情况。
首先看一下各维度的统计数据:平均值、标准差、最小值、25%、50%、75%、最大值
TimbreAvg1
TimbreAvg2
TimbreAvg3
TimbreAvg4
TimbreAvg5
TimbreAvg6
TimbreAvg7
TimbreAvg8
TimbreAvg9
TimbreAvg10
TimbreAvg11
TimbreAvg12
count
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
mean
43.387126
1.289554
8.658347
1.164124
-6.553601
-9.521975
-2.391089
-1.793236
3.727876
1.882385
-0.146527
2.546063
std
6.067558
51.580351
35.268585
16.322790
22.860785
12.857751
14.571873
7.963827
10.582861
6.530232
4.370848
8.320190
min
1.749000
-337.092500
-301.005060
-154.183580
-181.953370
-81.794290
-188.214000
-72.503850
-126.479040
-41.631660
-69.680870
-94.041960
25%
39.954690
-26.059520
-11.462710
-8.487500
-20.666450
-18.440990
-10.780600
-6.468420
-2.293660
-2.444850
-2.652090
-2.550060
50%
44.258500
8.417850
10.476320
-0.652840
-6.007770
-11.188390
-2.046670
-1.736450
3.822310
1.783520
-0.097950
2.313700
75%
47.833890
36.124010
29.764820
8.787540
7.741870
-2.388960
6.508580
2.913450
9.961820
6.147220
2.435660
7.360330
max
61.970140
384.065730
322.851430
335.771820
262.068870
166.236890
172.402680
126.741270
146.297950
60.345350
88.020820
87.913240
为了直观地展现特征的数值分布,下面绘制每个维度的分布直方图
每一幅图的横坐标代表值的大小,纵坐标代表频数/分度值。
1 2 3 4 5 6 7 8 fig = plt.figure(figsize=(12 ,10 )) fig.suptitle("Distribution of features" , fontsize=20 ) plt.subplots_adjust(hspace=0.4 ) for i in range (12 ): ax = plt.subplot(4 ,3 ,i+1 ) sns.distplot(X.iloc[:,i]) plt.xlabel(f'Feature {i+1 } ' , fontsize=14 )
可见:各个维度的数值范围差异是很大的,为此我们需要进行特征缩放 。
3. 特征缩放
特征缩放:将各项特征的值缩放到相同或相近的大小范围。
为什么要进行特征缩放?
模型在训练时会主要受数值大的特征影响
如果不进行特征缩放,回归分析的训练时间会变长
我们接下来使用的模型对数据的规模特别敏感,不缩放就很难训练
特征缩放的方法:
标准化(Standardization) :一般指使数据的均值为0,方差为1
有时也指将数据的数值范围压缩到一个固定的范围,比如[ 0 , 1 ] [0, 1] [ 0 , 1 ]
归一化(Normalization) :使数据的特征向量缩放到单位长度
在本次实验中,我们选用scikit-learn
中内建的特征缩放方法来处理数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 scaler = preprocessing.MinMaxScaler() X_scaled = pd.DataFrame(scaler.fit_transform(X)) X_scaled.describe()
0
1
2
3
4
5
6
7
8
9
10
11
count
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
515345.000000
mean
0.691420
0.469220
0.496370
0.317065
0.395025
0.291384
0.515292
0.354893
0.477338
0.426704
0.440923
0.530834
std
0.100755
0.071524
0.056533
0.033315
0.051486
0.051839
0.040408
0.039970
0.038797
0.064036
0.027716
0.045727
min
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
25%
0.634423
0.431296
0.464117
0.297366
0.363241
0.255425
0.492028
0.331428
0.455263
0.384271
0.425035
0.502827
50%
0.705890
0.479105
0.499284
0.313357
0.396254
0.284665
0.516247
0.355178
0.477685
0.425735
0.441231
0.529557
75%
0.765261
0.517524
0.530202
0.332624
0.427220
0.320143
0.539971
0.378515
0.500192
0.468526
0.457297
0.557293
max
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1.000000
1 2 3 4 5 6 7 8 fig = plt.figure(figsize=(12 ,10 )) fig.suptitle("Distribution of features" , fontsize=20 ) plt.subplots_adjust(hspace=0.4 ) for i in range (12 ): ax = plt.subplot(4 ,3 ,i+1 ) sns.distplot(X.iloc[:,i]) plt.xlabel(f'Feature {i+1 } ' , fontsize=14 )
4. 特征展示
下面我们尝试直观展示一下不同年代音乐之间的特征差异
1 2 3 4 5 X_sample=X.sample(n=4 , axis=1 ) X_sample['decade' ]=data['decade' ] X_sample=X_sample.sample(1000 ) sns.pairplot(data=X_sample, hue='decade' , vars =X_sample.columns[:-1 ])
<seaborn.axisgrid.PairGrid at 0x1a396749f10>
用热力图(heatmap)展现不同年代音乐的特征向量。
我们剔除样本过少的20~40年代音乐,然后从每个年代的音乐中取相同数量,对它们的特征向量求平均值。
颜色越浅代表这一年代音乐的某一平均特征的值越大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 data_t = data[data.decade>1940 ] min_samples = data_t.decade.value_counts().min () decades = data_t.decade.unique() data_sampled = pd.DataFrame(columns=data_t.columns) for decade in decades: data_sampled = data_sampled.append(data_t[data_t.decade==decade].sample(min_samples)) data_sampled.decade = data_sampled.decade.astype(int ) labels = ["{:02d}'s'" .format (l%100 ) for l in sorted (data_sampled.decade.unique())] fig, ax = plt.subplots(figsize=(12 ,5 )) sns.heatmap(data_sampled.groupby(['decade' ]).mean(), yticklabels=labels) plt.ylabel("Decade" ) plt.xlabel("Average of features" ) plt.show()
5. 数据集划分
回归问题是一类监督学习 的问题
我们把数据集划分为训练数据集 和测试数据集
模型仅使用训练集中的数据进行训练
训练完成后,然后在测试集中进行测试
一般来说,需要指定训练集和测试集的比例。
本课程采用的数据集推荐的划分是:前463,715个样本为训练集,后51,630个样本为测试集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 x_train=X_scaled[:463715 ] x_test=X_scaled[463715 :] y_train=Y[:463715 ] y_test=Y[463715 :] print (x_train.shape)print (y_train.shape)print (x_test.shape)print (y_test.shape)
(463715, 12)
(463715,)
(51630, 12)
(51630,)
四. 线性回归
1. 线性回归
回归方程(regression equation) ,又叫假设(hypothesis)或 预测函数 。
回归方程就是通过学习建立起来的,输入的特征向量与输出的关系式。
有了回归方程,将一个未知样本的特征x ⃗ \vec x x 输入进去,就可得到他的输出值y y y 的预测值。
当回归方程是一个线性函数时,称为线性回归 。
h θ ( x ) = θ 0 + θ 1 x 1 + θ 2 x 2 + ⋯ + θ n x n = θ T x h_\theta(x)=\theta_0+\theta_1x_1+\theta_2x_2+\dots+\theta_nx_n=\theta^Tx
h θ ( x ) = θ 0 + θ 1 x 1 + θ 2 x 2 + ⋯ + θ n x n = θ T x
2. 代价函数
如何评估模型的好坏
代价函数 :评估真实值与预测值之间的差异
在线性回归中,我们常用平方损失(squared loss)来描述误差
J ( θ ) = 1 2 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 , ( m 为样本数) J(\theta)=\frac{1}{2m}\sum\limits_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})^2,\quad \text{($m$ 为样本数)}
J ( θ ) = 2 m 1 i = 1 ∑ m ( h θ ( x ( i ) ) − y ( i ) ) 2 , ( m 为样本数 )
J ( θ ) = 1 2 m ( X θ − y ) T ( X θ − y ) J(\theta)=\frac{1}{2m}(X\theta-y)^T(X\theta-y)
J ( θ ) = 2 m 1 ( Xθ − y ) T ( Xθ − y )
回归模型的训练过程就是为了得到最好的模型,也就是让代价函数最小
解析求解(Sklearn
里的LinearRegression
)
可以通过最小二乘法 得到回归公式中θ \theta θ 的解析解
只需要将上面的回归公式对θ \theta θ 求导,令其为0,即可解得θ \theta θ 的解析解。这个解是:
θ = ( X T X ) − 1 X T y \theta=(X^TX)^{-1}X^Ty
θ = ( X T X ) − 1 X T y
上面的这个解叫做正规方程(normal equation)
解析求解对于大规模的数据有什么问题
当样本特征数大的时候,计算( X T X ) − 1 (X^TX)^{-1} ( X T X ) − 1 十分困难
我们还可以使用梯度下降的方法得到所需的θ \theta θ
3. 梯度下降
梯度下降
沿着J ( θ ) J(θ) J ( θ ) 的负梯度方向走,我们就能接近其最小值,或者极小值,从而接近更高的预测精度。
重复计算下面的公式,直到收敛:
θ j + 1 = θ j − α ∂ ∂ θ j J ( θ ) α 为学习率 \theta_{j+1} = \theta_j-\alpha\frac{\partial}{\partial\theta_j}J(\theta) \quad \text{$\alpha$ 为学习率}
θ j + 1 = θ j − α ∂ θ j ∂ J ( θ ) α 为学习率
θ j + 1 = θ j + α 1 m ∑ i = 1 m ( y ( i ) − h θ ( x ( i ) ) ) x j ( i ) \theta_{j+1} = \theta_j+\alpha\frac{1}{m}\sum\limits_{i=1}^{m}(y^{(i)}-h_\theta(x^{(i)}))x_j^{(i)}
θ j + 1 = θ j + α m 1 i = 1 ∑ m ( y ( i ) − h θ ( x ( i ) )) x j ( i )
收敛 :
这个沿着负梯度向下的过程一直迭代重复,直到J ( θ ) J(θ) J ( θ ) 的值基本不变了,就称为收敛。
这时候我们就知道达到了极小值。
随机梯度下降(Stochastic Gradient Descent) (Sklearn
里的SGDRegressor
)
根据上面的公式,每调整一次θ \theta θ ,都需要把整个训练数据集都过一遍,效率太低了。
随机梯度下降就是每次迭代时只任选一个样本进行更新,不再对所有样本都计算一遍并求和。
\begin{aligned}& \text{重复直到收敛(Repeat until convergence):} \\
& \quad \quad \theta_j = \theta_j+\alpha(y^{(i)}-h_\theta(x^{(i)}))x_j^{(i)}
\end{aligned}
- 可能会出现抖动,不一定能获得全局最优
- ***学习率(步长)***
- 梯度下降公式中,梯度前面的系数$\alpha$就是学习率,又叫步长。
- 学习率标识了沿梯度方向行进的速率,是一个重要的超参数。
- 如果学习率太大,很可能“迈过”最小值。如果太小,则会收敛得很慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 old_stdout = sys.stdout sys.stdout = mystdout = StringIO() lin_reg=linear_model.SGDRegressor(verbose=1 ,eta0=0.005 ) lin_reg.fit(x_train,y_train) sys.stdout = old_stdout loss_history = mystdout.getvalue() loss_list = [] for line in loss_history.split('\n' ): if len (line.split("loss: " )) == 1 : continue loss_list.append(float (line.split("loss: " )[-1 ])) print ('迭代次数:' , len (loss_list))plt.figure() plt.plot(np.arange(len (loss_list)), loss_list) plt.xlabel("Number of epochs" ) plt.ylabel("Loss" )
迭代次数: 876
Text(0, 0.5, 'Loss')

- 得到训练好的线性回归模型之后,下面评估其预测结果
- 分别在训练集、测试集上评估模型预测值的准确率和均方误差
- 计算准确率时,不能以完全相等作为相等,应该给定一个范围。
- 本题中,当预测值与真实值的偏差小于等于5时,认为预测准确。
- 譬如,若真实年份为2000年,预测年份为1995~2005之间都算作预测准确。
- ***均方误差(Mean square error, MSE)***也可以作为评价标准
- 然后从训练集、测试集上分别抽取一些样本,直观对比的它们真实值和预测值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def evaluate_model (model,x,y ): y_pred = model.predict(x) y_true = np.array(y) diff = abs (y_pred-y_true) correct_num = diff[diff<=5 ].size accuracy = correct_num/y_true.size mse = metrics.mean_squared_error(y_true,y_pred) return accuracy,mse accuracy_rate_lr_train, mse_lr_train = evaluate_model(lin_reg, x_train, y_train) print ('模型训练准确率为:' , accuracy_rate_lr_train)print ('模型训练均方误差为:' , mse_lr_train)accuracy_rate_lr_test, mse_lr_test = evaluate_model(lin_reg, x_test, y_test) print ('模型测试准确率为:' , accuracy_rate_lr_test)print ('模型测试均方误差为:' , mse_lr_test)
> 模型训练准确率为: 0.45917427730394744
> 模型训练均方误差为: 101.69800987928029
> 模型测试准确率为: 0.4543869843114468
> 模型测试均方误差为: 100.94908156645756
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def barplot (true, pred, yi=None , ym=None ): """ 用于绘制柱状图以对比真实值和预测值的函数 yi: 纵坐标的下限 ym: 纵坐标的上限 """ width=0.3 ; x = np.array([x for x in range (1 ,len (true)+1 )]); x1 = x - width; plt.bar(x1,true,width=width, label='true' ); plt.bar(x,pred,width=width, label='predicted' ); plt.legend(loc='upper left' ); if yi and ym: plt.ylim(yi,ym); plt.show(); samples_x_train, samples_y_train = shuffle(x_train, y_train, n_samples=10 , random_state=0 ) predicted_train = lin_reg.predict(samples_x_train) print ('训练集样本的原始标签:\n' , samples_y_train.values)print ('训练集样本的预测结果:\n' , predicted_train)barplot(samples_y_train, predicted_train, 1900 , 2030 ) print ('-------------------------------------------' )samples_x_test, samples_y_test = shuffle(x_test, y_test, n_samples=10 , random_state=0 ) predicted_test = lin_reg.predict(samples_x_test) print ('测试集样本的原始标签:' , samples_y_test.values)print ('测试集样本的预测结果:' , predicted_test)barplot(samples_y_test, predicted_test, 1900 , 2030 )
> 训练集样本的原始标签:
> [2006 1992 1994 2007 1966 2000 2001 1996 1999 2009]
> 训练集样本的预测结果:
> [2001.58127171 1993.26745612 1999.75829637 2005.59255932 1992.13467085
> 1998.29617644 1995.8651914 1999.77619584 1998.80305912 2004.77065429]
> 
---
> 测试集样本的原始标签: [2004 2008 1940 1981 2007 1979 2005 1997 2004 2010]
> 测试集样本的预测结果: [1996.26416028 2001.55710336 1990.09362799 1993.38272902 2004.83357094
> 1990.53759828 2002.69854745 2000.56446419 2001.9340644 1980.2019072 ]
> 
### 对比模型对于训练数据集和测试数据集的预测结果
- 可以看到,模型对测试集的预测效果往往不如训练集的效果。
- 如果训练集训练出来的模型也能很好地预测测试集的数据,那么这个模型的**泛化**能力就好。
# 五. 多项式回归
## 1. 多项式回归介绍
- **多项式回归**
- 不再使用单纯的线性函数去拟合数据,而是使用一个多项式函数。
- 例如,一个二次多项式:
P(x)=\theta^T[1,x_1,x_2,x_1^2,x_2^2]
- 会拓展数据集特征空间的维度
- 当线性回归效果不好时,即***欠拟合***时,需要尝试多项式回归
- 欠拟合:当模型在训练集中表现不好时,自然更不会在测试数据中表现得好。这种情况就是欠拟合。
## 2. 交互项
- 多项式回归中可以增加***交互项***
- 什么是交互项
- 例如,在二次多项式$P(x)=\theta^T[1,x_1,x_2,x_1^2,x_1x_2,x_2^2]$中,$x_1x_2$就是交叉项
- 以本课问题为例,如果某一音乐家在某一年代倾向于创作较长的音乐,另一时间段倾向于创作较短的音乐。这种情况下,可以增加创作者这一特征和音乐时长这一特征的交互项,帮助模型拟合
- 我们这里并没有这么做,因为选取的特征并不符合这一特征
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 poly = preprocessing.PolynomialFeatures(degree=3 ,include_bias=True ) X_trib = poly.fit_transform(X) scaler_trib = preprocessing.MinMaxScaler() X_trib_scaled = pd.DataFrame(scaler_trib.fit_transform(X_trib)) poly_x_train=X_trib_scaled[:463715 ] poly_x_test=X_trib_scaled[463715 :] print (poly_x_train.shape)print (poly_x_test.shape)
> (463715, 455)
> (51630, 455)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 old_stdout = sys.stdout sys.stdout = mystdout = StringIO() pol_reg=linear_model.SGDRegressor(verbose=1 ,eta0=0.01 ,tol=None ,max_iter=1000 ) pol_reg.fit(poly_x_train,y_train) sys.stdout = old_stdout loss_history = mystdout.getvalue() loss_list = [] for line in loss_history.split('\n' ): if (len (line.split("loss: " )) == 1 ): continue loss_list.append(float (line.split("loss: " )[-1 ])) print (len (loss_list))plt.figure() plt.plot(np.arange(len (loss_list)), loss_list) plt.xlabel("Time in epochs" ) plt.ylabel("Loss" )
> 1000
> Text(0, 0.5, 'Loss')
> 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 accuracy_rate_pr_train, mse_pr_train = evaluate_model(pol_reg, poly_x_train, y_train) print ('模型训练准确率为:' , accuracy_rate_pr_train)print ('模型训练均方误差为:' , mse_pr_train)accuracy_rate_pr_test, mse_pr_test = evaluate_model(pol_reg, poly_x_test, y_test) print ('模型测试准确率为:' , accuracy_rate_pr_test)print ('模型测试均方误差为:' , mse_pr_test)print ('-------------------------------------------' )samples_poly_x_train, samples_y_train = shuffle(poly_x_train, y_train, n_samples=10 , random_state=0 ) poly_predicted_train = pol_reg.predict(samples_poly_x_train) print ('训练集样本的原始标签:' , samples_y_train.values)print ('训练集样本的预测结果:' , poly_predicted_train)barplot(samples_y_train, poly_predicted_train, 1900 , 2030 ) print ('-------------------------------------------' )samples_poly_x_test, samples_y_test = shuffle(poly_x_test, y_test, n_samples=10 , random_state=0 ) poly_predicted_test = pol_reg.predict(samples_poly_x_test) print ('测试集样本的原始标签:' , samples_y_test.values)print ('测试集样本的预测结果:' , poly_predicted_test)barplot(samples_y_test, poly_predicted_test, 1900 , 2030 )
> 模型训练准确率为: 0.41047626235942336
> 模型训练均方误差为: 99.71850133606817
> 模型测试准确率为: 0.4090451288010846
> 模型测试均方误差为: 100.73636768027744模型训练准确率为: 0.41047626235942336
> 模型训练均方误差为: 99.71850133606817
> 模型测试准确率为: 0.4090451288010846
> 模型测试均方误差为: 100.73636768027744
> 训练集样本的原始标签: [2006 1992 1994 2007 1966 2000 2001 1996 1999 2009]
> 训练集样本的预测结果: [2000.61202988 1989.1528712 1999.36643953 2005.43103627 1987.8140294
> 1995.44083552 1993.03184727 1996.60640708 1999.09366353 2002.60016725]
> 
---
> 测试集样本的原始标签: [2004 2008 1940 1981 2007 1979 2005 1997 2004 2010]
> 测试集样本的预测结果: [1993.33772763 1999.5744086 1986.88562316 1992.77738124 2006.41072662
> 1986.45687003 2001.59073335 1999.02043011 1999.11527887 1991.54463823]
> 
- 使用多项式回归可以解决欠拟合的问题,但又可能带来过拟合
- ***过拟合***:指模型可以非常好地拟合训练数据,预测测试集时却表现很差
### 理解欠拟合与过拟合
例如我们想要拟合下面的数据
- 如果采用线性回归,那么拟合效果显然不好,数据距离拟合曲线较远
- 采用二次多项式回归,拟合效果刚刚好
- 采用100次多项式回归,虽然貌似拟合几乎每一个数据,但是丢失了信息规律,显然不能很好地预测未知的数据

# 六. 岭回归
## 1. 正则化
- 正如上面过拟合的例子显示,当模型**复杂度**高(比如参数很多)的时候,很容易出现过拟合。
- 训练集误差很低,测试集表现较差——模型的泛化能力较差。
- ***正则化(Regularization)*** 是一种减少**测试**误差的手段。
- 我们试图拉伸函数曲线使之更加平滑
- 为了拉伸曲线,也就要弱化一些高阶项(曲线曲折的罪魁祸首)
- 为此,我们想要降低高阶项的系数$\theta_i$大小——这叫做**惩罚**
- 做法:
- 设模型的代价函数是$L(\theta)$,而我们的惩罚项是$R(\theta)$
- 那么,将目标函数写为$J(\theta)=L(\theta)+\alpha R(\theta)$,再最小化目标函数即可
- 其中$\alpha$是一个超参数,它决定了正则化惩罚的“力度”。$\alpha$越大,惩罚“力度”越大。
## 2. 岭回归
- ***岭回归***是一种常用的正则化线性回归。
- 使用线性回归的代价函数,而惩罚项$R(\theta)=\sum\limits_{i=1}^{n}\theta_i^2$
- 也就是目标函数写为:
J(\theta)=L(\theta)+\alpha\sum\limits_{i=1}^{n}\theta_i^2
- 惩罚项的含义是空间中参数点$\theta$到原点的**欧式距离**的平方
- 因此惩罚的效果是迫使参数点靠近原点,也就是迫使参数变小。
- 可以降低模型的复杂度,提高模型的泛化能力,即模型适用于新的数据集的能力
1 2 3 4 5 6 7 8 9 10 ridge_reg = sklearn.linear_model.Ridge(alpha=1.0 ) ridge_reg.fit(poly_x_train,y_train)
Ridge()
- 预测数据是否会符合我们预期,波动变小呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 accuracy_rate_rr_train, mse_rr_train = evaluate_model(ridge_reg, poly_x_train, y_train) print ('模型训练准确率为:' , accuracy_rate_rr_train)print ('模型训练均方差为:' , mse_rr_train)print ('-------------------------------------------' )accuracy_rate_rr_test, mse_rr_test = evaluate_model(ridge_reg, poly_x_test, y_test) print ('模型测试准确率为:' , accuracy_rate_rr_test)print ('模型测试均方差为:' , mse_rr_test)print ('-------------------------------------------' )samples_x_train, samples_y_train = shuffle(poly_x_train, y_train, n_samples=10 , random_state=0 ) predicted_train = ridge_reg.predict(samples_x_train) print ('训练集样本的原始标签:' , samples_y_train.values)print ('训练集样本的预测结果:' , predicted_train)barplot(samples_y_train, predicted_train, 1900 , 2030 ) print ('-------------------------------------------' )samples_x_test, samples_y_test = shuffle(poly_x_test, y_test, n_samples=10 , random_state=0 ) predicted_test = ridge_reg.predict(samples_x_test) print ('测试集样本的原始标签:' , samples_y_test.values)print ('测试集样本的预测结果:' , predicted_test)barplot(samples_y_test, predicted_test, 1900 , 2030 )
> 模型训练准确率为: 0.4958843255016551
> 模型训练均方差为: 93.07491749727983模型训练准确率为: 0.4958843255016551
> 模型训练均方差为: 93.07491749727983
> 模型测试准确率为: 0.4905481309316289
> 模型测试均方差为: 93.51400717023266模型测试准确率为: 0.4905481309316289
> 模型测试均方差为: 93.51400717023266
> 训练集样本的原始标签: [2006 1992 1994 2007 1966 2000 2001 1996 1999 2009]
> 训练集样本的预测结果: [2002.73643893 1991.12225283 2001.42164601 2007.23058105 1987.80480768
> 1997.97548257 1994.13579631 1998.32564721 2002.88598941 2006.32989283]
> 
---
> 测试集样本的原始标签: [2004 2008 1940 1981 2007 1979 2005 1997 2004 2010]
> 测试集样本的预测结果: [1995.27325062 2002.00472514 1991.56437322 1995.51972086 2009.06087812
> 1986.12915134 2004.48084521 2000.76110235 1999.82108022 1996.69017132]
> 
### 线性回归、多项式回归、岭回归模型的表现差异
- 对比三个模型对于测试集的预测准确率、均方差
1 2 3 4 5 6 7 8 9 10 11 12 13 data_present=[['线性回归' , '训练集' , accuracy_rate_lr_train, mse_lr_train], ['线性回归' , '测试集' , accuracy_rate_lr_test, mse_lr_test], ['多项式回归' , '训练集' , accuracy_rate_pr_train, mse_pr_train], ['多项式回归' , '测试集' , accuracy_rate_pr_test, mse_pr_test], ['岭回归' , '训练集' , accuracy_rate_rr_train, mse_rr_train], ['岭回归' , '测试集' , accuracy_rate_rr_test, mse_rr_test]] table = pt.PrettyTable() table.field_names = ["模型" , "数据集" , "准确率" , "均方差" ] for row in data_present: table.add_row(row) print (table)
1 2 3 4 5 6 7 8 9 | 模型 | 数据集 | 准确率 | 均方差 | +------------+--------+---------------------+--------------------+ | 线性回归 | 训练集 | 0.45917427730394744 | 101.69800987928029 | | 线性回归 | 测试集 | 0.4543869843114468 | 100.94908156645756 | | 多项式回归 | 训练集 | 0.41047626235942336 | 99.71850133606817 | | 多项式回归 | 测试集 | 0.4090451288010846 | 100.73636768027744 | | 岭回归 | 训练集 | 0.4958843255016551 | 93.07491749727983 | | 岭回归 | 测试集 | 0.4905481309316289 | 93.51400717023266 | +------------+--------+---------------------+--------------------+