根据新闻内容预测股市动向

背景

项目用到的数据集来自kaggle,共包含1989条记录,每条记录包含每天的Top25条新闻,目标是通过对新闻的词频分析判断股市涨跌。

技术与方法

项目分为四部分:
第一部分是解读数据,主要用到Python文本分析工具NLTK中的分词tokenize,需要说明的是,本项目是对英文做分词处理,单词间以空格作为自然分界符,而中文是根据语义做分词,常用工具是结巴分词,得到分词结果后,中英文后续的处理是一样的。
第二部分是特征工程,包括特征提取、特征选择、特征降维。特征提取使用TF-IDF和Word2Vec结合的方式。TF-IDF是词频-逆文档频率,TF表示某个词在该文件中出现的次数,IDF衡量某个词普遍的重要性。Word2Vec是将语言中字词转为向量形式表达。特征选择使用sklearn中的VarianceThreshold方法,去除方差小的特征。特征降维使用主成分分析PCA方法。
第三部分是模型调参及训练,模型调参使用sklearn的网格搜索GridSearchCV方法,模型训练部分选择朴素贝叶斯、逻辑回归、支持向量机SVC和随机森林四种模型。
第四部分是模型预测与评价,使用accuracy_score指标和混淆矩阵评价分类准确度。

解读数据

处理原始数据集

读取原始数据集:

raw_text_df = pd.read_csv(constant.raw_text_csv_file)


第1列是日期,label为0是跌,label为1是涨,后面25列是新闻内容。接下来对原始数据集进行分词处理,这里用到NLTK的分词工具tokenize,和NLTK语料库中的停用词库stopwords,针对原始数据集的每一条新闻做分词处理,去除停用词,得到分词结果后,将分词后的25列新闻合并为1列,存在cln_text_df[‘text’]中,保存处理后的文件cln_text.csv。
处理后的数据:

分割训练集和测试集

读取cln_text.csv,查看总样本数量:

各类样本数量:
label
0 924
1 1065
正负类别样本量均衡,接下来按时间段分割训练集和测试集,训练集时间范围 2008-08-08 ~ 2014-12-31,测试集时间范围 2015-01-02 ~ 2016-07-01。


得到
训练集中各类的数据个数:
label
0 738
1 873
测试集中各类的数据个数:
label
0 186
1 192

特征工程

提取文本特征

这里选择TF-IDF及word2vec特征,对于word2vec简单使用max pooling方法处理。

训练word2vec模型

word2vec可以把字词转为向量形式,参数size是转换后的特征维度(取值100),sg为0时是CBOW模式,为1时是Skip-Gram模式,windows是滑窗尺寸,即寻找相邻词的个数,得到w2v_model.wv即是单词对应的向量。

在训练集上统计词频

将训练集中的单词拿出来放到word_list中,利用NLTK中的词频统计工具FreqDist,统计每个单词出现的次数,这里要注意去除空字符串,否则后续在提取Word2Vec特征时会报错,然后取出现最多的200个词及出现的频率存放在common_words_freqs中:(n_common_words取值200)

得到结果:出现最多的200个词是:
us: 2499次
israel: 1948次
new: 1872次
says: 1841次
world: 1829次
china: 1729次
government: 1724次
police: 1666次
……

在训练集和测试集上提取特征

定义提取特征的函数extract_feat_from_data,遍历输入的数据集的每一行单词和高频词数组common_words,如果高频词有出现在输入的文本,则计算该词在这一行文本的TF-IDF值,同时将训练word2vec生成的wv相应词的向量赋值给w2v_val,最终得到存放了TF-IDF值的tf_idf_feat_val_list和存放了word2vec特征的w2v_feat_val_list。
对w2v_feat_val_list做max pooling处理,即每列取最大值作为代表特征,再将得到的结果和TF-IDF合并,得到特征矩阵X,每个样本特征维度是300。

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
    text_collection = TextCollection(train_text_df['text'].values.tolist())
train_X, train_y = extract_feat_from_data(train_text_df, text_collection, common_words_freqs)
test_X, test_y = extract_feat_from_data(test_text_df, text_collection, common_words_freqs)
def extract_feat_from_data(text_df, text_collection, common_words_freqs):
#特征提取
#选择TF-IDF及word2vec特征
#对于word2vec使用max pooling做处理
n_sample = text_df.shape[0]
n_feat = constant.n_common_words + constant.w2v_dim
common_words = [word for word, _ in common_words_freqs]

#初始化
X = np.zeros([n_sample, n_feat])
y = np.zeros(n_sample)

for i, r_data in text_df.iterrows():

text = r_data['text']

tf_idf_feat_val_list = []
w2v_feat_val_list = []
for word in common_words:
if word in text:
#如果在高频词中
#计算TF-IDF值
tf_idf_val = text_collection.tf_idf(word, text)

#获取word2vec特征
w2v_val = keyed_vectors[word]
else:
tf_idf_val = 0
w2v_val = np.zeros(constant.w2v_dim)

tf_idf_feat_val_list.append(tf_idf_val)
w2v_feat_val_list.append(w2v_val)

#max pooling
w2v_feat_val_arr = np.array(w2v_feat_val_list)
max_pooled_w2v_feat_val_list = np.max(w2v_feat_val_arr, axis=0).tolist()

#赋值
X[i, :] = np.array(tf_idf_feat_val_list + max_pooled_w2v_feat_val_list)
y[i] = int(r_data['label'])
return X, y

特征处理

特征范围归一化

使用sklearn.preprocessing中的StandardScaler工具将特征范围归一化到(-1,1)

