86

有关“苹果”的 50 条推文,请参见下文。我已经手动标记了有关 Apple Inc. 的正面匹配项。它们在下面标记为 1。

这里有几行:

1|“@chrisgilmer: Apple targets big business with new iOS 7 features http://bit.ly/15F9JeF ”. Finally.. A corp iTunes account!
0|“@Zach_Paull: When did green skittles change from lime to green apple? #notafan” @Skittles
1|@dtfcdvEric: @MaroneyFan11 apple inc is searching for people to help and tryout all their upcoming tablet within our own net page No.
0|@STFUTimothy have you tried apple pie shine?
1|#SuryaRay #India Microsoft to bring Xbox and PC games to Apple, Android phones: Report: Microsoft Corp... http://dlvr.it/3YvbQx  @SuryaRay

这是总数据集: http: //pastebin.com/eJuEb4eB

我需要建立一个分类“Apple”(Inc)的模型。从其余的。

我不是在寻找机器学习的一般概述,而是在寻找代码中的实际模型(首选Python )。

4

13 回答 13

73

您要查找的内容称为Named Entity Recognition。它是一种统计技术,(最常见)使用条件随机场来查找命名实体,基于已接受过有关命名实体的培训。

本质上,它查看单词的内容和上下文,(回顾和向前几个单词),以估计单词是命名实体的概率。

好的软件可以查看单词的其他特征,例如它们的长度或形状(例如“Vcv”,如果它以“Vowel-consonant-vowel”开头)

一个非常好的库(GPL)是斯坦福的NER

这是演示:http ://nlp.stanford.edu:8080/ner/

一些示例文本尝试:

我在苹果总部吃苹果,想起了酷玩乐队的女儿苹果马丁

(3class 和 4class 分类器是正确的)

于 2013-06-29T07:25:37.540 回答
39

我会这样做:

  1. 将句子拆分成单词,规范化它们,构建字典
  2. 对于每个单词,存储它们在关于公司的推文中出现的次数,以及它们在关于水果的推文中出现的次数——这些推文必须由人工确认
  3. 当有新推文出现时,在字典中查找推文中的每个单词,计算加权分数——与公司相关的经常使用的单词会得到高公司分数,反之亦然;很少使用的词,或者与公司和水果一起使用的词,不会有太多的分数。
于 2013-06-27T20:29:02.657 回答
31

我有一个解决这个问题的半工作系统,使用 scikit-learn 开源,有一系列博客文章描述了我在做什么。我要解决的问题是词义消歧(选择多个词义选项之一),这与命名实体识别不同。我的基本方法与现有解决方案有一定的竞争力,并且(至关重要)是可定制的。

有一些现有的商业 NER 工具(OpenCalais、DBPedia Spotlight 和 AlchemyAPI)可能会为您提供足够好的商业结果 - 请先尝试这些!

我将其中一些用于客户项目(我在伦敦使用 NLP/ML 进行咨询),但我对他们的召回率(精确度和召回率)不满意。基本上它们可以是精确的(当他们说“这是苹果公司”时,他们通常是正确的),但召回率很低(即使对于人类来说,推文显然是关于苹果公司的,他们也很少说“这是苹果公司”)。我认为构建一个为推文量身定制的开源版本将是一项智力上有趣的练习。这是当前代码: https ://github.com/ianozsvald/social_media_brand_disambiguator

我会注意 - 我不是试图用这种方法解决广义的词义消歧问题,只是当你已经有了他们的名字时的品牌消歧(公司、人等)。这就是为什么我相信这种直截了当的方法会奏效。

我是六周前开始的,它是使用 scikit-learn 用 Python 2.7 编写的。它使用一种非常基本的方法。我使用 1-3 n-grams使用二进制计数矢量化器进行矢量化(我只计算一个单词是否出现,而不是出现多少次)  。我不使用 TF-IDF 进行扩展(当您的文档长度可变时,TF-IDF 很好;对我来说,推文只有一两个句子,而且我的测试结果没有显示使用 TF-IDF 有所改善)。

我使用基本的分词器,它非常基本但非常有用。它会忽略 @#(因此您会丢失一些上下文),当然也不会扩展 URL。然后我使用逻辑回归进行训练,似乎这个问题在某种程度上是线性可分的(一个类的很多术语对于另一个类不存在)。目前我正在避免任何阻止/清理(我正在尝试可能工作的最简单的事情)。

该代码有一个完整的 README,您应该能够相对轻松地摄取您的推文,然后按照我的建议进行测试。

