前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在30分钟内编写一个文档分类器

在30分钟内编写一个文档分类器

作者头像
磐创AI
发布2021-08-05 10:12:30
5230
发布2021-08-05 10:12:30
举报
文章被收录于专栏:磐创AI技术团队的专栏

在我过去的一次采访中,我被要求实现一个模型来对论文摘要进行分类。我们的目标不是要有一个完美的模型,而是要看看我在最短时间内完成整个过程的能力。我就是这么做的。

数据

数据由PubMed数据库的论文摘要组成。PubMed是所有生物医学文献的资料库。管理PubMed的机构NCBI提供了下载论文的API。许多库已经存在,可以用几种语言与API交互。我使用了Python,找到的最简单的库是Bio及其用于这个特定数据库的模块Entrez。

我们导入模块,并配置email,这是必须的,这可以让他们跟踪每秒的请求数。

代码语言:javascript
复制
from Bio import Entrez

Entrez.email = 'your@email.com'
Entrez.api_key = "abcdefghijklmnopqrstuvwxyz42"

为了从PubMed获取文章,我们首先执行一个查询,返回每个文档的元数据,比如它的ID,然后使用ID获取细节(在我的例子中是abstracts)。

代码语言:javascript
复制
def search(query, max_documents=1000):

    handle = Entrez.esearch(db=’pubmed’,
                            sort=’relevance’,
                            retmax=max_documents,
                            retmode=’xml’,
                            term=query)
    results = Entrez.read(handle)

 return results

该函数将在PubMed数据库的参数中执行查询,按相关性对结果进行排序,并将结果数限制为max_documents。

查询实际上非常简单。可以使用文档关键字和逻辑运算符。PubMed文档详细解释了如何构建查询。

在面试中,我被要求获取4个主题的文件。我们通过在查询中指定每个类的相关关键字来实现这一点。

该函数的结果是一个文档详细信息列表,不包含其内容。然后我们使用这些id来获取文档的所有细节。

代码语言:javascript
复制
def fetch_details(id_list):
    handle = Entrez.efetch(db=”pubmed”, id=’,’.join(map(str,                 id_list)),rettype=”xml”, retmode=”text”)

    records = Entrez.read(handle)
    abstracts = [pubmed_article[‘MedlineCitation’][‘Article’]   [‘Abstract’][‘AbstractText’][0] for pubmed_article in records[‘PubmedArticle’] if ‘Abstract’ in pubmed_article[‘MedlineCitation’][‘Article’].keys()]

 return abstracts

函数将获取ID列表并返回一个包含所有摘要的数组。获取特定类的所有摘要的完整函数是:

代码语言:javascript
复制
def get_abstracts_for_class(ab_class):
    list_abstracts = []

    ## 获取类的关键字

    query = " AND ".join(keywords[ab_class])

    res = search(query)
    list_abstracts = fetch_details(res["IdList"])

    return list_abstracts

我将所有关键字保存在字典中,并使用它们构建查询。

我们为每个类调用函数,以获得所有类的所有摘要。最后,我们将它们重新格式化为一个可用的数据帧。

代码语言:javascript
复制
list_all_classes = []

list_all_classes += [{“abs”: a, “class”: 1} for a in list_abs_class1]
list_all_classes += [{“abs”: a, “class”: 2} for a in list_abs_class2]
list_all_classes += [{“abs”: a, “class”: 3} for a in list_abs_class3]
list_all_classes += [{“abs”: a, “class”: 4} for a in list_abs_class4]

abs_df = pd.DataFrame(list_all_classes)
数据清理

同样,这里的目标不是完美地清理数据集,但是需要一个小的预处理。我个人大部分时间都在使用NLTK,但你可以对几乎所有的NLP库执行相同的操作。

代码语言:javascript
复制
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
import string

## 1) 小写化
abs_df[“abs”] = abs_df[“abs”].str.lower()

## 2) 移除标签
abs_df[“abs”] = abs_df.apply(lambda x: re.sub(“<[^>]*>”, “”, x[“abs”]), axis=1)

## 3) 标识化
abs_df[“abs_proc”] = abs_df.apply(lambda x: word_tokenize(x[“abs”]), axis=1)

## 4) 删除标点符号
nltk.download('punkt')
table = str.maketrans(‘’, ‘’, string.punctuation)
abs_df[“abs_proc”] = abs_df.apply(lambda x: [w.translate(table) for w in x[“abs_proc”]], axis=1)

## 5) 去除非α
abs_df[“abs_proc”] = abs_df.apply(lambda x: [w for w in x[“abs_proc”] if w.isalpha()], axis=1)

## 6) 删除停用词
nltk.download('stopwords')
stop_words = set(stopwords.words(‘english’))
abs_df[“abs_proc”] = abs_df.apply(lambda x: [w for w in x[“abs_proc”] if not w in stop_words], axis=1)

## 7) 重新格式化为一个文本
abs_df[“abs_proc_res”] = abs_df.apply(lambda x: ‘ ‘.join(x[“abs_proc”]), axis=1)

