用 Scikit-Learn 进行命名实体识别和分类
如何使用 Scikit-Learn 的库为 NER 训练机器学习模型
命名实体识别和分类 (NERC)是一个从非结构化文本中识别信息单元的过程,如名称,包括人、组织和位置名称,以及数字表达式,包括时间、日期、金钱和百分比表达式。目标是开发实用的和独立于领域的技术,以自动检测高精度的命名实体。
上周,我们给介绍了 NLTK 和 SpaCy 中的命名实体识别(NER)。今天,我们更进一步——使用 Scikit-Learn 的一些库为 NER 训练机器学习模型。我们开始吧!
数据
这些数据是特征工程语料库,标注有 IOB 和 POS 标签,可以在 Kaggle 找到。我们可以快速浏览一下前几行数据。
Figure 1
实体基本信息:
- 地理=地理实体
- org =组织
- per =人
- 地缘政治实体
- tim =时间指示器
- 艺术=艺术品
- eve =事件
- 自然现象
内部-外部-开始(标记)
IOB(内、外、始的简称)是标记令牌的常用标记格式。
- 标签前的 I 前缀表示标签在块中。
- 标签前的 b 前缀表示标签是块的开始。
- O 标记表示令牌不属于任何块(外部)。
import pandas as pd
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import Perceptron
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.linear_model import PassiveAggressiveClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
单个计算机的内存无法容纳整个数据集,因此我们选择前 100,000 条记录,并使用核外学习算法来有效地获取和处理数据。
df = pd.read_csv('ner_dataset.csv', encoding = "ISO-8859-1")
df = df[:100000]
df.head()
Figure 2
df.isnull().sum()
Figure 3
数据预处理
我们注意到在“句子#”列中有许多 NaN 值,我们用前面的值填充 NaN。
df = df.fillna(method='ffill')df['Sentence #'].nunique(), df.Word.nunique(), df.Tag.nunique()
(4544,10922,17)
我们有 4544 个句子,包含 10922 个独特的单词,由 17 个标签标记。
标签分布不均匀。
df.groupby('Tag').size().reset_index(name='counts')
Figure 4
以下代码使用[**DictVectorizer**](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html#sklearn.feature_extraction.DictVectorizer)
将文本日期转换为向量,然后拆分为训练集和测试集。
X = df.drop('Tag', axis=1)
v = DictVectorizer(sparse=False)
X = v.fit_transform(X.to_dict('records'))
y = df.Tag.valuesclasses = np.unique(y)
classes = classes.tolist()X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state=0)
X_train.shape, y_train.shape
((67000,15507),(67000),)
核外算法
我们将尝试一些核外算法,这些算法旨在处理太大而无法放入支持**partial_fit**
方法的单个计算机内存中的数据。
感知器
per = Perceptron(verbose=10, n_jobs=-1, max_iter=5)
per.partial_fit(X_train, y_train, classes)
Figure 5
因为标签“O”(外部)是最常见的标签,它会使我们的结果看起来比实际好得多。因此,在评估分类指标时,我们删除了标签“O”。
new_classes = classes.copy()
new_classes.pop()
new_classes
Figure 6
print(classification_report(y_pred=per.predict(X_test), y_true=y_test, labels=new_classes))
Figure 7
带 SGD 训练的线性分类器
sgd = SGDClassifier()
sgd.partial_fit(X_train, y_train, classes)
Figure 8
print(classification_report(y_pred=sgd.predict(X_test), y_true=y_test, labels=new_classes))
Figure 9
多项式模型的朴素贝叶斯分类器
nb = MultinomialNB(alpha=0.01)
nb.partial_fit(X_train, y_train, classes)
Figure 10
print(classification_report(y_pred=nb.predict(X_test), y_true=y_test, labels = new_classes))
Figure 11
被动攻击分类器
pa =PassiveAggressiveClassifier()
pa.partial_fit(X_train, y_train, classes)
Figure 12
print(classification_report(y_pred=pa.predict(X_test), y_true=y_test, labels=new_classes))
Figure 13
上述分类器都没有产生令人满意的结果。很明显,使用常规分类器对命名实体进行分类并不容易。
条件随机字段
CRFs 通常用于标记或解析序列数据,例如自然语言处理和 CRFs 在词性标记、命名实体识别等方面的应用。
sklearn-crfsuite
我们将在数据集上使用 sklearn-crfsuite 训练一个用于命名实体识别的 CRF 模型。
import sklearn_crfsuite
from sklearn_crfsuite import scorers
from sklearn_crfsuite import metrics
from collections import Counter
下面的代码将检索带有词性和标签的句子。感谢托拜厄斯的提示。
class SentenceGetter(object):
def __init__(self, data):
self.n_sent = 1
self.data = data
self.empty = False
agg_func = lambda s: [(w, p, t) for w, p, t in zip(s['Word'].values.tolist(),
s['POS'].values.tolist(),
s['Tag'].values.tolist())]
self.grouped = self.data.groupby('Sentence #').apply(agg_func)
self.sentences = [s for s in self.grouped]
def get_next(self):
try:
s = self.grouped['Sentence: {}'.format(self.n_sent)]
self.n_sent += 1
return s
except:
return Nonegetter = SentenceGetter(df)
sentences = getter.sentences
特征提取
接下来,我们提取更多的特征(单词部分、简化的 POS 标签、下方/标题/上方标志、附近单词的特征),并将其转换为**sklearn-crfsuite**
格式——每个句子都应转换为一个字典列表。以下代码摘自 sklearn-crfsuites 官方网站。
def word2features(sent, i):
word = sent[i][0]
postag = sent[i][1]
features = {
'bias': 1.0,
'word.lower()': word.lower(),
'word[-3:]': word[-3:],
'word[-2:]': word[-2:],
'word.isupper()': word.isupper(),
'word.istitle()': word.istitle(),
'word.isdigit()': word.isdigit(),
'postag': postag,
'postag[:2]': postag[:2],
}
if i > 0:
word1 = sent[i-1][0]
postag1 = sent[i-1][1]
features.update({
'-1:word.lower()': word1.lower(),
'-1:word.istitle()': word1.istitle(),
'-1:word.isupper()': word1.isupper(),
'-1:postag': postag1,
'-1:postag[:2]': postag1[:2],
})
else:
features['BOS'] = True
if i < len(sent)-1:
word1 = sent[i+1][0]
postag1 = sent[i+1][1]
features.update({
'+1:word.lower()': word1.lower(),
'+1:word.istitle()': word1.istitle(),
'+1:word.isupper()': word1.isupper(),
'+1:postag': postag1,
'+1:postag[:2]': postag1[:2],
})
else:
features['EOS'] = Truereturn featuresdef sent2features(sent):
return [word2features(sent, i) for i in range(len(sent))]def sent2labels(sent):
return [label for token, postag, label in sent]def sent2tokens(sent):
return [token for token, postag, label in sent]
拆分训练和测试集
X = [sent2features(s) for s in sentences]
y = [sent2labels(s) for s in sentences]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=0)
训练一个 CRF 模型
crf = sklearn_crfsuite.CRF(
algorithm='lbfgs',
c1=0.1,
c2=0.1,
max_iterations=100,
all_possible_transitions=True
)
crf.fit(X_train, y_train)
Figure 14
评估
y_pred = crf.predict(X_test)
print(metrics.flat_classification_report(y_test, y_pred, labels = new_classes))
Figure 15
好多了。我们会坚持 sklearn-crfsuite,探索更多!
我们的分类器学到了什么?
def print_transitions(trans_features):
for (label_from, label_to), weight in trans_features:
print("%-6s -> %-7s %0.6f" % (label_from, label_to, weight))print("Top likely transitions:")
print_transitions(Counter(crf.transition_features_).most_common(20))print("\nTop unlikely transitions:")
print_transitions(Counter(crf.transition_features_).most_common()[-20:])
Figure 16
解释:地理实体(B-geo)的开头很可能会跟随着地理实体(I-geo)内部的令牌,但是从带有其他标签的令牌过渡到组织名称(I-org)内部会受到巨大的惩罚。
检查状态特性
def print_state_features(state_features):
for (attr, label), weight in state_features:
print("%0.6f %-8s %s" % (weight, label, attr))print("Top positive:")
print_state_features(Counter(crf.state_features_).most_common(30))print("\nTop negative:")
print_state_features(Counter(crf.state_features_).most_common()[-30:])
****
Figure 17
观察结果:
1).该模型了解到,如果附近的单词是“day ”,那么该令牌很可能是时间指示符的一部分。
2).**3.370614 B-per word.lower():president**
模型了解到令牌“president”可能在人名的开头。
3).**-3.521244 O postag:NNP**
该模型了解到专有名词通常是实体。
4).**-3.087828 O word.isdigit()**
数字可能是实体。
5).**-3.233526 O word.istitle()**
带标题的单词可能是实体。
ELI5
ELI5 是一个 Python 包,允许检查 sklearn_crfsuite 的重量。CRF 模型。
检查模型重量
import eli5
eli5.show_weights(crf, top=10)
****
Figure 18
观察结果:
- I-entity 必须跟随 B-entity 确实有道理,比如 I-geo 跟随 B-geo,I-org 跟随 B-org,I-per 跟随 B-per,等等。
- 我们还可以看到,在这个数据集中,一个组织名称后面紧跟着一个人的情况并不常见(B-org -> I-per 具有很大的负权重)。
- 该模型学习了像 O -> I-geo、O -> I-org 和 O -> I-tim 等不可能转换的大的负权重。
为了便于阅读,我们只能检查标签的子集。
eli5.show_weights(crf, top=10, targets=['O', 'B-org', 'I-per'])
Figure 19
或者只检查所有标签的一些特征。
eli5.show_weights(crf, top=10, feature_re='^word\.is',
horizontal_layout=False, show=['targets'])
Figure 20
暂时就这样了。我喜欢在 sklearn-crfsuite 和 ELI5 上动手动脚,希望你也是。源代码可以在 Github 找到。祝你一周愉快!
参考资料:
命名实体识别:应用和用例
命名实体识别是一个过程,其中算法将一串文本(句子或段落)作为输入,并识别该串中提到的相关名词(人、地点和组织)。在我们之前的博客中,我们给了你一瞥我们的命名实体识别 API 是如何工作的。在这篇文章中,我们列出了命名实体识别技术的一些场景和用例。
命名实体识别的用例
为新闻提供商分类内容
新闻和出版社每天都会生成大量在线内容,正确管理这些内容对于充分利用每篇文章非常重要。命名实体识别可以自动扫描整篇文章,并揭示其中讨论的主要人物、组织和地点。了解每篇文章的相关标签有助于在定义的层次结构中自动对文章进行分类,并实现顺畅的内容发现。下面的例子说明了这种工作方式。
命名实体识别 API 已经成功地识别了文章的所有相关标签,并且这可以用于分类。
高效搜索算法
假设您正在为一家拥有数百万篇文章的在线出版商设计一个内部搜索算法。如果对于每一个搜索查询,算法最终都要搜索数百万篇文章中的所有单词,那么这个过程将会花费很多时间。相反,如果命名实体识别可以在所有文章上运行一次,并且与这些文章中的每一个相关联的相关实体(标签)被分开存储,这可以大大加快搜索过程。使用这种方法,搜索词将只与每篇文章中讨论的少量实体匹配,从而加快搜索执行速度。
支持内容推荐
命名实体识别的一个主要用例涉及推荐过程的自动化。推荐系统主导着我们在当今世界发现新内容和新想法的方式。网飞的例子表明,开发一个有效的推荐系统可以为媒体公司创造奇迹,让他们的平台更有吸引力,更容易吸引人。对于新闻出版商来说,使用命名实体识别来推荐类似的文章是一种行之有效的方法。BBC 新闻下面的例子展示了类似文章的推荐在现实生活中是如何实现的。这可以通过从特定的文章中提取实体并推荐其他文章来完成,这些文章中提到了最相似的实体。这是一种方法,我们已经有效地用于为媒体行业客户开发内容推荐。
客户支持
有许多方法可以使客户反馈处理过程顺利进行,命名实体识别可能是其中之一。我们举个例子来了解一下过程。如果您正在处理一家在全球有多个分支机构的电子商店的客户支持部门,您会在客户反馈中看到许多提及。比如像这样,
现在,如果您通过命名实体识别 API 传递它,它会提取出实体 Bandra(位置)和 Fitbit(产品)。这可以用来对投诉进行分类,并将其分配给组织内应该处理该投诉的相关部门。
同样,也可能有其他反馈推文,你可以根据它们的位置和提到的产品对它们进行分类。您可以创建一个按不同部门分类的反馈数据库,并运行分析来评估每个部门的能力。
研究论文
在线期刊或出版网站拥有数百万篇研究论文和学术文章。一个主题可能有数百篇论文,只需稍加修改。以结构良好的方式组织所有这些数据会变得非常复杂。在网上“浏览”那么多数据,寻找一个特定的信息可能不是最好的选择。根据相关实体对论文进行分类可以省去浏览大量主题信息的麻烦。例如,可能有大约 20 万篇关于机器学习的论文。如果你根据提取的实体给它们加上标签,你会很快找到讨论使用卷积神经网络进行人脸检测的文章。
非结构化的文本内容富含信息,但是找到相关的内容总是一项具有挑战性的任务。随着来自社交媒体、电子邮件、博客、新闻和学术文章的大量数据,从这些信息中提取、分类和学习变得越来越困难,也越来越重要。对于流程发现,可能有其他 NLP 技术,但是当您希望您的分类数据结构良好时,命名实体识别 API 是您的最佳选择。尝试我们的命名实体识别 API 并亲自检查。如果您对命名实体识别的用例有其他想法,请在下面的评论部分分享。你也可以注册获得一个免费的 API 密匙。
parallel dots AI API,是由 ParallelDots Inc 提供的深度学习支持的 web 服务,可以理解大量的非结构化文本和视觉内容,为您的产品提供支持。您可以查看我们的一些文本分析&视觉智能API并通过填写此处的表格或在 apis@paralleldots.com 给我们写信来联系我们。
基于 keras 和 tensorflow 的命名实体识别(NER)
通过应用最先进的深度学习方法满足行业需求
photo credit: pexels
几年前,当我在一家初创公司做软件工程实习生时,我在一个招聘网站上看到了一个新功能。该应用程序能够识别和解析简历中的重要信息,如电子邮件地址、电话号码、学位头衔等。我开始和我们的团队讨论可能的方法,我们决定用 python 建立一个基于规则的解析器来解析简历的不同部分。在花了一些时间开发解析器之后,我们意识到答案可能不是基于规则的工具。我们开始在谷歌上搜索这是如何做到的,我们遇到了术语自然语言处理(NLP) 以及更具体的与机器学习相关的命名实体识别(NER)** 。**
photo credit: meenavyas
NER 是一种信息提取技术,用于识别和分类文本中的命名实体。这些实体可以是预定义的和通用的,如地点名称、组织、时间等,也可以是非常具体的,如简历中的例子。 NER 在业务中有各种各样的使用案例。我认为 gmail 应用了 NER,当你写邮件时,如果你在邮件中提到时间或附上文件,gmail 会设置日历通知或提醒你附上文件,以防你发送没有附件的邮件。NER 的其他应用包括:从法律、金融和医疗文档中提取重要的命名实体,为新闻提供者分类内容,改进搜索算法等等。在本文的其余部分,我们将简短介绍解决 NER 问题的不同方法,然后我们将开始编写最先进的方法。下面是苏沃洛对 NER 更详细的介绍。
photo credit: pexels
接近 NER
- 经典方法:大多基于规则。这里有一个链接,链接到 Sentdex 的一个很短很棒的视频,它在 python 中为 NER 使用了 NLTK 包。
- 机器学习方法:这一类主要有两种方法: A- 把问题当做多类分类,其中命名实体就是我们的标签,这样我们就可以应用不同的分类算法。这里的问题是识别和标注命名实体需要彻底理解句子的上下文和其中单词标签的序列,而这种方法忽略了这一点。 B- 这一类的另一种方法是条件随机场(CRF)模型。它是一个概率图形模型,可用于模拟顺序数据,如句子中单词的标签。关于 CRF 在 python 中的更多细节和完整实现,请参见 Tobias 的 文章。CRF 模型能够捕获序列中当前和先前标签的特征,但是它不能理解前向标签的上下文;这个缺点加上与训练 CRF 模型相关的额外的特征工程,使得它不太适合于工业应用。
photo credit: abajournal
- 深度学习方法:
在讨论 NER 的深度学习方法(最先进的)的细节之前,我们需要分析适当和清晰的指标来评估我们模型的性能。在不同迭代(时期)中训练神经网络时,通常使用准确度作为评估度量。但是,在 NER 案例中,我们可能会处理重要的金融、医疗或法律文档,准确识别这些文档中的命名实体决定了模型的成功与否。换句话说,**在 NER 任务中,误报和漏报都有商业成本。**因此,我们评估模型的主要指标将是 F1 分数,因为我们需要在精确度和召回率之间取得平衡。
建立高性能深度学习方法的另一个重要策略是,考虑到文本是一种序列数据格式,了解哪种类型的神经网络最适合处理 NER 问题。是的,你猜对了…长短期记忆(LSTM)。此链接中关于 LSTMs 的更多详情。但不是任何类型的 LSTM,我们需要使用双向 LSTM,因为使用标准 LSTM 进行预测只会考虑文本序列中的“过去”信息。对 NER 来说,由于上下文在序列中涵盖了过去和未来的标签,我们需要将过去和未来的信息都考虑在内。双向 LSTM 是两个 lstm 的组合——一个从“右到左”向前运行,一个从“左到右”向后运行。
我们将通过参考实际的研究论文来快速浏览四种不同的最新方法的架构,然后我们将继续实施精度最高的方法。
更多细节和 keras 中的实现。
from the paper(Bidirectional LSTM-CRF Models for Sequence Tagging)
更多细节和 keras 中的实现。
from the paper( Named Entity Recognition with Bidirectional LSTM-CNNs)
from the paper (End-to-end Sequence Labeling via Bi-directional LSTM-CNNs-CRF)
4.ELMo(从语言模型嵌入):
最近的一篇论文(深度语境化单词表示)介绍了一种新类型的深度语境化单词表示,它模拟了单词使用的复杂特征(例如,句法和语义),以及这些使用如何在语言语境中变化(例如,模拟一词多义)。新方法(ELMo)有三个重要的代表:
1.上下文:每个单词的表示取决于使用它的整个上下文。
2。深度:单词表示结合了深度预训练神经网络的所有层。
3。基于字符的 : ELMo 表示完全基于字符,允许网络使用形态学线索为训练中看不到的词汇外标记形成健壮的表示。
ELMo 对语言有很好的理解,因为它是在大规模数据集上训练的,ELMo 嵌入是在 10 亿词基准上训练的。这种训练被称为双向语言模型(biLM),它可以从过去学习,并预测像句子一样的单词序列中的下一个单词。让我们看看如何实现这种方法。我们将使用来自 kaggle 的数据集。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")data = pd.read_csv("ner_dataset.csv", encoding="latin1")
data = data.drop(['POS'], axis =1)
data = data.fillna(method="ffill")
data.tail(12)words = set(list(data['Word'].values))
words.add('PADword')
n_words = len(words)
n_words**35179**tags = list(set(data["Tag"].values))
n_tags = len(tags)
n_tags**17**
我们的数据集中有 47958 个句子,35179 个不同的单词和 17 个不同的命名实体(标签)。
让我们来看看句子长度在数据集中的分布:
class SentenceGetter(object):
def __init__(self, data):
self.n_sent = 1
self.data = data
self.empty = False
agg_func = lambda s: [(w, t) for w, t in zip(s["Word"].values.tolist(),s["Tag"].values.tolist())]
self.grouped = self.data.groupby("Sentence #").apply(agg_func)
self.sentences = [s for s in self.grouped]
def get_next(self):
try:
s = self.grouped["Sentence: {}".format(self.n_sent)]
self.n_sent += 1
return s
except:
return None
这个类负责将每个带有命名实体(标签)的句子转换成一个元组列表[(单词,命名实体),…]
getter = SentenceGetter(data)
sent = getter.get_next()
print(sent)**[('Thousands', 'O'), ('of', 'O'), ('demonstrators', 'O'), ('have', 'O'), ('marched', 'O'), ('through', 'O'), ('London', 'B-geo'), ('to', 'O'), ('protest', 'O'), ('the', 'O'), ('war', 'O'), ('in', 'O'), ('Iraq', 'B-geo'), ('and', 'O'), ('demand', 'O'), ('the', 'O'), ('withdrawal', 'O'), ('of', 'O'), ('British', 'B-gpe'), ('troops', 'O'), ('from', 'O'), ('that', 'O'), ('country', 'O'), ('.', 'O')]**sentences = getter.sentences
print(len(sentences))**47959**largest_sen = max(len(sen) for sen in sentences)
print('biggest sentence has {} words'.format(largest_sen))**biggest sentence has 104 words**
所以最长的句子有 140 个单词,我们可以看到几乎所有的句子都少于 60 个单词。
这种方法最大的好处之一是我们不需要任何特征工程;我们所需要的是句子和它的标记词,剩下的工作由 ELMo embeddeds 继续进行。为了将我们的句子输入到 LSTM 网络中,它们都需要一样大。看分布图,我们可以把所有句子的长度设置为 50 并为空格添加一个通用词;这个过程叫做填充。(50 是个好数字的另一个原因是我的笔记本电脑无法处理更长的句子)。
max_len = 50
X = [[w[0]for w in s] for s in sentences]
new_X = []
for seq in X:
new_seq = []
for i in range(max_len):
try:
new_seq.append(seq[i])
except:
new_seq.append("PADword")
new_X.append(new_seq)
new_X[15]**['Israeli','officials','say','Prime','Minister','Ariel',
'Sharon', 'will','undergo','a', 'medical','procedure','Thursday',
'to','close','a','tiny','hole','in','his','heart','discovered',
'during','treatment', 'for','a', 'minor', 'stroke', 'suffered', 'last', 'month', '.', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword',
'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword',
'PADword', 'PADword']**
这同样适用于命名实体,但我们这次需要将标签映射到数字:
from keras.preprocessing.sequence import pad_sequencestags2index = {t:i for i,t in enumerate(tags)}
y = [[tags2index[w[1]] for w in s] for s in sentences]
y = pad_sequences(maxlen=max_len, sequences=y, padding="post", value=tags2index["O"])
y[15]**array([4, 7, 7, 0, 1, 1, 1, 7, 7, 7, 7, 7, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7,7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,7, 7, 7, 7, 7, 7])**
接下来,我们将数据分为训练集和测试集,然后导入 tensorflow Hub(一个用于发布、发现和消费机器学习模型的可重用部分的库)来加载 ELMo 嵌入功能和 keras,以开始构建我们的网络。
from sklearn.model_selection import train_test_split
import tensorflow as tf
import tensorflow_hub as hub
from keras import backend as KX_tr, X_te, y_tr, y_te = train_test_split(new_X, y, test_size=0.1, random_state=2018)sess = tf.Session()
K.set_session(sess)
elmo_model = hub.Module("[https://tfhub.dev/google/elmo/2](https://tfhub.dev/google/elmo/2)", trainable=True)
sess.run(tf.global_variables_initializer())
sess.run(tf.tables_initializer())
第一次在代码块上运行需要一些时间,因为 ELMo 差不多有 400 MB。接下来,我们使用一个函数将我们的句子转换为 ELMo 嵌入:
batch_size = 32
def ElmoEmbedding(x):
return elmo_model(inputs={"tokens": tf.squeeze(tf.cast(x, tf.string)),"sequence_len": tf.constant(batch_size*[max_len])
},
signature="tokens",
as_dict=True)["elmo"]
现在让我们建立我们的神经网络:
from keras.models import Model, Input
from keras.layers.merge import add
from keras.layers import LSTM, Embedding, Dense, TimeDistributed, Dropout, Bidirectional, Lambdainput_text = Input(shape=(max_len,), dtype=tf.string)
embedding = Lambda(ElmoEmbedding, output_shape=(max_len, 1024))(input_text)
x = Bidirectional(LSTM(units=512, return_sequences=True,
recurrent_dropout=0.2, dropout=0.2))(embedding)
x_rnn = Bidirectional(LSTM(units=512, return_sequences=True,
recurrent_dropout=0.2, dropout=0.2))(x)
x = add([x, x_rnn]) # residual connection to the first biLSTM
out = TimeDistributed(Dense(n_tags, activation="softmax"))(x)model = Model(input_text, out)
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
由于我们的批处理大小为 32,因此必须以 32 的倍数为单位向网络提供数据:
X_tr, X_val = X_tr[:1213*batch_size], X_tr[-135*batch_size:]
y_tr, y_val = y_tr[:1213*batch_size], y_tr[-135*batch_size:]
y_tr = y_tr.reshape(y_tr.shape[0], y_tr.shape[1], 1)
y_val = y_val.reshape(y_val.shape[0], y_val.shape[1], 1)history = model.fit(np.array(X_tr), y_tr, validation_data=(np.array(X_val), y_val),batch_size=batch_size, epochs=3, verbose=1)**Train on 38816 samples, validate on 4320 samples
Epoch 1/3
38816/38816 [==============================] - 834s 21ms/step - loss: 0.0625 - acc: 0.9818 - val_loss: 0.0449 - val_acc: 0.9861
Epoch 2/3
38816/38816 [==============================] - 833s 21ms/step - loss: 0.0405 - acc: 0.9869 - val_loss: 0.0417 - val_acc: 0.9868
Epoch 3/3
38816/38816 [==============================] - 831s 21ms/step - loss: 0.0336 - acc: 0.9886 - val_loss: 0.0406 - val_acc: 0.9873**
最初的目标是调整参数以实现更高的精度,但我的笔记本电脑无法处理超过 3 个时期和大于 32 的批量或增加测试规模。我在 Geforce GTX 1060 上运行 keras,花了将近 45 分钟来训练这 3 个纪元,如果你有更好的 GPU,可以通过改变一些参数来尝试一下。
0.9873 的验证准确性是一个很好的分数,但是我们没有兴趣用准确性指标来评估我们的模型。让我们看看如何获得精确度、召回率和 F1 分数:
from seqeval.metrics import precision_score, recall_score, f1_score, classification_reportX_te = X_te[:149*batch_size]
test_pred = model.predict(np.array(X_te), verbose=1)**4768/4768 [==============================] - 64s 13ms/step**idx2tag = {i: w for w, i in tags2index.items()}def pred2label(pred):
out = []
for pred_i in pred:
out_i = []
for p in pred_i:
p_i = np.argmax(p)
out_i.append(idx2tag[p_i].replace("PADword", "O"))
out.append(out_i)
return outdef test2label(pred):
out = []
for pred_i in pred:
out_i = []
for p in pred_i:
out_i.append(idx2tag[p].replace("PADword", "O"))
out.append(out_i)
return out
pred_labels = pred2label(test_pred)
test_labels = test2label(y_te[:149*32])print(classification_report(test_labels, pred_labels)) **precision recall f1-score support
org 0.69 0.66 0.68 2061
tim 0.88 0.84 0.86 2148
gpe 0.95 0.93 0.94 1591
per 0.75 0.80 0.77 1677
geo 0.85 0.89 0.87 3720
art 0.23 0.14 0.18 49
eve 0.33 0.33 0.33 33
nat 0.47 0.36 0.41 22
avg / total 0.82 0.82 0.82 11301**
0.82 的 F1 成绩是一个很突出的成绩。它击败了本节开始时提到的所有其他三种深度学习方法,并且它可以很容易地被业界采用。
最后,让我们看看我们的预测是什么样的:
i = 390
p = model.predict(np.array(X_te[i:i+batch_size]))[0]
p = np.argmax(p, axis=-1)
print("{:15} {:5}: ({})".format("Word", "Pred", "True"))
print("="*30)
for w, true, pred in zip(X_te[i], y_te[i], p):
if w != "__PAD__":
print("{:15}:{:5} ({})".format(w, tags[pred], tags[true]))**Word Pred : (True)
==============================
Citing :O (O)
a :O (O)
draft :O (O)
report :O (O)
from :O (O)
the :O (O)
U.S. :B-org (B-org)
Government :I-org (I-org)
Accountability :I-org (O)
office :O (O)
, :O (O)
The :B-org (B-org)
New :I-org (I-org)
York :I-org (I-org)
Times :I-org (I-org)
said :O (O)
Saturday :B-tim (B-tim)
the :O (O)
losses :O (O)
amount :O (O)
to :O (O)
between :O (O)
1,00,000 :O (O)
and :O (O)
3,00,000 :O (O)
barrels :O (O)
a :O (O)
day :O (O)
of :O (O)
Iraq :B-geo (B-geo)
's :O (O)
declared :O (O)
oil :O (O)
production :O (O)
over :O (O)
the :O (O)
past :B-tim (B-tim)
four :I-tim (I-tim)
years :O (O)
. :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)
PADword :O (O)**
像往常一样,代码和 jupyter 笔记本在我的 Github 上可用。
非常感谢您的提问和评论。
参考资料:
- https://www . depends-on-the-definition . com/named-entity-recognition-with-residual-lstm-and-elmo/
- http://www . wild ml . com/2016/08/rnns-in-tensor flow-a-practical-guide-and-documentated-features/
- https://allennlp.org/elmo
- https://jalammar.github.io/illustrated-bert/
使用 Python 中的 Enron 电子邮件数据集识别命名实体:第 2 部分
上一次,我们从安然电子邮件数据集中选出了前 20 个最多发邮件的人。
他们是谁?在这份 20 人名单中,至少有两人(约翰·阿诺德和埃里克巴斯)曾经或已经成为对冲基金经理。对于一家破产公司的校友来说,这并不坏,因为有一篇关于这家公司的文章。我个人也曾和安然的前雇员一起工作过,我只和他们有过顶级的经历。
别管历史,让我们进入数据科学。
分散文本
https://github.com/JasonKessler/scattertext
分散文本最初是通过将一个类别与另一个类别进行比较(如共和党人与民主党人),来找到“非常常见”的单词。相反,我在自己的案例中使用它来比较每个电子邮件发件人与所有其他发件人;一个对所有,而不是一个对一个。
This is from the github readme, that gives just one example of the visualization power of this tool.
为此,我选择不使用可视化工具,而只使用 Pythonic API,它公开了每个类别(在我们的例子中是电子邮件发件人)的 F 分数的计算。
F-scores for each email sender, sorted by top F-scores from “other” category
代码如下:
nlp = spacy.load('en')corpus = st.CorpusFromPandas(df_parsed.sample(10000),
category_col='FromCat',
text_col='Body',
nlp=nlp).build()scaled_f_scores_vs_background = corpus.get_scaled_f_scores_vs_background()term_freq_df = corpus.get_term_freq_df()for c in term_freq_df:
f = c[:c.find(' freq')]
term_freq_df[f+' fscore'] = corpus.get_scaled_f_scores(f)columns = pd.Series([x[:x.find(' ')] for x in term_freq_df.columns]).drop_duplicates()term_freq_df.columns = pd.MultiIndex.from_product([['freq','fscore'],columns])term_freq_df.loc[:,'fscore'].sort_values('other',ascending=False).iloc[:200].to_excel('fscores.xlsx')
嗯,你可以种开始看到人们之间的关系,通过他们的“不寻常的共同”的话,但它不是那么漂亮或有用。因此,我们将尝试通过从每封电子邮件中提取命名实体来总结电子邮件,然后使用网络图将顶级实体链接在一起。
命名实体识别
至少有三个流行的 python 库具有 NER 功能:1)spaCy2)polyglot和 3) Stanford 的 CoreNLP 。
我在这个项目中尝试使用 spaCy 和 polyglot,但最终选择了 polyglot,因为它的结果更直观。Polyglot 只将实体分为 I-LOC、I-ORG 和 I-PER,而 spaCy 有更多的类别,如数字、日期等。
该代码使用 dask 进行多重处理,在数据帧中添加了一个新的“实体”列。
最后,我们按照前 20 名电子邮件发件人(FromCat)进行分组,并找到他们最常见的实体。下面是一个输出示例:
网络图可视化
好吧。我们真的很亲密。网络图需要节点和边。我们有一个流行实体表,按前 20 名电子邮件发件人分组。因此,在我们的例子中,节点(或者图上的气泡)将是和实体以及电子邮件发送者。为了区分它们,我们将发件人涂成黄色。边将是从每个发送者到他们各自实体的连接。比如上面的克里斯德国,像安然、加州、杰夫、德州等词。会和他联系在一起。
Nodes.json 示例
[{"id":0,"label":"[chris.germany@enron.com](mailto:chris.germany@enron.com)","group":"person"},{"id":1,"label":"[debra.perlingiere@enron.com](mailto:debra.perlingiere@enron.com)","group":"person"},{"id":2,"label":"[drew.fossum@enron.com](mailto:drew.fossum@enron.com)","group":"person"},{"id":3,"label":"[enron.announcements@enron.com](mailto:enron.announcements@enron.com)","group":"person"},{"id":4,"label":"[eric.bass@enron.com](mailto:eric.bass@enron.com)","group":"person"},{"id":5,"label":"[gerald.nemec@enron.com](mailto:gerald.nemec@enron.com)","group":"person"},...]
Edges.json 示例
[{"from":0,"to":21},{"from":1,"to":21},{"from":2,"to":21},{"from":4,"to":21},{"from":5,"to":21},{"from":6,"to":21},{"from":7,"to":21},{"from":8,"to":21},{"from":9,"to":21},{"from":10,"to":21},{"from":11,"to":21},{"from":13,"to":21},{"from":15,"to":21},{"from":16,"to":21},{"from":17,"to":21},{"from":18,"to":21},{"from":19,"to":21},{"from":20,"to":21},...]
Visjs
对于网络图可视化,我们使用了构建在 d3 之上的名为 Visjs 的 javascript 库。它实际上有一个 python 包装器,但是我无法在笔记本中找到或让它工作,所以我只是处理了样本 javascript 代码,并嵌入了来自 pandas 节点和边的数据帧的 json 输出。
现在,如果我们打开静态 html 文件,将会出现如下所示的交互式可视化效果:
由于 Visjs 使用了 d3 物理学,最终发生的情况是最常见的单词(有许多联系的单词)聚集在中间,而“非常常见”的单词成为外围。
例如,Pete Davis 似乎是一名加州电力调度人员,他在电子邮件中使用特定于其调度工作的术语。
通过浏览实体网络,你可以真正开始注意到谁在做什么。马特·伦哈特负责佛罗里达州的业务,而约翰·阿诺德和萨拉·沙克尔顿则谈论涉及巴西的潜在交易。几乎每个人都在谈论 FERC(联邦能源监管委员会),它负责监管州际贸易。等等,等等…
我们可以利用安然电子邮件数据集做更多的事情,例如训练单词嵌入、对电子邮件进行分类、欺诈交易的异常检测等。然而,在这篇文章中,我想向你展示一个简单的实体网络图在总结一个大的文本语料库时是多么的有用。
你可以在这里找到这个项目的 Github 库:https://github.com/raymondmyu/enron-email-dataset
想看更多请留下评论鼓掌!
基于 NLTK 和 SpaCy 的命名实体识别
Photo credit: Pixabay
NER 在自然语言处理的许多领域都有应用
命名实体识别 (NER)可能是信息提取的第一步,它试图定位文本中的命名实体并将其分类成预定义的类别,如人名、组织、位置、时间表达式、数量、货币值、百分比等。NER 在自然语言处理 (NLP)的很多领域都有应用,它可以帮助回答很多现实世界的问题,比如:
- 新闻中提到了哪些公司?
- 投诉或评论中是否提到了特定产品?
- 推文包含人名吗?推文包含这个人的位置吗?
本文描述了如何使用 NLTK 和 SpaCy 构建命名实体识别器,以识别事物的名称,例如原始文本中的人员、组织或位置。我们开始吧!
NLTK
import nltk
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
信息提取
我引用了《纽约时报》的一句话:“欧洲当局周三对谷歌处以创纪录的 51 亿美元罚款,原因是谷歌在手机市场滥用权力,并责令该公司改变做法。”
ex = 'European authorities fined Google a record $5.1 billion on Wednesday for abusing its power in the mobile phone market and ordered the company to alter its practices'
然后,我们对句子应用单词标记化和词性标注。
def preprocess(sent):
sent = nltk.word_tokenize(sent)
sent = nltk.pos_tag(sent)
return sent
让我们看看我们得到了什么:
sent = preprocess(ex)
sent
Figure 1
我们得到一个元组列表,其中包含句子中的单个单词及其相关的词性。
现在,我们将实现名词短语分块,以使用由规则组成的正则表达式来识别命名实体,这些规则指示句子应该如何分块。
我们的组块模式由一个规则组成,即每当组块者找到可选的限定词 DT,后跟任意数量的形容词 JJ,然后是名词 NN 时,就应该形成名词短语 NP。
pattern = 'NP: {<DT>?<JJ>*<NN>}'
组块
使用这个模式,我们创建了一个组块解析器,并在我们的句子上测试它。
cp = nltk.RegexpParser(pattern)
cs = cp.parse(sent)
print(cs)
Figure 2
输出可以读作树或层次结构,S 是第一级,表示句子。我们也可以用图形显示出来。
Figure 3
IOB 标签已经成为在文件中表示块结构的标准方式,我们也将使用这种格式。
from nltk.chunk import conlltags2tree, tree2conlltags
from pprint import pprintiob_tagged = tree2conlltags(cs)
pprint(iob_tagged)
Figure 4
在这个表示中,每行有一个标记,每个标记都有其词性标记和命名实体标记。基于这个训练语料,我们可以构建一个可以用来标注新句子的 tagger 并使用 nltk.chunk.conlltags2tree()函数将标记序列转换成块树。
通过函数 nltk.ne_chunk(),我们可以使用分类器识别命名实体,分类器添加了类别标签,如个人、组织和 GPE。
ne_tree = ne_chunk(pos_tag(word_tokenize(ex)))
print(ne_tree)
Figure 5
谷歌是公认的人。挺让人失望的,你不觉得吗?
空间
SpaCy 的命名实体识别已经在 OntoNotes 5 语料库上进行训练,它支持以下实体类型:
Figure 6 (Source: SpaCy)
实体
import spacy
from spacy import displacy
from collections import Counter
import en_core_web_sm
nlp = en_core_web_sm.load()
我们用的是同一句话,“欧洲当局周三对谷歌处以创纪录的 51 亿美元罚款,原因是其在手机市场滥用权力,并责令该公司改变做法。”
Spacy 的一个好处是我们只需要应用 nlp 一次,整个后台管道将返回对象。
doc = nlp('European authorities fined Google a record $5.1 billion on Wednesday for abusing its power in the mobile phone market and ordered the company to alter its practices')
pprint([(X.text, X.label_) for X in doc.ents])
Figure 7
European 是 NORD(国籍或宗教或政治团体),Google 是一个组织,51 亿美元是货币值,星期三是日期对象。都是对的。
代币
在上面的例子中,我们在实体级别上工作,在下面的例子中,我们演示了使用罗比标记方案来描述实体边界的令牌级实体注释。
Figure 8 (Source: SpaCy)
pprint([(X, X.ent_iob_, X.ent_type_) for X in doc])
Figure 9
"B"
表示令牌开始于实体,"I"
表示令牌在实体内部,"O"
表示令牌在实体外部,""
表示没有设置实体标签。
从文章中提取命名实体
现在,让我们认真对待空间问题,从《纽约时报》的一篇文章中提取命名实体——“在短信中批评特朗普的联邦调查局特工彼得·斯特佐克被解雇了”
from bs4 import BeautifulSoup
import requests
import redef url_to_string(url):
res = requests.get(url)
html = res.text
soup = BeautifulSoup(html, 'html5lib')
for script in soup(["script", "style", 'aside']):
script.extract()
return " ".join(re.split(r'[\n\t]+', soup.get_text()))ny_bb = url_to_string('https://www.nytimes.com/2018/08/13/us/politics/peter-strzok-fired-fbi.html?hp&action=click&pgtype=Homepage&clickSource=story-heading&module=first-column-region®ion=top-news&WT.nav=top-news')
article = nlp(ny_bb)
len(article.ents)
188
文章中有 188 个实体,它们被表示为 10 个独特的标签:
labels = [x.label_ for x in article.ents]
Counter(labels)
Figure 10
以下是三个最常见的令牌。
items = [x.text for x in article.ents]
Counter(items).most_common(3)
Figure 11
让我们随机选择一句话来了解更多。
sentences = [x for x in article.sents]
print(sentences[20])
Figure 12
让我们运行[displacy.render](https://spacy.io/api/top-level#displacy.render)
来生成原始标记。
displacy.render(nlp(str(sentences[20])), jupyter=True, style='ent')
Figure 13
一个错误的分类是联邦调查局。这很难,不是吗?
使用 spaCy 的内置 displaCy visualizer ,上面的句子及其依赖项看起来是这样的:
displacy.render(nlp(str(sentences[20])), style='dep', jupyter = True, options = {'distance': 120})
Figure 14
接下来我们逐字逐句,提取词性,把这个句子词条化。
[(x.orth_,x.pos_, x.lemma_) for x in [y
for y
in nlp(str(sentences[20]))
if not y.is_stop and y.pos_ != 'PUNCT']]
Figure 15
dict([(str(x), x.label_) for x in nlp(str(sentences[20])).ents])
Figure 16
命名实体提取是正确的,除了“F.B.I”。
print([(x, x.ent_iob_, x.ent_type_) for x in sentences[20]])
Figure 17
最后,我们将整篇文章的实体形象化。
Figure 18
你自己试试。很有趣!源代码可以在 Github 上找到。星期五快乐!
拿破仑是有史以来最好的将军,数学证明了这一点。
对战争史上的每一位将军进行排名
*几乎每
当 Africanus 问汉尼拔认为谁是最伟大的将军时,汉尼拔说出了Alexander……至于谁会排在第二位,汉尼拔选择了皮拉斯……问汉尼拔认为谁是第三位,他毫不犹豫地说出了自己的名字。接着,西庇阿大笑起来,说道:“如果你打败了我,你会说什么?”
就像汉尼拔一样,我想在战争史上给强有力的领导人排名。与汉尼拔不同,我试图用数据来确定一个将军的能力,而不是对将军成就的具体描述。其结果是军事史上每一位杰出指挥官的排名系统。
该方法
受棒球赛计量法的启发,我选择使用胜于败的系统。战争经常被用来评估一个棒球运动员对他的球队的贡献。它计算玩家相对于替补玩家增加(或减少)的总胜率。例如,与高水平小联盟球员的平均贡献相比,一个打了 5 场仗的棒球球员为他的球队贡献了 5 场额外的胜利。WAR 远非完美,但提供了一种基于一项统计数据来比较玩家的方法。
我用战争来评估一个特定的军事战术家的贡献是高于还是低于一个普通的将军。我的模型,我在下面解释,提供了一个在任何给定情况下的一般一般性能的估计。然后,我可以根据他们在相同情况下超过或低于替代将军的程度来评估将军的素质(假设替代将军的表现处于平均水平)。换句话说,我会在战争中找到将军们的战争。
Interactive visualization showing the 6619 ranked generals’ WARs
点击此处查看互动可视化!
数据
我的第一个挑战是构建一个可靠的数据集。由于我无法找到一个全面的历史战斗数据集,我决定建立自己的数据集。我使用维基百科的战斗列表作为起点。虽然不全面,维基百科的列表包括 3580 场独特的战斗和 6619 名将军,这提供了足够的样本来创建一个模型。然后,我开发了一个函数,可以收集每场战斗的关键信息,包括参与战斗的所有指挥官、这些指挥官可用的总兵力以及战斗的结果。由此产生的数据集提供了一个大的战斗样本,以创建一个基线(替换级别)性能,我将根据它来比较个别将军的性能。
上的战斗数据样本 上的维基上的上的上的上的
战场数据样本被刮取并处理成数据帧
然后我从战斗样本中构建了一个线性模型。对于每场战斗,我将战斗人员的部队分为步兵、骑兵、炮兵、空军和海军。然后,我可以衡量一个将军与他们的对手相比在数量上的优势或劣势,并更好地隔离将军作为战术家的能力。最终的模型在权重上惊人地保守,这表明与地形或技术等其他因素相比,原始士兵数量的影响相对较小,进一步的研究可以更详细地调查这些因素。然而,在这个项目中,与其他因素相比,结果潜在地夸大了指挥官战术敏锐度的重要性。
我准备给每一位将军排名并研究结果。我这样做是通过隔离每个将军的战斗,并为他们在每场战斗中的表现分配一个战争分数。例如,法国皇帝拿破仑因在波罗底诺战役中获胜而赢得了 49 年战争。由于法国军队略多于俄罗斯帝国的军队,该模型给出了拿破仑位置上的替代将军 51%的胜算。战争系统分配拿破仑 1 胜作为他的胜利,但是减去一个替代将军无论如何都会赢的机会。因此,拿破仑获得 0.49 胜以上的替换。
该系统使用类似的方法来处理失败。例如,俄国将军米哈伊尔·库图佐夫,拿破仑在波罗底诺战役中的对手之一,被认为是这场对抗导致了. 49 战争。通过遭受失败,他取得了-1 的胜利,但是有 51%的几率一个替代将军会失败。
结果呢
在所有将领中,拿破仑的战力最高(16.679)遥遥领先。事实上,排名第二的凯撒大帝(7.445 年战争),他的战争时间还不到拿破仑历次战争的一半。拿破仑从他领导的大量战斗中受益匪浅。在他参加的 43 场战斗中,他赢了 38 场,只输了 5 场。拿破仑在 17 次胜利中克服了困难,在所有 5 次失败中处于劣势。在所有战役中,没有其他将军能与拿破仑相提并论。拿破仑在 43 场战役中指挥军队,第二多产的将军是罗伯特·李,27 场战役(平均 1.5 场)。拿破仑的大量战斗让他有更多的机会展示他的战术能力。亚历山大大帝,尽管赢得了全部 9 场战斗,但积累的战争较少,主要是因为他的职业生涯较短且较少产。
Napoleon’s height was 5’7", slightly taller than average for his time.
然而,除了拿破仑外围的成功,将军们的战争很大程度上遵循正态分布。这表明他的成功归因于指挥才能,而不是模型发现的异常。事实上,拿破仑的总战争比数据集中将军们累积的平均战争高出近 23 个标准差。
Napoleon is a huge outlier with nearly 17 WAR
也有一些将军尽管被誉为战术大师,但总体战数却出奇的低。罗伯特·e·李,邦联军队的指挥官,以一场消极的战争结束(-1.89),暗示一个普通的将军会比李领导的邦联军队更成功。李将军背负着相当大的劣势,包括军队规模和可用资源的巨大赤字。尽管如此,他作为一个老练的战术家的声誉可能是不应该的,他的战争支持了那些批评他的总体战略和关键战役处理的历史学家,例如在葛底斯堡战役的最后一天下令灾难性的’皮克特冲锋。用南卡罗来纳大学教授托马斯·康纳利的话来说,“人们会想,如果没有罗伯特·李,南方是否会过得更好。”
德国陆军元帅埃尔温·隆美尔(Erwin Rommel)因二战期间在北非的成功而被昵称为“沙漠之狐”,他在这一模式中也表现不佳,最终以-1.953 战。这一发现质疑了隆美尔作为战术家从现代将军那里得到的赞誉,包括诺曼·施华蔻和阿里尔·沙龙。然而,和李将军一样,隆美尔也是历史争论的焦点。尤其是,批评家们将他作为战术天才的声誉归功于德国和盟国的宣传。据报道,英国将军夸大了隆美尔的战术能力,以尽量减少对他们失败的不满。
现代将领在模型中表现相对较差。美国将军乔治·S·巴顿被历史学家特里·布莱顿描述为“二战中最伟大的将军之一”,他只打了 0.9 场战争。现代将军在战争中表现不佳,可能是由于战争的变化使个别将军无法参加大量的战斗。
Moshe Dayan boosted his WAR by winning the Six-Day War, despite a numerical disadvantage
在二战后的将军中,以色列指挥官脱颖而出。以色列军事领导人摩西·达扬(Moshe Dayan)以 2.109 战结束(第 60 名),对于一个现代将军来说这是一个令人印象深刻的数字,但与 20 世纪前的战术家相比相对温和。类似地,以色列前总理阿里埃勒·沙龙因在苏伊士危机、六日战争和赎罪日战争中的战场胜利而累积了 2.171 场战争(总排名第 58 位)。
最后,我把汉尼拔对有史以来最高将领的评估与我的模型进行了对比。根据 WAR,汉尼拔低估了自己的能力——在迄今为止的所有将军中,汉尼拔的 WAR 最高,为 5.519(总排名第六)。亚历山大大帝,汉尼拔任命为最高将领,以 4.391 战(总排名第 10)仅次于汉尼拔的标记。然而,亚历山大只打了 9 场仗就死了,而且全部都赢了。汉尼拔有 17 场战斗来积累价值,赢了 13 场,输了 2 场,平了 2 场。因此,我同意汉尼拔的评估,即亚历山大是更有能力的战术家,尽管汉尼拔提供了更多的总价值——亚历山大展示了他赢得战斗的能力,如果他没有死于疾病,很可能会继续获胜。
Depiction of the Battle of Cannae, a decisive victory for Hannibal against Roman consuls Varro and Paulus
我的发现与汉尼拔对伊庇鲁斯的皮拉斯的评价截然不同,伊庇鲁斯是一位希腊将军,也是早期罗马的对手。我的模型认为皮拉斯只有 3 场战斗和-0.53 场战争。尽管汉尼拔将创新的军事战术归功于皮拉斯,但我对他的整体战术敏锐度深表怀疑,甚至在考虑到他在胜利期间无法防止军队灾难性伤亡之前。
这个项目和由此产生的可视化有望提供一种有趣的方式来探索和比较将军们的相对成功。WAR 为经验性地比较将军们提供了一个有用的范例,尽管未来的研究可以通过扩大数据集或考虑其他因素(如对手的实力)来改进这一模型。请玩玩可视化,如果你想找一个特定的将军,只需输入网址’https://ethanarsht.github.io/military_rankings/* * 。其中**是将军的名字,和维基百科上显示的完全一样。
2011 年 12 月 11 日更新:基于许多我非常尊重其工作的人的反馈,我想对上述分析明确提出一些警告。首先,这篇文章旨在作为一个有趣的思想实验,而不是一个权威的排名,或对军事历史领域的学术贡献。我相信这个项目的一些结果,特别是李和隆美尔,为更广泛地讨论他们的战术能力提供了有趣的数据点。我绝不声称我的分析提供了全貌,或任何接近全貌的东西。
此外,由于我非常依赖维基百科获取数据和数据分类,我的输入中存在漏洞和不一致。鉴于我个人资源的缺乏,对我来说,在检查每一个数据点的准确性的同时进行如此规模的项目是不现实的。
最后,我必须重申,我的排名是一个将军的战术附加值,而不是他们的整体战略能力,或者谁会在一场假设的兵力和装备均等的对决中获胜。
同样,我相信绝大多数读者都按照我的意图解读了这篇文章——一个有着有趣结果和娱乐性互动的思想实验。感谢所有阅读这篇文章和/或提供反馈的人。
更新 12/8:根据大众的需求,可视化现在包括平均每场战斗增加的战争。只需将鼠标悬停在一个将军的点上,弹出窗口中就会出现“每场战争”。
此外,有些人在浏览与项目相关的 GitHub 库时遇到了麻烦。我在这个 Google Drive 中放了两个重要的电子表格:一个包含所有的部队人数数据,另一个包含每场战斗的战争结果。
更新 12/6:我想对过去几天收到的一些合理且持续的建设性批评做出回应。
- 缺少数据!许多人准确地指出了数据中缺失的战斗/将军,特别是关于蒙古人,包括成吉思汗和苏布泰。这是一个主要问题,源于我对维基百科战斗列表的依赖。这是我应该早点发现的,我计划更新数据集以包含更多的战斗。然而,处理这些数据需要大量的手动数据输入/清理,并且在向数据集添加重大更新之前将花费我相当多的时间。
- 战略与战术:人们认为将军被低估/高估是因为他们战役的最终结果。我特别关注将军的战术敏锐性,而不是他们的战略决策。因此,拿破仑不应因其灾难性的俄罗斯战役而失去信誉,乔治·华盛顿也不应因其对美国独立战争的战略方针而获得信誉。
- 增加了胜率高于替代胜率与胜率高于平均胜率与胜率的概率:那些熟悉棒球赛的人很快指出,我的模型并没有在所有方面反映胜率高于替代胜率的方法,因为棒球的战争使用一个普通的顶级小联盟球员作为基线。我只是将平均质量作为我的替换级别。可能不完全准确,但我认为我在上述方法中使用什么作为基线是非常清楚的。
感谢你们当中提出建设性批评的人。
请不要犹豫,在 twitter (@ethanarsht)上提供反馈。此处提供的代码和数据:【https://github.com/ethanarsht/military_rankings】。很抱歉弄得一团糟。
内特·西尔弗诉纳西姆·尼古拉斯·塔勒布和艾萨克·费伯
Isaac Faber 最近在博客上写道,为什么你应该关心 Nate Silver 和 Nassim Taleb 的推特之战:两个数据专家怎么会有这么大的分歧?在阅读他的简介时,我强烈反对他关于争论的性质和原因的观点。作为一名曾在芝加哥商业交易所从事衍生品交易的交易员,我发现塔勒布的观点很准确,但并不令人信服,而西尔弗的回应则简单粗暴,而且跑题了。我想是塔勒布先开始的,但他是出了名的尖酸刻薄,整个对话都是在 twitter 上进行的,所以我希望西尔弗能站在高处,像一个以严谨著称的统计学家那样,解决一些相关的数学问题。
塔勒布认为,对选举的准确预测,无论是概率预测还是非概率预测,都不会随着时间的推移而大幅修改,除非出现彻底的混乱(比如宣布内战)。西尔弗认为,对选举的准确预测包括选举过程的不确定性,选民的变化会准确地导致结果概率预测的大幅波动。
费伯的文章没有抓住塔勒布的重点,引发了西尔弗经常提到的担忧,好像这些担忧是对他不利的证据。这些担忧对对话的健康发展至关重要,我相信塔勒布为西尔弗提供了另一个解决问题的途径。但显然西尔弗和费伯都没有注意到这三个人都在同一边,所以开始了。第一,Faber 的优秀背景:【如果你熟悉背景和 Taleb 和 Silver,就跳到酒吧的最后。]
“张瀚银是fivethirtyeeight的联合创始人。一个非常受欢迎的专注于数据的博客,因其准确预测 2008 年美国大选的结果而闻名。Silver 使用一种聪明的民意调查汇总技术来生成预测,这种技术考虑到了偏见,例如民意调查者只给使用座机的人打电话。
作为一名训练有素的统计学家,通过经济学,他将自己对棒球(sabermetrics)和扑克分析的热情引入了政治领域。事实上,FiveThirtyEight 这个名字是对美国选举人票数(538 张)的认可。然而,博客也涵盖了其他兴趣领域,如体育。内特把他的博客卖给了 ESPN,并接受了主编的工作。他们(ESPN)利用它作为一个平台,向观众提供体育赛事的预测,FiveThirtyEight 后来搬到了美国广播公司。对他们网站的例行访问是政治和体育文章的混合,带有详细的预测和数据可视化。
Silver (left) in conversation with NY1’s Pat Kiernan
内特的预测能力已经成为大众媒体公认的标准。他是许多全国电视节目的常客,在每个全国选举周期讨论他的预测。因此,当畅销书作家、量化风险专家纳西姆·塔勒布(Nassim Taleb)公开宣布 FiveThirtyEight 不知道如何正确预测选举时,人们感到非常震惊!
对塔勒布来说,由于他对现实世界中概率的敏锐理解,他已经变得非常成功。他的书既有哲学的,也有技术的,重点是不确定性和风险。具体来说,他认为实践中使用的绝大多数定量模型都没有充分考虑到现实世界的风险。相反,它们给人一种短期价值的错觉(比如在一些很好理解的情况下是准确的),但当不知情的用户经历模型设计不理解的情况时,他们会面临巨大的系统风险。
Nassim Taleb
塔勒布之所以出名,部分是因为他通过暴露自己的财富将自己的哲学付诸实践。马尔科姆·格拉德威尔在《纽约客》上写了一篇文章,讲述了塔勒布是如何将他的风险哲学转变为一种极其成功的投资策略的。从那以后,他在不可预见的市场事件中获得了巨额财富,如黑色星期一、俄罗斯债务违约和 2008 年金融危机。塔勒布现在花很多时间写作和硬拉(我很嫉妒这一点)。他不羞于公开告诉别人他不同意他们的观点:内特·西尔弗就是其中之一。
Taleb’s Tweets directed at Silver November 2018
然而,西尔弗并没有屈服于这些侮辱!
Silver 和 Taleb 分别拥有 300 万和 30 万粉丝,在这些交易所引起了巨大的轰动(从 2016 年开始)。然而,快速浏览评论线程,你会发现很少有人理解这些论点。甚至西尔弗自己似乎也被塔勒布的攻击弄得措手不及。
两人都是熟练的统计学家,西尔弗是更好的民意测验专家,而塔勒布是更好的数学家。塔勒布从未声称对民调了解多少,而西尔弗也没有数学博士学位。他们都在各自的领域享有专业的认可和成功,都是畅销书作家。他们都是贝叶斯统计的支持者,贝叶斯统计是统计学的一个实证主义子集,坚持通过合理的框架随着时间的推移纳入额外的信息。他们都喜欢使用蒙特卡罗方法,这是一种统计/算法方法,使用模型的重复随机抽样来获得数字结果。他们的共同点比争论点多得多。但是这个特殊的论点很重要。
他们的争论归结为一点:预测准确性的定义。准确性被定义为正确或精确的性质或状态,因此准确的预测是正确的、精确的和准确的(从精确的定义中得出)。一个数学家(或任何人,给定信息、工具、时间和倾向)可以说,“这里是候选人,这里是选举人团的选票,这里是投票区,如果一切都按特朗普的方式进行,这里会发生什么,如果一切都按希拉里的方式进行,这里会发生什么,如果 X 支持特朗普,Y 支持希拉里,这里会发生什么,”并继续对所有潜在的结果进行组合建模。这种组合建模是严格正确的,除非对所有可能的输入建模失败。
塔勒布认为对选举的准确预测不会随着时间的推移而大幅修正,除非出现完全的混乱(比如宣布内战)。 他有明确的标准来定义不可接受的大修改,以及在给定的选举中找到它的等式【ARXIV 文章,数学很重】。但他也坚持认为,预测的结果包括更高的不确定性,如果它存在的情况下。按照这些思路对 6 个月后的选举进行概率描述可能会是这样的:“希拉里有 75%的机会获胜,特朗普有 20%,还有 5%的可能性发生,不确定性给这些结果增加了大约 30%的灵活性。”固有的预测是‘希拉里会赢’。这种统计模型是整体的——它一次看到整个结果,概率完全是基于结果的。数值预测是如何得到的并不重要,一个超出修正标准的预测就是错误的。上述预测在 11 月份是错误的,尽管概率部分只有在结果不包含在组合可能性空间中时才是错误的。
Silver 在选举期间的帖子表明了他的想法,即在正常情况下,对选举的准确预测会随着时间的推移而被大规模修正(根据 Taleb 的定义)。他创建了每个附属选举的模型,使用民意调查的知情知识为每个附属选举的每个结果分配加权概率,并创建了所谓的选举蒙特卡洛模拟。在蒙特卡洛模拟中,统计学家创建他们的模型,然后通过数百或数千次模拟运行来运行该模型,并限制附属数字的随机性。这种受限的随机性导致了对所有可能结果的了解。蒙特卡罗方法可用于产生每个最终结论的净概率,如通过使用其加权随机因子多次运行该模型所证明的,但是辅助成分权重的小变化可导致大的修正。假设共和党的投票率增加 5%,但特别是那些在摇摆州的摇摆区的投票率,可以改变整个模拟结果,使其对特朗普更加有利。由于净概率是模型的组合潜力的叠加,任何包含在组合图中的结果都是可能的。
预测和概率是有区别的。概率是对某事可能发生的程度、某事发生的可能性或事实的估计。预测是对未来将会发生的特定事情或者某件事情的结果的陈述性估计。概率预测表明某一特定事件发生的概率是给定的。
西尔弗做概率预测。如果一个概率预测包含了已经发生的结果,它就没有错,就像一个组合分析不会因为一个结果在模型中比实际结果出现得更频繁而出错一样。说一件事情有 70%的可能性发生,并不意味着如果其他事情发生了,你就是错的,只要其他事情也包括在你的模型中。然而,如果你可以重复实验数百万次,并看到在绝大多数实际结果中有 80%或 50%的可能性发生,那就错了。预测只发生一次,所以实验不能重复。
Isaac Faber 的帖子描述了随机不确定性和认知不确定性的区别:
随机不确定性与基本系统有关(在标准骰子上掷出六点的概率)。认知不确定性与系统的不确定性有关(骰子有几个面?掷出 6 的概率是多少?).像 FiveThirtyEight 这样的定制模型只向公众报告偶然的不确定性,因为它涉及到它们的统计输出(在这种情况下由蒙特卡洛进行推断)。麻烦在于认知的不确定性很难(有时不可能)估计。
他接着断言“塔勒布竭力反对的”是
如果一个预测不符合一些基本特征,它就不应该作为一种可能性被推销。更重要的是,一个预测应该从发布给公众的时候就开始判断,而不仅仅是在事件发生之前。预测者应该对偶然性和认知性的不确定性负责。
费伯的苏格拉底式的塔勒布(我个人没有发现塔勒布在哪里提出这个字面上的例子)抱怨内特·西尔弗不可能是错的,因为他在报告概率,但他这样做的方式导致他们很容易被视为没有概率元素的预测,这是不负责任的。这可能是真的,但是断言在这个问题上有目的的不负责任的疏忽是没有根据的,因为内特·西尔弗和五个三十岁的男孩已经尽力向那些感兴趣的人解释他们的方法。西尔弗同意,将他的概率作为严格预测的二次报道是新闻和媒体中的一个常见问题。
艾萨克的观点有些道理。像内特·西尔弗这种地位的人公开做出的概率预测,将被代理人解读,并根据内部程序采取行动。人类的一个这样的过程是把一个概率性的预测折叠成一个预测,然后从那里得到一个断言。有证据表明,史蒂夫·班农和川普竞选团队利用这一事实和对民调的不信任,来保持自信和有目的,而他们自己的内部民调反映了不同的现实。这一结果的部分责任在于 Nate Silver、FiveThirtyEight 和任何报道概率预测的人,这些预测被认为到降低了选民投票率 在受青睐的一方 中,如果它们被解读为预示井喷。随着来自 Silver 的头条新闻,如“特朗普正在加倍押注一个失败的策略”以及概率预测,作者对读者认为民主党胜利已是囊中之物负有一定责任。
如果公众和二手报道没有明智地整合你提供的信息的倾向,并且你的网站显然会被报道和阅读,我个人同意有义务以一种不容易被误解的方式报道你自己的发现。对于 Nate Silver 和 FiveThirtyEight 来说,创建一个带有相关输入的蒙特卡罗模型的图形表示并显示过程的模糊性,而不生成或公布一个容易被误解的基于百分比的概率预测是很简单的。
然而,我认为 Silver 和 Taleb 之间的分歧比其他人报告预测的概率更重要。塔勒布曾亲自断言,精确的蒙特卡洛衍生的基于百分比的概率预测,就像任何概率预测一样,不应该偏离或随时间变化超过任何相关的、微小的修订标准。西尔弗坚持认为这是应该的。
这是一个重要的区别,但如果你考虑到他们截然不同的背景,这就不足为奇了。 塔勒布,其背景是量化金融,习惯于波动的基础工具,以及复杂衍生品的创造和部署,以捕捉其内在的不确定性 。如果概率预测是不正确的,那么他在管理投资基金时会损失数十亿美元。一旦对基础资产的概率预测被锁定,市场就会创造衍生品来描述各种结果,并根据这种不确定性来定价。在塔勒布的世界里,情况的不确定性不是预测的不确定性的基础,而我和你的预测之间的差异是我们相应地给自己的东西定价并相互买卖的基础。然后,随着时间的推移,我们都将是正确的,或者我们都将被纠正,退出市场。
另一方面,西尔弗在民意调查方面的背景是习惯于动荡的政治和体育局势,但除了人为的终结之外,没有捕捉它们的不确定性的机制——一场比赛的结束,一场选举的结果。在白银领域,与市场的瞬间过程相比,反馈相对较少。与体育不同,政治中的规则和信息具有很强的可塑性和开放性。民意测验专家提供信息,选民和候选人在评判候选人、创造信息和开展竞选活动时都会考虑到这一点。
两者背景的关键区别在于每时每刻决定行动路线的绝对正确性的价值。根据塔勒布的预测准确性标准,民意调查者、选民、候选人、竞选经理,这些人都不会从竞选期间对选举概率预测的准确公众评估中获得多少好处。这些信息将完全不在他们的任何决策阈值之内,并且没有机制来捕捉或受益于该过程固有的不确定性。
一个公开的组合分析对于一个竞选经理来说已经足够了,他正在寻求把每一个可能的结果都推向他们的方向。他们想让选民知道哪些比赛正在进行。之后,预测的准确性是有价值的,只要它明确地*不是公开的,*特权信息在政治上不像在市场上那样是非法的。
对概率预测的准确解读会导致投票人理论上只关心娱乐价值。如前所述,这种娱乐以一种令人担忧的方式对投票本身施加激励,表明它们不是随机产生的。
候选人或许可以从准确性中受益,但往往无法亲自利用微妙的信息,特别是在权衡真正的未知可能给那些患有公共职位竞选精神病的人带来的额外压力时。此外,上述关于公共信息价值的说明对他们和他们的竞选经理都适用。班农从一开始就臭名昭著地告诉川普,“你有百分之百的机会赢得。”
西尔弗正在向市场报告他的蒙特卡洛方法的结果,作为概率预测,而市场并不像塔勒布那样关心准确性或从准确性中获取价值,西尔弗的新闻报道照原样非常受欢迎。作为一名记者/统计学家,他小心翼翼地将自己的预测描述为概率性的,按照塔勒布的标准,他报告的结果显然是不准确的。但是,只有在存在利用信息的机制的情况下,对绝对正确的实时概率模型的要求才是有价值的。
我发现西尔弗和塔勒布在谈话中都非常傲慢。考虑到塔勒布的数学并没有失去任何严谨性,西尔弗的人身攻击(对不起,兄弟,但你已经有点成为一个知识分子,但白痴’)令人尴尬。尽管你很难责怪他,考虑到塔勒布愿意抄袭特朗普总统的剧本,称西尔弗为“Klueless”[原文如此],以及塔勒布故意忽视缺乏任何激励机制来改变广泛变异的概率预测。
从历史上看,Silver 的模型在汇总民意调查信息方面比大多数公共来源更加准确。实际上,西尔弗可能必须每小时发表一篇毫无信息但准确的文章《我们不知道谁会赢》,才能成为塔勒布标准的“好预测者”,而塔勒布的标准忽视了民调新闻的激励作用。内特·西尔弗在新闻业的游戏中有太多的经验,不能忽视它的惯例。塔勒布声称“预测选举不是内特·西尔弗的事情”,这是天真的表现。这可能是对 Twitter 的控诉(我避开了这个平台,即使是对 Taleb 这样的热门平台,因为它总是奖励刻薄和尖刻的讽刺而不是理性),但我发现令人难过的是,当他们有如此多的共同点时,他们却在互相攻击。
因此,fivethirtyeeight和纳西姆·尼古拉斯·塔勒布注意,如果我能挑选两个人来承担选举报道和新闻业中概率预测的失败,那将是你们两个。如果你们两个能更好地讨论准确的政治预测是什么样的,对选民和 Silver 的利益相关者有用,这对民主的未来非常重要,如果它导致知情人士中意外的选民自我抑制,我将不胜感激。
谷歌研究院的自然语言生成
在本集《云人工智能历险记》中,我将谷歌研究工程师贾斯汀·赵(Justin Zhao)带入演播室,了解生成自然发音的文本有多难。我们还讨论了这项技术如何应用在你的日常生活中,比如当你向谷歌主页询问天气时。
我们还讨论了一些应用于自然语言生成问题的机器学习技术,以及这一领域的最新研究进展。这是一次有趣而有意义的讨论,尽情享受吧!
我们正在为云人工智能冒险测试一种新的形式,在那里我采访人工智能和机器学习专家,讨论他们在该领域的一些研究和进展。这是一次真正的,嗯,冒险为这一集做准备和拍摄,请让我知道你是否喜欢这个结构!
要了解更多机器学习动作,请务必关注 Medium 上的me或订阅 YouTube 频道以观看未来的剧集。更多剧集即将推出!
社交媒体内容的自然语言处理
备用标题:全世界都在谈论华特·迪士尼公司吗?是的,是的,它是。
该设置
华特·迪士尼公司是一家多产的跨国公司,拥有大约 20 万名员工,仅根据我写这篇文章时的股票价值和已发行股票的数量,我估计其总价值约为 1500 亿至 1600 亿美元。与大多数公司一样,你最好相信,他们有既得利益来衡量他们的客户和公众对他们提供的服务和他们创造的产品的感受,但收集这些信息,无论是通过亲自,通过电话,电子邮件或邮寄客户调查和问卷,都需要花费金钱和时间来创建,传播,收集和分析。我想看看我是否可以使用自然语言处理(NLP)工具和无监督的机器学习,以最低的成本实时衡量公众对迪士尼的看法。
获取数据
首先,我需要文本形式的数据,这是公开发布的,可能包含特定主题的观点;我转向推特。我使用 Twitter API(应用程序接口)编写了一个脚本来下载与关键字和标签“迪士尼”相关的推文,因为它们发布在网上。是的,任何人都可以对任何推文自由地这样做,甚至可以从存储的后备目录中查询它们。除了推文文本之外,你还可以下载与该推文以及发布或转发该状态的用户相关的大量数据和元数据,包括但绝对不限于:时间、日期、地点、语言、关注者数量、关注的账户数量、账户创建日期、个人资料图片以及发布该推文和转发该状态的用户的用户名。我将实时 tweet 流式传输到 MongoDB 集合中,因为我有很多 tweet 要分析,所以我将它们存储在个人 AWS (Amazon Web Services)实例中。如果有人对这些工具感兴趣,或者只是想了解整个电脑世界可以获得哪些关于你的 Twitter 账户的信息,我会在这篇文章的底部提供一些链接,你可以查看一下。
初步材料
我每天收集大约 100,000 到 120,000 条推文,使用 30 多种不同的语言,都与迪士尼有关。当我研究这些数据时,我发现了我的第一个偏见来源。当我搜索关键字 disney 的许多排列时(例如“disney”、“disney’s”、“Disney # Disney”等)。),这不会收集与我的主题相关的所有可能的推文,只会收集提到我的搜索列表中某个单词的推文。例如,我的搜索条件会收集到一条类似“我爱迪斯尼世界!”,而不是“我爱 EPCOT!”,因为第二条推文不包含类似于“迪士尼”的词我可以扩展我的搜索列表,以具体包括每个主题公园,但这会使我的推文偏向主题公园,我根本无法包括每部迪士尼电影、电视节目和角色的名称。为了让我的分析尽可能客观,我保持了我的搜索标准的普遍性,这使我的推文偏向于标题中有“迪士尼”字样的过度代表属性,如华特·迪士尼世界和迪士尼频道。
我把我的大约 50 万条迪士尼推文,削减到 32 万条英文,我实际上可以阅读,然后我通过一个名为 TextBlob 的 Python 库运行它们,它分析文本的每个数据点,在这种情况下是一条推文,并计算极性,介于-1(不利)和 1(有利)之间,0 表示情绪是中性的。所有推文的分布平均值约为 0.115,或正或负,如下图所示。
Image by Author
我想这是非常巧妙的,但事实是,迪士尼公司实际上是其他公司的巨大联合企业,包括漫威、卢卡斯影业、ESPN、ABC、迪士尼公园和度假村、迪士尼游轮公司等等,以及相关知识产权的非常多样化的组合。我不认为了解总体情绪会像了解特定知识产权或品牌的情绪那样对公司有用。为了将这些推文分组到单独的类别中,而不是单独查看每个类别,我们需要通过无监督学习转向聚类,但首先我们需要探索矢量化和降维!令人兴奋的东西。
数据清理和矢量化
Photo by LoboStudioHamburg on pixabay
总的来说,我觉得数据清理很有趣,但是读起来很乏味。不幸的是它也是超级重要的,所以我不得不提到它。使用 tweet 的最大问题是重复的 tweet。我不能排除收集转发,因为我觉得,一条被转发的正面推文仍然表明对该主题的新的积极情绪,并且可能在以后有用,但你不能用重复的数据点聚集数据;我稍后会谈到这一点,但现在请相信我的话,有大量完全相同的文本对我的分析非常不利。因此,我删除了任何推文中的任何 url、符号或转发标签,然后我只保留了每个文本数据点的唯一集合,以清除任何转发或自动机器人的内容,结果产生了大约 98,000 条推文。
所有算法的输入都是数字。有许多不同的方法可以将其他形式的数据转化为数字格式,例如将一张图片解构为每个像素的红色、绿色和蓝色值,或者在我的情况下,将一条 tweet 的单词转化为 tweet 中每个单词的计数,这称为标记化和矢量化。首先,定义什么是标记,它可以是每个唯一的单词、相邻单词的组合,甚至是一定数量的相邻字母的任意组合。根据您的需要,这些技术当然可以单独使用或组合使用。接下来,对文本进行矢量化,为每个数据点的每个标记分配一个值。最基本的例子是以文本“我喜欢汤”为例,它向量化为[ 我 :1,喜欢 :1,汤 :1]。我们现在有了文本的数字表示!接下来,为了将我们所有的推文放在同一个表中,我们不能只为“我喜欢汤”设置一行,为每个“我”、“喜欢”和“汤”设置一列,但是我们需要为包含在任何数据点中的每个单词设置一列。例如,如果我们的第二个数据点是“我不喜欢飞行”,我喜欢汤的矢量化现在必须扩展以指定它不包含新单词,这样它的新值是[ i :1, like :1, soup :1, do :0, not 而“我不喜欢飞”的值为[ i :1, like :1,汤 :0, do :1, not :1,飞 :1],这样我们两个数据点的值不同,但格式完全相同。 你可以看到 98,000 条独特的推文,每个文本数据点的计数将迅速变得巨大,即使大多数计数将是 0。
还和我在一起吗?太棒了。所以现在我们有一个漂亮的表格/数据集,有 98,000 行,每行代表一条推文,还有大约 10,000 列,每列代表一个独特的词在那条推文中出现的次数。这个表大部分是 0,也有一些 1,2,偶尔还有 3 混在里面,现在其实是另外一种问题了。如果有人(比如一个计算机程序)只看表格中的数字,并决定这些推文是相似还是不同,它们看起来都差不多,因为几乎每一行都由大约 10,000 个 0 组成,还有少量的 1,2 和 3。这是维数灾难的一个例子,这是许多分析领域每天都要面对的问题。
在继续进行降维之前,我还想提一下,我删除了停用词,如“、、*、*和“ if”、,这些词的分析价值很小,并且对这些词进行了加权,以便如果像“迪士尼”这样的词出现在许多数据点中,那么它在确定推文主题之间的差异方面的价值就较小。
降维
哇,这个帖子越来越长了,所以让我们向前冲,尝试覆盖一些地面。如果你休息了一会儿再回来,这里是我们的位置:我们想按迪士尼主题对一堆推文进行分组,我们已经将我们的文本转换成数字格式,现在我们有一个大约 10,000 列的数据集,其中大部分是零,这是准确的,但不利于分析。为了减少数据集中的列数,但仍然包含大量描述性信息,我使用了奇异值分解(SVD)。基本上,它所做的是获取 tweets 中的所有信息(可变性),并将其提取到少量新创建的专栏中。这个过程很抽象,线性代数很重,也很复杂。重要的是,我们可以将最初包含在 10,000 个令牌列中的大量信息压缩到几十个新列中,这很好。
我们现在已经创建了一个所谓的 LSI 空间(潜在语义索引),可以帮助我们确定每个新列在将推文分成具有共同词汇的主题组方面的有用性。不幸的是,由于新列的“提炼”性质,这些数据比直接的字数统计更难解释,所以让我们将这些列提供给聚类算法。补充说明:新的专栏将把有共同词汇的推文放在一起,这就是为什么转发和机器人是不好的。如果完全相同的 20 个单词在相同的推文中出现 15000 次,那么它就会人为地在这些单词之间创建一种关系,从而混淆模型。
使聚集
我很想深入研究 K-Means 无监督聚类算法的来龙去脉,但我不会,相反,我只会说两件事。首先,该算法(或模型)将每个数据点分配给一个聚类数,以便文本相似的推文被聚集在一起,理想情况下是某种逻辑主题或话题。第二,让它不受监督的是,你不用用它来创建预测,然后对照已知数据进行测试,这将受到监督。监督学习的一个例子是给一个模型一些信息,比如星期几、天气状况、一天中的时间等等。,并让模型预测它认为公共汽车是否会准时或晚点。一旦你有了一个预测,你就将它与实际发生的事情进行比较,并决定它是对还是错;相当直接。对于像 k-means 这样的无监督模型,我给它一堆数据,如矢量化的推文,然后算法将数据点分组到集群中,但由于我们不知道推文的内容以查看模型的表现如何,我需要筛选每个分组的推文,以查看集群是否遵循一些有用的主题。开门见山地说,在这种情况下,他们没有。
Image by Author
上面是一个轮廓图,它基本上显示了我的大多数推文是在一个巨大的、相当分散的集群中(12),而其他集群相比之下要小得多。当查看每个分组中包含的推文时,在某些情况下,有明确的主题,尽管主题之间几乎总是有很多重叠和信号中的混乱。换句话说,我无法找出一个单独的聚类,也无法相信它包含了某个特定主题的所有推文,比如品牌、人物、电影等等。
Image by Author
为什么迪士尼推文很难处理
因此,在进行了大量的模型调整和尝试不同的东西来让我的聚类算法产生一些真正令人兴奋和有见地的东西(给我发一条消息,我会很高兴地详细研究所有这些东西)之后,有一段时间我不得不得出结论,这些推文就是不想与我合作。也就是说,我认为仍然有一些东西可以学习,你甚至可能会发现它很有趣,所以让我们深入研究几个集群,看看为什么迪士尼推文不喜欢分成组。
首先,这是我的数据集中一条推文的常见例子:
- “我想去迪士尼!” 尽管我很欣赏,甚至可能同意这种观点,但如果你能想象这条推文的矢量化版本会是什么样,就没有多少模型可以使用了。如果非要给这条推文贴上一个话题的标签,会是什么?也许我们可以推断他们在谈论一个主题公园,但是并没有真正提到一个特定的公园,或者一部电影,或者甚至是一个角色作为主题标签。这条推文中肯定缺乏信号,或可用的信息。
Image by Author
当深入到另一个确定的集群时,我会看到下面的推文:
- *“爱丽儿是唯一一个生过孩子的迪士尼公主。”
——“风中奇缘怎么不是迪士尼公主?”从这些照片中,我可以看到某种类型的“迪士尼公主”主题开始形成,这将是伟大的!但是当我深入研究时,我发现在许多其他的分类中也提到了公主,这表明我的主题之间的混淆。 - “迪士尼同时拥有漫威和卢卡斯的电影,idc 你怎么说首里和莱娅是我眼中的迪士尼公主” 这是一条与前两条属于同一组的推文的例子,但第三条不可否认地包括了与漫威和卢卡斯电影有关的材料。如果我对漫威和星球大战/卢卡斯的电影有一个分类,模型会把这条推文放在哪里?*
最后一个例子:
——“迪士尼不仅仅是迪士尼 乐园 !#迪士尼smmc*@迪士尼* 巡航*@迪士尼* 奥拉尼*@DVC新闻@Disneymoms"* 在这条推文中我看到了‘公园’,‘smmc’(迪士尼的一个特别活动——社交媒体妈妈们与前面的例子一样,我将这解释为一条推文中的大量潜在信号,但没有足够明显的信号让模型能够将它与同一主题的文本相似的推文放在一个类别中。
Image by Author
结束语
因此,也许我不能像我希望的那样代表迪士尼完成高效、信息丰富、以品牌为中心的情感分析,但我确实带你快速或不那么快速地浏览了一个简单的 NLP 项目是如何构建的,以及在此过程中如何以及为什么要完成某些步骤。事实证明,迪士尼公司非常擅长获取知识产权,比如一个成功的角色,并将其传播到许多业务中。像阿拉丁这样的角色出现在电影、电视剧、主题公园、游轮、商品中,甚至更多,有时甚至与看似不相关的角色一起出现,这并不罕见。不仅如此,似乎当有人喜欢迪士尼电影时,他们可能也会喜欢公园、游轮,甚至可能会举办迪士尼婚礼,在一条 280 字的推特中提到这一切。这只是迪士尼如此成功的几个原因,也是为什么我的模型很难将这些推文分成不同的主题。
Image by Author
感谢你在整个过程中坚持下来,或者至少跳过技术部分阅读结尾!我希望你发现了一些有趣的东西,并随时在这里或 LinkedIn 上让我知道你的想法!(https://www.linkedin.com/in/sky-williams/
资源
Twitter API:【https://developer.twitter.com/en/docs
亚马逊 Web 服务:https://aws.amazon.com
MongoDB:https://www.mongodb.com
如果你想了解我使用的任何技术、算法、方法或库的细节:https://www.linkedin.com/in/sky-williams/
自然语言处理与说唱歌词
梅蒂斯的第四个项目侧重于无监督学习和自然语言处理。我选择探索说唱歌词,希望能根据说唱歌手使用的单词找到不同的组合。
数据
我想看一大堆说唱歌手,包括我不认识的。为了做到这一点,我使用了一个用户创建的有史以来最伟大说唱歌手的列表和另一个有史以来最伟大说唱组合的列表。然后,我从歌词网站 genius 中为每一位艺术家提取了所有的歌曲。
为了获取所有这些数据,我需要对两个网站进行网络抓取。幸运的是,我找到了一个由约翰·米勒创建的 python 包。他的这个 github 页面包括了如何使用代码的例子。
我从 350 多名说唱歌手那里收集了 55000 多首歌曲。我对自己能够得到的金额感到满意,但这非常耗时。这个剧本在我身上失败了几次,所以我不得不运行几次来吸引所有的艺术家。
谁的词汇量最大?
我最初想做一些数据探索,我决定通过使用每个艺术家独有的单词数量来找出谁的词汇量最大。这是受马特·丹尼尔斯(Matt Daniels)关于同一主题的博客帖子的启发。他使用了 85 位艺术家,所以我很想知道,考虑到我使用了 350 多位艺术家,我的结果会和他的结果相比。
为了计算每一位艺术家的独特词汇,我从每一位艺术家的歌曲中随机抽取了一个样本,并查看了歌曲中的前 35000 个词。我想能够比较苹果和苹果,所以如果一个艺术家没有那么多单词,我就把它们排除在外。我不得不考虑的一个问题是如何处理像“走”、“走”和“走”这样的词。将它们全部包括在内意味着,当它们都是同一个单词的变体时,我将把它们作为单独的单词来计算。我决定用一种叫做的方法来解决这个问题。雪球词干器把这三个字都变成了“走”。因此,它们都被认为是同一个词,而不是三个不同的词。
总的来说,东海岸说唱歌手用的词最多,而南方说唱歌手用的最少。最大的异常值是伊索岩石,它以 6542 个独特的单词位居榜首。说唱歌手 NF 的独特词汇最少,为 2084 个。
Jay-Z, one of the most successful artists, landed right in the middle of east region with 4,174 words.
看到地区细分很有趣,但我想展示一些更细致的东西。下面的视频显示了一个可视化,它使用 d3 根据唯一单词的数量来绘制每个说唱歌手。你可以在这里玩。
Demo of how to use the visualization
每个泡泡代表一个特定的说唱歌手,根据他们使用的独特词汇的数量,他们被放在页面上。泡沫越右,他们使用的独特词汇就越多。迄今为止,说话最独特的说唱歌手是伊索·洛克。我把它与两位作家查尔斯·狄更斯和赫尔曼·梅尔维尔进行了比较,以表明《伊索岩石》甚至比得上著名作家。
我还包括不同的过滤器。在“按地区查看”过滤器中,每个艺术家都根据他们来自的地区(东部、西部、中西部或南部)进行颜色编码。在“群组”过滤器中,代表 rap 群组的气泡会高亮显示。我原以为它们会更集中在右边(表示更多的单词),但它们分布得很均匀。使用大量词汇的一个群体是吴唐族。我加入了一个过滤器,只突出显示了武堂家族的成员,以显示他们每个人比一般说唱歌手使用更多的词。我的最后一个过滤器,非说唱过滤器,突出了三个不同的非说唱歌手(泰勒斯威夫特,披头士和鲍勃迪伦)。我把他们包括进来是因为我想展示说唱歌手和其他流派的比较。事实上,这三个词都在图表的左侧,这表明即使是使用平均或低于平均字数的说唱歌手也比其他流派的著名艺术家使用更多的词。
基于歌词的分组
观察哪些说唱歌手用词最多很有趣,但我的目标是看看我能否根据歌词创建有意义的说唱歌手群体。为了做到这一点,我决定在我的歌曲集上使用主题建模。
我首先使用了术语频率-逆文档频率 (TFIDF)来显示每个单词在语料库中的重要性。这种方法对较罕见的单词给予较大的权重,而对较常见的单词给予较小的权重。TFIDF 基本上表明了一个人看到这个词应该有多惊讶。例如,假设语料库包括 1000 首歌曲。如果一个词出现在每一首歌中,人们看到这个词就不会感到惊讶。但是如果一个词只出现在三首歌里,那么当你在一首特定的歌里看到这个词时,你应该会非常惊讶。
然后,我对我的歌曲集应用了非负矩阵分解(NMF)。我用了 NMF,因为我想让每个主题中的每个单词都有正的权重来帮助解释。一旦我有了每首歌的主题,我就把它们平均到每个艺术家身上,因为我感兴趣的是所有的艺术家,而不是任何一首特定的歌。我期望看到至少两个形式良好的主题,但事实并非如此。遗憾的是,我从 NMF 那里得到的话题并不连贯,也没有形成有意义的群体。我尝试调整许多不同的参数,但没有改善我的话题。
然后,我使用 K-最近邻(KNN)法,根据我的艺术家的个人话题向量对他们进行聚类,看看将说唱歌手分组是否会比我的话题产生更好的分组。我希望看到的一件事是根据说唱歌手来自的地区进行分组。我以为会有地区俚语把西海岸说唱歌手和南方说唱歌手区分开来,或者纽约说唱歌手会自成一派。总的来说,这种情况没有发生。当我尝试三个集群时,我能够得到一个主要是南方说唱歌手的集群和一个有社会意识的说唱歌手的集群。当我增加聚类的数量时,这是我唯一能理解的两个聚类,而其余的聚类看起来是随机的。
虽然我能够得到几个不错的组,但我没有得到我想要的整体清晰的分离。这种失望让我创建了一个推荐系统。
推荐系统
我创建了这个系统,这样用户可以输入一个特定的说唱歌手,然后会有 5 个说唱歌手推荐。用户可以选择调整他们希望推荐的冒险程度。用户越喜欢冒险,推荐就越不相似。有 6 个不同的级别,所以用户可以看到每个说唱歌手输入总共 30 个推荐。这里 可以玩 app 。
Demo of recommendation app
为了建立我的推荐,我使用我的 NMF 模型中的向量,并确定哪些向量彼此接近。为此,我找到了突出显示的说唱歌手和其他说唱歌手之间的欧几里德距离。推荐的是基于这个距离最近的说唱歌手。我也测试了余弦距离,但结果几乎相同,所以我还是用欧几里得。
我还试着使用了 word2vec 。我想看看使用单词的语义关系来避开歌词中的所有拼写错误是否会提供更好的推荐。我使用 50 的大小来训练,这意味着我的输出是每首歌的 50 维向量。然后,我对每个艺术家的矢量进行平均,得到每个艺术家的一个矢量,并使用与前面相同的距离公式。
NMF 和 word2vec 的推荐略有不同,在我看来,word2vec 提供了更好的建议。然而,因为我并不熟悉我包括的每一位艺术家,所以在其他人身上测试一下会很有用。
结论
我发现做这个项目很有趣。推荐系统看起来运行良好,但是推荐本身是主观的,所以很难说。我的系统的主要限制是我只能用歌词来做推荐。歌词绝对是决定一个人是否喜欢某个特定歌手/歌曲的重要因素,但这不是唯一的因素。有些人对歌曲的节拍或速度更感兴趣。又或许有人根本不在乎歌词,只在乎歌曲整体的声音。包含这些信息将是我改进系统的第一步。
我学到了什么
- ****NLP 技术如 TF-IDF and 和 word2vec
- ****话题建模利用 NMF
- ****聚类方法如 KNN
- 创建推荐系统
- 使用 d3 和烧瓶。我使用 d3 和 flask 来创建我的视觉效果。
- 使用 Docker 将 Flask 应用程序部署到 AWS EC2 容器服务(ECS)。
自然语言处理:用 scikit-learn 计算矢量化
这是一个关于如何使用 scikit-learn 对真实文本数据进行计数矢量化的演示。
计数矢量化(也称为一键编码)
如果你还没有,看看我之前关于单词嵌入的博文:单词嵌入介绍
在那篇博客文章中,我们讨论了很多不同的方法来表示机器学习中使用的单词。这是一个高层次的概述,我们将在这里展开,并检查我们如何在一些真实的文本数据上实际使用计数矢量化。
计数向量化概述
今天,我们将探讨用数字表示文本数据的最基本方法之一:一键编码(或计数矢量化)。想法很简单。
我们将创建向量,其维数等于我们的词汇表的大小,如果文本数据以该词汇为特征,我们将在该维数上放置一个 1。每当我们再次遇到这个词,我们将增加计数,留下 0 到处我们没有找到这个词甚至一次。
这样做的结果将是非常大的向量,但是,如果我们在真实的文本数据上使用它们,我们将得到我们的文本数据的单词内容的非常精确的计数。不幸的是,这不能提供任何语义或关系信息,但这没关系,因为这不是使用这种技术的目的。
今天,我们将使用来自 scikit-learn 的软件包。
一个基本例子
以下是使用计数矢量化获取矢量的基本示例:
from sklearn.feature_extraction.text import CountVectorizer
# To create a Count Vectorizer, we simply need to instantiate one.
# There are special parameters we can set here when making the vectorizer, but
# for the most basic example, it is not needed.
vectorizer = CountVectorizer()
# For our text, we are going to take some text from our previous blog post
# about count vectorization
sample_text = ["One of the most basic ways we can numerically represent words "
"is through the one-hot encoding method (also sometimes called "
"count vectorizing)."]
# To actually create the vectorizer, we simply need to call fit on the text
# data that we wish to fix
vectorizer.fit(sample_text)
# Now, we can inspect how our vectorizer vectorized the text
# This will print out a list of words used, and their index in the vectors
print('Vocabulary: ')
print(vectorizer.vocabulary_)
# If we would like to actually create a vector, we can do so by passing the
# text into the vectorizer to get back counts
vector = vectorizer.transform(sample_text)
# Our final vector:
print('Full vector: ')
print(vector.toarray())
# Or if we wanted to get the vector for one word:
print('Hot vector: ')
print(vectorizer.transform(['hot']).toarray())
# Or if we wanted to get multiple vectors at once to build matrices
print('Hot and one: ')
print(vectorizer.transform(['hot', 'one']).toarray())
# We could also do the whole thing at once with the fit_transform method:
print('One swoop:')
new_text = ['Today is the day that I do the thing today, today']
new_vectorizer = CountVectorizer()
print(new_vectorizer.fit_transform(new_text).toarray())
我们的产出:
Vocabulary:
{'one': 12, 'of': 11, 'the': 15, 'most': 9, 'basic': 1, 'ways': 18, 'we': 19,
'can': 3, 'numerically': 10, 'represent': 13, 'words': 20, 'is': 7,
'through': 16, 'hot': 6, 'encoding': 5, 'method': 8, 'also': 0,
'sometimes': 14, 'called': 2, 'count': 4, 'vectorizing': 17}
Full vector:
[[1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1]]
Hot vector:
[[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
Hot and one:
[[0 0 0 0 0 0 1 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 1 0 0 0 0 0 0 0 0]]
One swoop:
[[1 1 1 1 2 1 3]]
在真实数据上使用它:
所以还是用在一些真实数据上吧!我们将查看 scikit-learn 附带的 20 个新闻组数据集。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
# Create our vectorizer
vectorizer = CountVectorizer()
# Let's fetch all the possible text data
newsgroups_data = fetch_20newsgroups()
# Why not inspect a sample of the text data?
print('Sample 0: ')
print(newsgroups_data.data[0])
print()
# Create the vectorizer
vectorizer.fit(newsgroups_data.data)
# Let's look at the vocabulary:
print('Vocabulary: ')
print(vectorizer.vocabulary_)
print()
# Converting our first sample into a vector
v0 = vectorizer.transform([newsgroups_data.data[0]]).toarray()[0]
print('Sample 0 (vectorized): ')
print(v0)
print()
# It's too big to even see...
# What's the length?
print('Sample 0 (vectorized) length: ')
print(len(v0))
print()
# How many words does it have?
print('Sample 0 (vectorized) sum: ')
print(np.sum(v0))
print()
# What if we wanted to go back to the source?
print('To the source:')
print(vectorizer.inverse_transform(v0))
print()
# So all this data has a lot of extra garbage... Why not strip it away?
newsgroups_data = fetch_20newsgroups(remove=('headers', 'footers', 'quotes'))
# Why not inspect a sample of the text data?
print('Sample 0: ')
print(newsgroups_data.data[0])
print()
# Create the vectorizer
vectorizer.fit(newsgroups_data.data)
# Let's look at the vocabulary:
print('Vocabulary: ')
print(vectorizer.vocabulary_)
print()
# Converting our first sample into a vector
v0 = vectorizer.transform([newsgroups_data.data[0]]).toarray()[0]
print('Sample 0 (vectorized): ')
print(v0)
print()
# It's too big to even see...
# What's the length?
print('Sample 0 (vectorized) length: ')
print(len(v0))
print()
# How many words does it have?
print('Sample 0 (vectorized) sum: ')
print(np.sum(v0))
print()
# What if we wanted to go back to the source?
print('To the source:')
print(vectorizer.inverse_transform(v0))
print()
我们的产出:
Sample 0:
From: lerxst@wam.umd.edu (where's my thing)
Subject: WHAT car is this!?
Nntp-Posting-Host: rac3.wam.umd.edu
Organization: University of Maryland, College Park
Lines: 15
I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.
Thanks,
- IL
---- brought to you by your neighborhood Lerxst ----
Vocabulary:
{'from': 56979, 'lerxst': 75358, 'wam': 123162, 'umd': 118280, 'edu': 50527,
'where': 124031, 'my': 85354, 'thing': 114688, 'subject': 111322,
'what': 123984, 'car': 37780, 'is': 68532, 'this': 114731, 'nntp': 87620,
'posting': 95162, 'host': 64095, 'rac3': 98949, 'organization': 90379,
'university': 118983, 'of': 89362, 'maryland': 79666,
'college': 40998, ... } (Abbreviated...)
Sample 0 (vectorized):
[0 0 0 ... 0 0 0]
Sample 0 (vectorized) length:
130107
Sample 0 (vectorized) sum:
122
To the source:
[array(['15', '60s', '70s', 'addition', 'all', 'anyone', 'be', 'body',
'bricklin', 'brought', 'bumper', 'by', 'called', 'can', 'car',
'college', 'could', 'day', 'door', 'doors', 'early', 'edu',
'engine', 'enlighten', 'from', 'front', 'funky', 'have', 'history',
'host', 'if', 'il', 'in', 'info', 'is', 'it', 'know', 'late',
'lerxst', 'lines', 'looked', 'looking', 'made', 'mail', 'maryland',
'me', 'model', 'my', 'name', 'neighborhood', 'nntp', 'of', 'on',
'or', 'organization', 'other', 'out', 'park', 'please', 'posting',
'production', 'rac3', 'really', 'rest', 'saw', 'separate', 'small',
'specs', 'sports', 'subject', 'tellme', 'thanks', 'the', 'there',
'thing', 'this', 'to', 'umd', 'university', 'wam', 'was', 'were',
'what', 'whatever', 'where', 'wondering', 'years', 'you', 'your'],
dtype='<U180')]
Sample 0:
I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.
Vocabulary:
{'was': 95844, 'wondering': 97181, 'if': 48754, 'anyone': 18915, 'out': 68847,
'there': 88638, 'could': 30074, 'enlighten': 37335, 'me': 60560, 'on': 68080,
'this': 88767, 'car': 25775, 'saw': 80623, 'the': 88532, 'other': 68781,
'day': 31990, 'it': 51326, 'door': 34809, 'sports': 84538, 'looked': 57390,
'to': 89360, 'be': 21987, 'from': 41715, 'late': 55746, '60s': 9843,
'early': 35974, '70s': 11174, 'called': 25492, 'bricklin': 24160, 'doors': 34810,
'were': 96247, 'really': 76471, ... } (Abbreviated...)
Sample 0 (vectorized):
[0 0 0 ... 0 0 0]
Sample 0 (vectorized) length:
101631
Sample 0 (vectorized) sum:
85
To the source:
[array(['60s', '70s', 'addition', 'all', 'anyone', 'be', 'body',
'bricklin', 'bumper', 'called', 'can', 'car', 'could', 'day',
'door', 'doors', 'early', 'engine', 'enlighten', 'from', 'front',
'funky', 'have', 'history', 'if', 'in', 'info', 'is', 'it', 'know',
'late', 'looked', 'looking', 'made', 'mail', 'me', 'model', 'name',
'of', 'on', 'or', 'other', 'out', 'please', 'production', 'really',
'rest', 'saw', 'separate', 'small', 'specs', 'sports', 'tellme',
'the', 'there', 'this', 'to', 'was', 'were', 'whatever', 'where',
'wondering', 'years', 'you'], dtype='<U81')]
现在怎么办?
所以,你现在可能想知道什么?我们知道如何基于计数对这些东西进行矢量化,但是我们实际上能利用这些信息做什么呢?
首先,我们可以做一系列的分析。我们可以查看词频,我们可以删除停用词,我们可以可视化事物,我们可以尝试聚类。现在我们有了这些文本数据的数字表示,我们可以做很多以前做不到的事情!
但是让我们更具体一点。我们一直在使用来自 20 个新闻组数据集中的文本数据。为什么不把它用在任务上呢?
20 个新闻组数据集是一个论坛帖子的数据集,分为 20 个不同的类别。为什么不使用我们的矢量化工具来尝试对这些数据进行分类呢?
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
# Create our vectorizer
vectorizer = CountVectorizer()
# All data
newsgroups_train = fetch_20newsgroups(subset='train',
remove=('headers', 'footers', 'quotes'))
newsgroups_test = fetch_20newsgroups(subset='test',
remove=('headers', 'footers', 'quotes'))
# Get the training vectors
vectors = vectorizer.fit_transform(newsgroups_train.data)
# Build the classifier
clf = MultinomialNB(alpha=.01)
# Train the classifier
clf.fit(vectors, newsgroups_train.target)
# Get the test vectors
vectors_test = vectorizer.transform(newsgroups_test.data)
# Predict and score the vectors
pred = clf.predict(vectors_test)
acc_score = metrics.accuracy_score(newsgroups_test.target, pred)
f1_score = metrics.f1_score(newsgroups_test.target, pred, average='macro')
print('Total accuracy classification score: {}'.format(acc_score))
print('Total F1 classification score: {}'.format(f1_score))
我们的产出:
Total accuracy classification score: 0.6460435475305364
Total F1 classification score: 0.6203806145034193
嗯……所以不是超级神奇,但我们只是使用计数向量!一个更丰富的表现将为我们的分数创造奇迹!
包扎
希望您感觉已经学到了很多关于计数矢量化的知识,如何使用它,以及它的一些潜在应用!
如果你喜欢读这篇文章,给我留言或者给我的 GoFundMe 捐款来帮助我继续我的 ML 研究!
敬请关注即将推出的更多单词嵌入内容!
用 Python 实现模糊字符串匹配的自然语言处理
photo credit: Pexels
当我们比较不同网站的酒店房价时,我们必须确保我们是在进行比较
在计算机科学中,模糊字符串匹配是一种寻找与模式近似(而不是精确)匹配的字符串的技术。换句话说,模糊字符串匹配是一种即使用户拼错单词或只输入部分单词也能找到匹配项的搜索类型。也被称为近似串匹配。
模糊字符串搜索可用于各种应用,例如:
- 拼写检查和拼写错误,错别字校正器。例如,用户在谷歌中输入“密西沙加”,会返回一个点击列表以及“显示密西沙加的结果”。也就是说,即使用户输入包含额外的或缺失的字符,或其他类型的拼写错误,搜索查询也将返回结果。
- 一个软件可以用来检查重复的记录。例如,如果由于客户姓名的不同拼法(例如 Abigail Martin vs . Abigail Martinez)而在数据库中多次列出不同的购买记录,则可能是新地址或错误输入的电话号码。
说到重复数据删除,它可能不像听起来那么简单,尤其是当您有成千上万条记录时。甚至 Expedia 也没有做到 100%正确:
Source: Expedia
这篇文章将解释什么是模糊字符串匹配及其用例,并给出使用 Python 的 Fuzzywuzzy 库的例子。
每家酒店都有自己的房间命名法,在线旅行社也是如此。例如,同一家酒店的一个房间,Expedia 称之为“工作室,一张特大床和沙发床,角落”,Booking.com 可能会发现简单地将房间显示为“角落特大工作室”是安全的。
这没有错,但当我们想要比较在线旅行社之间的房价时,或者一家在线旅行社想要确保另一家在线旅行社遵守价格平价协议时,这可能会导致混乱。换句话说,为了能够比较价格,我们必须确保我们是在比较苹果与苹果。
对于价格比较网站和应用程序来说,最令人沮丧的问题之一是试图自动判断两件商品(或酒店房间)是否是同一件东西。
Python 中的 FuzzyWuzzy
Fuzzywuzzy 是一个 Python 库,使用 Levenshtein 距离来计算一个简单易用的包中序列之间的差异。
为了演示,我创建了自己的数据集,也就是说,对于同一个酒店,我从 Expedia 获取一个房间类型,比如“套房,一张特大床(客厅)”,然后我将其与 Booking.com 的一个房间类型“特大客厅套房”进行匹配。只要有一点点经验,大多数人就会知道它们是一样的。按照这种方法,我创建了一个小型数据集,包含 100 多个房间类型对,可以在 Github 上找到。
利用这个数据集,我们将测试 Fuzzywuzzy 是如何思考的。换句话说,我们使用 Fuzzywuzzy 来匹配两个数据源之间的记录。
import pandas as pddf = pd.read_csv('room_type.csv')
df.head(10)
Figure 1
数据集是我自己创建的,所以,很干净。
Fuzzywuzzy 中比较两个字符串有几种方法,我们一个一个来试试。
ratio
,比较整个字符串的相似度,按顺序排列。
from fuzzywuzzy import fuzz
fuzz.ratio('Deluxe Room, 1 King Bed', 'Deluxe King Room')
62
这告诉我们“豪华客房,一张特大床”和“豪华大床房”大约有 62%是相同的。
fuzz.ratio('Traditional Double Room, 2 Double Beds', 'Double Room with Two Double Beds')
69
“传统双人房,两张双人床”和“双人房,两张双人床”大约有 69%相同。
fuzz.ratio('Room, 2 Double Beds (19th to 25th Floors)', 'Two Double Beds - Location Room (19th to 25th Floors)')
74
“房间,两张双人床(19 至 25 层)”和“两张双人床-位置房间(19 至 25 层)”对大约 74%相同。
我对这些感到失望。事实证明,这种天真的方法对词序的微小差异、遗漏或多余的单词以及其他类似问题过于敏感。
partial_ratio
,比较部分字符串相似度。
我们仍然使用相同的数据对。
fuzz.partial_ratio('Deluxe Room, 1 King Bed', 'Deluxe King Room')
69
fuzz.partial_ratio('Traditional Double Room, 2 Double Beds', 'Double Room with Two Double Beds')
83
fuzz.partial_ratio('Room, 2 Double Beds (19th to 25th Floors)', 'Two Double Beds - Location Room (19th to 25th Floors)')
63
对于我的数据集,比较部分字符串并不能带来整体更好的结果。我们继续。
token_sort_ratio
,忽略词序。
fuzz.token_sort_ratio('Deluxe Room, 1 King Bed', 'Deluxe King Room')
84
fuzz.token_sort_ratio('Traditional Double Room, 2 Double Beds', 'Double Room with Two Double Beds')
78
fuzz.token_sort_ratio('Room, 2 Double Beds (19th to 25th Floors)', 'Two Double Beds - Location Room (19th to 25th Floors)')
83
目前为止最好的。
token_set_ratio
,忽略重复单词。它类似于令牌排序比率,但更灵活一点。
fuzz.token_set_ratio('Deluxe Room, 1 King Bed', 'Deluxe King Room')
100
fuzz.token_set_ratio('Traditional Double Room, 2 Double Beds', 'Double Room with Two Double Beds')
78
fuzz.token_set_ratio('Room, 2 Double Beds (19th to 25th Floors)', 'Two Double Beds - Location Room (19th to 25th Floors)')
97
看起来token_set_ratio
最适合我的数据。根据这个发现,我决定将token_set_ratio
应用于我的整个数据集。
def get_ratio(row):
name = row['Expedia']
name1 = row['Booking.com']
return fuzz.token_set_ratio(name, name1)
len(df[df.apply(get_ratio, axis=1) > 70]) / len(df)
0.9029126213592233
当设置比率> 70 时,超过 90%的配对超过 70 的匹配分数。没那么寒酸!
Jupyter 笔记本可以在 Github 上找到。星期五快乐!
自然语言处理的应用——三大商业应用
Photo by Gonard Fluit on Unsplash
如果你曾经问过亚马逊的 Alexa 或苹果的 Siri 一个问题,并得到了答案,那么你就体验过自然语言处理(NLP)的实际应用。你的设备能听到你说话,理解你的意图,并执行一个动作,所有这一切只需要 4 秒钟。你知道它起作用了,因为它告诉你——在一个恰当表达和完美说出的人类句子中。
自然语言处理处理计算机如何理解、解释和处理人类语言。这项技术并不新鲜,但由于计算技术的快速进步和更容易获取大数据,它正在快速发展。
让我们来看看企业将 NLP 付诸实践的三种最常见的方式。
1。解决客户难题—通过情绪分析进行监控
精明的消费者在网上表达他们的抱怨(有时也表达他们的欣赏),这就是为什么品牌声誉监测如此重要。了解社交媒体和其他地方对你的公司或产品的评价是了解客户声音的简单方法。
然而,随着数据比以往任何时候都多,手动分析几乎是不可能的,这就是人工智能的帮助所在。通过情感分析,公司部署了执行文本分析和自然语言处理的算法,以理解单词背后的情感或意义。
无论是识别你的品牌在社会上的提及,发现负面评论,还是深入了解公众意见,了解人们对你的企业的看法和原因,就有可能推动战略,开展更好地满足他们需求的活动。
情感分析的最终结果会产生深远的影响。作为锡拉丘兹大学自然语言处理课程的一部分,三名学生构建了一个原型情感分析仪来测量媒体对唐纳德·川普的情感。该模型分析了数千篇关于当选总统唐纳德·特朗普的文章,可用于帮助未来的政治人物制定更好的媒体战略计划。
2。收集市场情报—从非结构化数据中提取
了解你的竞争对手是做什么的,以及在更大的范围内,你的行业整体是做什么的,可以帮助你制定一个有效的商业策略。然而,今天收集的大部分数据都是非结构化的,这意味着这些数据是从社交媒体、电子邮件甚至是通过与客户服务代表的互动中产生的。
关于竞争对手、客户和市场如何相互作用的见解,往往隐藏在新闻文章、报告、SEC 文件和公司网站的文本、信息图表和图像中。自然语言处理通过文本提取和分类,帮助企业快速、大规模地理解这些信息。例如,及时披露公司合并的消息会对交易决策产生重大影响。
3。减少客户挫折感—增强混合机器人的体验
虚拟协助极大地改善了客户体验。有了自助式数字解决方案,消费者可以避免漫长的等待时间,并实时获得最紧迫问题的答案。随着 NLP 技术的改进,“混合”机器人正在取代传统机器人——传统机器人有时不知道如何完全回答查询。
NLP 支持的助手将虚拟和人工支持结合在一起,提供了更好的客户体验,当他们不明白客户想要什么时,可以快速将机器主导的对话转移给人类。在挫败感出现之前,将对话交给现场客服代表是确保有意义的互动和屡获殊荣的客户体验的关键。
像今天的许多品牌一样,可口可乐将虚拟辅助软件与客户服务部门集成在一起,以更好地满足消费者的需求。据报道, Ask Coca-Cola 助手每月成功处理 3 万次对话,减少了电话互动的需求。
总之
随着处理能力的提高,企业正在使用 NLP 等人工智能技术,通过情感分析来更好地理解客户意图,从非结构化数据中提取洞察力,并减轻客户的挫折感。
自然语言处理技术可以比人类更快地分析基于语言的数据,没有偏见,不会累,也不需要休假。随着每天产生大量数据,全面分析文本(来自每个来源)的能力将成为一项优势。
人工智能中的自然语言处理几乎是人类级别的精确。更糟糕的是,它变聪明了!
深度学习时代之前
早在深度学习时代之前的日子里——那时神经网络更像是一种可怕、神秘的数学好奇心,而不是一种强大的机器学习或人工智能工具——在自然语言处理(NLP) 领域,经典数据挖掘算法有许多令人惊讶的相对成功的应用。似乎像 T2 的垃圾邮件过滤和 T4 的词性标注这样的问题可以用简单易懂的模型来解决。
但并不是每个问题都能这样解决。简单的模型无法充分捕捉语言的微妙之处,如上下文、习语或讽刺(尽管人类也经常在这方面失败)。基于整体摘要的算法(例如词袋)被证明不足以捕捉文本数据的顺序性质,而 n 元语法努力为一般上下文建模,并严重遭受维数灾难。即使是基于嗯的模型也很难克服这些问题,因为它们具有马尔可夫性(比如,它们的无记忆性)。当然,这些方法也用于处理更复杂的 NLP 任务,但不是很成功。
首次突破— Word2Vec
NLP 领域在单词的语义丰富表示形式方面经历了第一次重大飞跃,这是通过应用神经网络实现的…在此之前,最常见的表示方法是所谓的 one-hot 编码,其中每个单词都被转换为唯一的二进制向量,只有一个非零条目。这种方法很大程度上受到稀疏性的影响,并且根本没有考虑特定单词的含义。
图 1: Word2Vec 将单词投影到二维空间上的表示法。
相反,想象从一串单词开始,删除中间的一个,并让神经网络预测给定中间单词的上下文( skip-gram )。或者,要求它基于上下文预测中心单词(即,连续的单词包, CBOW )。当然,这样的模型是没有用的,但事实证明,作为一个副作用,它产生了一个惊人强大的向量表示,保留了单词的语义结构。
进一步的改进
尽管新的强大的 Word2Vec 表示提高了许多经典算法的性能,但仍然需要一种能够捕获文本中顺序依赖关系的解决方案(长期和短期)。这个问题的第一个概念是所谓的香草递归神经网络(RNNs)。传统的 rnn 利用数据的时间特性,在使用存储在隐藏状态中的关于先前单词的信息的同时,将单词顺序地馈送给网络。
图 2: 一个递归神经网络。图片来自一位优秀的 Colah 在 LSTMs 上的 帖子
事实证明,这些网络很好地处理了局部依赖性,但由于消失梯度而难以训练。为了解决这个问题,计算机科学家&机器学习研究人员开发了一种新的网络拓扑结构,称为长短期记忆( LSTM )。LSTM 通过在网络中引入被称为存储单元的特殊单元来解决这个问题。这种复杂的机制允许在不显著增加参数数量的情况下找到更长的模式。许多流行的架构也是 LSTM 的变体,如 mLSTM 或 GRU ,由于存储单元更新机制的智能简化,这些架构显著减少了所需的参数数量。
在计算机视觉中卷积神经网络取得惊人成功之后,它们被纳入 NLP 只是时间问题。今天,1D 卷积是许多成功应用的热门构建模块,包括语义分割,快速机器翻译,以及通用序列到序列学习框架——它胜过递归网络,并且由于更容易并行化,可以更快地训练一个数量级。
👀 卷积神经网络,最初用于解决计算机视觉问题,并在该领域保持最先进水平。了解更多关于他们的应用和功能。
典型的 NLP 问题
有各种各样的语言任务,虽然简单并且是人类的第二天性,但对机器来说却非常困难。这种混淆主要是由于语言上的细微差别,如反语和习语。让我们来看看研究人员正在努力解决的 NLP 的一些领域(大致按照它们的复杂性排序)。
最常见也可能是最简单的是情感分析。本质上,这决定了说话者/作者对某个特定话题(或总体)的态度或情绪反应。可能的情绪有积极的、中立的和消极的。查看这篇伟大的文章关于使用深度卷积神经网络来衡量推文中的情绪。另一个有趣的实验表明,一个深度循环网络可以偶然地学习情绪。
图 3 : 激活用于生成文本下一个字符的网络中的神经元。很明显,它学会了这种情绪,尽管它是在完全无人监督的环境中接受训练的。
前一种情况的自然推广是文档分类,我们解决了一个普通的分类问题,而不是给每篇文章分配三个可能的标志中的一个。根据对算法的综合比较,可以肯定地说深度学习是文本分类的必由之路。
现在,我们开始真正的交易:机器翻译。很长一段时间以来,机器翻译一直是一个严峻的挑战。重要的是要理解,这是一个完全不同的任务,与前两个我们已经讨论过。对于这个任务,我们需要一个模型来预测单词序列,而不是标签。机器翻译清楚地表明了深度学习有什么值得大惊小怪的,因为当涉及到序列数据时,它已经是一个令人难以置信的突破。在这篇博文中,你可以读到更多关于如何——是的,你猜对了——递归神经网络处理翻译的内容,在这篇中,你可以了解它们如何实现最先进的结果。
假设你需要一个自动的文本摘要模型,你希望它只提取文本中最重要的部分,同时保留所有的意思。这需要一种算法,它可以理解整个文本,同时专注于承载大部分意思的特定部分。这个问题被注意机制巧妙地解决了,它可以作为模块引入到端到端解决方案中。
最后,还有问答,这是你能得到的最接近人工智能的东西。对于这项任务,模型不仅需要理解一个问题,还需要对感兴趣的文本有全面的理解,并确切地知道在哪里寻找答案。对于一个问题回答解决方案的详细解释(当然是使用深度学习),请查看这篇文章。
图 4: 漂亮 可视化 一个被训练成将英语翻译成法语的递归神经网络中的注意机制。
由于深度学习为各种类型的数据(例如,文本和图像)提供了向量表示,因此您可以建立模型来专攻不同的领域。研究人员就是这样想出了 视觉问答 。任务很“琐碎”:只需回答一个关于某个图像的问题。听起来像是 7 岁小孩的工作,对吧?尽管如此,深度模型是第一个在没有人类监督的情况下产生合理结果的模型。该模型的结果和描述见本文。
概述
现在你知道了。由于计算问题,深度学习最近才出现在 NLP 中,我们需要学习更多关于深度神经网络的知识,以了解它们的能力。但一旦我们做到了,它就永远改变了游戏。
转帖: 用 NLP 提升你的解决方案,sigmoid id . io
python 中多列的自然语言处理
当我第一次听说 NLP 的时候,我很惊讶,也有点不知所措。用最基本的术语来说,NLP 或自然语言处理通过文本数据并检查每个特征(在这种情况下是单词或字符)对于我们的因变量的预测能力的重要性。鉴于语言在我们的世界中的重要性,将数据科学扩展到定性变量的能力似乎是必不可少的。根据数据集的大小,这可能意味着它会遍历 1,000 个要素,因此计算量可能会很大,但有一些方法可以限制处理需求。
有几种不同类型的 NLP,我们在训练营中使用的主要是 CountVectorizer (CVec)和 TFIDF Vectorizer(词频逆文档频率)。知道如何在不同种类的数据中进行选择,通常只是对您的数据(或数据的子集)进行测试,看看哪一种性能更好。考虑这种差异的最简单的方法是,CVec 将计算一个单词在文档中出现的次数,而 TFIDF 将比较一个单词在文档中出现的次数和该短语在多少个文档中出现。如果一个单词只在一小部分文档中出现,TFIDF 将赋予它更高的“特征重要性”
我发现使用 NLP 的主要缺点是,除了计算量大以外,您只能在一列上运行 CVec 或 TFIDF。我正在处理从 Indeed.com 搜集的数据,我想检验一下职位和城市是否会对工资是高于还是低于中位数产生预测作用。我选择使用 CVec 来做这件事,纯粹是因为它对我正在处理的数据更有预测性。
Here’s the head of my data. Avg = salary averaged if given range, Dumsal= a dummy variable if my average salary was above or below my median salary of $106,750
我开始在我的数据顶部导入一些东西。因为我要做交叉验证和训练测试分割,所以我需要导入一些你可能不需要的东西。
在我完成导入之后,我设置了我的 X 和 y 变量,并完成了我的训练测试分割。我选择了 0.33 的测试大小,因为这是行业标准。由于我试图根据我的职位对我的虚拟工资变量进行分类,我将它们设置为我的 X 和 y。我最初试图放入多个列,当我发现我不能时,我决定写这篇博客!
当您创建 CountVectorizer 时,有一些事情需要记住:
- 你需要在 X 列车上适应和转换
- 您只在 X_test 上转换
- 在转换过程中,将它们都更改为数据帧,以便稍后可以对它们执行回归
我们只适合 X_train,就像我们在 python 中处理过的其他模型一样。我们希望只根据训练数据来训练模型,然后我们想对测试数据做的就是根据我们的训练数据来转换它。我们仍然需要这两个都是数据帧,所以我们将 fit 中制作的矩阵转换为密集矩阵,这样它们就可以很容易地制作成数据帧。
在将我的 X_test 转换为密集矩阵之后,我想检查并确保我的 X_train 数据帧和 y_train 具有相同数量的特征,并且对于我的测试集也是相同的。当我发现它们确实如此时,我可以用这些术语进行回归!
我对这些数据框架进行了线性回归,发现我的得分在 0.77 左右。虽然这还不错,但我希望做得更好,所以我决定也在我的城市专栏上做 NLP!
I followed the same steps as above and fit/transformed a CVec on my city column as well.
下一步对于在多个列上完成 NLP 是最重要的。在为您选择的每个列拟合/转换 CVecs 之后,您需要将它们连接起来,这样我们就可以将它们作为一个数据帧来运行分类算法。因为我们希望它们成为特性,所以我们将沿着列连接它们,而不是将它们放在数据帧的底部。
我们可以看到,我的新 X_train 数据框架和 y_train 数据框架具有相同的列数,因此我们可以成功地对它们运行回归。最后一步只是再次运行逻辑回归,看看这个模型是否比我的第一个模型更好。
因为这个分数明显比我的原始分数差,所以我选择不使用串联数据帧,但是,嘿,我在这个过程中学到了一些新东西!
[## sk learn . feature _ extraction . text . count vectorizer-sci kit-learn 0 . 18 . 1 文档
本文档适用于 sci kit-学习版本 0.18.1 -其他版本
scikit-learn.org](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html)
用 Python 进行自然语言处理
有许多方法可以使用自然语言处理,也称为 NLP。在这篇博客中,我们将讨论计数矢量器,以及它在制作模型时如何有用。
让我们从什么是 NLP 开始。这是一种将单词转化为数值的方法,这样我们就可以根据这些数据进行分析并建立预测模型。
我喜欢用例子来展示,所以今天我们将根据类别用不同的电子邮件从 sklearn 中读取数据集。我们将只阅读其中的四个类别,并在它们上面练习使用 nlp。我们将制作一个模型来确定电子邮件是否属于某个类别。
首先,我们需要阅读一些字典
**import** **pandas** **as** **pd**
**import** **numpy** **as** **np**
**import** **matplotlib.pyplot** **as** **plt**
%matplotlib inline
*# Getting that SKLearn Dataset*
**from** **sklearn.datasets** **import** fetch_20newsgroups
这给了我们一些可以使用的库,并通过我们的电子邮件信息从 sklearn 导入数据集。
categories = [
'alt.atheism',
'talk.religion.misc',
'comp.graphics',
'sci.space',
]
*# Setting out training data*
data_train = fetch_20newsgroups(subset='train',categories=categories,
shuffle=True, random_state=42,
remove=('headers', 'footers', 'quotes'))
*# Setting our testing data*
data_test = fetch_20newsgroups(subset='test', categories=categories,
shuffle=True, random_state=42,
remove=('headers', 'footers', 'quotes'))
在这个代码块中,我们决定了我们想要的 4 个类别。然后制作我们的训练集和测试集。我们将类别设置为与上述四个类别相同,并删除了页眉、页脚和引号。
data_train.keys() #tells us what keys are in our datasetlen(data_train['data']) #looks at the length of the data_train
len(data_train['target'])#looks at the length of the data_test
我们要确保数据列和目标列的长度相等,否则在建模时会出现错误。
**from** **sklearn.feature_extraction.text** **import** CountVectorizer
*# Setting the vectorizer just like we would set a model*
cvec = CountVectorizer(stop_words='english')*# Fitting the vectorizer on our training data* cvec.fit(data_train['data'])
我们将 cvec 设置为等于 CountVectorizer,这样以后就可以方便地调用它了。此外,我在 stop_words = 'english '中添加了。这是用我们常用的英语单词,如“the”、“a”、“and”等。这是有用的,所以我们不要把我们的模型建立在这些实际上没有预测意义的单词上。然后,它需要安装在 data_train[‘data’]上。
X_train = pd.DataFrame(cvec.transform(data_train['data']).todense(),
columns=cvec.get_feature_names())
这一步是转换训练数据。的。todense()将把它变成一个矩阵(并且很容易转换成 pandas DataFrame)。使 columns = cvec.get_feature_names()将使每一列等于我们正在分析的一个不同的单词。
*# Which words appear the most.*
word_counts = X_train.sum(axis=0)
word_counts.sort_values(ascending = False).head(20)
然后,我制作了一个单词计数器,它将查看我们转换后的 X_train 并向上计数单词,制作 sort_values(升序= False)将从最高计数的单词开始,到最低计数的单词。
names = data_train['target_names']
名称现在等于我们最初读入数据帧的 4 个类别。
X_test = pd.DataFrame(cvec.transform(data_test['data']).todense(),
columns=cvec.get_feature_names())
然后,我们需要像上面对 X_train 所做的那样转换我们的 X_test。
y_test = data_test['target']
并设置我们的 y_test 来测试我们的模型有多精确
**from** **sklearn.linear_model** **import** LogisticRegression
lr = LogisticRegression()
lr.fit(X_train, y_train)
lr.score(X_test, y_test)
我们的最后一步是在 X_train 和 y_train 上拟合我们的模型,然后在 X_test 和 y_test 上测试我们的模型。lr.score(X_test,y_test)让我们知道我们的模型执行得有多好。在这种情况下,它大约有 75%准确性。
用 Quora 进行自然语言处理
在推荐系统中使用单词嵌入之后,我现在想使用这些嵌入来创建一个自然语言处理神经网络。
内容:
0.设置和单词嵌入
- 我的第一个自然语言处理神经网络
- 清理文本
- 添加附加功能
**挑战:**正确评估 Quora 上的问题是否互相重复。
Kaggle 提供的数据来自一个 csv 文件,共有五行:
id 3
qid1 7
qid2 8
question1 Why am I mentally very lonely? How can I solve...
question2 Find the remainder when [math]23^{24}[/math] i...
is_duplicate 0
其中,如果问题的意图相同,is_duplicate 为 1,否则为 0。我想用这些数据来预测测试集中的问题是否重复。
0.设置
首先,我需要将这个 csv 文件中的数据转换成可以输入神经网络的东西。
0.1 标记和填充问题
做到这一点的第一步是将问题中的单词转换成我的神经网络更容易比较的东西。幸运的是,我已经使用单词嵌入完成了这项工作(我在这里探索了),它将单词转换成浮点数组。
Keras 的神经网络也喜欢接收相同大小的所有输入。因为不是所有的问题都是一样长的,所以我需要选择一个问题长度(每个问题我将输入多少单词到我的神经网络中)。
为了找到最佳问题长度,让我们看看问题长度在整个数据集中是如何变化的:
The longest question length is 237 words
The mean question lengths are 10.9422317754 and 11.1820410203
这不是很有帮助。我还可以画出问题长度的频率:
A plot of the frequency of question lengths in the dataset
这更能说明问题!这个数据集有一条很长的尾巴,但是绝大多数问题都少于 35 个单词。选择这个作为我的最大问题长度将比 237 个单词计算效率高得多(我将使用 300 维单词嵌入,所以这样我将训练 35300 个数组,而不是 237300 个数组)。
MAX_LENGTH = 35
既然我已经定义了这个长度,我想把我的问题变成由单词 embeddings 定义的 35 个元素的数组。
一种低效的方法是将字符串传递给神经网络,并有一个带有字典的嵌入层来嵌入单词。更好的方法是用一个整数表示每个单词,并有一个字典将每个单词翻译成整数。
Keras 有一个名为 Tokenizer 的函数可以做到这一点。我可以选择有多少单词被编入索引(在我的例子中,我选择了数据集中最常见的 20,000 个单词)
tokenizer = Tokenizer(nb_words = 20000)
tokenizer.fit_on_texts(train.question1 + train.question2)q1sequence = tokenizer.texts_to_sequences(train.question1)
q2sequence = tokenizer.texts_to_sequences(train.question2)word_index = tokenizer.word_index
word_index
现在是一个字典,它将每个单词链接到一个唯一的整数(例如:’replaces': 28265
)。
Keras 还有一个确保所有输入序列长度相同的功能:
q1_input = pad_sequences(q1sequence, maxlen = MAX_LENGTH)
q2_input = pad_sequences(q2sequence, maxlen = MAX_LENGTH)
我几乎准备好训练神经网络了!
0.2.添加单词嵌入
最后一步是获取这些输入,这些输入现在是长度为 35 的整数数组(其中每个整数都可以链接到一个单词),并添加由单词嵌入捕获的有用信息。
我不能只为包含单词 embeddings 的输入创建一个数组,因为生成的数组太大了,它占用了 python 的所有内存(我试过了,当我把它保存为文本文件时,它是 18Gb)。
一个更好的方法是给我的神经网络添加一个嵌入层,其中每个输入只有在通过网络时才被赋予它的嵌入。为此,我需要制作另一个字典,它接受q1_input
和q2_input
中的整数,并将它们翻译成各自的单词嵌入。
第一步是打开 Glove 嵌入,这是一个文本文件,并将它们转换成一个字典。然后我可以创建一个嵌入矩阵,它包含所有的手套嵌入,并将它们(通过索引)链接到word_index
中的所有单词。
当然,并不是我的数据中的每个单词都有一个手套式的等价物(例如,有些人会拼错单词);如果是这种情况,我让向量只有零。
有了这个嵌入矩阵,我就可以创建一个嵌入层,它将成为我的神经网络的输入层:
embedding_layer = Embedding(len(word_index) + 1,
EMBEDDING_DIM,
weights=[embedding_matrix],
input_length=MAX_LENGTH,
trainable=False)
我现在有了训练神经网络的所有要素!
1.我的第一个自然语言处理神经网络
递归神经网络(我在这里看的是)有很大的意义;为了让我的神经网络理解一个问题背后的意图,它需要记住第一个单词是什么,即使它到了第十个单词。
我还想让我的神经网络分别考虑这两个问题,从中提取意义,然后再一起考虑。
The architecture of my neural network. The LSTM (recurrent) layers extract meaning from the questions, before a dense layer compares them. A batch normalization layer was present between every layer, and dropout was used to prevent overfitting. Note: here is a fantastic blog post on how LSTMs work.
训练该神经网络 5 个时期产生 1.4161 的确认损失:
Epoch 5/5
322894/322894 [==============================] - 131s - loss: 0.6615 - val_loss: 1.4161
这不是一个很好的结果,但是当你考虑我是如何处理这个问题的时候,这是有意义的;我根本没有对问题进行预处理。这势必会降低我的网络的准确性。例如,在 Quora 上,人们很容易出现拼写错误。
事实上,在我的word_index
字典中的 96500 个单词中,有 36169 个没有手套的对等词。这意味着超过 1/3 的被发送到我的神经网络的嵌入数组都是零!
2.清理文本
让我们来看看 Quora 数据集中的一些问题:
question 1: Which one dissolve in water quikly sugar, salt, methane and carbon di oxide?
question 2: Which fish would survive in salt water?
单词“quikly”拼错了,并且没有手套的对等词,尽管很明显作者想要写“quickly ”,事实也的确如此。
幸运的是,一个 Kaggle 用户(Currie32)已经找到了一个清理文本的好方法,我可以在我的模型中实现它。它去掉了无用的词(如the
),并清除了文本中明显的拼写错误。例如,相同的问题对后清理是
question 1: one dissolve in water quickly sugar salt methane carbon di oxide
question 2: fish would survive in salt water
在干净的文本上运行模型(注意:这需要重新制作word_index
字典和嵌入矩阵)在 5 个时期后产生 1.0955 的验证损失,明显更好的性能:
The output of my neural network with cleaned text. For comparison, the validation loss of the first network is plotted in red as ‘old val_loss’.
虽然这个性能比最好的 Kaggle 分数差了很多,但是验证损失根本没有收敛。
收敛训练
Training to convergence yielded a minimum loss of 0.4008. Overfitting still occurs.
注意:作为 Keras 的一个怪癖,这个模型在 Tensorflow 上的训练比在 Theano 上好得多。
虽然这是一个不坏的模型(它有 81%的准确率),但它与 Kaggle 最好的提交材料相去甚远。为了与 Kaggle 的排行榜竞争,在训练我的神经网络时,我还需要考虑一件事:
3.(奖金!)添加附加功能
Kaggle 将泄漏定义为“在训练数据中创建意外的附加信息”。换句话说,除了问题本身,我还能从我得到的训练数据中提取什么来改进我的模型?
因为这些特征是在进行竞赛时意外创建的,所以它们不能真实地反映数据集(即 Quora 本身不能使用这些特征来识别问题是否重复,这些特征对于如何创建该数据集是唯一的)。
有两个额外的特征被证明非常能预测问题是否是重复的:
仅仅增加这两个泄漏就减少了我在 5 个纪元后对0.7754
的验证损失!(与清理数据集的1.0955
和最初使用 Theano 后端时的1.4161
相比)。
结论
从这里可以得到一些启示:
- lstm 很厉害!清理文本将“空”向量的百分比从 37%降低到 29%,因此即使只有 71%的单词,网络也能够以 80%的准确率正确判断问题的意图。
- **我输入数据的方式很重要:**除了清理数据,在训练收敛时改进我的网络的一个重要步骤是重复数据,因此问题 1 的输入是
question1 + question2
,问题 2 的输入是question 2 + question 1
;事后看来,这完全有道理,因为问题的顺序与我的网络是否认为它们是重复的没有关系,这很容易增加我的网络可用的训练数据量。 - 寻找数据集中存在但不明显的额外数据可以显著提高我的网络性能。与其盲目地建立一个网络并把数据扔向它,不如看看数据集来判断什么可能是重要的。