用于文本分类和情感分析的标准深度学习模型使用词嵌入层和一维卷积神经网络。
该模型可以通过使用多个并行卷积神经网络来扩展,这些网络使用不同的核大小读取源文档。这实际上创建了一个用于文本的多通道卷积神经网络,它以不同的 n-gram 大小(词组)读取文本。
在本教程中,您将学习如何开发一个多通道卷积神经网络,用于对文本电影评论数据进行情感预测。
完成本教程后,您将了解:
- 如何为建模准备电影评论文本数据。
- 如何在 Keras 中开发用于文本的多通道卷积神经网络。
- 如何评估未经训练的电影评论数据上的拟合模型。
通过我的新书《自然语言处理深度学习》来启动您的项目,其中包括分步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2018 年 2 月更新:代码进行了少量更改以反映 Keras 2.1.3 API 的变化。
- 2020 年 8 月更新:更新了电影评论数据集的链接。

如何开发用于情感分析的 N-gram 多通道卷积神经网络
图片来自 Ed Dunens,保留部分权利。
教程概述
本教程分为4个部分,它们是:
- 电影评论数据集
- 数据准备
- 开发多通道模型
- 评估模型
Python 环境
本教程假设您已安装 Python 3 SciPy 环境。
您必须安装 Keras(2.0 或更高版本),并使用 TensorFlow 或 Theano 后端。
本教程还假设您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。
如果您在环境方面需要帮助,请参阅此帖子
需要深度学习处理文本数据的帮助吗?
立即参加我的免费7天电子邮件速成课程(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
电影评论数据集
电影评论数据是 Bo Pang 和 Lillian Lee 在 2000 年代初期从 imdb.com 网站检索的电影评论集合。这些评论是作为他们自然语言处理研究的一部分而收集和提供的。
这些评论最初于 2002 年发布,但更新和清理后的版本于 2004 年发布,被称为“v2.0”。
该数据集包含从 imdb.com 上的 rec.arts.movies.reviews 新闻组存档中提取的 1,000 条正面和 1,000 条负面电影评论。作者将此数据集称为“极性数据集”。
我们的数据包含 1000 条褒义和 1000 条贬义评论,所有评论均在 2002 年之前撰写,每个作者(共 312 位作者)每个类别最多 20 条评论。我们将此语料库称为极性数据集。
—— 《情感教育:基于最小割的主观性摘要情感分析》,2004 年。
数据已经过一些清理;例如
- 数据集仅包含英文评论。
- 所有文本都已转换为小写。
- 标点符号(如句号、逗号和括号)周围有空格。
- 文本已分成每行一句。
这些数据已被用于一些相关的自然语言处理任务。对于分类,机器学习模型(如支持向量机)在数据上的表现范围在 70% 到 80% 之间(例如 78%-82%)。
更复杂的数据准备可能会通过 10 折交叉验证获得高达 86% 的结果。这使我们得到一个大致的范围,如果我们要将此数据集用于现代方法的实验中,大约在 80% 到 80% 之间。
...根据下游极性分类器的选择,我们可以实现高度统计学意义的改进(从 82.8% 提高到 86.4%)
—— 《情感教育:基于最小割的主观性摘要情感分析》,2004 年。
您可以从此处下载数据集
- 电影评论极性数据集 (review_polarity.tar.gz, 3MB)
解压文件后,您将得到一个名为“txt_sentoken”的目录,其中包含两个子目录,分别包含“neg”和“pos”文本,用于负面和正面评论。评论以每个文件一条的形式存储,命名约定为每个 neg 和 pos 的 cv000 到 cv999。
接下来,我们来看看如何加载和准备文本数据。
数据准备
在本节中,我们将探讨 3 件事
- 将数据分为训练集和测试集。
- 加载并清理数据以去除标点符号和数字。
- 准备所有评论并保存到文件。
拆分为训练集和测试集
我们假装正在开发一个系统,可以预测电影评论文本的情感是褒义还是贬义。
这意味着模型开发完成后,我们需要对新的文本评论进行预测。这将需要对这些新评论执行与模型训练数据相同的全部数据准备。
我们将通过在任何数据准备之前分割训练和测试数据集来确保此约束内置到模型评估中。这意味着测试集中可能有助于我们更好地准备数据的任何知识(例如,使用的单词)在用于训练模型的数据准备中不可用。
话虽如此,我们将使用最后 100 条褒义评论和最后 100 条贬义评论作为测试集(200 条评论),其余 1,800 条评论作为训练数据集。
这是 90% 训练,10% 拆分的数据。
通过使用评论的文件名可以轻松实现分割,其中名为 000 到 899 的评论用于训练数据,名为 900 及以上的评论用于测试。
加载和清理评论
文本数据已经相当干净;不需要太多准备。
我们不会过于拘泥于细节,而是按照以下方式准备数据
- 按空格分割标记。
- 从单词中删除所有标点符号。
- 删除所有不纯粹由字母字符组成的单词。
- 删除所有已知停用词。
- 删除所有长度小于等于 1 个字符的单词。
我们可以将所有这些步骤放入一个名为 `clean_doc()` 的函数中,该函数接受从文件中加载的原始文本作为参数,并返回一个清理后的标记列表。我们还可以定义一个 `load_doc()` 函数,该函数从文件中加载文档,以便与 `clean_doc()` 函数一起使用。下面列出了清理第一个正面评论的示例。
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 |
from nltk.corpus import stopwords import string # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将文档转换为干净的令牌 def clean_doc(doc): # 按空格分割成标记 tokens = doc.split() # 从每个标记中删除标点符号 table = str.maketrans('', '', string.punctuation) tokens = [w.translate(table) for w in tokens] # 删除所有非字母字符的标记 tokens = [word for word in tokens if word.isalpha()] # 过滤停用词 stop_words = set(stopwords.words('english')) tokens = [w for w in tokens if not w in stop_words] # 过滤短标记 tokens = [word for word in tokens if len(word) > 1] return tokens # 加载文档 filename = 'txt_sentoken/pos/cv000_29590.txt' text = load_doc(filename) tokens = clean_doc(text) print(tokens) |
运行该示例将加载并清理一条电影评论。
打印出清理后评论的标记以供查看。
1 2 |
... 'creepy', 'place', 'even', 'acting', 'hell', 'solid', 'dreamy', 'depp', 'turning', 'typically', 'strong', 'performance', 'deftly', 'handling', 'british', 'accent', 'ians', 'holm', 'joe', 'goulds', 'secret', 'richardson', 'dalmatians', 'log', 'great', 'supporting', 'roles', 'big', 'surprise', 'graham', 'cringed', 'first', 'time', 'opened', 'mouth', 'imagining', 'attempt', 'irish', 'accent', 'actually', 'wasnt', 'half', 'bad', 'film', 'however', 'good', 'strong', 'violencegore', 'sexuality', 'language', 'drug', 'content'] |
清理所有评论并保存
现在我们可以使用该函数清理评论并将其应用于所有评论。
为此,我们将在下面开发一个名为 `process_docs()` 的新函数,该函数将遍历目录中的所有评论,清理它们,并将其作为列表返回。
我们还将向函数添加一个参数,以指示函数是否正在处理训练或测试评论,这样就可以过滤文件名(如上所述),并且只清理和返回请求的训练或测试评论。
完整的函数如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 加载目录中的所有文档 def process_docs(directory, is_trian): documents = list() # 遍历文件夹中的所有文件 for filename in listdir(directory): # 跳过测试集中的任何评论 if is_trian and filename.startswith('cv9'): continue if not is_trian and not filename.startswith('cv9'): continue # 创建要打开的文件的完整路径 path = directory + '/' + filename # 加载文档 doc = load_doc(path) # 清理文档 tokens = clean_doc(doc) # 添加到列表 documents.append(tokens) return documents |
我们可以按如下方式调用此函数来处理负面训练评论
1 |
negative_docs = process_docs('txt_sentoken/neg', True) |
接下来,我们需要训练和测试文档的标签。我们知道有 900 个训练文档和 100 个测试文档。我们可以使用 Python 列表推导式为训练和测试集中的负面 (0) 和正面 (1) 评论创建标签。
1 2 |
trainy = [0 for _ in range(900)] + [1 for _ in range(900)] testY = [0 for _ in range(100)] + [1 for _ in range(100)] |
最后,我们希望将准备好的训练和测试集保存到文件中,以便以后用于建模和模型评估。
下面名为 `save_dataset()` 的函数将使用 pickle API 将给定的准备好的数据集(X 和 y 元素)保存到文件中。
1 2 3 4 |
# 保存数据集到文件 def save_dataset(dataset, filename): dump(dataset, open(filename, 'wb')) print('Saved: %s' % filename) |
完整示例
我们可以把所有这些数据准备步骤结合起来。
完整的示例如下所示。
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 61 62 63 64 65 66 67 68 69 70 |
from string import punctuation from os import listdir from nltk.corpus import stopwords from pickle import dump # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将文档转换为干净的令牌 def clean_doc(doc): # 按空格分割成标记 tokens = doc.split() # 从每个标记中删除标点符号 table = str.maketrans('', '', punctuation) tokens = [w.translate(table) for w in tokens] # 删除所有非字母字符的标记 tokens = [word for word in tokens if word.isalpha()] # 过滤停用词 stop_words = set(stopwords.words('english')) tokens = [w for w in tokens if not w in stop_words] # 过滤短标记 tokens = [word for word in tokens if len(word) > 1] tokens = ' '.join(tokens) return tokens # 加载目录中的所有文档 def process_docs(directory, is_trian): documents = list() # 遍历文件夹中的所有文件 for filename in listdir(directory): # 跳过测试集中的任何评论 if is_trian and filename.startswith('cv9'): continue if not is_trian and not filename.startswith('cv9'): continue # 创建要打开的文件的完整路径 path = directory + '/' + filename # 加载文档 doc = load_doc(path) # 清理文档 tokens = clean_doc(doc) # 添加到列表 documents.append(tokens) return documents # 保存数据集到文件 def save_dataset(dataset, filename): dump(dataset, open(filename, 'wb')) print('Saved: %s' % filename) # 加载所有训练评论 negative_docs = process_docs('txt_sentoken/neg', True) positive_docs = process_docs('txt_sentoken/pos', True) trainX = negative_docs + positive_docs trainy = [0 for _ in range(900)] + [1 for _ in range(900)] save_dataset([trainX,trainy], 'train.pkl') # 加载所有测试评论 negative_docs = process_docs('txt_sentoken/neg', False) positive_docs = process_docs('txt_sentoken/pos', False) testX = negative_docs + positive_docs testY = [0 for _ in range(100)] + [1 for _ in range(100)] save_dataset([testX,testY], 'test.pkl') |
运行该示例会清理电影评论文档,创建标签,并将准备好的训练和测试数据集分别保存到 `train.pkl` 和 `test.pkl` 中。
现在我们准备开发我们的模型。
开发多通道模型
在本节中,我们将开发一个多通道卷积神经网络来解决情感分析预测问题。
本节分为 3 部分
- 编码数据
- 定义模型。
- 完整示例。
编码数据
第一步是加载清理过的训练数据集。
可以调用下面名为 `load_dataset()` 的函数来加载经过 pickle 序列化的训练数据集。
1 2 3 4 5 |
# 加载干净的数据集 def load_dataset(filename): return load(open(filename, 'rb')) trainLines, trainLabels = load_dataset('train.pkl') |
接下来,我们必须在训练数据集上拟合 Keras Tokenizer。我们将使用此 tokenizer 来定义嵌入层的词汇表,并将评论文档编码为整数。
下面的函数 `create_tokenizer()` 将根据文档列表创建一个 Tokenizer。
1 2 3 4 5 |
# 拟合分词器 def create_tokenizer(lines): tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) return tokenizer |
我们还需要知道作为模型输入的最大输入序列长度,并将所有序列填充到固定长度。
下面名为 `max_length()` 的函数将计算训练数据集中所有评论的最大长度(词数)。
1 2 3 |
# 计算最大文档长度 def max_length(lines): return max([len(s.split()) for s in lines]) |
我们还需要知道嵌入层的词汇量大小。
这可以从准备好的 Tokenizer 计算出来,如下所示
1 2 |
# 计算词汇表大小 vocab_size = len(tokenizer.word_index) + 1 |
最后,我们可以对清理后的电影评论文本进行整数编码和填充。
下面名为 `encode_text()` 的函数将对文本数据进行编码并填充到最大评论长度。
1 2 3 4 5 6 7 |
# 编码行列表 def encode_text(tokenizer, lines, length): # 整数编码 encoded = tokenizer.texts_to_sequences(lines) # 填充编码序列 padded = pad_sequences(encoded, maxlen=length, padding='post') return padded |
定义模型
文档分类的标准模型是使用嵌入层作为输入,然后是一维卷积神经网络、池化层,最后是预测输出层。
卷积层中的核大小定义了当卷积在输入文本文档上滑动时要考虑的词数,提供了一个分组参数。
用于文档分类的多通道卷积神经网络涉及使用多个具有不同大小核的标准模型。这允许以不同的分辨率或不同的 n-gram(词组)同时处理文档,同时模型学习如何最好地整合这些解释。
这种方法最早由 Yoon Kim 在他 2014 年题为《用于句子分类的卷积神经网络》的论文中描述。
在论文中,Kim 实验了静态和动态(更新的)嵌入层,我们可以简化方法,只关注不同核大小的使用。
从 Kim 的论文中截取的图表最能说明这种方法

