NLP入门——基于TF-IDF算法的应用

news2024/12/24 10:22:38

从json格式数据中抽出句子和标签

首先查看json格式的数据文件:

:~/nlp/tnews/src$ less train.json

在这里插入图片描述
可以看到json字符串表示一个对象,我们利用json.loads() 函数会将其转换为一个 Python 字典。docs python json

#ext.py
#encoding: utf-8

import sys
from json import loads

def handle(srcf, rsf, rstf):#打开三个文件,将句子写入rsf,将标签写入rstf

    ens = "\n".encode("utf-8") 
    with open(srcf,"rb") as frd, open(rsf,"wb") as fwrts, open(rstf, "wb") as fwrtt:
        for line in frd:
            tmp = line.strip()
            if tmp:
                tmp = loads(tmp.decode("utf-8"))#将json对象取出转换成字典
                src, tgt = tmp.get("sentence",""), tmp.get("label_desc", "")#python dict的get方法,取出sentence标签的值,若不存在则返回空串
                if src and tgt: #若非空
                    fwrts.write(src.encode("utf-8"))
                    fwrts.write(ens)
                    fwrtt.write(tgt.encode("utf-8"))
                    fwrtt.write(ens)

if __name__=="__main__":
    handle(*sys.argv[1:])

在命令行输入:

:~/nlp/tnews/src$ python ext.py train.json src.train.txt tgt.train.txt 
:~/nlp/tnews/src$ python ext.py dev.json src.dev.txt tgt.dev.txt 

得到的分别为原数据中训练集以及验证集中的句子和标签,src.文件中每个句子占一行,tgt.文件中每个句子的类别占一行。

对训练集和验证集做数据的预处理

随后我们对这四个提取出来的数据文件做数据预处理的编码规范化以及繁体转简体,得到四个预处理后的文件:

:~/nlp/tnews$ python ~/bin/text-tools/data/normu8.py src.train.txt - | python ~/bin/text-tools/data/zht2s.py - src.train.s.txt &
:~/nlp/tnews$ python ~/bin/text-tools/data/normu8.py src.dev.txt - | python ~/bin/text-tools/data/zht2s.py - src.dev.s.txt &
:~/nlp/tnews$ python ~/bin/text-tools/data/normu8.py tgt.dev.txt - | python ~/bin/text-tools/data/zht2s.py - tgt.dev.s.txt &
:~/nlp/tnews$ python ~/bin/text-tools/data/normu8.py tgt.train.txt - | python ~/bin/text-tools/data/zht2s.py - tgt.train.s.txt &
def handle(srcf, rsf, d="t2s"):
	with sys.stdin.buffer if srcf == "-" else open(srcf, "rb") as frd, sys.stdout.buffer if rsf == "-" else open(rsf, "wb") as fwrt:

normu8.pyzht2s.py中添加以上代码,如果输入是字符‘-’则从输入缓冲区中读文件,如果输出文件是字符‘-’则将文件写入输出缓冲区中。
这样做的好处是可以将文件在命令行中串起来,避免生成中间我们不需要的文件。例如:

python normu8.py tgt.train.txt - | python zht2s.py - tgt.train.s.txt &

顺序执行命令行,normu8.py输入文件是tgt.train.txt,输出文件是-,那么就把经过处理的输入文件输出到sys.stdout.buffer缓冲区中,利用管道线将前半部分的输出给后半部分的输入,执行zht2s.py输入是-,则从sys.stdin.buffer缓冲区中读入,输出文件是tgt.train.s.txt,则写入到这个文件中。
缓冲区在内存中,在缓冲区操作比文件操作要快很多。

执行预处理后查看他们的行号,发现行号正常:

:~/nlp/tnews/src$ wc -l *.s.txt
  10000 src.dev.s.txt
  53360 src.train.s.txt
  10000 tgt.dev.s.txt
  53360 tgt.train.s.txt
 126720 总计