我们使用Pandas apply函数的强大功能,对整个数据帧应用相同的处理:

  1. 把所有的文字小写化
  2. 我发现文本中有一些标记,例如以指示粗体文本。即使这些标签可能有重要的意义,但这对于一个1h的练习来说太复杂了。所以我决定用正则表达式删除它们。
  3. 我们首先标记文本:即将其拆分为单个单词列表。
  4. 删除所有标点符号,如问号(?)或逗号(,)。
  5. 我们删除非字母,即数字。
  6. 我们删除停用词。我们首先使用NLTK检索英语停用词词汇表,然后使用它过滤我们的标记。
  7. 最后,我们将处理的数据连接起来。
数据嵌入

如果你熟悉NLP问题,那么你知道处理文本数据时最重要的部分可能是向量表示,即嵌入。在这方面已经取得了很多进展,一些强大的模型已经被提出,如谷歌的伯特或OpenAI的GPT。

然而,这些都是非常棘手的模型,而且绝对不适合1小时的锻炼。而且,对于许多实际问题,一个非常简单的嵌入就足以使数据具有正确的矢量表示。

最简单的可能是TF-IDF。

sklearn库已经有TF-IDF模块,可以直接用于数据帧。

代码语言:javascript
复制
from sklearn.feature_extraction.text import TfidfVectorizer

vec = TfidfVectorizer()
x = vec.fit_transform(abs_df["abs_proc_res"])

此时,我们有一个矩阵X,它对应于我们所有的向量化抽象。然而,看看X的形状,我们注意到了:

代码语言:javascript
复制
print(x.shape)

(25054, 60329)

我们最终会有大量的列(即60329)。这是正常的,因为这个数字对应于整个语料库(即整个数据集)的词汇表的大小。这个数字有两个问题。

首先,它将使模型的训练变得复杂化。

其次,即使我们做了大量的预处理,词汇的大部分词都不会被关联到分类中,因为它们没有添加任何相关信息。

幸运的是,有一种方法可以减少列的数量,同时避免丢失相关信息。最常见的方法是PCA(主成分分析),它将矩阵分解为一组低维的不相关矩阵。我们应用奇异值分解(SVD),它是一种PCA。同样,还有一个sklearn模块来轻松地完成。

代码语言:javascript
复制
from sklearn.decomposition import TruncatedSVD

svd = TruncatedSVD(n_components=100)
res = svd.fit_transform(x)

print(res.shape)
(25054, 100)

我选择将初始矩阵减少到100个特征。这是一个优化的参数:我们越接近初始维度,在减少过程中松散的信息就越少,而少量的信息将降低模型训练的复杂性。

我们现在准备好训练分类器了。

模型

有很多分类模型在外面。支持向量机(SVM)是最简单的理解和实现方法之一。在nutshell中,它将尝试画一条线,尽可能多地将点与每个类分开。

我们还使用交叉验证来更好地表示度量。

代码语言:javascript
复制
from sklearn import svm
from sklearn.model_selection import RepeatedKFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import cross_validate
from numpy import mean
from numpy import std

y = abs_df["class"].values
X = res

cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1)

model = svm.SVC(kernel='linear', C=1, decision_function_shape='ovo')

我们使用线性核,即它将尝试画一条线来分隔数据。其他核也存在于多项式,它试图找到一个多项式函数,更好地分离点。

决策函数设置为ovo,即一对一,这将需要忽略其他类。

我们去训练吧!

代码语言:javascript
复制
metrics = cross_validate(model, res, y, scoring=['precision_macro', 'recall_macro'], cv=cv, n_jobs=-1)


print('Precision: %.3f (%.3f)' % (mean(metrics["test_precision_macro"]), std(metrics["test_precision_macro"])))
print('Recall: %.3f (%.3f)' % (mean(metrics["test_recall_macro"]), -std(metrics["test_recall_macro"])))

-----------------------------------

Precision: 0.740 (0.021)
Recall: 0.637 (0.014)

这里有两个有趣的指标:精确性和召回率。

精度意味着,在预测的文档中,每类预测的正确率为74%,这一点并不差。

另一方面,召回意味着,在某一类的所有文件中,我们能够捕获63%。

结论与展望

如你所见,实现快速分类器相对容易,只需使用机器学习的基础知识。当然这不是完美的,但是当你什么都没有的时候,即使是坏的模型也是可以接受的。

显然,我们可以做很多改进。预处理可能是模型中影响最大的部分。例如,我们可以尝试更复杂的算法,比如BERT,而不是使用TF-IDF。在模型方面,我们还可以尝试其他分类器,甚至可以堆叠多个分类器以获得更好的性能。

也就是说,如果你的目标是拥有一个工作模型来对文档进行分类,那么这是一个很好的起点。

下一步就是把它投入生产!我将在另一篇文章中介绍这一部分。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据
    • 数据清理
      • 数据嵌入
      • 模型
      • 结论与展望
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档