本文为摘抄原文地址:原文

TUSHARE 金融与技术学习兴趣小组

编译整理 | 一只小绿怪兽

译者简介:北京第二外国语学院国际商务专业研二在读,目前在学习Python编程和量化投资相关知识。

监督学习的过程,总结起来可以分为三个步骤,

① 在训练数据集中提取特征(学习),即找规律;

② 找到一个规律(模型);

③ 根据这个规律(模型)对未知数据进行预测。

常见的监督学习算法包括分类与回归

【工具】Python 3

【数据】tushare.pro

【注】本文注重的是方法的讲解,请大家灵活掌握。

01 分类
顾名思义,解决的就是分类问题,可以是二分类,也可以是多分类。

KNN算法(K-Nearest Neighbor)

import tushare as ts
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns


pd.set_option("expand_frame_repr", False)       # 当列太多时不换行
plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号


ts.set_token('your token')
pro = ts.pro_api()


# 导入000002.SZ前复权日线行情数据,保留收盘价列
df = ts.pro_bar(ts_code='000002.SZ', adj='qfq', start_date='20190101', end_date='20190930')
df.sort_values('trade_date', inplace=True)
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)
df = df[['close']]
print(df.head())


            close
trade_date
2019-01-02  22.97
2019-01-03  23.14
2019-01-04  23.97
2019-01-07  24.08
2019-01-08  24.03


# 计算当前、未来1-day涨跌幅
df['1d_future_close'] = df['close'].shift(-1)
df['1d_close_future_pct'] = df['1d_future_close'].pct_change(1)
df['1d_close_pct'] = df['close'].pct_change(1)
df['ma5'] = df['close'].rolling(5).mean()
df['ma5_close_pct'] = df['ma5'].pct_change(1)
df.dropna(inplace=True)
feature_names = ['当前涨跌幅方向', 'ma5当前涨跌幅方向']


df.loc[df['1d_close_future_pct'] > 0, '未来1d涨跌幅方向'] = '上涨'
df.loc[df['1d_close_future_pct'] <= 0, '未来1d涨跌幅方向'] = '下跌'

df.loc[df['1d_close_pct'] > 0, '当前涨跌幅方向'] = 1    # 上涨记为1
df.loc[df['1d_close_pct'] <= 0, '当前涨跌幅方向'] = 0   # 下跌记为0

df.loc[df['ma5_close_pct'] > 0, 'ma5当前涨跌幅方向'] = 1
df.loc[df['ma5_close_pct'] <= 0, 'ma5当前涨跌幅方向'] = 0


feature_and_target_cols = ['未来1d涨跌幅方向'] + feature_names
df = df[feature_and_target_cols]
print(df.head())


           未来1d涨跌幅方向  当前涨跌幅方向  ma5当前涨跌幅方向
trade_date
2019-01-09        下跌      1.0         1.0
2019-01-10        上涨      0.0         1.0
2019-01-11        下跌      1.0         1.0
2019-01-14        上涨      0.0         0.0
2019-01-15        上涨      1.0         1.0


from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split


# 创建特征 X 和标签 y
y = df['未来1d涨跌幅方向'].values
X = df.drop('未来1d涨跌幅方向', axis=1).values


# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 创建一个k为6的k-NN分类器
knn = KNeighborsClassifier(n_neighbors=6)

# 放入训练集数据进行学习
knn.fit(X_train, y_train)

# 在测试集数据上进行预测
new_prediction = knn.predict(X_test)
print("Prediction: {}".format(new_prediction))

# 测算模型的表现:预测对的个数 / 总个数
print(knn.score(X_test, y_test))


Prediction: ['上涨' '下跌' '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '下跌' '上涨' '上涨' '上涨' '上涨' '上涨'
 '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '上涨' '下跌' '下跌' '下跌' '上涨'
 '上涨' '上涨' '上涨' '上涨' '下跌' '上涨' '下跌' '上涨']
0.5555555555555556

上面的示例中用的是k=6,我们也可以尝试选取不同的k,再来看看预测的效果。

import numpy as np

# 创建用于储存训练和测试集预测准确度的数组
neighbors = np.arange(1, 15)
train_accuracy = np.empty(len(neighbors))
test_accuracy = np.empty(len(neighbors))