接着,对编码规范化后的文件分词并查看分词后的词表大小:

 :~/nlp/tnews/src$ python ~/nlp/token/seg.py src.train.s.txt src.train.c.txt &
 :~/nlp/tnews/src$ python ~/nlp/token/seg.py src.dev.s.txt src.dev.c.txt
 :~/nlp/tnews/src$ python ~/nlp/token/vcb.py src.train.c.txt 
67733

对训练集和验证集做子词切分

:~/nlp/tnews/src$ subword-nmt learn-bpe -s 8000 < src.train.c.txt > src.cds

由于文件词表本身的长度较小,因此合并操作不能太多,我们设置学习8000个词,随后利用得到的词表进行子词切分,并得到每个子词的频率src.bvcb

:~/nlp/tnews/src$ subword-nmt apply-bpe -c src.cds < src.train.c.txt | subword-nmt get-vocab > src.bvcb

最后我们设置阈值8,利用学习到的src.bvcb子词频率表来对整个子词表进行过滤,得到最终完整的经过bpe算法处理后的文件:

:~/nlp/tnews/src$ subword-nmt apply-bpe -c src.cds --vocabulary src.bvcb --vocabulary-threshold  8 < src.train.c.txt > src.train.bpe.txt
:~/nlp/tnews/src$ subword-nmt apply-bpe -c src.cds --vocabulary src.bvcb --vocabulary-threshold  8 < src.dev.c.txt > src.dev.bpe.txt

我们可以查看bpe算法后的分词效果:

less src.train.bpe.txt

在这里插入图片描述

less src.dev.bpe.txt

在这里插入图片描述

统计词表中每个子词在该类别出现的频率

#p.train.py
#encoding: utf-8

import sys
from json import dump
from math import log

def count(srcf, tgtf):

    #{class: {word: freq}} #model为字典嵌套,外层为每个类别,内层统计每个类别出现的子词频率
    model = {}
    with open(srcf,"rb") as fsrc, open(tgtf,"rb") as ftgt:
        for sline, tline in zip(fsrc, ftgt):
            _s, _t = sline.strip(), tline.strip()
            if _s and _t:
                _s, _class = _s.decode("utf-8"), _t.decode("utf-8")
                if _class not in model:#如果model中没有这个类别
                    model[_class] = {}#为model中这个类别作初始化
                _ = model[_class]#取出model中_class的类别的字典
                for word in _s.split():#遍历一行里面空格隔开的每个分词
                    _[word] = _.get(word,0) + 1 #出现一次则将分词的词频+1
    return model              

def log_normalize(modin):#将子词出现次数转化成频率

    rs = {}
    for _class, v in modin.items():#遍历整个词典;键为modin.keys(),值为modin.values()
        _ = float(sum(v.values()))#统计这个类别中所有子词出现的总个数,转化成浮点数
        rs[_class] = {word: log(freq / _) for word, freq in v.items()}#遍历所有的子词,用每个子词出现的频率除以总数
    #为了防止溢出,将频率取对数
    return rs#得到的rs字典即为每个子词在这个类别中出现的频率
    
def save(modin, frs):
    
    with open(frs, "w") as f:
        dump(modin, f) #用dump方法向文件写str
        
if __name__=="__main__":
    save(log_normalize(count(*sys.argv[1:3])),sys.argv[3])
    #第一和第二个文件传给count,之后将返回model字典传给log_normalize,再将第三个写入文件传给save保存

为了防止因为频率太低导致的精度丢失,我们将频率取对数后存储成json格式文件:

:~/nlp/tnews/src$ python p.train.py src.train.bpe.txt  tgt.train.s.txt logp.model.txt

查看logp.model.txt文件,由于log(a)+log(b)=log(ab),我们可以采用对取log后的频率相加得到组合子词的概率对数:
在这里插入图片描述

对每行每句话的类别作预测、比较准确率

#pnorm.predict.py
#encoding: utf-8

import sys
from json import loads
from math import inf

