NLP:从头开始的文本矢量化方法

一、说明

        NLP 项目使用文本,但机器学习算法不能使用文本,除非将其转换为数字表示。这种表示通常称为向量,它可以应用于文本的任何合理单位:单个标记、n-gram、句子、段落,甚至整个文档。

        在整个语料库的统计 NLP 中,应用了不同的向量化技术,例如 one-hot、计数或频率编码。在神经 NLP 中,词向量(也称为词嵌入)占主导地位。可以使用预先训练的向量以及复杂神经网络中学习的向量表示。

        本文解释并展示了所有提到的向量化技术的 Python 实现:one-hot 编码、计数器编码(词袋)、词频以及最后的词向量。

本文的技术背景是和几个Python v3.11附加库:gensim v4.3.1pandas v2.0.1numpy v1.26.1nltk v3.8.1scikit-learn v1.2.2所有示例也应该适用于较新的库版本。

本文最初出现在我的博客admantium.com上。

二、要求和使用的 Python 库

        请务必阅读并运行我上一篇文章的要求,以便拥有 Jupyter Notebook 来运行所有代码示例。

对于本文,需要以下库:

Collections

  • Counter用于计算文档中标记数量的对象

Gensim

  • downloader对象允许加载多个预先训练的词向量

Pandas

  • DataFrame用于存储文本、标记和向量的对象

Numpy

  • 创建和使用的几种方法arrays

NLTK

  • PlaintextCorpusReader用于提供对文档的访问、提供标记化方法并计算有关所有文件的统计信息的可遍历对象
  • sent_tokenizerword_tokenizer用于生成令牌
  • stopwords代币减持清单

SciKitLearn

  • Pipeline对象来实现处理步骤链
  • BaseEstimatorTransformerMixin构建代表管道步骤的自定义类

所有示例都需要这些导入和基类:

import numpy as np
import re
from copy import deepcopy
from collections import Counter
from gensim import downloader
from nltk.corpus import stopwords
from nltk.corpus.reader.plaintext import PlaintextCorpusReader
from nltk.tokenize import sent_tokenize, word_tokenize
from sklearn.base import BaseEstimator, TransformerMixin
from time import time

class SciKitTransformer(BaseEstimator, TransformerMixin):
  def fit(self, X=None, y=None):
    return self
  def transform(self, X=None):
    return self

三、基本示例

        根据之前的文章,NLTK PlaintextCorpusReader 将被重用。

        这是该类的更新版本,WikipediaCorpus带有一个附加filter()方法 - 它将词汇表减少为仅文本,没有任何停用词。

class WikipediaCorpus(PlaintextCorpusReader):
    def __init__(self, root_path):
        PlaintextCorpusReader.__init__(self, root_path, r'.*[0-9].txt')

    def filter(self, word):
        #only keep letters, numbers, and sentence delimiter
        word = re.sub('[\(\)\.,;:+\--"]', '', word)
        #remove multiple whitespace
        word = re.sub(r'\s+', '', word)
        if not word in stopwords.words("english"):
            return word.lower()
        return ''

    def vocab(self):
        return sorted(set([self.filter(word) for word in corpus.words()]))

    def max_words(self):
        max = 0
        for doc in self.fileids():
            l = len(self.words(doc))
            max = l if l > max else max
        return max

    def describe(self, fileids=None, categories=None):
        started = time()
        return {
            'files': len(self.fileids()),
            'paras': len(self.paras()),
            'sents': len(self.sents()),
            'words': len(self.words()),
            'vocab': len(self.vocab()),
            'max_words': self.max_words(),
            'time': time()-started
        }

为了使本文中的示例向量简短易懂,该语料库由维基百科有关机器学习的文章的前三个句子组成。