这适用于苹果,因为人们不吃也不喝苹果电脑,我们也不打字或玩水果,所以这些词很容易被分为一类或另一类。在考虑电视节目的#definance 之类的内容时,这种情况可能不成立(人们也使用#definance 来表示阿拉伯之春、板球比赛、考试复习和乐队)。这里很可能需要更聪明的方法。

我有一系列描述这个项目的博客文章,包括我在 BrightonPython 用户组的一个小时的演示(它变成了 DataScienceLondon 的 140 人的简短演示)。

如果您使用诸如 LogisticRegression 之类的东西(您可以在其中获得每个分类的概率),您可以只选择有信心的分类,这样您就可以通过与召回进行交易来强制实现高精度(因此您可以获得正确的结果,但结果较少)。您必须将其调整到您的系统。

这是使用 scikit-learn 的一种可能的算法方法:

  • 使用 Binary CountVectorizer(我认为短消息中的术语计数不会增加太多信息,因为大多数单词只出现一次)
  • 从决策树分类器开始。它将具有可解释的性能(有关示例,请参见使用决策树过度拟合)。
  • 转向逻辑回归
  • 调查分类器产生的错误(读取 DecisionTree 的导出输出或查看 LogisticRegression 中的系数,通过 Vectorizer 处理错误分类的推文,以查看底层的 Bag of Words 表示形式 - 那里的标记将少于你从原始推文开始 - 有足够的分类吗?)
  • 查看我在https://github.com/ianozsvald/social_media_brand_disambiguator/blob/master/learn1.py中的示例代码,了解此方法的工作版本

需要考虑的事项:

  • 您需要更大的数据集。我正在使用 2000 条带标签的推文(我花了五个小时),并且至少你想要一个平衡的集合,每个班级 > 100 条(请参阅下面的过度拟合说明)
  • 改进标记器(使用 scikit-learn 非常容易)以将 #@ 保留在标记中,并可能添加大写品牌检测器(如用户 @user2425429 注释)
  • 当事情变得更难时,考虑一个非线性分类器(如上面@oiez 的建议)。就我个人而言,我发现 LinearSVC 比逻辑回归做得更差(但这可能是由于我尚未减少的高维特征空间)。
  • 一个特定于推文的词性标注器(我的拙见不是@Neil 建议的斯坦福 - 根据我的经验,它在糟糕的 Twitter 语法上表现不佳)
  • 一旦你有很多令牌,你可能想要做一些降维(我还没有尝试过 - 请参阅我关于 LogisticRegression l1 l2 惩罚的博客文章)

回覆。过拟合。在我的包含 2000 个项目的数据集中,我有一个来自 Twitter 的“苹果”推文的 10 分钟快照。大约 2/3 的推文用于苹果公司,1/3 用于其他苹果用途。我提取每个类的平衡子集(我认为大约 584 行),并进行五重交叉验证以进行训练。

因为我只有 10 分钟的时间窗口,所以我有很多关于同一主题的推文,这可能就是为什么我的分类器相对于现有工具做得这么好 - 它会过度拟合训练特征而没有很好的泛化能力(而现有的商业工具在这个 snapshop 上表现更差,但在更广泛的数据集上更可靠)。我将扩大我的时间窗口,将其作为后续工作进行测试。

于 2013-07-06T10:43:34.890 回答
12

您可以执行以下操作:

  1. 制作包含它们在水果和公司相关推文中出现次数的单词字典。这可以通过向它提供一些我们知道其倾向的示例推文来实现。

  2. 使用足够的先前数据,我们可以找出关于苹果公司的推文中出现单词的概率。

  3. 将单词的单个概率相乘得到整条推文的概率。

一个简化的例子:

p_f = 水果推文的概率。

p_w_f = 水果推文中出现单词的概率。

p_t_f = 推文中所有单词出现水果推文的组合概率 = p_w1_f * p_w2_f * ...

p_f_t = 给定特定推文的水果概率。

p_c、p_w_c、p_t_c、p_c_t是公司的各自值。

添加了值为 1 的拉普拉斯平滑器,以消除我们数据库中不存在的新词的零频率问题。

old_tweets = {'apple pie sweet potatoe cake baby https://vine.co/v/hzBaWVA3IE3': '0', ...}
known_words = {}
total_company_tweets = total_fruit_tweets =total_company_words = total_fruit_words = 0