# 循环输入不同的 k值
for i, k in enumerate(neighbors):
    # 构建knn分类器
    knn = KNeighborsClassifier(n_neighbors=k)
    # 用训练集数据学习
    knn.fit(X_train, y_train)
    # 计算在训练集数据上的准确度
    train_accuracy[i] = knn.score(X_train, y_train)
    # 计算在测试集数据上的准确度
    test_accuracy[i] = knn.score(X_test, y_test)

print(train_accuracy)
print(test_accuracy)
# 画图
plt.title('k-NN: Varying Number of Neighbors')
plt.plot(neighbors, test_accuracy, label='测试集预测准确度')
plt.plot(neighbors, train_accuracy, label='训练集预测准确度')
plt.legend()
plt.xlabel('k的取值')
plt.ylabel('准确度')
plt.show()


[0.46099291 0.4751773  0.53900709 0.4964539  0.4964539  0.4964539
 0.53900709 0.53900709 0.53900709 0.53900709 0.53900709 0.53900709
 0.53900709 0.53900709]
[0.36111111 0.47222222 0.63888889 0.55555556 0.55555556 0.55555556
 0.63888889 0.63888889 0.63888889 0.63888889 0.63888889 0.63888889
 0.63888889 0.63888889]

knn.png
02 回归

另一类监督学习的算法是回归(regression),适用于当预测值为连续值的情况,比如预测股票的价格。

首先,从tushare.pro导入数据,并做一些数据探索EDA(Exploratory Data Analysis)工作。

# 导入沪深300指数、平安银行的日线涨跌幅数据
hs300 = pro.index_daily(ts_code='399300.SZ', start_date='20190101', end_date='20190930')[['trade_date', 'pct_chg']]
df_000001 = ts.pro_bar(ts_code='000001.SZ', adj='qfq', start_date='20190101', end_date='20190930')[['trade_date', 'pct_chg']]
df = pd.merge(hs300, df_000001, how='left', on='trade_date', sort=True, suffixes=['_hs300', '_000001'])
df.iloc[:, 1:] = df.iloc[:, 1:] / 100
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)

print(df.head())
print(df.info())

            pct_chg_hs300  pct_chg_000001
trade_date
2019-01-02      -0.013658         -0.0205
2019-01-03      -0.001580          0.0099
2019-01-04       0.023958          0.0512
2019-01-07       0.006070         -0.0010
2019-01-08      -0.002161         -0.0083

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 183 entries, 2019-01-02 to 2019-09-30
Data columns (total 2 columns):
pct_chg_hs300     183 non-null float64
pct_chg_000001    183 non-null float64
dtypes: float64(2)
memory usage: 4.3 KB
None

# 画图:查看相关性
plt.figure()
sns.heatmap(df.corr(), annot=True, square=True, cmap='RdYlGn')
plt.show()

xgx.png

从sklearn.linear_model模块调用LinearRegression类做线性回归。

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import numpy as np


# 创建特征和标签
y = df['pct_chg_hs300'].values
X = df['pct_chg_000001'].values
print("转换前y的维度: {}".format(y.shape))
print("转换前X的维度: {}".format(X.shape))

# 转换成 n × 1维数组
y = y.reshape(-1, 1)
X = X.reshape(-1, 1)
print("转换后y的维度: {}".format(y.shape))
print("转换后X的维度: {}".format(X.shape))

# 创建训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 创建线性回归模型
reg_all = LinearRegression()

# 用训练集数据学习
reg_all.fit(X_train, y_train)

# 在测试集上进行预测
y_pred = reg_all.predict(X_test)

# 计算评价指标R^2:
print("R^2: {}".format(reg_all.score(X_test, y_test)))


            pct_chg_hs300  pct_chg_000001
trade_date
2019-01-02      -0.013658         -0.0205
2019-01-03      -0.001580          0.0099
2019-01-04       0.023958          0.0512
2019-01-07       0.006070         -0.0010
2019-01-08      -0.002161         -0.0083
转换前y的维度: (183,)
转换前X的维度: (183,)
转换后y的维度: (183, 1)
转换后X的维度: (183, 1)
R^2: 0.617019402603039

决定系数R^2是线性回归算法的评价指标,它是回归平方和与总离差平方和的比值,表示总离差平方和中可以由回归平方和解释的比例。R^2的值介于0-1之间,越接近1,则说明回归拟合效果越好,数学公式如下:R2.png

上例中计算出的R^2约等于0.6,是通过划分一次训练集和测试集进行回归得到的评价结果,但这里有一个值得思考的问题是,单次的评价结果不够可靠,因此,下面介绍一个交叉验证的方法:K折交叉验证法。