用于文本的多通道卷积神经网络的图示。
摘自《用于句子分类的卷积神经网络》。
在 Keras 中,可以使用函数式 API 定义多输入模型。
我们将定义一个具有三个输入通道的模型,用于处理电影评论文本的 4-gram、6-gram 和 8-gram。
每个通道由以下元素组成
- 定义输入序列长度的输入层。
- 嵌入层,设置为词汇表大小和 100 维实值表示。
- 一维卷积层,包含 32 个滤波器,核大小设置为一次读取的单词数。
- 最大池化层,用于整合卷积层的输出。
- 展平层,用于将三维输出降维为二维,以便进行拼接。
三个通道的输出被连接成一个单一的向量,并由一个密集层和一个输出层处理。
下面的函数定义并返回模型。作为模型定义的一部分,会打印已定义模型的摘要,并创建模型图的绘图并将其保存到文件中。
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 |
# 定义模型 def define_model(length, vocab_size): # 通道 1 inputs1 = Input(shape=(length,)) embedding1 = Embedding(vocab_size, 100)(inputs1) conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1) drop1 = Dropout(0.5)(conv1) pool1 = MaxPooling1D(pool_size=2)(drop1) flat1 = Flatten()(pool1) # 通道 2 inputs2 = Input(shape=(length,)) embedding2 = Embedding(vocab_size, 100)(inputs2) conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2) drop2 = Dropout(0.5)(conv2) pool2 = MaxPooling1D(pool_size=2)(drop2) flat2 = Flatten()(pool2) # 通道 3 inputs3 = Input(shape=(length,)) embedding3 = Embedding(vocab_size, 100)(inputs3) conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3) drop3 = Dropout(0.5)(conv3) pool3 = MaxPooling1D(pool_size=2)(drop3) flat3 = Flatten()(pool3) # 合并 merged = concatenate([flat1, flat2, flat3]) # 解释 dense1 = Dense(10, activation='relu')(merged) outputs = Dense(1, activation='sigmoid')(dense1) model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs) # 编译 model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 总结 print(model.summary()) plot_model(model, show_shapes=True, to_file='multichannel.png') return model |
完整示例
将所有这些整合在一起,完整的示例如下所示。
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
from pickle import load from numpy import array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.utils.vis_utils import plot_model from keras.models import Model from keras.layers import Input from keras.layers import Dense from keras.layers import Flatten 从 keras.layers 导入 Dropout from keras.layers import Embedding from keras.layers.convolutional import Conv1D from keras.layers.convolutional import MaxPooling1D from keras.layers.merge import concatenate # 加载干净的数据集 def load_dataset(filename): return load(open(filename, 'rb')) # 拟合分词器 def create_tokenizer(lines): tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) return tokenizer # 计算最大文档长度 def max_length(lines): return max([len(s.split()) for s in lines]) # 编码行列表 def encode_text(tokenizer, lines, length): # 整数编码 encoded = tokenizer.texts_to_sequences(lines) # 填充编码序列 padded = pad_sequences(encoded, maxlen=length, padding='post') return padded # 定义模型 def define_model(length, vocab_size): # 通道 1 inputs1 = Input(shape=(length,)) embedding1 = Embedding(vocab_size, 100)(inputs1) conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1) drop1 = Dropout(0.5)(conv1) pool1 = MaxPooling1D(pool_size=2)(drop1) flat1 = Flatten()(pool1) # 通道 2 inputs2 = Input(shape=(length,)) embedding2 = Embedding(vocab_size, 100)(inputs2) conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2) drop2 = Dropout(0.5)(conv2) pool2 = MaxPooling1D(pool_size=2)(drop2) flat2 = Flatten()(pool2) # 通道 3 inputs3 = Input(shape=(length,)) embedding3 = Embedding(vocab_size, 100)(inputs3) conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3) drop3 = Dropout(0.5)(conv3) pool3 = MaxPooling1D(pool_size=2)(drop3) flat3 = Flatten()(pool3) # 合并 merged = concatenate([flat1, flat2, flat3]) # 解释 dense1 = Dense(10, activation='relu')(merged) outputs = Dense(1, activation='sigmoid')(dense1) model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs) # 编译 model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 总结 print(model.summary()) plot_model(model, show_shapes=True, to_file='multichannel.png') return model # 加载训练数据集 trainLines, trainLabels = load_dataset('train.pkl') # 创建分词器 tokenizer = create_tokenizer(trainLines) # 计算最大文档长度 length = max_length(trainLines) # 计算词汇表大小 vocab_size = len(tokenizer.word_index) + 1 print('Max document length: %d' % length) print('Vocabulary size: %d' % vocab_size) # 编码数据 trainX = encode_text(tokenizer, trainLines, length) print(trainX.shape) # 定义模型 model = define_model(length, vocab_size) # 拟合模型 model.fit([trainX,trainX,trainX], array(trainLabels), epochs=10, batch_size=16) # 保存模型 model.save('model.h5') |
运行示例首先打印准备好的训练数据集的摘要。
1 2 3 |
最大文档长度:1380 词汇量大小:44277 (1800, 1380) |
接下来,打印出已定义模型的摘要。
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 |
____________________________________________________________________________________________________ 层 (类型) 输出形状 参数数量 连接到 ==================================================================================================== input_1 (InputLayer) (None, 1380) 0 ____________________________________________________________________________________________________ input_2 (InputLayer) (None, 1380) 0 ____________________________________________________________________________________________________ input_3 (InputLayer) (None, 1380) 0 ____________________________________________________________________________________________________ embedding_1 (Embedding) (None, 1380, 100) 4427700 input_1[0][0] ____________________________________________________________________________________________________ embedding_2 (Embedding) (None, 1380, 100) 4427700 input_2[0][0] ____________________________________________________________________________________________________ embedding_3 (Embedding) (None, 1380, 100) 4427700 input_3[0][0] ____________________________________________________________________________________________________ conv1d_1 (Conv1D) (None, 1377, 32) 12832 embedding_1[0][0] ____________________________________________________________________________________________________ conv1d_2 (Conv1D) (None, 1375, 32) 19232 embedding_2[0][0] ____________________________________________________________________________________________________ conv1d_3 (Conv1D) (None, 1373, 32) 25632 embedding_3[0][0] ____________________________________________________________________________________________________ dropout_1 (Dropout) (None, 1377, 32) 0 conv1d_1[0][0] ____________________________________________________________________________________________________ dropout_2 (Dropout) (None, 1375, 32) 0 conv1d_2[0][0] ____________________________________________________________________________________________________ dropout_3 (Dropout) (None, 1373, 32) 0 conv1d_3[0][0] ____________________________________________________________________________________________________ max_pooling1d_1 (MaxPooling1D) (None, 688, 32) 0 dropout_1[0][0] ____________________________________________________________________________________________________ max_pooling1d_2 (MaxPooling1D) (None, 687, 32) 0 dropout_2[0][0] ____________________________________________________________________________________________________ max_pooling1d_3 (MaxPooling1D) (None, 686, 32) 0 dropout_3[0][0] ____________________________________________________________________________________________________ flatten_1 (Flatten) (None, 22016) 0 max_pooling1d_1[0][0] ____________________________________________________________________________________________________ flatten_2 (Flatten) (None, 21984) 0 max_pooling1d_2[0][0] ____________________________________________________________________________________________________ flatten_3 (Flatten) (None, 21952) 0 max_pooling1d_3[0][0] ____________________________________________________________________________________________________ concatenate_1 (Concatenate) (None, 65952) 0 flatten_1[0][0] flatten_2[0][0] flatten_3[0][0] ____________________________________________________________________________________________________ dense_1 (Dense) (None, 10) 659530 concatenate_1[0][0] ____________________________________________________________________________________________________ dense_2 (Dense) (None, 1) 11 dense_1[0][0] ==================================================================================================== 总参数:14,000,337 可训练参数:14,000,337 不可训练参数: 0 ____________________________________________________________________________________________________ |
该模型拟合速度相对较快,并且在训练数据集上表现出良好的性能。
1 2 3 4 5 6 7 8 9 10 11 |
... 第 6/10 纪元 1800/1800 [==============================] - 30s - 损失:9.9093e-04 - 准确率:1.0000 第 7/10 纪元 1800/1800 [==============================] - 29s - 损失:5.1899e-04 - 准确率:1.0000 第 8/10 纪元 1800/1800 [==============================] - 28s - 损失:3.7958e-04 - 准确率:1.0000 第 9/10 纪元 1800/1800 [==============================] - 29s - 损失:3.0534e-04 - 准确率:1.0000 第 10/10 纪元 1800/1800 [==============================] - 29s - 损失:2.6234e-04 - 准确率:1.0000 |
已将定义的模型图保存到文件中,清晰地显示了模型的三个输入通道。