_Source: [Wikipedia](https://en.wikipedia.org/wiki/Artificial_intelligence)_

Artificial intelligence (AI) is intelligence-perceiving, synthesizing, and inferring information-demonstrated by machines, as opposed to intelligence displayed by humans or by other animals.
Example tasks in which this is done include speech recognition, computer vision, translation between (natural) languages, as well as other mappings of inputs.
As machines become increasingly capable, tasks considered to require "intelligence" are often removed from the definition of AI, a phenomenon known as the AI effect. For instance, optical character recognition is frequently excluded from things considered to be AI, having become a routine technology.

使用语料库类来解析这些句子,得到以下统计数据: 词汇量为 49 个单词,总单词数为 113 个。它的大小足以让下面的解释保持简短。

corpus = WikipediaCorpus('ai_sentences')

print(corpus.fileids())
# ['sent1.txt', 'sent2.txt', 'sent3.txt']

print(corpus.describe())
# {'files': 3, 'paras': 3, 'sents': 3, 'words': 91, 'vocab': 40, 'max_words': 32, 'time': 0.01642608642578125}

print(corpus.vocab())
# ['', 'ai', 'animals', 'artificial', 'as', 'become', 'capable', 'computer', 'considered', ..., 'well']

四、一次性编码

        one-hot 编码基于所有文档的总词汇量来表示单词在特定文档中出现的关系。因此,实施需要以下步骤:

  • 计算所有文档的总有序词汇表
  • 迭代每个文档并标记出现的单词

        以下实现构建一个vocab_dict填充有默认浮点值的对象0.0,然后将这些值设置1.0为出现在句子中的每个标记。

class OneHotEncoder(SciKitTransformer):
    def __init__(self, vocab):
        self.vocab_dict = dict.fromkeys(vocab, 0.0)

    def one_hot_vector(self, tokens):
        vec_dict = deepcopy(self.vocab_dict)
        for token in tokens:
            if token in self.vocab_dict:
                vec_dict[token] = 1.0
        vec = [v for v in vec_dict.values()]
        return np.array(vec)

以下是前两个句子的 one-hot 向量:

encoder = OneHotEncoder(corpus.vocab())

sent1 = [word for word in word_tokenize(corpus.raw('sent1.txt'))]
vec1 = encoder.one_hot_vector(sent1)

print(vec1)
# [0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 1. 0. 0. 1. 0. 0.
# 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]

print(vec1.shape)
# (40,)

sent2 = [word for word in word_tokenize(corpus.raw('sent2.txt'))]
vec2 = encoder.one_hot_vector(sent2)

print(vec2)
# [0. 0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 1.
# 0. 1. 1. 0. 0. 0. 0. 1. 0. 0. 1. 0. 1. 1. 1. 1.]

print(vec2.shape)
# (40,)

五、计数器编码

        计数器编码是创建向量的中间形式。基于所有文档的完整有序词汇表,确定文档中所有单词的数量和出现次数。该数字通常按比例缩放,例如按文档的长度。

        这是 Python 中的计数器编码实现。和以前一样,它构建一个vocab_dict填充有默认浮点值 的对象0.0,并为每个文档设置一个值number(word)/len(document)

from collections import Counter

class CountEncoder(SciKitTransformer):
    def __init__(self, vocab):
        self.vocab = dict.fromkeys(vocab, 0.0)

    def count_vector(self, tokens):
        vec_dict = deepcopy(self.vocab)
        token_vec = Counter(tokens)
        doc_length = len(tokens)
        for token, count in token_vec.items():
            if token in self.vocab:
                vec_dict[token] = count/doc_length
        vec = [v for v in vec_dict.values()]
        return np.array(vec)

使用计数器编码会产生以下结果:

encoder = CountEncoder(corpus.vocab())

sent1 = [word for word in word_tokenize(corpus.raw('sent1.txt'))]
vec1 = encoder.count_vector(sent1)

print(vec1)
# [0.         0.         0.03571429 0.         0.03571429 0.
# 0.         0.         0.         0.         0.         0.03571429
# 0.         0.         0.         0.03571429 0.         0.
# 0.03571429 0.         0.         0.07142857 0.         0.
# 0.03571429 0.         0.         0.         0.03571429 0.
# 0.         0.         0.         0.         0.         0.03571429
# 0.         0.         0.         0.        ]

print(vec1.shape)
# (40,)
sent2 = [word for word in word_tokenize(corpus.raw('sent2.txt'))]
vec2 = encoder.count_vector(sent2)

print(vec2)
# [0.         0.         0.         0.         0.06896552 0.
#  0.         0.03448276 0.         0.         0.         0.
#  0.03448276 0.         0.         0.         0.03448276 0.
#  0.         0.         0.03448276 0.         0.         0.03448276
#  0.         0.03448276 0.03448276 0.         0.         0.
#  0.         0.03448276 0.         0.         0.03448276 0.
#  0.03448276 0.03448276 0.03448276 0.03448276]

print(vec2.shape)
# (40,)

六、词频编码

        前两种编码导致的问题是,当与机器学习算法一起使用时,非常罕见的术语没有足够的权重来发挥重要作用。特别是为了解决这个问题,术语频率、术语间接频率指标平衡了大型文档语料库中的罕见术语。详细的数学可以在TfIdf 维基百科文章中研究- 以下是基本摘要:

  • TF,术语频率,是术语在文档中出现的次数除以文档的总长度,以伪代码表示word_occurences_in_doc/doc_len
  • IDF,间接文档频率,是包含某个单词的文档数除以语料库中文档总数的对数,以伪代码表示log(number_of_docs/number_of_docs_containing_word)

实现非常复杂,根据以下考虑因素构建:

  1. 编码器以列表形式接收语料库词汇,并接收以下形式的字典对象{document_name: [tokens]}(否则此实现将与语料库对象耦合得太紧)
  2. 在初始化过程中,会创建一个word_frequency字典,其中包含某个术语在所有文档中出现的频率总数
  3. TfIdf 方法确定文档总数为number_of_docs,文档长度为doc_lenCounter然后,它为文档中的所有单词创建一个 TfIdf值,然后为词汇表中包含的每个单词计算 TfIdf 值
  4. 所有值都转换为 Numpy 数组并返回

这是实现:

class TfIdfEncoder(SciKitTransformer):
    def __init__(self, doc_arr, vocab):
        self.doc_arr = doc_arr
        self.vocab = vocab
        self.word_frequency = self._word_frequency()

    def _word_frequency(self):
        word_frequency = dict.fromkeys(self.vocab, 0.0)
        for doc_name in self.doc_arr:
            doc_words = Counter([word for word in self.doc_arr[doc_name]])
            for word, _ in doc_words.items():
                if word in self.vocab:
                    word_frequency[word] += 1.0
        return word_frequency

    def TfIdf_vector(self, doc_name):
        if not doc_name in self.doc_arr:
            print(f'Document "{doc_name}" not found.')
            return
        number_of_docs = len(self.doc_arr)
        doc_len = len(self.doc_arr[doc_name])
        doc_words = Counter([word for word in self.doc_arr[doc_name]])
        TfIdf_vec = dict.fromkeys(self.vocab, 0.0)
        for word, word_count in doc_words.items():
            if word in self.vocab:
                tf = word_count/doc_len
                idf = np.log(number_of_docs/self.word_frequency[word])
                idf = 1 if idf == 0 else idf
                TfIdf_vec[word] = tf * idf
        vec = [v for v in TfIdf_vec.values()]
        return np.array(vec)

        对于我们只有三个句子的示例,向量足以表示文档,但它们的全部潜力只有在大型校园中才能实现。

doc_list = [doc for doc in corpus.fileids()]
words_list = [corpus.words(doc) for doc in [doc for doc in corpus.fileids()]]
doc_arr = dict(zip(doc_list, words_list))

encoder = TfIdfEncoder(doc_arr, corpus.vocab())
vec1 = encoder.TfIdf_vector('sent1.txt')

print(vec1)
# [0.         0.         0.03433163 0.         0.03125    0.
#  0.         0.         0.         0.         0.03433163 0.03433163
#  0.         0.         0.         0.03433163 0.         0.
#  0.03433163 0.03433163 0.         0.03801235 0.         0.
#  0.01267078 0.         0.         0.         0.03433163 0.03433163
#  0.         0.         0.         0.         0.         0.03433163
#  0.         0.         0.         0.        ]

print(vec1.shape)
# (40,)
vec2 = encoder.TfIdf_vector('sent2.txt')

print(vec2)
# [0.         0.         0.         0.         0.06896552 0.
# 0.         0.03788318 0.         0.         0.         0.
# 0.03788318 0.         0.         0.         0.03788318 0.
# 0.         0.         0.03788318 0.         0.         0.03788318
# 0.         0.03788318 0.03788318 0.         0.         0.
# 0.         0.03788318 0.         0.         0.03788318 0.
# 0.01398156 0.03788318 0.03788318 0.03788318]

print(vec2.shape)
# (40,)

七、词向量

        最终的编码类型是词向量。本质上,每个单词都用一个 n 维向量表示。该向量表示单词之间的细粒度关系,并且它使向量算术能够进行向量的比较和组合,例如满足 的向量代数king + women = queen

        词向量为大规模自然语言处理任务提供了巨大且令人惊讶的价值。三个主要的词向量实现是原始的 Word2Vec、FastText 和 Glove。

       Word2Vec是第一个模型,根据新闻文章进行训练,并使用不同的 n-gram 大小来捕获周围上下文中单词的含义。FastText使用类似的连续 n 元语法方法,但它不仅考虑训练数据中单词的实际上下文,还考虑其他上下文。这改善了稀疏单词的表示并处理训练期间不存在的未知单词。Glove考虑整个语料库,根据训练数据计算词与词的共现矩阵,并构建一个关于采样数据中任何词出现的可能性的概率模型。

        词向量表示训练数据中出现的结构。如果该数据足够大并且接近语料库的文本,则可以使用预训练的向量。否则,他们需要在校园内接受培训。

        在下面的实现中,Gensim库将用于加载预训练的Word2Vec向量并将其应用到语料库中。要使用预训练模型之一,您需要使用 Gensim 助手下载其模型。请注意,模型可能非常大。例如,word2vec-google-news-300模型为 1.6GB,为每个单词提供 300 维向量。

>>> wv = downloader.load('word2vec-google-news-300')
# [=======-------------------------------------------] 15.5% 258.5/1662.8MB downloaded

        矢量化器实现使用与其他结构相同的已知结构。它的实现非常简单:它将处理文档标记列表并输出一个向量,其中包含存在向量表示的每个单词的数值。

class Word2VecEncoder(SciKitTransformer):
    def __init__(self, vocab):
        self.vocab = vocab
        self.vector_lookup = downloader.load('word2vec-google-news-300')

    def word_vector(self, tokens):
        vec = np.array([])
        for token in tokens:
            if token in self.vocab:
                if token in self.vector_lookup:
                    print(f'Add {token}')
                    vec = np.append(self.vector_lookup[token], vec)
        return vec

这是一个示例输出。

encoder = Word2VecEncoder(corpus.vocab())

sent1 = [word for word in word_tokenize(corpus.raw('sent1.txt'))]
vec1 = encoder.word_vector(sent1)

print(vec1)
# [ 0.01989746  0.24707031 -0.23632812 ... -0.24707031  0.05249023
#  0.19824219]

print(vec1.shape)
# (3000,)

sent2 = [word for word in word_tokenize(corpus.raw('sent2.txt'))]
vec2 = encoder.word_vector(sent2)

print(vec2)
# [-0.11767578 -0.13769531 -0.140625   ... -0.03295898 -0.01733398
#  0.13476562]

print(vec2.shape)
# (4500,)

        正如您所看到的,两个句子的向量分别为 3000 和 4500 个值。结果是特定于文档的矩阵,其中每列代表按原样出现的文档标记,列数是列中包含的单词数。

八、结论

        本文展示了如何从头开始实现文本矢量化方法。它展示了 one-hot 编码、计数器编码、TfIdf 频率编码以及 Word2Vec 词向量的实现。它还展示了将所得向量应用于维基百科有关人工智能的文章中的句子时的具体示例。

参考资料:

NLP: Text Vectorization Methods from Scratch | by Sebastian | Oct, 2023 | Medium

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/105244.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决Windows出现找不到mfcm90u.dll无法打开软件程序的方法

今天,我非常荣幸能够在这里与大家分享关于mfc90u.dll丢失的5种解决方法。在我们日常使用电脑的过程中,可能会遇到一些软件或系统错误,其中之一就是mfc90u.dll丢失。那么,mfc90u.dll究竟是什么文件呢?接下来&#xff0c…

十八、字符串(3)

本章概要 正则表达式 基础创建正则表达式量词CharSequencePattern 和 Matcherfinde()组(Groups)start() 和 end()Pattern 标记split()替换操作reset()正则表达式与 Java I/0 正则表达式 很久之前,_正则表达式_就已经整合到标准 Unix 工具…

算法通关村第三关-青铜挑战数组专题

本期大纲 线性表基础线性表概念数组概念 数组的基本操作数组创建和初始化查找一个元素增加一个元素修改一个元素删除一个元素 小题一道 - - 单调数组问题小题一道 - - 数组合并问题小结 线性表基础 线性表概念 我们先搞清楚几个基本概念,在很多地方会看到线性结构…

cmd命令快速打开MATLAB

文章目录 复制快捷方式添加 -nojvm打开 复制快捷方式 添加 -nojvm 打开 唯一的缺点是无法使用plot,这一点比不上linux系统,不过打开速度还是挺快的。

计算机网络 第四章网络层

文章目录 1 网络层的功能2 数据交换方式:电路交换3 数据交换方式:报文交换4 数据交换方式:分组交换5 数据交换方式:数据报方式6 数据交换方式:虚电路方式及各种方式对比7 路由算法及路由协议8 IP数据报的概念和格式9 I…

疫情集中隔离

系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…

WebSocket协议:5分钟从入门到精通

一、内容概览 WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要介绍了针对WebSocket的安全攻击,以及协议是如何抵御类…

EasyAR使用

EazyAR后台管理,云定位服务 建模 需要自行拍摄360度视频,后台上传,由EazyAR工作人员完成构建。 标注数据 需要在unity安装EazyAR插件,在unity场景编辑后,上传标注数据。 uinity标注数据 微信小程序中使用&#x…

TCP 协议的可靠传输机制是怎样实现的?

TCP 协议是一种面向连接的、可靠的、基于字节流的传输层协议。 1 它通过以下几种方法来保证数据传输的可靠性: 检验和:TCP 在发送和接收数据时,都会计算一个检验和,用来检测数据是否在传输过程中发生了错误或损坏。如果检验和不匹…

CSS中 通过自定义属性(变量)动态修改元素样式(以 el-input 为例)

传送门:CSS中 自定义属性(变量)详解 1. 需求及解决方案 需求:通常我们动态修改 div 元素的样式,使用 :style 和 :class 即可;但想要动态修改 如:Element-ui 中输入框(input&#x…

ElasticSearch安装、插件介绍及Kibana的安装与使用详解

ElasticSearch安装、插件介绍及Kibana的安装与使用详解 1.安装 ElasticSearch 1.1 安装 JDK 环境 因为 ElasticSearch 是用 Java 语言编写的,所以必须安装 JDK 的环境,并且是 JDK 1.8 以上,具体操作步骤自行百度 安装完成查看 java 版本 …

【代码随想录01】数组总结

抄去吧,保存去吧!

ES SearchAPI----Query DSL语言

文章目录 Getting Startedmatch_all查询全部sort排序from\size分页_source指定字段 match匹配查询match_phrase短语匹配multi_match多字段匹配range范围查询bool复合查询must必须匹配,可贡献得分must_not必须不匹配,可贡献得分should可有可无&#xff0c…

GoLong的学习之路(八)语法之Map

文章目录 Map初始化方式判断某个键是否存在map的遍历对value值遍历。对key值遍历 使用delete()函数删除键值对按照指定顺序遍历map元素为map的切片值为切片类型的map 做个题吧 Map 哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的…

MFI芯片I2C地址转换(写读转7位传入API接口)

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务 MFI芯片I2C地址转换(写读转7位传入API接口) #define MFI_I2C_CHIP_ADDR 0x10// 芯片写/读 0x20/0x21(写/读) 七位地址 0x10 //zk 使用读地址…

ubuntu18.4(后改为20.4)部署chatglm2并进行基于 P-Tuning v2 的微调

下载驱动 NVIDIA显卡驱动官方下载地址 下载好对应驱动并放在某个目录下, 在Linux系统中安装NVIDIA显卡驱动前,建议先卸载Linux系统自带的显卡驱动nouveau。 禁用nouveau 首先,编辑黑名单配置。 vim /etc/modprobe.d/blacklist.conf 在文件的最后添加…

Windows客户端下pycharm配置跳板机连接内网服务器

问题:实验室服务器仅限内网访问,无法在宿舍(外网)访问实验室的所有内部服务器,但同时实验室又提供了一个外网可以访问的跳板机,虽然可以先ssh到跳板机再从跳板机ssh到内网服务器,但这种方式不方…

Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析

最近DOTS发布了正式的版本, 我们来分享一下DOTS里面Baking核心机制,方便大家上手学习掌握Unity DOTS开发。今天给大家分享的Baking机制中的Filter Baking Output与Prefab In Baking。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础…

Linux区分文件类型,file指令,目录权限,umask掩码,共享文件,Linux中的一些有趣指令

file指令,Linux区分文件类型,目录权限,umask掩码,共享文件,Linux中的一些有趣指令 1.Linux中是如何区分文件类型的2. file指令3.目录权限4.umask掩码5.粘滞位6.Linux中的一些有趣指令 所属专栏:Linux学习❤…

海外广告投放保姆级教程,如何使用Quora广告开拓新流量市场?

虽然在Quora 上学习广告相对容易,但需要大量的试验和错误才能找出最有效的方法。一些广告技巧可以让您的工作更有效率。这篇文章将介绍如何有效进行quora广告投放与有价值的 Quora 广告要点,这将为您节省数万美元的广告支出和工作时间!往下看…