def load(fname):
    
    with open(fname, "r") as f:
        model = loads(f.read())#从json对象取出转化成字典
        
    return model
    
#model: {class: {word,logp}}    
def predict_instance(lin, model):
    
    _max_score, _max_class = -inf, None
    for _class, _cd in model.items():#取出 类别 和 {子词 : log频率}
        #_s = sum([_cd.get(word, 0.0) for word in lin])
        _s = 0.0    #记录总分数
        _n = 0      #记录总词数
        for word in lin:        #遍历每行的分词
            if word in _cd:     #如果分词在分词表里面
                _s += _cd[word] #累加总频率
                _n += 1         #总词数+1
        if _s > 0.0:            #排除n=0的情况
            _s = _s / _n        #求平均分数
            if _s > _max_score: #更新每行出现词标签频率最高的logp以及类别
                _max_class = _class
                _max_score = _s
    if _max_class is None:
        _max_class = _class
    
    return _max_class, _max_score#返回封装成一个tuple:(_max_class,_max_score)
      
def predict(fsrc, fmodel, frs):
    
    model = load(fmodel)
    ens = '\n'.encode("utf-8")
    with open(fsrc, "rb") as frd, open(frs, "wb") as fwrt:
        for line in frd:
            _ = line.strip() 
            if _: #将每行数据传入predict_instance()函数
                _ =predict_instance( _.decode("utf-8").split(), model)[0]#取出类别_max_class
                fwrt.write(_.encode("utf-8")) #将类别写入文件,作为每行(每句话)的预测类别
            fwrt.write(ens) 
        
if __name__=="__main__":
    predict(*sys.argv[1:])

我们对整个model文件进行遍历,统计每句话的分词出现的频率,在遍历完整个model后,会得到一句话中分词出现频率最高的情况,将这个分词属于的类别标记为这句话的类别。

在命令行执行输入以下代码,src.dev.bpe.txt是分词后的验证集;模型文件是logp.model.txt,是json格式的文件;最后pred.dev.txt 是预测的标签写入的文件。

:~/nlp/tnews$ python pnorm.predict.py src.dev.bpe.txt logp.model.txt pred.dev.txt 

查看pred.dev.txt文件:
在这里插入图片描述
可以看到,预测是股票类的句子最多。下面我们统计对比预测的准确率:

#acc.py
#encoding: utf-8
import sys

def handle(predf, ref):

    t = c = 0 #t是总标签数,c是预测正确的标签数
    with open(predf, "rb") as fp, open(ref, "rb") as fr:
        for lp, lr in zip(fp, fr):
            _p, _r = lp.strip(), lr.strip()
            if _p and _r:
                _p, _r = _p.decode("utf-8"), _r.decode("utf-8")
                if _p == _r:
                    c += 1 #如果预测正确,累加c
                t += 1 #统计总标签数
                
    return float(c) / t *100.0 #返回预测的准确率
    
if __name__ == "__main__":
    print(handle(*sys.argv[1:]))

第一个输入文件是预测的标签文件,第二个是真实的验证集标签:

:~/nlp/tnews$ python acc.py pred.dev.txt  tgt.dev.s.txt 
0.44999999999999996

预测的准确率大约是是0.45%,这个准确率是很低的。

准确率分析

将模型加载到内存中,我们查看模型文件的全部类别:

>>> from predict import load
>>> model=load("logp.model.txt")
>>> model.keys()
dict_keys(['news_edu', 'news_finance', 'news_house', 'news_travel', 'news_tech', 'news_sports', 'news_game', 'news_culture', 'news_car', 'news_story', 'news_entertainment', 'news_military', 'news_agriculture', 'news_world', 'news_stock'])

接着,我们查看逗号这个分词在不同类别中的频率:

>>> model["news_edu"][',']
-3.3306962741282318
>>> model["news_finance"][',']
-3.507571369597401
>>> model["news_house"][',']
-3.1864756857742638