文本多通道卷积神经网络图
模型经过多次训练,并保存到 `model.h5` 文件中,以供后续评估。
评估模型
在本节中,我们可以通过预测未见测试数据集中所有评论的情感来评估拟合模型。
使用上一节中开发的数据加载函数,我们可以加载和编码训练数据集和测试数据集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 加载数据集 trainLines, trainLabels = load_dataset('train.pkl') testLines, testLabels = load_dataset('test.pkl') # 创建分词器 tokenizer = create_tokenizer(trainLines) # 计算最大文档长度 length = max_length(trainLines) # 计算词汇表大小 vocab_size = len(tokenizer.word_index) + 1 print('Max document length: %d' % length) print('Vocabulary size: %d' % vocab_size) # 编码数据 trainX = encode_text(tokenizer, trainLines, length) testX = encode_text(tokenizer, testLines, length) print(trainX.shape, testX.shape) |
我们可以加载保存的模型并在训练和测试数据集上进行评估。
完整的示例如下所示。
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 |
from pickle import load from numpy import array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.models import load_model # 加载干净的数据集 def load_dataset(filename): return load(open(filename, 'rb')) # 拟合分词器 def create_tokenizer(lines): tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) return tokenizer # 计算最大文档长度 def max_length(lines): return max([len(s.split()) for s in lines]) # 编码行列表 def encode_text(tokenizer, lines, length): # 整数编码 encoded = tokenizer.texts_to_sequences(lines) # 填充编码序列 padded = pad_sequences(encoded, maxlen=length, padding='post') return padded # 加载数据集 trainLines, trainLabels = load_dataset('train.pkl') testLines, testLabels = load_dataset('test.pkl') # 创建分词器 tokenizer = create_tokenizer(trainLines) # 计算最大文档长度 length = max_length(trainLines) # 计算词汇表大小 vocab_size = len(tokenizer.word_index) + 1 print('Max document length: %d' % length) print('Vocabulary size: %d' % vocab_size) # 编码数据 trainX = encode_text(tokenizer, trainLines, length) testX = encode_text(tokenizer, testLines, length) print(trainX.shape, testX.shape) # 加载模型 model = load_model('model.h5') # 评估训练数据集上的模型 loss, acc = model.evaluate([trainX,trainX,trainX], array(trainLabels), verbose=0) print('Train Accuracy: %f' % (acc*100)) # 评估测试数据集上的模型 loss, acc = model.evaluate([testX,testX,testX],array(testLabels), verbose=0) print('Test Accuracy: %f' % (acc*100)) |
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。考虑运行几次示例并比较平均结果。
运行该示例将打印模型在训练和测试数据集上的技能。
1 2 3 4 5 6 |
最大文档长度:1380 词汇量大小:44277 (1800, 1380) (200, 1380) 训练准确率:100.000000 测试准确率:87.500000 |
正如预期的那样,我们可以看到训练数据集上的技能非常出色,准确率达到了 100%。
我们还可以看到,模型在未见测试数据集上的技能也令人印象深刻,达到了 87.5%,这高于 2014 年论文中报告的模型技能(尽管不是直接的同类比较)。
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 不同的 n-grams。通过改变模型中通道使用的核大小(n-gram 数量)来探索模型,以查看其如何影响模型技能。
- 更多或更少的通道。探索在模型中使用更多或更少通道,并查看其如何影响模型技能。
- 更深的网络。卷积神经网络在计算机视觉中,当网络更深时,性能会更好。在这里探索使用更深的模型,并查看其如何影响模型技能。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
总结
在本教程中,您学习了如何开发用于对文本电影评论数据进行情感预测的多通道卷积神经网络。
具体来说,你学到了:
- 如何为建模准备电影评论文本数据。
- 如何在 Keras 中开发用于文本的多通道卷积神经网络。
- 如何评估未经训练的电影评论数据上的拟合模型。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
嗨
好文章
我认为有一个小错误,根据这个代码,trainLines 将是一个列表的列表,其中每个列表包含一个评论的标记。但是传递给 trainLines 的所有函数(即 texts_to_sequences、fit_on_texts、max_length)都接受一个字符串列表作为输入。我认为 trainX 和 testX 中的所有列表都应该在转储到文件之前转换为字符串。
令牌列表被转换回字符串
你好,Jason
是的,但是您的代码中缺少这个指令
我收到了一个带有此消息的追溯列表对象没有属性 lower
所以我向 `clean_doc` 函数中添加了一行
tokens = [word for word in tokens if len(word) > 1]
tokens = ' '.join(tokens)
return tokens
它运作良好
祝好
你好,
感谢您的文章。是否可以使用一个输入和一个嵌入层,然后在此之后分支到卷积?
那将如何运作?
下面的模型难道不是完全一样的吗?(只有一个输入,用于三个通道?)当然,如果我们也有一个单一的嵌入(将嵌入放在通道之前,并让所有卷积作用于那一个),模型将是不同的。
是的,好方法。
训练/预测方面如何比较?
鉴于我使用不同的数据集和嵌入运行代码,Jason 先生的代码给出了我
ModelMulitCNN 模型准确率:0.8785238339313173
ModelMulitCNN 模型精确率:0.7633378932968536
ModelMulitCNN 模型召回率:0.6495925494761351
ModelMulitCNN 模型 f1_score:0.7018867924528303
而 Francesco 先生的代码给了我
ModelMulitCNN 模型准确率:0.8782675550999487
ModelMulitCNN 模型精确率:0.7424242424242424
ModelMulitCNN 模型召回率:0.6845168800931315
ModelMulitCNN 模型 f1_score:0.7122955784373107
很好,谢谢分享。
嗨!我用自己的数据集跟着你的教程,并更改了最后一层进行回归任务。我收到一个这样的错误
AssertionError: 无法计算输出 Tensor("dense_19/Identity:0", shape=(None, 1), dtype=float32)
我该如何解决呢?非常感谢。
您需要调试错误,我无法立即说出原因。
我尝试调试错误,但没有改进。我按照 Francesco 的修改,使用 1 个输入层处理所有 3 个通道,它工作正常。
//*
在定义模型中
…
model = Model(inputs=[inputs], outputs=outputs)
…
*//
如果我使用 1 层作为所有 3 个通道,它工作得很好。但是如果我像您那样使用分离的输入
//*
inputs1 = Input(shape=(length,))
embedding1 = Embedding(vocab_size, 100)(inputs1)
…
inputs2 = Input(shape=(length,))
embedding2 = Embedding(vocab_size, 100)(inputs2)
…
inputs3 = Input(shape=(length,))
embedding3 = Embedding(vocab_size, 100)(inputs3)
…
model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
*//
它不起作用,并出现了上述错误。我想知道这行代码是否有问题:model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)?
谢谢!
也许可以尝试将您的代码和错误发布到 stackoverflow。
我是深度学习新手。想从头开始开发一个 CNN 模型,用于场景图像中的文本检测。你能帮帮我吗?
这更像是一个物体检测问题,然后才能应用文本分类。例如,请参阅 https://machinelearning.org.cn/how-to-train-an-object-detection-model-with-keras/
你好,
谢谢您的努力和精彩清晰的文章。
不客气。
仅供参考,评论极性数据集的链接有误。正确的链接是:https://www.cs.cornell.edu/people/pabo/movie-review-data/review_polarity.tar.gz
已修正,谢谢。
嗨,Jason,
感谢您的精彩工作。我想问一下,我们能够评估模型的准确性,但我们如何预测测试文档的类别以详细分析结果。在顺序模型中,我们可以像 model.predict_classes(x_test) 那样执行。但是对于 Model(inputs=...) 对象,不支持 predict_classes 功能。您有什么建议吗?
谢谢。
是的,文档必须以数组形式提供 3 次。
yhat = model.predict([doc, doc, doc])
是的,我也试过那样,但我不确定如何解释多类别分类的概率。
问题究竟是什么?
感谢您的关注,Jason。
我实际上正在尝试将您的帖子应用于多类别案例(0:负面,1:中性,2:正面)。在训练部分之后,我想比较每个类别的准确率,以详细衡量模型的准确性。然后我想计算精确率和召回率。但是,我无法在 Model() 对象中使用 predict_classes() 函数,它只允许用于 Sequential() 对象。当我选择 model.predict([test_doc, test_doc, test_doc]) 函数时,它会给我一些下面的概率,但我不确定如何将它们映射到类别标签(0,1,2)。
[0.03045881]
[0.39043367]
[0.01636862]
…
[0.7408592 ]
[0.17758404]
[0.21271853]
您可以使用 `predict()` 结果上的 `argmax` 来获取类索引。
嗨,Jason,
我也尝试过您的文本分类帖子。但是,当我尝试应用 argmax() 函数时,我得到一个 0 的数组。
数组([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0])
你有什么建议吗?提前感谢。
附言
在预测类别之前,我像这样准备文本样本
sample = “这是用于分类的示例文本”
tokenizer.fit_on_texts(sample)
sequences = tokenizer.texts_to_sequences(sample)
test_data = pad_sequences(sequences, maxlen=maxlen)
predict = model.predict([test_data,test_data,test_data])
predict.argmax(axis=-1)
如果我做错了什么,请纠正我!谢谢
也许这会有帮助。
https://machinelearning.org.cn/how-to-make-classification-and-regression-predictions-for-deep-learning-models-in-keras/
我尝试过类似的架构,并得出结论,这类架构(具有并行路径)不好,因为当误差反向传播时,其中一条会正确学习的良好路径会受到一条会增加全局误差的糟糕路径的影响。所以一条良好的路径会朝着正确的方向前进,但它会认为这是一个糟糕的学习,因为全局误差会增加。(这个想法对吗?)
当我有“并行层”时,我必须找到一种方法来预训练,然后冻结值并将这些层添加到模型中。
也许这取决于所涉及的数据集和模型。
有一些反向传播类型对每个连接使用不同的学习率,这样可以更好地缓解每个连接上的错误……也许这有助于“并发路径”在学习过程中。这里有一种这样的反向传播类型:https://en.wikipedia.org/wiki/Rprop
是的,我推荐 Adam
https://machinelearning.org.cn/adam-optimization-algorithm-for-deep-learning/
Yoon Kim 论文中描述的架构
– 有一个嵌入层(不是每个分支一个),
– 使用全局(最大时间)池化(而不是池化大小为 2),
– 仅在最大特征(而不是池化操作之前的每个分支)的串联上应用一次 dropout。
这里提出的更改是否能带来更好的预测性能?
太棒了!
不知道,为什么不比较一下呢?
嗨,Jason,
感谢这篇文章。非常有帮助。
我遇到了输出问题,比如每次运行同一个模型时都会得到不同的结果。损失和准确率也一直在变化。
对此有什么想法吗?
机器学习算法在设计上是随机的。这是一个特性,而不是错误。在这里了解更多信息
https://machinelearning.org.cn/randomness-in-machine-learning/
嗨,Jason,
感谢精彩的教程,我执行了所有步骤,在最后一步尝试评估模型时,我得到了以下结果!
训练准确率:50.000000
测试准确率:50.000000
这意味着什么!我觉得这不合逻辑。
此外,在运行评估代码时,我收到了一些警告,如下所示。
2018-01-20 22:21:41.333198:W c:\tf_jenkins\home\workspace\release-win\m\windows
\py\36\tensorflow\core\platform\cpu_feature_guard.cc:45] TensorFlow 库
未编译为使用 AVX2 指令,但您的机器上可用这些指令
并且可以加快 CPU 计算。
2018-01-20 22:21:41.333808:W c:\tf_jenkins\home\workspace\release-win\m\windows
\py\36\tensorflow\core\platform\cpu_feature_guard.cc:45] TensorFlow 库
未编译为使用 FMA 指令,但您的机器上可用这些指令
并且可以加快 CPU 计算。
我将不胜感激您的回复。提前谢谢
忽略这些警告。
考虑到算法的随机性,也许可以尝试第二次运行代码。
抱歉,先生,我没有收到您回复我的通知。
您能告诉我,考虑到算法的随机性,运行代码是什么意思吗?提前谢谢。
是的,请看这篇文章
https://machinelearning.org.cn/randomness-in-machine-learning/
大家好,首先非常感谢这篇文章。
我尝试以不同的方式利用多通道概念。我正在从事自然语言中的关系抽取工作,其中关系已经标记,但需要弄清楚这些实体之间是否存在有效的句子结构。我正在考虑获取同一句子的不同表示。例如,句子(巴黎是法国的首都)的实际单词(Paris、is、the、capital、of、France)作为第一个通道的数据,这些相同标记的 POS 标签(propn、verb、det、noun、adp、propn)作为第二个通道的数据,以及依赖树标签(nsubj、root、det、attr、prep、pobj)作为第三个通道的数据。
我困惑的是,我是否需要将所有单词、POS 标签和依赖标签都视为同一个词汇的一部分进行编码?还是我需要将这些不同的标记/表示在其各自的词汇范围内进行编码?
此致
有趣的方法。
我想你可以尝试几种不同的表示方式,并测试它们是否对模型技能有任何影响?
实际上,我对文本到整数编码步骤感到困惑。我的意思是,我是否应该将同一句子的所有不同表示形式视为一个文档列表?例如,假设我得到了句子“Paris is the capital of France”的三种不同表示形式,并将它们收集在一个文档列表中,如下所示:
all_docs = [[Paris is the capital of France]…, [propn verb det noun adp propn]…, [nsubj root det attr prep pobj]…]
现在,如果我像 tokenizer.fit_on_texts(all_docs) 那样将分词器拟合到这些文档中,那么所有标记都将具有不同的唯一整数,对吗?如果我将这三种编码表示分别馈送到不同的通道,例如 input1 = [标记的整数],input2 = [更多标记的整数],input3 = [一些标记的整数],那么这样做有价值吗?
或者
我应该考虑分别对三种不同的表示形式拟合分词器,然后根据拟合的分词器分别对它们进行编码。我认为分词器会提供一些公共整数。因为编码范围是隔离的。这会以某种方式帮助连接层吗?
再次感谢 Jason 的回复以及为这个博客所做的努力。
也许这是一个很好的起点,可以帮助您掌握数据准备
https://machinelearning.org.cn/start-here/#nlp
嗨,Jason,
是否有可能通过查看激活模式来识别对分类影响最大的 n-gram?对于图片来说这是可能的,但我猜对于文本来说应该非常困难,因为中间有嵌入……
谢谢
弗朗西斯科
可能吧,但这需要一些深入的思考和开发。抱歉,这并不明显。
你好,
感谢这篇简洁的文章! 🙂
我遇到了一个问题。
AttributeError: ‘int’ object has no attribute ‘ndim’
在 model.fit 中
你尝试过复制最后一个例子中的所有代码吗?
我也遇到了同样的问题。原因是变量是一个列表对象,而 Keras 期望的是一个 numpy 数组。我通过导入 numpy 并插入 “trainLabels = numpy.array(trainLabels)” 解决了这个问题。
我的意思是变量 trainLabels。
我已更新示例以纠正此问题。
这似乎是 Keras 2.1.3 中的一个更改。
感谢 Jason 的这篇有用的文章。有趣的是,即使是这种极简的预处理似乎也总是效果很好。在进行文本分类(或回归)任务时,您通常认为在预处理过程中保留/包含额外信息是否有价值,例如
(1) 基本的句子分隔标点符号,例如“.”、“!” 和 “?”
(2) 句子组之间的段落分隔符
(3) POS 标签增强标记,例如,“walking” –> “walking_VERB”,“apple” –> “apple_NOUN”
这些通常有任何显著影响吗?
这真的取决于具体的应用,恐怕。
对于像任务这样的简单分类,通常更简单的表示会带来更好的模型技能。
您应该能够注释掉 inputs2、embedding2、inputs3 和 embedding3,然后将 embedding1 馈送到 conv2 和 conv3,对吗?然后您将从 [x_in, x_in, x_in] 变为 fit() 和 predict() 的 x_in。
嗨,Jason,
在您的 NLP 书(第 16 章)中,您深入介绍了 n-gram CNN。LSTM 的记忆模仿 n-gram。
我可以问您为什么不使用 LSTM 分类吗?
谢谢!
弗朗哥
你可以使用 LSTM,这里我演示了如何使用 CNN。
谢谢 Jason!
确实,您的 LSTM 书中有一个 NLP 示例。
嗨,Jason,
感谢又一篇精彩的帖子。除了纯粹的教育目的之外,您认为在情感分析中使用 LSTM 和 CNN 之间有什么区别?这两种方法是完全可以互换的,还是每个模型在不同的设置中都可能具有优势/局限性。
祝好,
鲍里斯
CNN 似乎达到了最先进的结果。我会从那里开始。
我们可以将这种方法应用于多类别问题吗?唯一的改变是在拟合期间和使用“分类交叉熵”作为损失。我说的对吗?
当然可以。
嗨,Jason,
我是 keras 和 CNN 的初学者,我想知道如何为 train_test_split 提供多个输入?
我的例子:(Xtrain_user,Xtrain_item,Xtest_user,Xtest_item),y_train,y_test=train_test_split((user_reviews,
item_reviews),rating , test_size=0.2, random_state=42)
其中 X= (user_reviews,item_reviews)
和 Y=rating
也许可以使用 NumPy 中的数组表示手动分割数据
https://machinelearning.org.cn/index-slice-reshape-numpy-arrays-machine-learning-python/
但是我有用户和项目的字典,我该如何拆分它呢?
也许选择一个轴(例如用户)并将用户列表一分为二?
首先,感谢您在这里所做的一切,以及您通常所做的一切 🙂
我用您的代码测试了 imdb 数据,得到了相似的结果,然后将输出更改为 2 个神经元,使用 softmax/argmax 进行训练/测试,结果略有改善。
然后我切换到我自己的 5 类情感数据集,但得到了 98% 左右的训练准确率和 40% 左右的测试准确率,这意味着它过拟合了,但后来在许多论文中读到,通常在 5 粒度 SST1 数据集中,每个人报告的准确率都是 40% 左右。对于二元 +/- 情感,当然每个人都能达到 80 年代后期到 90 年代的准确率。
您对此有什么建议?或者我可以在您的代码之上尝试的设置。
模型架构可能需要针对每个不同的问题进行调整。
嗨,Jason,
感谢这篇精彩的文章。
如果我有超过 2 个类别,例如积极/消极/中性,我的输出列表 (trainLabels) 会是什么样子?它会是一个包含 0、1 和 2 的列表,还是一个 n*3 矩阵,每列包含 0 和 1?
非常感谢
我建议使用独热编码,你可以在这里了解更多
https://machinelearning.org.cn/why-one-hot-encode-data-in-machine-learning/
你好,
我创建了一个网络,它接受两个整数序列(2 个输入)用于一个句子(一个与词嵌入相关,另一个与 POS 标签相关),以及相应的嵌入层,然后在应用卷积层之前将它们合并在一起。我称之为多输入模型。
inp = Input(shape=(data.shape[1],))
e= Embedding(vocab_size, 200, input_length=maxSeqLength, weights=[embedding_matrix], trainable=False)(inp)
inp2 = Input(shape=(embedding_pos.shape[1],))
e1= Embedding(54, 54, input_length=maxSeqLength, weights=[embedding_matrix_pos], trainable=False)(inp2)
mer=concatenate([e,e1],axis=-1)
conv1_1 = Conv1D(filters=100, kernel_size=3)(mer)
conv1_2 = Conv1D(filters=100, kernel_size=4)(mer)
从这篇文章中我了解到它可能是多通道的。我困惑的是,多个输入是否一定是多通道,或者即使在同一个输入上,多个并行卷积层是否也是多通道?如果我考虑第二个定义,我会称我的网络为多输入多通道?
此外,在这种情况下,所有卷积都应用于原始输入。如果我将卷积堆叠起来,使一个卷积的结果作为另一个卷积的输入,有什么区别呢?谢谢。
如果输入不同,最好使用多头 CNN 而不是多通道 CNN。
输入是同一句子的序列,并且都填充到相同的长度……那么它仍然被称为不同的输入吗?您能澄清一下,无论卷积是并行的还是序列的,多通道都是多输入吗?
模型的每个头部都会“读取”数据的方式不同。
也许将模型的多个输入称为“通道”是一个糟糕的选择。上面模型中的不同输入确实与一个输入的不同通道不同。重要的是,我们可以对不同的输入使用不同大小的核和滤波器数量,而一个输入上的每个通道都固定使用相同的核和滤波器数量。
非常感谢。我现在明白了,多通道意味着一个输入,该输入带有一个或多个嵌入。在您的示例中,您多次使用输入,但它仍然是相同的输入。我采纳了您的建议,对每个嵌入分别应用了不同滤波器大小的卷积,而不是将它们合并在一起,这提高了准确性。只有一点,这意味着它是多输入,我能理解它如何被视为多头……所以基本上有多个输入就使其成为多头吗?谢谢
是的。
非常感谢!我通过训练时期绘制的精确度和召回率图显示出精确度突然飙升,而召回率逐渐增加。尽管召回率高于精确度。现在我的数据集中阳性样本多于阴性样本,这让我相信假阴性多于假阳性,即召回率较低而精确度较高,但我的结果却相反。您能详细说明一下我是否有错误的理解吗?
或者我应该这样看,因为阳性样本更多,分类器偏向于阳性类别,导致更多的假阳性,因此精确度更低,召回率更高?谢谢
您需要为您的特定应用和问题找到一个合理的权衡。
我绘制了正类和负类的精确度-召回率曲线,结果很有趣,
正类的曲线看起来非常好,最优值在召回率 0.9 和精确度 0.8。负类的曲线是一条直线,在精确度和召回率上都给出 0.6,或者在精确度为 0.6 时召回率为 0.8,反之亦然。
对此有什么想法吗?
这可能表明一个类别比另一个类别更容易预测。
我完全按照教程操作,但我得到了较低的测试准确率,为 83.5%。训练准确率为 100%。
您可以尝试多次运行示例,看看是否会得到不同的结果。
(忘记回复了)是的,实际上运行了几次后得到了不同的结果。
def max_length(lines)
return max([len(s.split()) for s in lines])
我只是复制粘贴了代码,然后就出现了这个错误,我相信代码实际上是正确的,我不知道为什么我会收到错误
AttributeError: 'list' object has no attribute 'split'
环境
Python 3.6
Tensorflow 1.9
Keras 2.2
确保缩进正确,并且您拥有所有周围的代码和数据。
我这里有更多建议
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
我对“多通道卷积神经网络用于文本的图”有一个问题。
为什么每个方框里总有“none”?它表示什么?
找到了答案:形状元组中的 None 维度指的是批量维度,这意味着该层可以接受任何大小的输入。
是的。
好问题!
“None”表示未指定且可变的维度。
我有一个问题。根据这篇论文,https://arxiv.org/pdf/1510.03820.pdf(卷积神经网络用于句子分类的敏感性分析(和从业者指南)),他们指出“我们将此区域大小的特征图数量设置为 100”。我如何设置特征图的数量?特征图的数量是否基于每个区域大小的过滤器数量?
特征图的数量是卷积层 Conv1D() 的第一个参数。
我真的有很多问题,抱歉 xD。
直观地理解这种多通道 CNN(或者说一般的基本 CNN 架构)如何识别以下内容(假设表达的情绪只有积极或消极)?
1.) 如果推文中表达了不止一种情绪,但积极情绪更占主导地位,那么它就是一条积极推文,或者
2.) 如果推文中表达了不止一种情绪,但消极情绪更占主导地位,那么它就是一条消极推文。
我们无法知道 CNN 学习到了什么,只能评估模型的性能。
我试图理解的是 CNN 如何应用于句子分类。我仍然不明白这个想法。我所知道的是,当 CNN 用于计算机视觉时,它会发现图片特征。例如,一只狗有耳朵、鼻子和眼睛等特征。
我想我明白了。
在卷积神经网络中,每个网络层都充当原始数据中特定特征的检测过滤器。CNN 中的第一层检测可以相对容易识别和解释的(大)特征。随后的层检测越来越(小)的、更抽象的特征。CNN 的最后一层能够通过结合前几层在输入数据中检测到的所有特定特征来进行超特异性分类。
也许可以尝试这个更简单的教程
https://machinelearning.org.cn/develop-word-embedding-model-predicting-movie-review-sentiment/
你好,
我有一个关于模型输入的问题,基于我收到的错误。
我在不同的数据上运行了这段代码,没有问题。然后我尝试使用 RandomizedSearchCV 进行扩展,如下所示:
model = KerasClassifier(build_fn=create_model, verbose=1, epochs=3, batch_size=32)
param_dist= {“n_strides”: sp_randint(1,3)}
random_grid = RandomizedSearchCV(estimator=model, param_distributions=param_dist, n_iter = 3)
random_grid_result = random_grid.fit([X_train, X_train, X_train], y_train)
我收到以下错误
ValueError: Found input variables with inconsistent numbers of samples: [3, 25000]
错误可能是什么?25000 是我的训练数据的长度
抱歉,我不知道发生了什么。
也许将您的代码和错误发布到 stackoverflow?
抱歉,我没能理解。
我进一步研究了这个问题,发现 Keras 包装器不支持多输入网络。这就是问题所在 (https://github.com/keras-team/keras/issues/6451)
所以我决定使用 Francesco 上面(1 月 25 日)的评论,为所有三个通道使用一个输入,并且它正在工作!
太棒了!
你好,
我有一个关于否定词的问题。
当您通过排除停用词来清理数据时,您会丢失否定词(“not”等)。您不想保留这些词语以更好地解释句子吗?
例如:“I didn’t like the movie” 和 “I liked the movie” 在清理后看起来会一样。
这真的取决于它们是否在你正在解决的特定问题中增加了价值。
也许可以尝试有它们和没有它们进行建模?
非常感谢
这是我尝试使用串联操作合并的两个神经元网络。该网络应该将 IMDB 电影评论分为 1-好电影和 0-坏电影。
我的模型拟合(训练)出现错误
history = conc_model.fit([X_train, X_train], [y_train, y_train], np.reshape([y_train, y_train],(25000,2)),epochs=3, batch_size=(64,64))
TypeError: fit() got multiple values for argument ‘batch_size’。
这是应该返回训练模型的方法。顺便说一句,x_train 的形状是 (25000, 5),y_train 的形状是 (25000,)
def cnn_lstm_merged()
embedding_vecor_length = 32
cnn_model = Sequential()
cnn_model.add(Embedding(top_words, embedding_vecor_length,input_length=max_review_length))
cnn_model.add(Conv1D(filters=32, kernel_size=3, padding=’same’, activation=’relu’))
cnn_model.add(MaxPooling1D(pool_size=2))
cnn_model.add(Flatten())
lstm_model = Sequential()
lstm_model.add(Embedding(top_words, embedding_vecor_length, input_length=max_review_length))
lstm_model.add(LSTM(64, activation = ‘relu’, return_sequences=True))
lstm_model.add(Flatten())
merge = Concatenate([lstm_model, cnn_model])
hidden = Dense(1, activation = ‘sigmoid’)
conc_model = Sequential()
conc_model.add(merge)
conc_model.add(hidden)
conc_model.compile(loss=’binary_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])
history = conc_model.fit([X_train, X_train], [y_train, y_train], np.reshape([y_train, y_train],(25000,2)),epochs=3, batch_size=(64,64))
return history
抱歉,我没有能力调试你的新代码,也许可以把它发布到 stackoverflow?
非常棒的教程!
我调整了您的版本以适应我保存在 pandas 数据帧中的数据。由于我的数据集有限,评估分数达到了 100%,与我之前的 BOW 模型 98% 相比,这是一个巨大的改进。
您可以在我的 github 上找到完整的代码
https://github.com/eanunez/ArticleClassifier/blob/master/cnn/Multi-Channel%20CNN.ipynb
非常感谢 Jason!
此致,
伊曼纽尔
干得好!
你好伊曼纽尔,
感谢您在 github 上更新的版本,在进行多类别拟合时,您应该添加以下代码
在您的代码之后
再次感谢 Jason Brownlee,我真是你的粉丝。
嗨,Jason,
这是我对您这个精彩博客的第一个评论,尽管我读了很多您的文章,所有文章都很有用。
无论如何,我有一个关于 MaxPooling 层的问题:我们为什么需要它们?
只是为了降低维度吗?因为我感觉使用它会让我们丢失在卷积层中学到的一些信息。
###
在我的应用程序中,我想使用嵌入和 n-gram。如果我有句子“I like you”,我想最终得到一个维度为 [?, 6, d] 的张量(d 是嵌入的维度)。该张量将表示
‘I’
‘like’
‘you’
‘I like’
‘like you’
‘I like you’
所以我想对前 3 个标记使用基本嵌入,然后应用一个 2-gram 卷积层来获取接下来的 2 个标记,最后是一个 3-gram 卷积层来获取最后一个标记。然后我将所有内容连接起来(选择一个适当的核大小)。
在这种情况下,我为什么要应用 MaxPooling 层?
您认为我的方法可行吗?
谢谢您,并继续保持良好的工作 🙂
是的,我们使用池化层将大型特征图提炼成最基本的特征。
尝试使用和不使用池化层,并使用性能最好的模型。
嗨,Jason,
这对我学习 NLP 及其任务非常有帮助。非常感谢您的工作……请也为计算机视觉问题做同样的事情。我在任何地方都找不到像您这样的计算机视觉问题博客。请考虑一下……
感谢您的建议。
嗨,Jason,
对于我的其中一个输入值,当我运行 CNN 代码时
我收到的输出是
时期 1/100
250/250 [==============================] – 77s 308ms/步 – 损失:-2.8596 – 准确率:0.7843
时期 2/100
250/250 [==============================] – 76s 303ms/步 – 损失:-2.9239 – 准确率:0.7863
时期 3/100
250/250 [==============================] – 75s 300ms/步 – 损失:-2.8824 – 准确率:0.7904
时期 4/100
250/250 [==============================] – 74s 294ms/步 – 损失:-2.9322 – 准确率:0.7851
你认为模型是正确的吗,因为损失是负值。
谢谢,
负损失很有趣。可能发生了奇怪的事情。
你好 Jason,
我想通过遵循您的其他教程来对这个模型(3 通道)应用网格搜索,但我得到了这个错误
>ValueError: Found input variables with inconsistent numbers of samples: [3, 8000]
这是代码
# 网格搜索
epochs = [1, 10]
batch_size = [16, 32]
param_grid = dict(epochs=epochs, batch_size=batch_size)
grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=’accuracy’)
grid_result = grid.fit([trainX,trainX,trainX], array(trainLabels))
我搜索了 Google,但没有找到任何答案。
提前感谢您!
也许可以尝试手动运行网格搜索循环。
嗨 Jason!预测值接近任一类别是否意味着它具有更高的置信度?例如,句子 A 的预测值为 0.99,而句子 B 的预测值为 0.65,这意味着模型对句子 A 的预测置信度高于句子 B,还是与过拟合有关?像句子 A 这样的值是否是由于过于紧密地拟合数据造成的,这可能会导致模型未来出现错误的预测,因为它可能会优先考虑像句子 A 这样的句子?
较大的预测概率可以解释为对预测的置信度更高。
非常感谢 Jason!
我可以使用共享输入而不是在每个 CNN 通道上使用输入吗??
你具体指的是什么?
您在内存中存储数据一次,并提供多个引用。
抱歉,我没有收到您回复的通知。
我的意思是,如果我使用一个输入和一个嵌入,我可以使用它一次进行并行不同核卷积,这是否也称为多通道?
def define_model(length, vocab_size)
inputs1 = Input(shape=(length,))
embedding1 = Embedding(vocab_size, 100)(inputs1)
# 通道 1
conv1 = Conv1D(filters=32, kernel_size=4, activation=’relu’)(embedding1)
drop1 = Dropout(0.5)(conv1)
pool1 = MaxPooling1D(pool_size=2)(drop1)
flat1 = Flatten()(pool1)
# 通道 2
conv2 = Conv1D(filters=32, kernel_size=6, activation=’relu’)(embedding1)
drop2 = Dropout(0.5)(conv2)
pool2 = MaxPooling1D(pool_size=2)(drop2)
flat2 = Flatten()(pool2)
# 通道 3
conv3 = Conv1D(filters=32, kernel_size=8, activation=’relu’)(embedding1)
drop3 = Dropout(0.5)(conv3)
pool3 = MaxPooling1D(pool_size=2)(drop3)
flat3 = Flatten()(pool3)
# 合并
merged = concatenate([flat1, flat2, flat3])
# 解释
dense1 = Dense(10, activation=’relu’)(merged)
outputs = Dense(1, activation=’sigmoid’)(dense1)
model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
# 编译
model.compile(loss=’binary_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])
# 总结
打印(model.summary())
plot_model(model, show_shapes=True, to_file=’multichannel.png’)
return model
是的,我必须称之为多头模型,并将“通道”保留用于表示给定输入的深度。
你所说的“通道”指的是给定输入的深度是什么意思。
就像输入图像有 3 个通道一样,一个用于红色/蓝色/绿色。
而且,特征图堆栈的深度被称为通道。
先生,
我正在做一个关于“使用深度学习进行释义检测”的项目。
我有两个输入,作为两个句子。两个句子都希望单独训练。我该如何拟合我的模型???
也许可以从清晰定义你的预测问题开始
https://machinelearning.org.cn/how-to-define-your-machine-learning-problem/
你好 Jason
示例开头有一个小错别字
table = str.maketrans(”, ”, string.punctuation)
但整个示例中都已修复
table = str.maketrans(”, ”, punctuation)
祝好
我不相信,你确定吗?
是的,只有在第一个例子中。
# 将文档转换为干净的令牌
def clean_doc(doc)
# 按空格分割成令牌
tokens = doc.split()
# 从每个令牌中去除标点符号
table = str.maketrans(”, ”, string.punctuation)
tokens = [w.translate(table) for w in tokens]
在“加载和清理评论”部分中的第一个示例导入了字符串“import string”,然后使用了“string.punctuation”。
这是正确的 Python 代码。
也许我误解了您的评论?
你好,Jason
是的,你是对的。我合并了两个示例的代码,这就是为什么我得到了一个回溯
抱歉
不客气。
嗨,Jason,
我遵循了您的文章,它真的很有帮助。
现在我卡住了,模型的 val_loss 持续增加,而 val_acc 也持续增加。
我遵循了您关于改进过拟合的文章,但添加 dropout 层完全没有作用。我尝试增加训练数据量,但这实际上使结果变得更糟。
我已在 stackoverflow 上发布了一个问题,详细解释了我的问题。
如果您能查看一下这个问题,那将非常有帮助。
这是链接
https://stackoverflow.com/questions/55320567/validation-loss-increases-after-3-epochs-but-validation-accuracy-keeps-increasin
我期待您的回答。
谢谢。
也许可以尝试使用 SGD,降低学习率,并增加训练周期数。
CNN 会尝试直接学习填充后的句子,这将导致嘈杂的学习表示,我们如何忽略填充值,使其对 CNN 过滤器学习没有影响?
通常 CNN 会忽略零填充值,例如,它们在执行卷积时一直使用填充。
嗨,Jason,
感谢这个易于理解的教程。与带有 3-gram 核和更多过滤器(128 而不是 32)的单通道卷积网络以及 GlobalMaxPooling 相比,多通道卷积网络并没有带来任何改进。您有什么想法为什么会这样吗?您能建议任何有效的调整来改进多标签文本分类器吗?
此致
SK
我有一些建议可能会有所帮助
https://machinelearning.org.cn/start-here/#better
嗨,Jason,
感谢您提供非常有用的链接。
祝好
苏迪尔
不客气。
嗨,Jason,好文章
你能写一篇关于解决文本相关机器学习问题最常见方法的文章吗?
是的,从这里开始
https://machinelearning.org.cn/start-here/#nlp
我写了一篇虚假评论,如下:
“这部电影很精彩
这部电影太棒了
演员们表演得非常好,表现出色
我还会再看这部电影”
然后我用相同的模型来预测评论是好是坏
结果是 0.50316983,但我期望更高,因为评论很直白
我使用了更多积极的词语,避免了任何令人困惑的词语
那么问题出在哪里呢?
也许模型过拟合了,你可以尝试重新拟合吗?
嗨,Jason,
感谢您的精彩教程。一个疑问:它能支持多语言吗?我的意思是,如果数据集不是英语,它是否需要更改词嵌入?或者它仍然有效。
谢谢
据我所知,您可以为任何语言训练自己的词嵌入。
谢谢 Jason 的回复。您在代码中使用了任何词嵌入(例如 w2v/glove)吗?我只能看到 Keras 分词器函数(如果我错了请纠正我)。当我运行将您的代码适配到另一种语言和不同数据集时,它运行得很顺利,所以我问它是否需要特定的词嵌入。
谢谢
是的,很多次。也许从这里开始
https://machinelearning.org.cn/use-word-embedding-layers-deep-learning-keras/
CNN 和多通道 CNN 有什么区别?
我为什么需要使用多通道 CNN?
它可以在同一输入数据上使用不同的核大小,并有效地同时以不同的分辨率“查看”数据。
谢谢你
不客气。
以下是计算相关系数的函数。我用它来衡量回归问题(文本相似性)的准确度。我使用了 lstm 和多通道 cnn,但相关度结果从第二个 epoch 开始就出现了负值。
您能帮助我检查此函数的正确性吗?
这是我在这里回答的一个常见问题
https://machinelearning.org.cn/faq/single-faq/can-you-read-review-or-debug-my-code
当我使用相关系数作为度量时,
得到的相关系数值范围在 -1 到 -85 之间。
得到的值是否意味着模型存在问题?
也许可以先查看损失?
皮尔逊相关系数能否同时减小而损失也减小?这意味着什么?
我认为在训练期间,损失应该减小,而相关系数应该增大?
相关性不是一个好的损失函数。
我在文本相似性问题上运行了多通道 CNN 模型。
每次运行模型,我都会得到不同的结果(损失、准确率)。
每次运行代码时,得到不同的损失和准确率结果是正常的吗?
这是意料之中的,请看这里
https://machinelearning.org.cn/faq/single-faq/why-do-i-get-different-results-each-time-i-run-the-code
如何对 Glassdoor 数据进行情感分析,并在考虑评分的情况下获得最大准确率?
也许可以尝试一系列数据准备方法,并探索一系列 CNN 模型,看看哪种效果更好。
从这里开始
https://machinelearning.org.cn/start-here/#nlp
然后使用这里的教程来获得更好的结果
https://machinelearning.org.cn/start-here/#better
使用多通道 CNN 相对于单个 CNN 有什么优势?
它可以通过不同方式并行读取输入,例如,在同一个模型中从相同的输入中提取不同的特征。
嗨,Jason,
当我按原样运行完整的模型时,我得到了 50% 的准确率(参见以下输出)。我尝试过给 CNN 添加更多层,使用不同的核,调整嵌入层中的超参数……等等。但仍然得到相同的准确率。您能告诉我如何提高准确率吗?另外,我注意到当我第一次运行代码时,测试数据上的准确率是 99%,而测试数据上的准确率是 50%。
最大文档长度:1380
词汇量大小:44277
(1800, 1380) (200, 1380)
训练准确率:50.000000
测试准确率:50.000000
您可以在此处找到诊断和改进深度学习性能的建议
https://machinelearning.org.cn/start-here/#better
嗨 Jason,感谢您的教程。您能用 Keras 写一个基于方面的情感分析教程吗?那将太棒了!
什么是“基于方面的情感分析”?
如何使用您的教程对句子进行预测
以与训练数据相同的方式准备文本,然后调用 model.predict()
寻求帮助请看这里
https://machinelearning.org.cn/how-to-make-classification-and-regression-predictions-for-deep-learning-models-in-keras/
嗨 Jason – 我修改了这个模型,用上面的电影评论数据进行训练,然后对其他文本文档进行预测。我曾尝试对“这部电影太烂了,不推荐”这样的文本字符串进行评分预测,结果非常差(模型预测有 65% 的概率是正面评论)。
只是想知道是否有额外的训练资源可以构建到模型中,以获得真正全面的正面/负面情绪方法?它似乎对大型段落/流畅的评论效果很好,但对“这部电影太棒了”这样简短的 5-10 个单词的评论效果不佳。
做得好,发现很有趣。
是的,在更多样化的数据或更适合模型预期用途的数据上进行训练。
如果我在每个通道中放置不同的嵌入呢?
embedding1=随机嵌入(通道1)
embedding2=word2vec 嵌入(通道2)
这看起来是正确的想法吗?
试试看。结果比意见更重要。
嗨,Jason,
感谢您的精彩工作。我想问您,我们是否能够实现包含两个 CNN 通道的多通道 CNN:词嵌入和词性嵌入通道。如果可以,我们该如何实现。
感谢您的时间。
是的,为每个数据源设置一个单独的输入模型。
在本教程中,如果我们要实现包含两个输入的 CNN,第一个是词嵌入,第二个是词性嵌入通道。我们该如何实现?
定义两个嵌入并将其连接到 CNN 层,然后合并这些层的输出。所有这些都使用函数式 API。
以上示例将提供一个您可以适应的起点。
嗨,Jason,
感谢您的这份精彩作品。我想问您,我们是否能够使用多通道 CNN 的输出应用 LDA?
我没有试过。
嗨,Jason,
感谢分享这份工作。我正在按照您的工作构建文本分类器,并注意到您使用了大小为 2 的最大池化。但是,我相信原始论文中使用的是时间上的最大池化(在 Keras 中实现为 GlobalMaxPooling1D)。这是否有特殊原因?
谢谢。
谢谢。
好。没有理由。也许可以尝试其他池化方法并比较结果?
嗨,Jason,
我不明白你为什么添加这一层“dense1 = Dense(10, activation=’relu’)(merged)”。我似乎在原始论文架构中找不到它。
你能解释一下吗?
这只是一个解释合并输入的层。您可以尝试使用或不使用它。
感谢您的快速回复!这是 Kim (2014) 和 Zhang & Wallace (2015) 放置 dropout 和 l2 正则化的地方吗?
你能草拟一下如何在 keras 中写出来吗?
抱歉,我没有能力为您定制教程代码。
如果您是 Keras 新手,可以从这里开始
https://machinelearning.org.cn/start-here/#deeplearning
抱歉,我记不清了。
你可以随意修改模型。
我们如何将 k 折交叉验证应用于这个多通道 CNN?我需要这方面的帮助。
这是一个使用交叉验证进行 CNN 模型的示例
https://machinelearning.org.cn/how-to-develop-a-cnn-from-scratch-for-fashion-mnist-clothing-classification/
嗨 Jason。感谢您的有用教程。我只是想知道:您用于分类的模型是否可以通过对最后一个密集层进行少量更改(激活 = 线性而不是 sigmoid)用于回归任务?那么对于任何分类模型,我们只需要对最后一层进行少量修改即可用于回归任务?就这么简单吗?提前感谢!
不客气。
是的。更改输出层中的激活函数、损失函数和度量。
嗨 Jason
我正在处理 Kaggle 上的 160 万条数据,用于情感分析。
我只保留了两个字段:1. 情感 2. 文本
现在我想使用 ANN 来训练我的模型。
关于这一点,我有几个问题。
1:如何决定输入层和隐藏层应该有多少个输入。
classifier.add(Dense(units=1 , kernel_initializer=’uniform’, activation=’relu’))
classifier.add(Dense(units=1 , kernel_initializer=’uniform’ , activation=’relu’))
classifier.add(Dense(units=1 , kernel_initializer=’uniform’ , activation=’sigmoid’))
我有一个输入层,一个隐藏层和一个输出层。由于我只有两个字段,一个是依赖的,一个是非依赖的,所以我在输入层和隐藏层中将单元数设置为 1。
我试图知道它的形状,当我检查 X_train 的形状时,它显示为 100。
现在我在输入字段中保留的是否正确,或者我是否需要在输入字段中给出形状值。
请帮助
也许可以测试不同数量的输入,并找出最适合您的数据集的方法。
嗨,Jason,
执行代码时,我收到此错误:Attribute Error: ‘str’ object has no attribute ‘decode’。如何解决这个问题。
很抱歉听到您遇到麻烦,这些技巧会有帮助
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
感谢这篇文章。我尝试通过使用 1 个输入和 1 个嵌入层来改进它——这减少了参数数量
总参数:5,144,937
所以——在 12 个 epoch 和 batch=6 的训练之后,我得到了相同的结果
Epoch 12/12
300/300 – 7s – loss: 8.2556e-06 – accuracy: 1.0000
测试准确率:87.500000
据我了解——嵌入层是不可训练的,所以我不需要 3 个吗?
干得好!
更新:我一次运行得到了 90%(其他运行 84~88%)
纪元 15/15
300/300 – 7s – loss: 2.6125e-05 – accuracy: 1.0000
测试准确率:90.499997
干得漂亮!
感谢您的精彩分析。如果你想修改它以将不同的文本输入到 trainX,比如你有 trainX、trainY 和 trainZ。那么在修改上述内容方面应该怎么做呢?我想它需要相同的分词器,对吗?谢谢 Toby
我想你指的是一个多输出模型,如果是这样,你可以直接修改模型以拥有多个输出。
本教程将帮助您入门
https://machinelearning.org.cn/keras-functional-api-deep-learning/
感谢您的回复,我仍然有一个输出需要预测(即一个分类示例)。我基本上是想看看我的文本从一个文档到下一个文档的变化是否可以帮助预测我的目标变量。所以我希望同时输入两个文档。我修改了上面的代码,将不同的分词器、词长、词汇量输入到各自的嵌入矩阵中。
我的问题有点类似于 Quora 重复问题,但只是想从连接的 CNN 而不是 LSTM 开始。到目前为止,验证准确率看起来还可以,但很快就会针对测试集进行测试。
这看起来可以吗?如果您输入两个不同的文档,这在技术上是否称为多通道 CNN?
谢谢!
抱歉,我很难判断。
也许可以尝试一下,并与其他方法的结果进行比较。
嘿,Jason Brownlee
问候
首先,非常感谢您的精彩文章,您的文章确实比我的导师更能帮助我。每当我遇到问题时,我都会首先搜索与该问题相关的文章。
关于这篇文章我有一个问题,您能解释一下这种架构相关的成本吗?我正在尝试类似的想法,但我担心它所需的时间。它是否适用于需要快速反应行动的问题?
这取决于你想要多快,但神经网络本质上并不快。它比其他机器学习模型有更多的参数。
明白了。非常感谢。祝好运。
您好,Jason Brownlee
由于用于文本分类的多通道卷积神经网络涉及使用具有不同大小内核的标准模型的多个版本。既然它使用了标准 CNN 的多个版本,那么在这里我们能否在每个通道中保持不同数量的滤波器以及不同的嵌入维度?例如,我希望在通道一中保持 128 个滤波器,内核大小为 6,嵌入维度为 64。同样,我在通道二中拥有 64 个滤波器,内核大小设置为 4,嵌入维度为 32,并且我对第三个通道也执行相同的操作(我的意思是具有不同的滤波器数量、内核大小和嵌入维度)。我只有所有三个通道的一个输入。在这里,我希望每个通道以不同方式在相同的输入上进行卷积,并尝试以不同方式找到模式。
您能否阐明这种架构?
谢谢你
我不认为顺序模型允许你这样做,但你可以使用 Keras 的函数式 API 构建一个。
非常感谢您的快速回复
是的,我也在谈论本文中使用 Keras 的函数式 API 讨论的相同想法。由于您在每个通道中使用了相同数量的滤波器和相同的嵌入维度。所以我想通过在每个通道中使用不同数量的滤波器以及不同的核大小和不同的嵌入维度来进一步探索这个想法。
还有一件事是,我正在为所有三个通道使用相同的输入,并且我数据集中的所有字符串都具有我作为输入传递的固定长度。
那么这种架构的合适名称是什么?多通道还是多层?
您的回复将不胜感激。
如果你有三个通道,那肯定是多通道。但它也可以同时是多层。
感谢您的合作
不客气。