K折交叉验证法

该方法的原理是,把数据集平均分成k份,每次用其中的一份1/k作为测试集,剩下的部分作为训练集,按照顺序遍历,直到全部数据都覆盖到为止,如下图所示:

k折.png

每次划分出的训练集和测试集都计算一个R^2,一共有k个R^2,取它们的平均值作为最终的评价指标,这样得到的结果更具有代表性。

可以通过调用sklearn.model_selection模块中的cross_val_score方法实现这一功能。

from sklearn.model_selection import cross_val_score

reg = LinearRegression()

# 计算k折交叉验证得分:以k=5为例
cv_scores = cross_val_score(reg, X, y, cv=5)
print(cv_scores)
print("Average 5-Fold CV Score: {}".format(np.mean(cv_scores)))

[ 0.65150783  0.68547646  0.70675209  0.47252459 -0.19022462]
Average 5-Fold CV Score: 0.4652072684893508

正则化的线性回归

首先,我们要明确的是,正则化(regularization)的目的是防止模型过拟合。

回到文章最开头的例子,我们找到的规律是“0×一个实数=0”,如果过拟合的话,极端情况下,只有当未知数据和已知数据完全一样的时候,才能得到正确解,比如出现0×1,可以得到结果0,但是给出0×2,就不会算了。而正则化的作用,就是为了解决这类问题。

明确了正则化的目的,它的概念就会容易理解一些,简单来说,正则化就是通过“某种手段”,解决模型过拟合问题的过程。这里的“某种手段”具体是什么,下面会通过实例进行介绍,至于为什么这个概念的名字叫正则化,大家就不要过分纠结啦。

在文章《机器学习必备技能之“统计思维2.0”》中我们介绍过最小二乘法OLS的优化思路,本质是最小化“预测值和真实值之间误差的平方和”(引号里的内容是一个损失函数),也即最小化损失函数(loss function)。

正则化就是在最小化损失函数公式中添加一个系数惩罚项,这个系数惩罚项可以理解为一个约束条件,目的是防止模型过拟合。

还是回到文章最开始的那个例子,我们希望通过添加系数惩罚项,让过拟合的规律0×1=0更具有普遍性,变成“0×一个实数=0”。

为了达到上述目的,可以使用不同的正则化方法,即添加不同的系数惩罚项,下面我们就介绍两个正则化的线性回归:岭回归和Lasso回归。

岭回归,也叫L2正则化

# 从tushare.pro导入数据(过程省略)
print(df.head())
print(df.info())

            pct_chg_hs300  pct_chg_000001  pct_chg_000002
trade_date
20190102        -0.013658         -0.0205          0.0031
20190103        -0.001580          0.0099          0.0074
20190104         0.023958          0.0512          0.0359
20190107         0.006070         -0.0010          0.0046
20190108        -0.002161         -0.0083         -0.0021
<class 'pandas.core.frame.DataFrame'>
Index: 183 entries, 20190102 to 20190930
Data columns (total 3 columns):
pct_chg_hs300     183 non-null float64
pct_chg_000001    183 non-null float64
pct_chg_000002    183 non-null float64
dtypes: float64(3)
memory usage: 5.7+ KB
None

# --为了比较,计算没有惩罚项的OLS回归系数
from sklearn.linear_model import LinearRegression

reg_all = LinearRegression()
reg_all.fit(X, y)

linear_coef = reg_all.coef_
print("OLS回归系数:", linear_coef)

OLS回归系数: [[0.35175507 0.26499475]]

# --导入Ridge函数
from sklearn.linear_model import Ridge

# 创建特征和标签
y = df[['pct_chg_hs300']].values
X = df[['pct_chg_000001', 'pct_chg_000002']].values

# 创建ridge回归模型
ridge = Ridge(alpha=0.4, normalize=True)
ridge.fit(X, y)

# 计算系数
ridge_coef = ridge.coef_
print("岭回归系数:", ridge_coef)

岭回归系数: [[0.26394455 0.23546054]]

Lasso回归,也叫L1正则化

# --同理,计算Lasso回归的系数
from sklearn.linear_model import Lasso

lasso = Lasso(alpha=0.4, normalize=True)
lasso.fit(X, y)

lasso_coef = lasso.coef_
print("Lasso回归系数:", lasso_coef)

Lasso回归系数: [0. 0.]

【参考链接】
https://www.datacamp.com/courses/supervised-learning-with-scikit-learn【1】