for tweet in old_tweets:
    company = old_tweets[tweet]
    for word in tweet.lower().split(" "):
        if not word in known_words:
            known_words[word] = {"company":0, "fruit":0 }
        if company == "1":
            known_words[word]["company"] += 1
            total_company_words += 1
        else:
            known_words[word]["fruit"] += 1
            total_fruit_words += 1

    if company == "1":
        total_company_tweets += 1
    else:
        total_fruit_tweets += 1
total_tweets = len(old_tweets)

def predict_tweet(new_tweet,K=1):
    p_f = (total_fruit_tweets+K)/(total_tweets+K*2)
    p_c = (total_company_tweets+K)/(total_tweets+K*2)
    new_words = new_tweet.lower().split(" ")

    p_t_f = p_t_c = 1
    for word in new_words:
        try:
            wordFound = known_words[word]
        except KeyError:
            wordFound = {'fruit':0,'company':0}
        p_w_f = (wordFound['fruit']+K)/(total_fruit_words+K*(len(known_words)))
        p_w_c = (wordFound['company']+K)/(total_company_words+K*(len(known_words)))
    p_t_f *= p_w_f
    p_t_c *= p_w_c

    #Applying bayes rule
    p_f_t = p_f * p_t_f/(p_t_f*p_f + p_t_c*p_c)
    p_c_t = p_c * p_t_c/(p_t_f*p_f + p_t_c*p_c)
    if p_c_t > p_f_t:
        return "Company"
    return "Fruit"
于 2013-07-04T10:48:44.887 回答
9

如果您在使用外部库时没有问题,我建议您使用scikit-learn,因为它可能比您自己编写的任何代码都做得更好、更快。我会做这样的事情:

建立你的语料库。为了清楚起见,我做了列表推导,但根据您的数据存储方式,您可能需要做不同的事情:

def corpus_builder(apple_inc_tweets, apple_fruit_tweets):
    corpus = [tweet for tweet in apple_inc_tweets] + [tweet for tweet in apple_fruit_tweets]
    labels = [1 for x in xrange(len(apple_inc_tweets))] + [0 for x in xrange(len(apple_fruit_tweets))]
    return (corpus, labels)

重要的是您最终会得到两个如下所示的列表:

([['apple inc tweet i love ios and iphones'], ['apple iphones are great'], ['apple fruit tweet i love pie'], ['apple pie is great']], [1, 1, 0, 0])

[1, 1, 0, 0] 表示正负标签。

然后,您创建一个管道!Pipeline 是一个 scikit-learn 类,它可以轻松地将文本处理步骤链接在一起,因此您只需在训练/预测时调用一个对象:

def train(corpus, labels)
    pipe = Pipeline([('vect', CountVectorizer(ngram_range=(1, 3), stop_words='english')),
                        ('tfidf', TfidfTransformer(norm='l2')),
                        ('clf', LinearSVC()),])
    pipe.fit_transform(corpus, labels)
    return pipe

在 Pipeline 内部有三个处理步骤。CountVectorizer 对单词进行标记、拆分、计数并将数据转换为稀疏矩阵。TfidfTransformer 是可选的,您可能希望根据准确度等级将其删除(进行交叉验证测试和网格搜索以获取最佳参数有点涉及,所以我不会在这里讨论)。LinearSVC 是一种标准的文本分类算法。

最后,您预测推文的类别:

def predict(pipe, tweet):
    prediction = pipe.predict([tweet])
    return prediction

同样,推文需要在列表中,所以我假设它作为字符串输入函数。

把所有这些放到一个类或其他什么中,你就完成了。至少,在这个非常基本的例子中。

我没有测试这段代码,所以如果你只是复制粘贴它可能不起作用,但如果你想使用 scikit-learn,它应该让你知道从哪里开始。

编辑:试图更详细地解释这些步骤。

于 2013-07-03T00:43:56.903 回答
6

使用决策树似乎可以很好地解决这个问题。至少它比具有我选择的特征的朴素贝叶斯分类器产生更高的准确性。

如果您想尝试一些可能性,可以使用以下代码,该代码需要安装 nltk。nltk 书也可在线免费获得,因此您可能想了解一下所有这些实际上是如何工作的:http: //nltk.googlecode.com/svn/trunk/doc/book/ch06.html

#coding: utf-8
import nltk
import random
import re

def get_split_sets():
    structured_dataset = get_dataset()
    train_set = set(random.sample(structured_dataset, int(len(structured_dataset) * 0.7)))
    test_set = [x for x in structured_dataset if x not in train_set]

    train_set = [(tweet_features(x[1]), x[0]) for x in train_set]
    test_set = [(tweet_features(x[1]), x[0]) for x in test_set]
    return (train_set, test_set)