可以看到,由于log函数的单调性,在以上三个类别中逗号在news_house标签中的频率最高,因此对最终句子标签的判定产生影响。
因为 ',' 实际上并不具有标签的意义,只是作为标点符号存在,但在我们的模型以及判定方法中,对结果产生了不良影响。又由于逗号大量的存在,故使得准确率大大降低。

TF-IDF算法

TF(Term Frequency)即词频,表示某一词在某类文档中出现的频率,我们上文所求的logp中的p即为TF,公式是:
T F = 某类词中某个词出现的次数 这类词的总词数 TF=\frac {某类词中某个词出现的次数}{这类词的总词数} TF=这类词的总词数某类词中某个词出现的次数
上面的例子中,逗号在news_house标签中的频率最高,TF值最大,因此TF认为逗号是此类别的单词。

IDF(Inverse Document Frequency)即逆向文件频率,表示一个词在整个语料库中的普遍性。IDF值越高,说明其频率越低,越能代表不同的文档。公式为:
I D F ( t ) = log ⁡ ( N d t ) IDF(t)= \log(\frac{N}{d_t}) IDF(t)=log(dtN)
N为文档总数,d_t为包含词语的文档数。上面的例子中,逗号广泛存在于几乎所有的类别文本中。因此,他的IDF值会极低,甚至接近于0.

TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。 T F − I D F = T F ∗ I D F TF-IDF=TF*IDF TFIDF=TFIDF
这样,对逗号的TF值乘以IDF值作为总体的值,则最终值会受到IDF值的影响而变得极小。则逗号就会大大减弱用其来分类文本的作用。

我们修改p.train.py,来求每个子词的 TF-IDF值,我们将类别的粒度设置在class,也就是说N为15(总标签数),d_t为每个子词拥有的标签数。

#tfidf.train.py
#encoding: utf-8

import sys
from json import dump
from math import log

def count(srcf, tgtf):

    #{class: {word: freq}} #model为字典嵌套,外层为每个类别,内层统计每个类别出现的子词频率
    model = {}
    with open(srcf,"rb") as fsrc, open(tgtf,"rb") as ftgt:
        for sline, tline in zip(fsrc, ftgt):
            _s, _t = sline.strip(), tline.strip()
            if _s and _t:
                _s, _class = _s.decode("utf-8"), _t.decode("utf-8")
                if _class not in model:#如果model中没有这个类别
                    model[_class] = {}#为model中这个类别作初始化
                _ = model[_class]#取出model中_class的类别的字典
                for word in _s.split():#遍历一行里面空格隔开的每个分词
                    _[word] = _.get(word,0) + 1 #出现一次则将分词的词频+1
    return model              


def getidf(modin):
    
    rs = {}
    for v in modin.values():
        for word in v.keys(): 
            if word in rs:
                continue 
            for _class in modin.keys():
                dic = modin[_class]
                if word in dic :
                    rs[word] = rs.get(word,0) + 1
    _ = len(modin) #统计所有类别的个数
    return {word: -log(freq / _) for word, freq in rs.items()}# 返回{分词:分词出现的类别数/总类别数}
    
    
def tfidf(modin)
    idf = getidf(modin)
    rs = {}
    for _class, v in modin.items():#遍历整个词典;键为modin.keys(),值为modin.values()
        _ = float(sum(v.values()))#统计这个类别中所有子词出现的总个数,转化成浮点数
        rs[_class] = {word: (freq / _ * idf[word]) for word, freq in v.items()}
    #tf * idf 
    return rs#得到的rs字典即为每个子词的 tf * idf 值
    
def save(modin, frs):
    
    with open(frs, "w") as f:
        dump(modin, f) #用dump方法向文件写str
        
if __name__=="__main__":
    save(tfidf(count(*sys.argv[1:3])),sys.argv[3])

和仅基于TF算模型的步骤相似,在命令行输入:

:~/nlp/tnews$ python tfidf.train.py src.train.bpe.txt tgt.train.s.txt model/tfidf/model.txt
:~/nlp/tnews$ python predict.py src.dev.bpe.txt model/tfidf/model.txt pred.tfidf.dev.txt
:~/nlp/tnews$ python acc.py pred.tfidf.dev.txt tgt.dev.s.txt 
47.4

可以看到,经过TF-IDF算法调整后,模型预测的准确率可以达到47.4%,相比于之前的0.45%有显著的提高。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络 —— 应用层(DNS域名系统)

计算机网络 —— 应用层&#xff08;DNS域名系统&#xff09; 什么是DNS域名的层次结构域名分类 域名服务器的分类域名解析方式递归查询&#xff08;Recursive Query&#xff09;迭代查询&#xff08;Iterative Query&#xff09;域名的高速缓存 我们今天来看DNS域名系统 什么…

Qt调用第三方库的通用方式(静态链接库.a或.lib、动态链接库.dll)

目录 一、前提 二、如何引用静态链接库 三、如何引用动态链接库 四、示例代码资源 在开发项目中经常会存在需要调用第三方库的时候&#xff0c;对于Qt如何来调用第三方库&#xff0c;为了方便自己特意记录下详细过程。 一、前提 1. window 10操作系统 2. 已安装了Qt6.7.…

[大模型]Phi-3-mini-4k-instruct langchain 接入

环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 。 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端开始环境配置、模型下载和运行演示。 创建工作…

10大wordpress外贸主题

手动工具wordpress外贸模板 适合生产套筒扳、管钳、工具箱、斧子、锤子、防爆工具、螺丝刀、扳手等手动工具的厂家。 https://www.jianzhanpress.com/?p4806 Invisible Trade WP外贸网站模板 WordPress Invisible Trade外贸网站模板&#xff0c;做进出口贸易公司官网的word…

7-25 数字三角形问题

7-25 数字三角形问题 分数 10 全屏浏览 作者 夏仁强 单位 贵州工程应用技术学院 给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法&#xff0c;计算出从三角形的顶至底的一条路径&#xff0c;使该路径经过的数字总和最大。 对于给定的由n行数字组成的数字三角…

【Spring】Spring事务相关源码分析

目录&#xff1a; 1.讲述事务的一些基础概念。 2.讲述事务的生命周期源码 3.配置事务&#xff0c;以及事务注解的源码 1.前言 具体事务中Spring是怎么管理事务&#xff0c;怎么去管理、创建、销毁等操作的呢&#xff1f;这一次来分解一下。 2.事务概述&#xff08;复习&a…

MySQL的增删查改(CRUD)

目录 一.CRUD 1.什么是CRUD 2.CRUD的特点 二.新增&#xff08;Create&#xff09; 单列插入全行数据 表的复制 额外小知识 三.阅读(Read) 1.全表查询指定列查询 2.查询字段为表达式 3.别名 ​编辑 4.去重 5.排序 1.根据列名进行排序 2.使用表达式及别名进行排序…

读AI未来进行式笔记12读后总结与感想兼导读

1. 基本信息 AI未来进行式 李开复 陈楸帆 著 浙江人民出版社,2022年5月出版 1.1. 读薄率 书籍总字数301千字&#xff0c;笔记总字数39650字。 读薄率39650301000≈13.2% 1.2. 读厚方向 千脑智能 脑机穿越 未来呼啸而来 虚拟人 AI3.0 新机器人 人工不智能&#xff…

搜索与图论:染色法判别二分图

搜索与图论&#xff1a;染色法判别二分图 题目描述参考代码 题目描述 输入样例 4 4 1 3 1 4 2 3 2 4输出样例 Yes参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010, M 200010;int n, m; i…

[C][数据结构][排序][下][快速排序][归并排序]详细讲解

文章目录 1.快速排序1.基本思想2.hoare版本3.挖坑法4.前后指针版本5.非递归版本改写 2.归并排序 1.快速排序 1.基本思想 任取待排序元素序列的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右…