1
2
3
scaler = StandardScaler()
tr_feat_scaled = scaler.fit_transform(train_X)
te_feat_scaled = scaler.transform(test_X)

特征选择

使用sklearn中的特征选择方法VarianceThreshold,去除方差小的特征,这里根据方差保留80%的向量

1
2
3
sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
tr_feat_scaled_sel = sel.fit_transform(tr_feat_scaled)
te_feat_scaled_sel = sel.transform(te_feat_scaled)

特征选择后每个样本特征维度: 294

特征降维

使用sklearn中的PCA实现特征降维,和特征选择不同,PCA通过线型变换将原数据映射到新的坐标系统中,使映射后的第一个坐标上的方差最大(即第一个主成分),依次递减。这里保留95%贡献率的特征向量。

1
2
3
4
pca = PCA(n_components=0.95)  # 保留95%贡献率的特征向量
tr_feat_scaled_sel_pca = pca.fit_transform(tr_feat_scaled_sel)
te_feat_scaled_sel_pca = pca.transform(te_feat_scaled_sel)
print('处理后每个样本特征维度:', tr_feat_scaled_sel_pca.shape[1])

处理后每个样本特征维度: 192

模型调参及训练

项目要解决的是分类问题,股市涨跌属于二分类问题,这里选择4种机器学习模型进行预测:朴素贝叶斯,逻辑回归,支持向量机,随机森林。其中朴素贝叶斯不需要调参,其他三个模型都需要参数调优。

训练朴素贝叶斯模型

1
2
3
4
5
models = []
print('1. 朴素贝叶斯模型:')
gnb_model = GaussianNB()
gnb_model.fit(tr_feat_scaled_sel_pca, train_y)
models.append(['朴素贝叶斯', gnb_model])

训练逻辑回归模型

逻辑回归为了平衡损失函数和正则项的关系,引入了损失函数的系数C作为模型超参数,C越大,损失函数的调节越重要,C越小,正则项的调节越重要。这里对C选取5个值,使用sklearn的网格搜索GridSearchCV进行交叉验证获取最优参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    print('2. 逻辑回归:')
lr_param_grid = [
{'C': [1e-3, 1e-2, 1e-1, 1, 10, 100]}
]
lr_model = LogisticRegression()
best_lr_model = get_best_model(lr_model,
tr_feat_scaled_sel_pca, train_y,
lr_param_grid, cv=3)
models.append(['逻辑回归', best_lr_model])
def get_best_model(model, X_train, y_train, params, cv):
#交叉验证获取最优模型
clf = GridSearchCV(model, params, cv=cv, verbose=3)
clf.fit(X_train, y_train)
return clf.best_estimator_

逻辑回归最佳参数: C=10

训练SVC模型

这里对SVC的3个参数进行调优,惩罚因子C:C依然是为了平衡损失函数和正则项的关系,与逻辑回归的C不同的是,SVC的C是正则项的系数,C越大,模型的容错空间越小,越容易过拟合,C越小,模型的容错空间越大,越容易欠拟合;kernel选择高斯核函数,gamma是高斯核函数的系数,gamma越大,高斯分布越窄,越容易过拟合,gamma越小,高斯分布越宽,越容易欠拟合,这里gamma分别取0.001和0.0001。

1
2
3
4
5
6
7
8
9
print('3. 支持向量机:')
svm_param_grid = [
{'C': [1e-2, 1e-1, 1, 10, 100], 'gamma': [0.001, 0.0001], 'kernel': ['rbf']},
]
svm_model = svm.SVC(probability=True)
best_svm_model = get_best_model(svm_model,
tr_feat_scaled_sel_pca, train_y,
svm_param_grid, cv=3)
models.append(['支持向量机', best_svm_model])

SVC的最佳参数是:C=0.01,gamma=0.001,kernel=’rbf’

训练随机森林模型

这里选择n_estimators作为调参对象,n_estimators是子模型数量,在临界值内,数量越大,效果越好,但是计算时间也会随之增加。

1
2
3
4
5
6
7
8
9
10
11
print('4. 随机森林:')
rf_param_grid = [
{'n_estimators': [10, 50, 100, 150, 200]}
]

rf_model = RandomForestClassifier()
best_rf_model = get_best_model(rf_model,
tr_feat_scaled_sel_pca, train_y,
rf_param_grid, cv=3)
rf_model.fit(tr_feat_scaled_sel_pca, train_y)
models.append(['随机森林', best_rf_model])

随机森林最佳参数: n_estimators=50

模型预测与评价

使用sklearn的accuracy_score评价分类准确度,得分越高,说明预测准确度越高;
最后输出每个模型的混淆矩阵。

1
2
3
4
5
6
7
for i, model in enumerate(models):
print('{}-{}'.format(i + 1, model[0]))
#输出准确率
print('准确率:', accuracy_score(test_y, model[1].predict(te_feat_scaled_sel_pca)))
#输出混淆矩阵
print('混淆矩阵')
print(confusion_matrix(test_y, model[1].predict(te_feat_scaled_sel_pca)))

1-朴素贝叶斯
准确率: 0.510582010582
混淆矩阵
[[ 64 122]
[ 63 129]]

2-逻辑回归
准确率: 0.502645502646
混淆矩阵
[[ 71 115]
[ 73 119]]

3-支持向量机
准确率: 0.507936507937
混淆矩阵
[[ 0 186]
[ 0 192]]

4-随机森林
准确率: 0.502645502646
混淆矩阵
[[ 40 146]
[ 42 150]]

可以看出,朴素贝叶斯准确率最高,SVC在预测跌势的能力最差,朴素贝叶斯、逻辑回归和随机森林在预测涨势时比跌势更准确。