def check_accurracy(times=5):
    s = 0
    for _ in xrange(times):
        train_set, test_set = get_split_sets()
        c = nltk.classify.DecisionTreeClassifier.train(train_set)
        # Uncomment to use a naive bayes classifier instead
        #c = nltk.classify.NaiveBayesClassifier.train(train_set)
        s += nltk.classify.accuracy(c, test_set)

    return s / times


def remove_urls(tweet):
    tweet = re.sub(r'http:\/\/[^ ]+', "", tweet)
    tweet = re.sub(r'pic.twitter.com/[^ ]+', "", tweet)
    return tweet

def tweet_features(tweet):
    words = [x for x in nltk.tokenize.wordpunct_tokenize(remove_urls(tweet.lower())) if x.isalpha()]
    features = dict()
    for bigram in nltk.bigrams(words):
        features["hasBigram(%s)" % ",".join(bigram)] = True
    for trigram in nltk.trigrams(words):
        features["hasTrigram(%s)" % ",".join(trigram)] = True  
    return features

def get_dataset():
    dataset = """copy dataset in here
"""
    structured_dataset = [('fruit' if x[0] == '0' else 'company', x[2:]) for x in dataset.splitlines()]
    return structured_dataset

if __name__ == '__main__':
    print check_accurracy()
于 2013-07-06T10:50:19.823 回答
5

感谢您迄今为止的评论。这是我用 PHP 准备的工作解决方案。我仍然有兴趣从其他人那里听到针对同一解决方案的更多算法方法。

<?php

// Confusion Matrix Init
$tp = 0;
$fp = 0;
$fn = 0;
$tn = 0;
$arrFP = array();
$arrFN = array();

// Load All Tweets to string
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://pastebin.com/raw.php?i=m6pP8ctM');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$strCorpus = curl_exec($ch);
curl_close($ch);

// Load Tweets as Array
$arrCorpus = explode("\n", $strCorpus);
foreach ($arrCorpus as $k => $v) {
    // init
    $blnActualClass = substr($v,0,1);
    $strTweet = trim(substr($v,2));

    // Score Tweet
    $intScore = score($strTweet);

    // Build Confusion Matrix and Log False Positives & Negatives for Review
    if ($intScore > 0) {
        if ($blnActualClass == 1) {
            // True Positive
            $tp++;
        } else {
            // False Positive
            $fp++;
            $arrFP[] = $strTweet;
        }
    } else {
        if ($blnActualClass == 1) {
            // False Negative
            $fn++;
            $arrFN[] = $strTweet;
        } else {
            // True Negative
            $tn++;
        }
    }
}

// Confusion Matrix and Logging
echo "
           Predicted
            1     0
Actual 1   $tp     $fp
Actual 0    $fn    $tn

";

if (count($arrFP) > 0) {
    echo "\n\nFalse Positives\n";
    foreach ($arrFP as $strTweet) {
        echo "$strTweet\n";
    }
}

if (count($arrFN) > 0) {
    echo "\n\nFalse Negatives\n";
    foreach ($arrFN as $strTweet) {
        echo "$strTweet\n";
    }
}

function LoadDictionaryArray() {
    $strDictionary = <<<EOD
10|iTunes
10|ios 7
10|ios7
10|iPhone
10|apple inc
10|apple corp
10|apple.com
10|MacBook
10|desk top
10|desktop
1|config
1|facebook
1|snapchat
1|intel
1|investor
1|news
1|labs
1|gadget
1|apple store
1|microsoft
1|android
1|bonds
1|Corp.tax
1|macs
-1|pie
-1|clientes
-1|green apple
-1|banana
-10|apple pie
EOD;

    $arrDictionary = explode("\n", $strDictionary);
    foreach ($arrDictionary as $k => $v) {
        $arr = explode('|', $v);
        $arrDictionary[$k] = array('value' => $arr[0], 'term' => strtolower(trim($arr[1])));
    }
    return $arrDictionary;
}

function score($str) {
    $str = strtolower($str);
    $intScore = 0;
    foreach (LoadDictionaryArray() as $arrDictionaryItem) {
        if (strpos($str,$arrDictionaryItem['term']) !== false) {
            $intScore += $arrDictionaryItem['value'];
        }
    }
    return $intScore;
}
?>

上述输出:

           Predicted
            1     0
Actual 1   31     1
Actual 0    1    17


False Positives
1|Royals apple #ASGame @mlb @ News Corp Building http://instagram.com/p/bBzzgMrrIV/