自然语言处理领域的重大挑战:解码器 Transformer 的局限性

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Undertow学习

Undertow介绍 Undertow是一个用java编写的灵活、高性能的web服务器&#xff0c;提供基于NIO的阻塞和非阻塞API。 Undertow有一个基于组合的体系结构&#xff0c;允许您通过组合小型单用途处理程序来构建web服务器。为您提供了在完整的Java EE servlet 4.0容器或低级别非阻塞处…

【JavaEE精炼宝库】多线程(5)单例模式 | 指令重排序 | 阻塞队列

目录 一、单例模式&#xff1a; 1.1 饿汉模式&#xff1a; 1.2 懒汉模式&#xff1a; 1.2.1 线程安全的懒汉模式&#xff1a; 1.2.2 线程安全的懒汉模式的优化&#xff1a; 二、指令重排序 三、阻塞队列 3.1 阻塞队列的概念&#xff1a; 3.2 生产者消费者模型&#xf…

计算机网络之网络层知识总结

网络层功能概述 主要任务 主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层传输单位是数据报。 分组和数据报的关系&#xff1a;把数据报进行切割之后&#xff0c;就是分组。 主要功能&#xff1a; 路由选择与分组转发 路由器…

ResNet——Deep Residual Learning for Image Recognition(论文阅读)

1.什么是ResNet ResNet是一种残差网络&#xff0c;咱们可以把它理解为一个子网络&#xff0c;这个子网络经过堆叠可以构成一个很深的网络。下面是ResNet的结构。 2.为什么要引入ResNet 理论上来说&#xff0c;堆叠神经网络的层数应该可以提升模型的精度。但是现实中真的是这…

SwiftUI中UIViewRepresentable的使用(UIKit与SwiftUI的桥梁)

UIViewRepresentable是一个协议&#xff0c;用于创建一个SwiftUI视图&#xff0c;该视图包装了一个UIKit视图。通过实现UIViewRepresentable协议&#xff0c;我们可以在SwiftUI中使用自定义的UIKit视图&#xff0c;并与SwiftUI进行交互。 实现UIViewRepresentable 创建一个遵…

DT浏览器很好用

简单的浏览器&#xff0c;又是强大的浏览器&#xff0c;界面简洁大方&#xff0c;操作起来非常流畅&#x1f60e;&#xff0c;几乎不会有卡顿的情况。 搜索功能也十分强大&#x1f44d;&#xff0c;能够快速精准地找到想要的信息。 而且还有出色的兼容性&#xff0c;各种网页都…

qt 实现模拟实际物体带速度的移动(水平、垂直、斜角度)——————附带完整代码

文章目录 0 效果1 原理1.1 图片旋转1.2 物体带速度移动 2 完整实现2.1 将车辆按钮封装为一个类&#xff1a;2.2 调用方法 3 完整代码参考 0 效果 实现后的效果如下 可以显示属性&#xff08;继承自QToolButton&#xff09;: 鼠标悬浮显示文字 按钮显示文字 1 原理 类继承…

单链表经典算法题 1

前言 学习了单链表&#xff0c;我们就做一些题来巩固一下。还有就是解题方法不唯一&#xff0c;我就只讲述为自己的方法。 目录 前言 1.移除链表元素 思路 代码 2.反转链表 思路 代码 3.链表的中间节点 思路 代码 总结 1.移除链表元素 思路 我们创建一个新的表…

FM全网自动采集聚合影视搜索源码

源码介绍 FM 全网聚合影视搜索(响应式布局)&#xff0c;基于 TP5.1 开发的聚合影视搜索程序&#xff0c;本程序无数据库&#xff0c;本程序内置P2P 版播放器&#xff0c;承诺无广告无捆绑。片源内部滚动广告与本站无关,谨防上当受骗&#xff0c;资源搜索全部来自于网络。 环境…