概要
学习使用神经网络进行音频、文本等非结构化数据和房价、信用等结构化数据分析的相关任务,以及通过学习股价预测任务研究时间序列数据的分析问题。
1. 房价预测
在本节中,我们将通过尝试预测房屋的价格来研究连续输出问题。使用 Boston
房价数据集,以可能影响房屋价格的13个变量作为输入。我们的目的是最大程度地减少我们预测房屋价格的误差。探索神经网络的实际应用。
鉴于目标是最小化误差,我们定义将要最小化的误差——绝对误差,或者也可以最小化平方误差。既然我们已经有了需要优化的目标,让我们定义解决这个问题的策略:
- 标准化输入数据集,将所有变量的范围缩放到0-1之间。
- 将给定数据拆分为训练和测试数据集。
- 模型使用一个隐藏层
- 使用Adam优化器编译模型,并定义损失函数为平均绝对误差值。
- 拟合模型。
- 对测试数据集进行预测。
- 计算测试数据集预测中的误差。
我们已经定义了模型方法,接下来,我们在的代码中实现它。
- 导入相关的数据集:
from keras.datasets import boston_housing
import numpy as np
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
- 标准化输入和输出数据集,以便所有变量的范围从零到一:
max_target = np.max(train_targets)
train_data2 = train_data/np.max(train_data,axis=0)
test_data2 = test_data/np.max(train_data,axis=0)
train_targets = train_targets/max_target
test_targets = test_targets/max_target
我们使用训练数据集中的最大值标准化测试数据集,因为在模型构建过程中不应使用测试数据集中的任何值。
- 准备好输入和输出数据集后,定义模型:
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import np_utils
from keras.regularizers import l1
model = Sequential()
model.add(Dense(64, input_dim=13, activation='relu', kernel_regularizer=l1(0.1)))
model.add(Dense(1, activation='relu', kernel_regularizer=l1(0.1)))
model.summary()
该模型的简要信息输出如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 64) 896
_________________________________________________________________
dense_1 (Dense) (None, 1) 65
=================================================================
Total params: 961
Trainable params: 961
Non-trainable params: 0
_________________________________________________________________
我们在模型构建过程中执行了 L1
正则化,因此模型不会过拟合(训练数据中的数据点数量很少)。
- 编译模型以最小化平均绝对误差值:
model.compile(loss='mean_absolute_error', optimizer='adam')
- 最后,拟合模型:
history = model.fit(train_data2,
train_targets,
validation_data=(test_data2, test_targets),
epochs=100,
batch_size=64,
verbose=1)
- 计算并打印测试数据集上的平均绝对误差:
print(np.mean(np.abs(model.predict(test_data2) - test_targets))*50)
可以看到,平均绝对误差约为 7.7
。
7.670271360777928
在下一节中,将学习使用自定义损失函数,以进一步降低平均绝对误差值。
1.1 使用自定义损失函数
在上一节中,我们使用预定义的平均绝对误差损失函数来执行目标优化。在本节中,我们将学习使用定义自定义损失函数。
我们将使用的自定义损失函数是一个修正的均方误差值,其中误差是实际值的平方根与预测值的平方根之差。自定义损失函数定义如下:
def loss_function(y_true, y_pred):
return K.square(K.sqrt(y_pred) - K.sqrt(y_true))
接下来,在拟合过程中使用自定义损失函数,使用与上一节中相同的输入、输出数据集以及神经网络模型,编译模型:
model.compile(loss=loss_function, optimizer='adam')
在前面的代码中,我们将损失函数定义为自定义损失函数 loss_function
,最后拟合模型,并计算模型在测试数据集上的损失值。
history = model.fit(train_data2, train_targets,
validation_data=(test_data2, test_targets),
epochs=100,
batch_size=64,
verbose=1)
print(np.mean(np.abs(model.predict(test_data2) - test_targets))*50)
拟合模型后,可以看到平均绝对误差约为 6.6
,这比使用 mean_absolute_error
损失函数的要小一些:
6.566271535383652
2. 信用预测
2.1 前言
在金融服务行业中,某些客户的违约是导致收入损失的主要来源之一。但是,只有极少数客户会违约。因此,这一分类问题的关键在于识别稀有事件。
2.2 任务与模型分析
数据集
在本节中,我们使用 Kaggle
中 Give Me Some Credit 数据集,其中包括 250000
个用户的历史数据,可以用于预测用户在未来两年内遇到财务困境的可能性,更新用户的信用评分,下载数据集后,可以看到其中包含用户年龄、收入、债务等信息:
信用预测任务分析
在本节中,我们将分析在给定时间点跟踪客户收入、支出等属性的数据集,并尝试预测客户是否存在违约的可能性。作为企业,可能希望关注更可能违约的客户——为他们提供替代的付款方式或降低信用额度等。因此,我们用来预测客户违约的任务需求如下:
- 目标:以较高的概率识别更有可能违约的客户。
- 衡量标准:将计算得到的测试数据集中违约概率最大的前
10%
用户作为候选违约用户,查看两年后实际违约的客户数量,以此衡量模型性能。这是由于绝大多数用户都不会违约,我们的关键在于识别违约的用户,如果模型将所有用户均预测为不会违约,就可以获得很高的准确率,但是这样是没有实际意义的,因此我们不能仅仅考虑模型的准确率,而要进一步考察模型将用户预测为违约用户后,这些用户的实际违约状况。
神经网络模型分析
我们计算每个用户违约概率的网络模型策略如下:
-
考虑所有用户的历史数据
-
构造可以用于识别可能违约客户的输入变量:
- 以收入/债务比 (
DebtRatio
) 作为指示用户是否可能违约的主要指标 - 使用与之相似的其他一些变量
- 以收入/债务比 (
-
接下来,创建输出变量:
- 查看在成员未来两年内是否有严重违约行为 (
SeriousDlqin2yrs
),以获取在未来两年内实际违约的成员
- 查看在成员未来两年内是否有严重违约行为 (
-
分类结果是二元变量,因此模型最小化二进制交叉熵损失
-
模型在输入层和输出层之间包含隐藏层
-
最后,计算测试数据集中违约概率最大的前10%用户中实际违约的成员数量
我们假设测试数据具有代表性,可以评估其在训练时没有见到的数据集上的性能,即假定该模型在一个看不见的数据集上的性能可以很好地指示该模型在未来数据上的表现。
2.3 使用神经网络实现信用预测
本节,我们将编写代码,实现上述神经网络模型。
- 导入相关的包和数据集:
import pandas as pd
# 读取下载的文件
data = pd.read_csv('cs-train.csv')
# 只需要使用指定列
data = data[['SeriousDlqin2yrs','age', 'DebtRatio', 'MonthlyIncome']]
我们已经在第 1 节中查看了数据集中用户包含的特征,这里我们只是用其中的一部分特征作为演示:用户两年后是否有重大违约情况 SeriousDlqin2yrs
,用户年龄 age
,收入/债务比 DebtRatio
以及每月收入MonthlyIncome
。
上图是原始数据集中变量的子集,SeriousDlqin2yrs
的变量是我们基于数据集中其余变量需要预测的输出变量。
- 查看汇总数据集以更好地理解变量:
print(data.describe())
数据汇总情况输出:
SeriousDlqin2yrs age DebtRatio MonthlyIncome
count 150000.000000 150000.000000 150000.000000 1.202690e+05
mean 0.066840 52.295207 353.005076 6.670221e+03
std 0.249746 14.771866 2037.818523 1.438467e+04
min 0.000000 0.000000 0.000000 0.000000e+00
25% 0.000000 41.000000 0.175074 3.400000e+03
50% 0.000000 52.000000 0.366508 5.400000e+03
75% 0.000000 63.000000 0.868254 8.249000e+03
max 1.000000 109.000000 329664.000000 3.008750e+06
查看输出后,可以观察到以下问题:
- 某些变量的范围较小 (
age
),而某些变量的范围较大 (MonthlyIncome
) - 某些变量缺失一些值 (
MonthlyIncome
) - 某些变量具有异常值 (
DebtRatio
)
- 接下来,我们纠正先前标记的所有问题,首先用变量的中位数代替变量中的缺失值:
vars = data.columns[1:]
import numpy as np
for var in vars:
data[var]= np.where(data[var].isnull(),data[var].median(),data[var])
除了第一个变量,因为它是我们要尝试预测的变量,然后将缺失值替换为该变量的中位数。
- 消除输入变量中的离群值:
for var in vars:
x=data[var].quantile(0.95)
data[var+"outlier_flag"]=np.where(data[var]>x,1,0)
data[var]=np.where(data[var]>x,x,data[var])
在前面的代码中,我们计算了每个变量的第 95
个百分位数并存入新变量 x
中,如果该行包含异常值,则使用一个标记位将其值设为 1
,否则为 0
。此外,我们将变量值的上限设置为原始值的第 95
个百分位数。 Note:
nnn 个数据按大小排列,处于 p%p%p% 位置的值称第 ppp 百分位数。
- 最后,我们限制
DebtRatio
的范围为[0, 1]
:
data['DebtRatio_outlier'] = np.where(data['DebtRatio']>1, 1, 0)
data['DebtRatio'] = np.where(data['DebtRatio']>1, 1, data['DebtRatio'])
- 将所有变量归一化为相同的标度,以得到介于 0 和 1 之间的值:
for var in vars:
data[var]= data[var]/data[var].max()
在前面的代码中,通过将每个输入变量值除以输入变量列的最大值,我们将所有变量限制为相似范围,即 [0, 1]
。
- 创建输入和输出数据集:
x = data.iloc[:, 1:]
y = data['SeriousDlqin2yrs']
- 将数据集分为训练和测试数据集:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
使用 train_test_split
方法将输入和输出数组拆分为训练和测试数据集,其中测试数据集占数据总数的 30%
。 9. 创建数据集之后,定义神经网络模型,如下所示:
model = Sequential()
model.add(Dense(1000, input_dim=x_train.shape[1], activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
该模型的结构信息如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1000) 8000
_________________________________________________________________
dense_1 (Dense) (None, 1) 1001
=================================================================
Total params: 9,001
Trainable params: 9,001
Non-trainable params: 0
_________________________________________________________________
模型将输入变量连接到具有 1000
个隐藏单元的隐藏层。
- 编译模型,使用二进制交叉熵,因为输出变量只有两个类,将指定优化器为
adam
优化:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
- 拟合模型:
history = model.fit(x_train,
y_train,
validation_data=(x_test, y_test),
epochs=50,
batch_size=512,
verbose=1)
训练和测试损失的变化,以及随着 epoch
的推移准确率的变化如下:
from matplotlib import pyplot as plt
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
epochs = range(1, len(val_loss_values) + 1)
plt.subplot(211)
plt.plot(epochs, loss_values, marker='x', label='Traing loss')
plt.plot(epochs, val_loss_values, marker='o', label='Test loss')
plt.title('Training and test loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.subplot(212)
plt.plot(epochs, acc_values, marker='x', label='Training accuracy')
plt.plot(epochs, val_acc_values, marker='o', label='Test accuracy')
plt.title('Training and test accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
- 对测试数据集进行预测:
pred = model.predict(x_test)
- 按概率递减的顺序排列,检查在测试数据集中,模型预测违约概率最高的前
10%
的用户中实际违约的用户数量:
test_data = pd.DataFrame([y_test]).T
test_data['pred'] = pred
test_data = test_data.reset_index(drop='index')
test_data = test_data.sort_values(by='pred', ascending=False)
print(test_data[:int(len(test_data) * 0.1)]['SeriousDlqin2yrs'].sum())
在前面的代码中,将预测值与实际值拼接在一起,然后按输出的预测概率对数据集进行了排序。我们统计了在测试数据集的违约预测概率前 10%
(即前 4500
个)的用户中违约者的实际数量。可以看到,通过对 4500
个高概率违约用户与其真实标签进行对比后,我们识别了 1625
个实际违约者,我们可以说这模型具有很好的预测性能,因为在整个数据集中大约只有 6%
的客户违约。
3. 新闻分类
在先前的应用实战中,我们分析了结构化的数据集,即数据集中包含变量及其对应实际输出值。在本项目实战中,我们将处理一个以文本作为输入的数据集,并且预期的输出是文本相关的主题。
3.1任务与模型分析
为了进行文本分析,本章使用Reuters数据集,其中每则新闻文章都被分类为46种可能的主题之一。我们将采用以下策略构建神经网络模型:
- 假设一个数据集可以包含数千个不同的单词,我们需要给定要考虑的单词。
- 对于此实战,我们使用最常用的10,000个单词。
- 一种替代方法是考虑累积构成数据集中80%内容的单词。这样可以确保排除数据集中的稀有词。
- 获取候选单词后,将根据这些单词对输入文章进行独热编码。
- 同样,对输出标签进行独热编码。
- 经过独热编码后,每个输入都是10,000维向量,而输出是46维向量:
- 将数据集分为训练和测试数据集。本项目使用Keras中内置的Reuters数据集,该数据集一些内置方法,包括识别前n个常用词以及将该数据集分为训练和测试数据集。
- 由于模型数据量较大,因此神经网络中包括多个隐藏层。
- 在输出层使用softmax函数,以获得输入数据的类别概率。
- 由于有多个可能的类别输出,因此使用分类交叉熵损失函数。
- 最后,编译并拟合该模型以在测试数据集上测量其准确性。
3.2 模型构建与训练
接下来,我们将实现上述定义的神经网络模型。 导入数据集:
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)
在前面的代码片段中,我们从Keras中可用的reuters数据集中加载了数据。此外,我们仅考虑数据集中的10000个最常见单词。 检查数据集:
print(train_data[0])
加载的训练数据集的示例如下:
[1, 2, 2, 8, ... , 17, 12]
先前输出中的数字表示输入文章中的单词索引。也可以获取单词对应的索引:
word_index = reuters.get_word_index()
print(word_index)
输出结果如下:
{'mdbl': 10996,
'fawc': 16260,
'degussa': 12089,
'woods': 8803,
'hanging': 13796,
'localized': 20672,
'sation': 20673,
...}
可以看到每个单词在数据集中对应的索引。我们可以调转键值对得到{索引:单词}对,然后使用此字典可以打印句子中包含的单词:
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
print(reverse_word_index)
#{30933: 'enahnce', 752: 'agreements', 20649: 'monoclonal', 9379: 'grieveson', 10063: 'emirate',... }
decoded_newswire = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])
print(decoded_newswire)
输出结果如下:
#? ? ? said as a result of its december acquisition of space co it expects earnings per share in 1987 of 1 15 to 1 30 dlrs per share up from 70 cts in 1986 the company said pretax net should rise to nine to 10 mln dlrs from six mln dlrs in 1986 and rental operation revenues to 19 to 22 mln dlrs from 12 5 mln dlrs it said cash flow per share this year should be 2 50 to three dlrs reuter 3
接下来构建向量化输入,通过以下方式将文本转换为向量:
- 对输入的单词进行独热编码——在输入数据集中总共包含
10000
列,每个文章是一个长度为10000
的数据。 - 如果给定文本中的一个单词,则将对应于单词
index
的列的值设为1
,而其他列的值为0
。 - 对文本中的所有单词重复上述步骤。如果一个文本有两个不重复的单词,那么该文本对应的向量总共有两列的值为
1
,而其余列的值为0
:
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
return results
在前面的函数中,我们基于输入序列中存在的索引值初始化了一个零矩阵变量,并将单词相应索引处的值设为 1
。 接下来,使用上述函数将文本向量化。
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
将输出值进行独热编码:
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
以上代码将每个输出标签转换为长度为46的独热向量,其中46个值中只有一个为1,其余为零,具体取决于标签的索引值。 定义模型并进行编译:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(128, activation='relu', input_shape=(10000, )))
model.add(Dense(64, activation='relu'))
model.add(Dense(46, activation='softmax'))
model.summary()
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
模型简要信息如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 128) 1280128
_________________________________________________________________
dense_1 (Dense) (None, 64) 8256
_________________________________________________________________
dense_2 (Dense) (None, 46) 2990
=================================================================
Total params: 1,291,374
Trainable params: 1,291,374
Non-trainable params: 0
_________________________________________________________________
在编译时,将损失定义为 categorical_crossentropy
,因为本例的输出是多分类结果(输出中有多个类)。 拟合模型:
history = model.fit(x_train, one_hot_train_labels,
epochs=20,
batch_size=512,
validation_data=(x_test, one_hot_test_labels),
verbose=1)
训练后的模型,能够以近80%的准确率将输入文本分类为正确的主题。
4. 音频分类
4.1 前言
在前面的部分中,我们已经了解了在结构化数据集以及非结构化文本数据上执行建模的策略。在本节中,我们将学习如何在输入为音频的情况下执行分类任务。
4.2 音频分类任务与神经网络模型分析
urbansound8k 数据集介绍
urbansound8k 数据集中包含 10
个类别的城市生活声音文件,包括孩子的嬉闹声、警报声、狗叫声等。共有 8732
个带有标签的声音,每个声音长度均在 4s
内。这些声音文件被预先分类存储在十个文件夹中(文件夹名为 fold1-fold10
),除了声音文件之外,数据集中还提供了一个 CSV
文件,其中包含每个声音片段文件的相关元数据。可以在数据集网页链接中,了解此数据更详细信息。
神经网络模型
我们用于音频分类的神经网络模型策略如下:首先从输入音频中提取用于神经网络模型的特征,其中每个音频信号都表示为具有固定数量特征的向量,有多种可以从音频中提取特征的方法,在本节中,我们将提取与音频文件相对应的梅尔倒谱系数 (Mel Frequency Cepstral Coefficients
, MFCC
) 作为音频特征。 提取特征后,构建与 MNIST 数据集分类模型的相似的架构执行音频分类任务,在该模型中,在输入层与输出层间包含一个隐藏层。
4.3 使用神经网络进行音频分类
根据我们在上节中定义的神经网络架构策略,将音频数据集进行分类,使用 Keras
实现如下。
- 首先,导入数据集:
import pandas as pd
data = pd.read_csv('https://b2.7b2.com/content/train.csv')
- 接下来,定义提取每个音频输入特征的函数:
# 读取每个音频文件的元数据
ids = data['ID'].values
def extract_features(file_name):
X, sample_rate = librosa.load(file_name)
stft = np.abs(librosa.stft(X))
mfccs = np.mean(librosa.feature.mfcc(y=X, sr=sample_rate, n_mfcc=40).T, axis=0)
return mfccs
在前面的代码中,我们定义了函数 extract_features
,该函数以 file_name
作为输入,提取与音频文件相对应的 MFCC
,然后将其返回。
- 创建输入和输出数据集:
x = []
y = []
for i in range(len(ids)):
try:
filename = '/content/Train/'+str(ids[i]) + '.wav'
y.append(data[data['ID']==ids[i]]['Class'].values)
x.append(extract_features(filename))
except:
continue
x = np.array(x)
在以上的代码中,我们一次循环遍历一个音频文件,提取其特征并将其存储在输入列表中。同样,我们将把输出类存储在输出列表中,并将输出列表转换为独热编码形式:
y2 = []
for i in range(len(y)):
y2.append(y[i][0])
y3 = np.array(pd.get_dummies(y2))
pd.get_dummies
方法的工作原理与 to_categorical
方法非常相似,都用于将输入参数转换为独热编码形式返回。不同的是,to_categorical
不适用于文本类(它仅适用将于数值转换为独热编码的情况)。
- 接下来,我们建立神经网络模型并进行编译:
model = Sequential()
model.add(Dense(1024, input_shape = (40,), activation = 'relu'))
#model.add(Dense(1000,activation='relu'))
model.add(Dense(10,activation='sigmoid'))
model.summary()
创建的模型简要概述信息输出如下,可以看到神将网络中仅包含一个含有 1024
个隐藏节点的隐藏层:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1024) 41984
_________________________________________________________________
dense_1 (Dense) (None, 10) 10250
=================================================================
Total params: 52,234
Trainable params: 52,234
Non-trainable params: 0
_________________________________________________________________
- 将数据集进行拆分,划分为训练和测试数据集,然后拟合模型:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(x, y3, test_size=0.30,random_state=10)
model.fit(X_train, y_train,
epochs=100,
batch_size=32,
validation_data=(X_test, y_test),
verbose = 1)
模型训练完成后,该模型在音频分类任务中可以达到 90%
以上的准确率。
5. 股价预测
5.1 前言
我们已经学习了使用神经网络进行音频、文本等非结构化数据和房价、信用等结构化数据分析的相关任务。在本节中,我们通过学习股价预测任务研究时间序列数据的分析问题。
5.2 数据集介绍
本文使用的股价数据集来自 GitHib,也可以使用格式与之类似的股价数据集。下载数据集后,查看其内容,可以看到数据集中包含时间、开盘时股价等一系列相关信息,本文需要预测的是股价当天的最终价格,即 Close
列的数据:
5.3 神经网络模型分析
为了预测股价,我们根据以下思路构建神经网络模型:
- 按照时间发生顺序对数据集进行排序
- 以前五个股票价格数据作为输入,第六个股票价格数据作为输出
- 滑动时间窗口,在模型的下一个输入使用第二个到第六个数据,并将第七个数据作为输出,依此类推,直到到达最后一个数据点
- 由于我们需要预测的是一个连续值,因此损失函数使用均方误差值
- 此外,我们还将在第
2
节尝试将文本数据集成到历史股票价格数据中以预测第二天股价的情况
5.4 使用神经网络进行股价预测
根据以上模型的思路,接下来,我们使用 Keras
实现神经网络模型用于预测股价。
- 导入相关的包和数据集:
import pandas as pd
data2 = pd.read_csv('content/stock_data.csv')
- 按照前述分析准备数据集,其中输入是前五天的股票价格,输出是第六天的股票价格:
x = []
y = []
for i in range(data2.shape[0]-5):
x.append(data2.loc[i:(i+4)]['Close'].values)
y.append(data2.loc[i+5]['Close'])
import numpy as np
x = np.array(x)
y = np.array(y)
- 准备训练和测试数据集,构建模型:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
from keras.layers import Dense
from keras.models import Sequential
model = Sequential()
model.add(Dense(100, input_dim=5, activation='relu'))
model.add(Dense(1, activation='linear'))
- 建立模型并进行编译:
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()
看以看到,模型的简要信息输出如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 100) 600
_________________________________________________________________
dense_1 (Dense) (None, 1) 101
=================================================================
Total params: 701
Trainable params: 701
Non-trainable params: 0
_________________________________________________________________
拟合模型后,可以看到,训练集股价的均方误差值约为 320
,而预测集股价的均方误差值约为 330
。
1579/1579 [==============================] - 0s 73us/step - loss: 319.5919 - val_loss: 329.0416
以这种方式预测股价存在较大的问题,在之后的学习会看到更加先进的模型(包括循环神经网络等)用于股价预测。在下一部分中,我们将学习如何在模型中将股价价格数据与新闻标题的文本数据集成在一起的方式进行预测。
5.5 使用函数式 API
神经网络模型分析
在本节中,我们将股票历史价格数据与我们要预测股价的公司最近的相关新闻标题相集成,以提高股价预测的准确性。我们将整合来自多个来源的数据——结构化(历史股价)数据和非结构化(新闻标题)数据——进行股价预测,模型构建策略如下:
- 将非结构化文本转换为结构化格式的方式类似于在新闻分类中所使用方式
- 通过神经网络传递文本信息,并提取隐藏层输出
- 最后,将隐藏层的输出传递到输出层,其中输出层具有一个节点
- 以类似的方式,将输入的历史价格数据通过神经网络传递,以提取隐藏层输出,然后将其传递到输出层,输出层具有一个节点
- 将两个神经网络的输出相乘以得到最终输出
- 最小化最终输出的平方误差值
神经网络模型分析
根据以上策略,模型实现程序如下。
- 从 Guardian 提供的
API
中获取标题数据,如下所示:
from bs4 import BeautifulSoup
import urllib, json
dates = []
titles = []
for i in range(100):
try:
url = 'https://content.guardianapis.com/search?from-date=2010-01-01§ion=business&page-size=200&order-by=newest&page='+str(i+1)+'&q=amazon&api-key=207b6047-a2a6-4dd2-813b-5cd006b780d7'
response = urllib.request.urlopen(url)
encoding = response.info().get_content_charset('utf8')
data = json.loads(response.read().decode(encoding))
for j in range(len(data['response']['results'])):
dates.append(data['response']['results'][j]['webPublicationDate'])
titles.append(data['response']['results'][j]['webTitle'])
except:
break
- 提取标题和日期后,对数据进行预处理,以将日期值转换为日期格式,如下所示:
import pandas as pd
data = pd.DataFrame(dates, titles)
data['date']=data['date'].str[:10]
data['date']=pd.to_datetime(data['date'], format = '%Y-%m-%d')
data = data.sort_values(by='date')
data_final = data.groupby('date').first().reset_index()
- 对于要预测股价的每个日期,我们获得相应日期的公司的相关新闻标题,接下来整合两个数据源,如下所示:
data2['Date'] = pd.to_datetime(data2['Date'],format='%Y-%m-%d')
data3 = pd.merge(data2,data_final, left_on = 'Date', right_on = 'date', how='left')
- 合并数据集后,我们将继续对文本数据进行规范化,首先对数据进行如下处理:
- 将文本中的所有单词都转换为小写字母,以便可以将
Text
和text
单词被视为相同值 - 删除标点符号,使诸如
text.
和text
被视为相同文本 - 删除停用词,例如
a
和and
,它们不会为文本增加过多上下文
import nltk
import re
nltk.download('stopwords')
stop = nltk.corpus.stopwords.words('english')
def preprocess(text):
text = str(text)
text=text.lower()
text=re.sub('[^0-9a-zA-Z]+',' ',text)
words = text.split()
words2=[w for w in words if (w not in stop)]
words4=' '.join(words2)
return(words4)
data3['title'] = data3['title'].apply(preprocess)
- 用连字符 “
-
” 替换标题中的空值:
data3['title']=np.where(data3['title'].isnull(),'-','-'+data3['title'])
- 现在我们已经预处理了文本数据,为每个单词分配一个
ID
。完成此任务后,我们可以按照与新闻分类中类似的方法执行文本分析,如下所示:
docs = data3['title'].values
from collections import Counter
counts = Counter()
for i,review in enumerate(docs):
counts.update(review.split())
words = sorted(counts, key=counts.get, reverse=True)
vocab_size=len(words)
word_to_int = {word: i for i, word in enumerate(words, 1)}
- 既然我们已经对所有单词进行了编码,那么我们在原始文本中将其替换为相应的索引值:
encoded_docs = []
for doc in docs:
encoded_docs.append([word_to_int[word] for word in doc.split()])
def vectorize_sequences(sequences, dimension=vocab_size):
results = np.zeros((len(sequences), dimension+1))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
return results
vectorized_docs = vectorize_sequences(encoded_docs)
现在我们已经对文本进行了编码,接下来,我们将两个数据源集成在一起,以进行更好的预测。
- 首先,准备训练和测试数据集,如下所示:
x1 = np.array(x)
x2 = np.array(vectorized_docs[5:])
y = np.array(y)
X1_train = x1[:2100,:]
X2_train = x2[:2100, :]
y_train = y[:2100]
X1_test = x1[2100:,:]
X2_test = x2[2100:,:]
y_test = y[2100:]
通常,当模型有多个输入或多个输出时,我们将使用函数式 API
构建模型,本例中模型有多个输入,因此将使用函数式 API
。
- 本质上,函数式
API
打破了顺序构建模型的过程,可以按照数据流的计算顺序构建模型:
from keras.layers import Dense, Input
from keras import Model
import keras.backend as K
input1 = Input(shape=(2406,))
model = (Dense(100, activation='relu'))(input1)
model = (Dense(1, activation='tanh'))(model)
在前面的代码中,没有使用顺序建模过程,而是使用 Dense
层定义了层与层之间的连接。输入的形状为 2406
,因为在上述过滤过程之后剩下 2406
个不同的词。
- 利用时间窗口策略,取
5
个股票价格作为输入并构建另一模型:
input2 = Input(shape=(5,))
model2 = (Dense(100, activation='relu'))(input2)
model2 = (Dense(1, activation='linear'))(model2)
- 将上述两个模型的输出相乘,得到最终结果:
from keras.layers import multiply
out = multiply([model, model2])
- 我们已经定义了输入、输出,接下来,就可以按照以下方式构建模型:
model = Model([input1, input2], out)
model.summary()
以上代码中,我们使用 “Model
” 层定义了输入(作为列表传递)和输出,模型的概要信息输出如下:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 2406) 0
__________________________________________________________________________________________________
input_2 (InputLayer) (None, 5) 0
__________________________________________________________________________________________________
dense_1 (Dense) (None, 100) 240700 input_1[0][0]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 100) 600 input_2[0][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 1) 101 dense_1[0][0]
__________________________________________________________________________________________________
dense_4 (Dense) (None, 1) 101 dense_3[0][0]
__________________________________________________________________________________________________
multiply_1 (Multiply) (None, 1) 0 dense_2[0][0]
dense_4[0][0]
==================================================================================================
Total params: 241,502
Trainable params: 241,502
Non-trainable params: 0
__________________________________________________________________________________________________
- 接下来将模型架构进行可视化,如下:
plot_model(model, show_shapes=True, show_layer_names=True, to_file='model.png')
- 编译并拟合模型:
model.compile(optimizer='adam', loss='mean_squared_error')
model.fit(x=[X2_train, X1_train], y=y_train,
epochs=100,
batch_size=32,
validation_data=([X2_test, X1_test], y_test),
verbose=1)
前面的模型在训练集上产生的均方误差仅约为 70
,并且可以看出模型过拟合,因为训练数据集的损失远低于测试数据集的损失。
2100/2100 [==============================] - 0s 200us/step - loss: 67.1472 - acc: 0.0024 - val_loss: 3383.6247 - val_acc: 0.0000e+00