False Negatives
-1|RT @MaxFreixenet: Apple no tiene clientes. Tiene FANS// error.... PAGAS por productos y apps, ergo: ERES CLIENTE.
于 2013-06-28T20:59:05.690 回答
4

在您提供的所有示例中, Apple(inc) 被称为A pple 或 apple inc,因此一种可能的方法是搜索:

  • 苹果中的大写“A”

  • 苹果后面的“inc”

  • 诸如“OS”、“操作系统”、“Mac”、“iPhone”等单词/短语...

  • 或它们的组合

于 2013-07-01T19:55:25.893 回答
4

为了简化基于条件随机字段的答案......这里的上下文很大。你会想在那些清楚地向苹果公司展示苹果公司与苹果水果的推文中挑选出来。让我在这里概述可能对您有用的功能列表。有关更多信息,请查看名词短语分块和称为 BIO 标签的东西。见(http://www.cis.upenn.edu/~pereira/papers/crf.pdf

周边词:为前一个词和下一个词构建一个特征向量,或者如果你想要更多特征,可能是前两个词和后两个词。您不希望模型中有太多单词,否则它不会很好地匹配数据。在自然语言处理中,您将希望尽可能保持一般性。

从周围单词中获得的其他特征包括:

第一个字符是否为大写

单词的最后一个字符是否为句点

词的词性(查找词性标记)

单词的文本本身

我不建议这样做,但要提供更多专门针对 Apple 的功能示例:

WordIs(苹果)

NextWordIs(Inc.)

你明白了。将命名实体识别视为描述一个序列,然后使用一些数学来告诉计算机如何计算它。

请记住,自然语言处理是一个基于管道的系统。通常,您将事物分解为句子,转向标记化,然后进行词性标记甚至依赖解析。

这一切都是为了让您获得可以在模型中使用的功能列表,以识别您正在寻找的内容。

于 2013-07-06T12:00:06.320 回答
3

有一个非常好的库用于处理Python中的自然语言文本,称为nltk. 你应该看看它。

您可以尝试的一种策略是查看包含单词“apple”的 n-gram(单词组)。在谈论水果时,有些词更可能出现在“苹果”旁边,而在谈论公司时,有些词更可能出现在“苹果”旁边,您可以使用这些词对推文进行分类。

于 2013-07-05T04:38:10.143 回答
3

使用LibShortText。这个Python实用程序已经针对短文本分类任务进行了调整,并且运行良好。您要做的最大工作就是编写一个循环来选择最佳的标志组合。我用它在电子邮件中进行有监督的语音行为分类,结果准确率高达 95-97%(在 5 折交叉验证期间!)。

它来自LIBSVMLIBLINEAR的制造商,他们的支持向量机(SVM) 实现用于 sklearn 和 cran,因此您可以合理地确保它们的实现没有错误。

于 2013-07-08T06:19:07.473 回答
2

制作一个 AI 过滤器来区分Apple Inc(公司)和苹果(水果)。由于这些是推文,请使用包含 140 个字段的向量定义您的训练集,每个字段都是在位置 X(0 到 139)处写入推文中的字符。如果推文较短,只需给出一个空白值。

然后建立一个足够大的训练集以获得良好的准确性(根据您的口味而定)。为每条推文分配一个结果值,一条Apple Inc推文得到 1(真),一条苹果推文(水果)得到 0。这将是逻辑回归中监督学习的一个案例。

那就是机器学习,通常更容易编码并且性能更好。它必须从你给它的集合中学习,而且它不是硬编码的。

我不知道Python,所以我不能为它编写代码,但是如果你要花更多时间学习机器学习的逻辑和理论,你可能想看看我正在学习的课程。

试试Andrew NgCoursera课程机器学习您将在MATLABOctave上学习机器学习,但是一旦您掌握了基础知识,如果您了解简单的数学(逻辑回归中的简单),您将能够用任何语言编写机器学习。

也就是说,从某人那里获取代码不会使您能够理解机器学习代码中发生的事情。你可能想在这个主题上投入几个小时,看看到底发生了什么。

于 2013-07-04T19:15:20.420 回答
0

我建议避免建议实体识别的答案。因为这个任务首先是文本分类,然后是实体识别(你完全可以在没有实体识别的情况下完成它)。

我认为最快的结果途径将是 spacy + prodigy。Spacy 对英语语言模型进行了深思熟虑,因此您不必构建自己的模型。虽然 prodigy 允许快速创建训练数据集并根据您的需要微调 spacy 模型。

如果您有足够的样品,您可以在 1 天内制作出像样的模型。

于 2019-01-12T12:42:17.767 回答