不要抓着枯叶哭泣,你要等待初春的新芽
—— 25.1.23
一、文本分类任务
定义:预先设定好一个文本类别集合,对于一篇文本,预测其所属的类别
例如:
情感分析:
这家饭店太难吃了 —> 正类
这家菜很好吃 —> 负类
领域分类:
今日A股行情大好 —> 经济
今日湖人击败勇士 —> 体育
二、文本分类 — 使用场景
1.资讯文章打标签
2.电商评论分析
可以进一步分析,当作不同类别,分析具体的好/差评原因
3.违规检测
涉黄、涉暴、涉恐、辱骂等
主要应用在客服、销售对话质检、或网站内容审查等
三、自定义类别任务
类别的定义方式是任意的
只要人基于文本能够判断,都可以作为分类类别
如:① 垃圾邮件分类、② 对话、文章是否与汽车交易相关、③ 文章风格是否与某作者风格一致、④ 文章是否是机器生成、⑤ 合同文本是否符合规范、⑥ 文章适合阅读人群(未成年、中年、老年、孕妇等)
四、文本分类 — 机器学习
① 定义类别 ——> ② 收集数据 ——> ③ 模型训练 ——> ④ 预测
1.定义类别
首先定义有几个类别
2.收集数据
对于每个类别作数据的收集和标注
3.模型训练
将类别数据送到一个分类器(神经网络 / 机器学习中的其他方法)中去
4.预测
用这个分类器对一些未知文本类别的文本去预测一些类别
五、传统机器学习算法 ① 贝叶斯算法
1.全概率公式
事件A的概率:等于在每种划分之下,划分事件 Bi 的概率 × 在这个划分下事件 A 的概率
2.贝叶斯公式
①
②
③
P(A|B):后验概率,表示在已知事件B发生的情况下,事件A发生的概率
P(B|A):似然度,在事件A发生的条件下,事件B发生的概率
P(A):先验概率,是在没有任何关于B的信息时,对事件A发生概率的初始估计
P(B):边缘概率,可以通过全概率公式进行计算
B事件发生下,A事件发生的概率 = A事件发生的概率 × A事件发生下,B事件发生的概率 除以 B事件发生的概率
3.贝叶斯公式的应用
求解:如果核酸检测呈阳性,感染新冠的概率是多少?
我们假定新冠在人群中的感染率为0.1%(先验概率:千分之一 0.001)【先验 / 前置概率】
核酸检测有一定误报率,我们假定如下:
P(A) = 感染新冠
P(B) = 核酸检测呈阳性
P(A | B) = 核酸检测呈阳性,确实感染新冠
P(B | A) = 感染了新冠,且检测结果呈阳性
P(B | ^A) = 未感染新冠,且检测结果呈阳性
计算如下:
P(A | B) = P(A) * P(B | A) / P (B)
P(B) = P(B | A) * P(A) + P(B | ^A) * P(^A) # 全概率公式
P(A | B) = P(A) * P(B | A) /【P(B | A) * P(A) + P(B | ^A) * P(^A)】
= 0.001 × 0.99 / (0.99 × 0.001 + 0.05 × 0.999)
≈ 0.019 ≈ 0.02
4.贝叶斯公式在NLP中的应用
用贝叶斯公式处理文本分类任务
一个合理假设:文本属于哪个类别,与文本中包含哪些词相关
任务:知道文本中有哪些词,预测文本属于某类别的概率
5.贝叶斯公式 — 文本分类
假定有3个类别A1, A2, A3
一个文本S有n个词组成,W1, W2, W3....Wn
想要计算文本S属于A1类别的概率:P(A1|S) = P(A1|W1, W2, W3....Wn)
除以公共分母,得出的所有类别可能性加和为1,近似于做自动归一化
# 贝叶斯公式
# 分母是公共的,可以不进行计算
P(A1 | S) = P(W1, W2…Wn | A1) * P(A1) / P(W1,W2…Wn)
P(A2 | S) = P(W1, W2…Wn | A2) * P(A2) / P(W1,W2…Wn)
P(A3 | S) = P(W1, W2…Wn | A3) * P(A3) / P(W1,W2…Wn)
# 词的独立性假设
P(W1, W2…Wn | A3) = P(W1 | A3) * P(W2 | A3) *… * P(Wn | A3)
6.贝叶斯公式 — 代码实现
jieba.initialize():是 jieba 分词库中的一个函数,用于初始化分词所需的词典和配置。在某些情况下,特别是在多线程或分布式环境中使用 jieba
时,显式调用 initialize()
可以确保分词器正确加载词典并优化性能。
参数 | 类型 | 说明 |
---|---|---|
enable_parallel | int 或 False | 是否启用并行分词。如果设置为 True ,则启用并行分词;如果设置为具体的整数,表示并行分词的线程数;默认为 False ,即不启用并行分词。 |
use_paddle | bool | 是否启用 PaddlePaddle 模式。如果设置为 True ,则启用 PaddlePaddle 模式进行分词;默认为 False ,即不启用该模式。使用此模式需要安装 PaddlePaddle 库。 |
user_dict | str | 用户自定义词典的路径。通过设置此参数,可以让 jieba 加载指定的自定义词典,以便在分词时识别特定的词汇。 |
cut_all | bool | 是否使用全模式分词。如果设置为 True ,则使用全模式分词,即把句子中所有的可以成词的词语都扫描出来;如果设置为 False ,则使用精确模式分词,即试图将句子最精确地切开;默认为 False 。 |
HMM | bool | 是否使用 HMM 模型来识别新词。如果设置为 True ,则使用 HMM 模型;如果设置为 False ,则不使用 HMM 模型;默认为 True 。 |
use_parallel | bool 或 int | 是否启用并行分词及并行分词的线程数。如果设置为 True ,则启用并行分词,默认使用CPU核心数;如果设置为整数,则指定并行分词的线程数;默认为 False 。 |
Ⅰ、初始化
defaultdict():Python 标准库 collections
模块中的一个类,用于创建具有默认值的字典。当访问一个不存在的键时,defaultdict
会自动创建该键并赋予一个默认值,而不会抛出 KeyError
异常。这在处理数据聚合、计数等场景中非常有用。
参数 | 类型 | 说明 |
---|---|---|
default_factory | 可调用对象(如函数、类等) | 用于生成默认值的工厂函数。当访问一个不存在的键时,defaultdict 会调用此工厂函数来生成默认值。常见的用法包括 int (默认值为 0 )、list (默认值为 [] )、set (默认值为 set() )等。如果未提供此参数,defaultdict 的行为类似于普通字典,访问不存在的键时会抛出 KeyError 。 |
**kwargs | 任意关键字参数 | 传递给字典的其他关键字参数,通常用于初始化字典。例如,可以传递一个可迭代对象来初始化字典的键值对。 |
def __init__(self, data_path):
self.p_class = defaultdict(int)
self.word_class_prob = defaultdict(dict)
self.load(data_path)
Ⅱ、加载语料文件
defaultdict():Python 标准库 collections
模块中的一个类,用于创建具有默认值的字典。当访问一个不存在的键时,defaultdict
会自动创建该键并赋予一个默认值,而不会抛出 KeyError
异常。这在处理数据聚合、计数等场景中非常有用。
参数 | 类型 | 说明 |
---|---|---|
default_factory | 可调用对象(如函数、类等) | 用于生成默认值的工厂函数。当访问一个不存在的键时,defaultdict 会调用此工厂函数来生成默认值。常见的用法包括 int (默认值为 0 )、list (默认值为 [] )、set (默认值为 set() )等。如果未提供此参数,defaultdict 的行为类似于普通字典,访问不存在的键时会抛出 KeyError 。 |
**kwargs | 任意关键字参数 | 传递给字典的其他关键字参数,通常用于初始化字典。例如,可以传递一个可迭代对象来初始化字典的键值对。 |
set(): Python 内置函数,用于创建一个无序且不重复的集合(set
)。集合中的元素必须是可哈希的(即不可变类型),如数字、字符串、元组等。集合常用于去重、集合运算(如并集、交集、差集)等操作。
参数 | 类型 | 说明 |
---|---|---|
iterable | 可迭代对象(可选) | 用于初始化集合的可迭代对象,如列表、元组、字符串等。如果未提供,则创建一个空集合。 |
json.loads(): Python json
模块中的函数,用于将 JSON 格式的字符串解析为 Python 对象(如字典、列表、字符串、数字等)。常用于处理来自网络请求、文件读取等的 JSON 数据。
参数 | 类型 | 说明 |
---|---|---|
s | str | 要解析的 JSON 格式字符串。 |
cls | 可选,json.JSONDecoder 的子类 | 用于解码的自定义解码器类,默认为 json.JSONDecoder 。 |
object_hook | 可选,函数 | 用于自定义解码特定类型的对象。 |
parse_float | 可选,函数 | 用于解析浮点数的函数,默认为 float 。 |
parse_int | 可选,函数 | 用于解析整数的函数,默认为 int 。 |
parse_constant | 可选,函数 | 用于解析 JSON 常量(如 null , true , false )的函数。 |
object_pairs_hook | 可选,函数 | 用于自定义解码对象对的函数,优先级高于 object_hook 。 |
strict | 可选,bool | 如果为 True ,则在遇到非法字符时抛出异常,默认为 True 。 |
buffering | 可选,bool | 已弃用,忽略此参数。 |
jieba.lcut():jieba
分词库中的函数,用于将输入的字符串进行分词,并返回一个列表。与 jieba.cut()
不同,lcut()
直接返回列表,而 cut()
返回一个生成器。
参数 | 类型 | 说明 |
---|---|---|
sentence | str | 需要分词的字符串。 |
cut_all | bool | 是否使用全模式分词。True 表示全模式,False 表示精确模式(默认)。 |
HMM | bool | 是否使用 HMM 模型识别新词。True 表示使用,False 表示不使用(默认)。 |
use_paddle | bool | 是否启用 PaddlePaddle 模式进行分词。需要安装 PaddlePaddle 库。True 或 False 。 |
user_dict | str | 用户自定义词典的路径,用于增强分词效果。 |
union():集合(set
)对象的方法,用于返回两个或多个集合的并集。并集包含所有出现在任一集合中的元素,且不重复。
参数 | 类型 | 说明 |
---|---|---|
*others | set 或可迭代对象 | 一个或多个要合并到当前集合的其他集合或可迭代对象。 |
def load(self, path):
self.class_name_to_word_freq = defaultdict(dict)
self.all_words = set() #汇总一个词表
with open(path, encoding="utf8") as f:
for line in f:
line = json.loads(line)
class_name = line["tag"]
title = line["title"]
words = jieba.lcut(title)
self.all_words = self.all_words.union(set(words))
self.p_class[class_name] += 1 #记录每个类别样本数量
word_freq = self.class_name_to_word_freq[class_name]
#记录每个类别下的词频
for word in words:
if word not in word_freq:
word_freq[word] = 1
else:
word_freq[word] += 1
self.freq_to_prob()
return
Ⅲ、将词频和样本频率转化为概率
sum():Python 的内置函数,用于计算可迭代对象(如列表、元组等)中所有元素的总和。它也可以用于将一个可迭代对象中的元素累加到一个初始值上。
参数 | 类型 | 说明 |
---|---|---|
iterable | 可迭代对象(如列表、元组、集合等) | 要计算总和的可迭代对象。 |
start | int 或 float (可选) | 累加的初始值,默认为 0 。 |
.values(): Python 字典(dict
)的方法,用于返回字典中所有值的视图对象。这个视图对象显示的是字典中所有值的动态视图,当字典的值发生变化时,视图也会反映这些变化。
dict(): Python 的内置函数,用于创建一个新的字典(dict
)。它可以通过多种方式初始化字典,包括从另一个字典、键值对的可迭代对象、关键字参数等。
参数 | 类型 | 说明 |
---|---|---|
object | 映射对象或可迭代对象(可选) | 如果提供,object 应该是一个映射(如另一个字典)或包含键值对的可迭代对象。 |
**kwargs | 关键字参数(可选) | 任意数量的关键字参数,用于初始化字典。每个关键字参数的键和值将成为字典中的键值对。 |
defaultdict():Python 标准库 collections
模块中的一个类,用于创建具有默认值的字典。当访问一个不存在的键时,defaultdict
会自动创建该键并赋予一个默认值,而不会抛出 KeyError
异常。这在处理数据聚合、计数等场景中非常有用。
参数 | 类型 | 说明 |
---|---|---|
default_factory | 可调用对象(如函数、类等) | 用于生成默认值的工厂函数。当访问一个不存在的键时,defaultdict 会调用此工厂函数来生成默认值。常见的用法包括 int (默认值为 0 )、list (默认值为 [] )、set (默认值为 set() )等。如果未提供此参数,defaultdict 的行为类似于普通字典,访问不存在的键时会抛出 KeyError 。 |
**kwargs | 任意关键字参数 | 传递给字典的其他关键字参数,通常用于初始化字典。例如,可以传递一个可迭代对象来初始化字典的键值对。 |
items():Python 字典(dict
)的方法,用于返回字典中所有键值对的视图对象。这个视图对象显示的是字典条目的动态视图,意味着当字典发生变化时,视图也会反映这些变化。
len():Python 的内置函数,用于返回对象(如字符串、列表、字典、集合等)的长度或项目数量。
参数 | 类型 | 说明 |
---|---|---|
object | 任何可迭代或可计数的对象(如字符串、列表、字典、集合、元组等) | 要计算长度的对象。 |
#将记录的词频和样本频率都转化为概率
def freq_to_prob(self):
#样本概率计算
total_sample_count = sum(self.p_class.values())
self.p_class = dict([c, self.p_class[c] / total_sample_count] for c in self.p_class)
#词概率计算
self.word_class_prob = defaultdict(dict)
for class_name, word_freq in self.class_name_to_word_freq.items():
total_word_count = sum(count for count in word_freq.values()) #每个类别总词数
for word in word_freq:
#加1平滑,避免出现概率为0,计算P(wn|x1)
prob = (word_freq[word] + 1) / (total_word_count + len(self.all_words))
self.word_class_prob[class_name][word] = prob
self.word_class_prob[class_name]["<unk>"] = 1/(total_word_count + len(self.all_words))
return
Ⅳ、计算给定单词序列的联合概率
.get():Python 字典(dict
)对象的一个方法,用于获取指定键对应的值。如果键不存在于字典中,.get()
方法可以返回一个默认值,而不是引发 KeyError
异常。这使得 .get()
方法在处理字典时更加安全和便捷。
#P(w1|x1) * P(w2|x1)...P(wn|x1)
def get_words_class_prob(self, words, class_name):
result = 1
for word in words:
unk_prob = self.word_class_prob[class_name]["<unk>"]
result *= self.word_class_prob[class_name].get(word, unk_prob)
return result
Ⅴ、计算特定事件类别x下的联合概率
#计算P(w1, w2..wn|x1) * P(x1)
def get_class_prob(self, words, class_name):
#P(x1)
p_x = self.p_class[class_name]
# P(w1, w2..wn|x1) = P(w1|x1) * P(w2|x1)...P(wn|x1)
p_w_x = self.get_words_class_prob(words, class_name)
return p_x * p_w_x
Ⅵ、文本分类
jieba.lcut():jieba
分词库中的函数,用于将输入的字符串进行分词,并返回一个列表。与 jieba.cut()
不同,lcut()
直接返回列表,而 cut()
返回一个生成器。
参数 | 类型 | 说明 |
---|---|---|
sentence | str | 需要分词的字符串。 |
cut_all | bool | 是否使用全模式分词。True 表示全模式,False 表示精确模式(默认)。 |
HMM | bool | 是否使用 HMM 模型识别新词。True 表示使用,False 表示不使用(默认)。 |
use_paddle | bool | 是否启用 PaddlePaddle 模式进行分词。需要安装 PaddlePaddle 库。True 或 False 。 |
user_dict | str | 用户自定义词典的路径,用于增强分词效果。 |
append():Python 列表(list
)的方法,用于在列表的末尾添加一个新的元素。
参数 | 类型 | 说明 |
---|---|---|
object | 任意类型 | 要添加到列表末尾的对象。可以是任何数据类型,如整数、字符串、列表等。 |
sorted():Python 的内置函数,用于对可迭代对象进行排序,并返回一个新的排序后的列表。原对象不会被修改。
参数 | 类型 | 说明 |
---|---|---|
iterable | 可迭代对象 | 需要排序的对象,如列表、元组、字符串等。 |
key | 可选,函数 | 用于提取比较键的函数。默认为 None ,即直接比较元素。 |
reverse | 可选,布尔值 | 如果设置为 True ,则列表元素将被倒序(从大到小)排列。默认为 False 。 |
sum():Python 的内置函数,用于计算可迭代对象(如列表、元组等)中所有元素的总和。它也可以用于将一个可迭代对象中的元素累加到一个初始值上。
参数 | 类型 | 说明 |
---|---|---|
iterable | 可迭代对象(如列表、元组、集合等) | 要计算总和的可迭代对象。 |
start | int 或 float (可选) | 累加的初始值,默认为 0 。 |
#做文本分类
def classify(self, sentence):
words = jieba.lcut(sentence) #切词
results = []
for class_name in self.p_class:
prob = self.get_class_prob(words, class_name) #计算class_name类概率
results.append([class_name, prob])
results = sorted(results, key=lambda x:x[1], reverse=True) #排序
#计算公共分母:P(w1, w2, w3...wn) = P(w1,w2..Wn|x1)*P(x1) + P(w1,w2..Wn|x2)*P(x2) ... P(w1,w2..Wn|xn)*P(xn)
#不做这一步也可以,对顺序没影响,只不过得到的不是0-1之间的概率值
pw = sum([x[1] for x in results]) #P(w1, w2, w3...wn)
results = [[c, prob/pw] for c, prob in results]
#打印结果
for class_name, prob in results:
print("属于类别[%s]的概率为%f" % (class_name, prob))
return results
Ⅶ、贝叶斯文本分类实践
import math
import jieba
import re
import os
import json
from collections import defaultdict
jieba.initialize()
"""
贝叶斯分类实践
P(A|B) = (P(A) * P(B|A)) / P(B)
事件A:文本属于类别x1。文本属于类别x的概率,记做P(x1)
事件B:文本为s (s=w1w2w3..wn)
P(x1|s) = 文本为s,属于x1类的概率. #求解目标#
P(x1|s) = P(x1|w1, w2, w3...wn) = P(w1, w2..wn|x1) * P(x1) / P(w1, w2, w3...wn)
P(x1) 任意样本属于x1的概率。x1样本数/总样本数
P(w1, w2..wn|x1) = P(w1|x1) * P(w2|x1)...P(wn|x1) 词的独立性假设
P(w1|x1) x1类样本中,w1出现的频率
公共分母的计算,使用全概率公式:
P(w1, w2, w3...wn) = P(w1,w2..Wn|x1)*P(x1) + P(w1,w2..Wn|x2)*P(x2) ... P(w1,w2..Wn|xn)*P(xn)
"""
class BayesApproach:
def __init__(self, data_path):
self.p_class = defaultdict(int)
self.word_class_prob = defaultdict(dict)
self.load(data_path)
def load(self, path):
self.class_name_to_word_freq = defaultdict(dict)
self.all_words = set() #汇总一个词表
with open(path, encoding="utf8") as f:
for line in f:
line = json.loads(line)
class_name = line["tag"]
title = line["title"]
words = jieba.lcut(title)
self.all_words = self.all_words.union(set(words))
self.p_class[class_name] += 1 #记录每个类别样本数量
word_freq = self.class_name_to_word_freq[class_name]
#记录每个类别下的词频
for word in words:
if word not in word_freq:
word_freq[word] = 1
else:
word_freq[word] += 1
self.freq_to_prob()
return
#将记录的词频和样本频率都转化为概率
def freq_to_prob(self):
#样本概率计算
total_sample_count = sum(self.p_class.values())
self.p_class = dict([c, self.p_class[c] / total_sample_count] for c in self.p_class)
#词概率计算
self.word_class_prob = defaultdict(dict)
for class_name, word_freq in self.class_name_to_word_freq.items():
total_word_count = sum(count for count in word_freq.values()) #每个类别总词数
for word in word_freq:
#加1平滑,避免出现概率为0,计算P(wn|x1)
prob = (word_freq[word] + 1) / (total_word_count + len(self.all_words))
self.word_class_prob[class_name][word] = prob
self.word_class_prob[class_name]["<unk>"] = 1/(total_word_count + len(self.all_words))
return
#P(w1|x1) * P(w2|x1)...P(wn|x1)
def get_words_class_prob(self, words, class_name):
result = 1
for word in words:
unk_prob = self.word_class_prob[class_name]["<unk>"]
result *= self.word_class_prob[class_name].get(word, unk_prob)
return result
#计算P(w1, w2..wn|x1) * P(x1)
def get_class_prob(self, words, class_name):
#P(x1)
p_x = self.p_class[class_name]
# P(w1, w2..wn|x1) = P(w1|x1) * P(w2|x1)...P(wn|x1)
p_w_x = self.get_words_class_prob(words, class_name)
return p_x * p_w_x
#做文本分类
def classify(self, sentence):
words = jieba.lcut(sentence) #切词
results = []
for class_name in self.p_class:
prob = self.get_class_prob(words, class_name) #计算class_name类概率
results.append([class_name, prob])
results = sorted(results, key=lambda x:x[1], reverse=True) #排序
#计算公共分母:P(w1, w2, w3...wn) = P(w1,w2..Wn|x1)*P(x1) + P(w1,w2..Wn|x2)*P(x2) ... P(w1,w2..Wn|xn)*P(xn)
#不做这一步也可以,对顺序没影响,只不过得到的不是0-1之间的概率值
pw = sum([x[1] for x in results]) #P(w1, w2, w3...wn)
results = [[c, prob/pw] for c, prob in results]
#打印结果
for class_name, prob in results:
print("属于类别[%s]的概率为%f" % (class_name, prob))
return results
if __name__ == "__main__":
path = "F:\人工智能NLP/NLP\Day7_文本分类问题\data/train_tag_news.json"
ba = BayesApproach(path)
query = "中国三款导弹可发射多弹头 美无法防御很急躁"
ba.classify(query)
7.贝叶斯算法的优点和缺点
缺点:
① 如果样本不均衡会极大影响先验概率
② 对于未见过的特征或样本,条件概率为零,失去预测的意义(可以引入平滑)
③ 特征独立假设只是个假设
④ 没有考虑语序,也没有词义
优点:
① 简单高效
② 一定的可解释性
③ 如果样本覆盖的好,效果是不错的
④ 训练数据可以很好的分批处理
六、传统机器学习算法 ② 支持向量机 SVM
SVM:support vector machine,1964年提出
属于有监督学习 supervised learning
通过数据样本,学习最大边距超平面
引例:尝试用一条线将蓝色球与红色三角分开
SVM分类器:将类别不同的样本进行切分,并且要尽可能的让两类数据中最近的支持向量 距离这条分割函数最远(目标是最大化margin,margin:分类线到两边最近的支持向量的距离)【寻找一个最优的决策边界距离两个类别的最近的样本(称为支持向量)最远】
线性(直线)可分问题下的决策函数:
线性(直线)不可分问题:SVM算法中,将空间映射到更高的维度来分类非线性数据
【神经网络中:添加激活函数来拟合非线性数据分布】
1.支持向量机 — 核函数
为了解决线性不可分问题,我们需要把输入映射到高维,即寻找函数,使其输出维度高于x
例如: x = [X1, X2, X3]
令 = [X1*X1, X1*X2, X1*X3, X2*X1, X2*X2, X2*X3, X3*X1, X3*X2, X3*X3] (对自己做笛卡尔积)
这样x就从3维上升到9维
向高维映射如何解决线性不可分问题?
示例:
考虑一组一维数据,[-1, 0, 1] 为正样本,[-3, -2, 2, 3]为负样本
将x映射为【x, x^2】后 ,可以用直线划分,更高维度也是同理
但是这样出现一个问题,维度过高的向量计算在进行内积运算非常耗时,而svm的求解中内积运算很频繁
所以我们希望内有一种方法快速计算内积运算【】
核函数的意义:
两个向量x1、x2过一个核函数之后的结果,恰好等于这两个向量x1、x2分别通过一个高维映射然后再做内积得到的结果,基于核函数可以简化支持向量机中的内积运算
绕过向高维空间映射的向量内积运算,直接代入计算核函数的公式,使得经过核函数运算后的向量恰好等于原向量经过向量内积运算映射到高维空间
所谓的【核函数】即为满足条件:【】的函数,这些函数统称为核函数
2.常见核函数
线性核函数:
多项式核函数:
高斯核函数:
双曲正切核函数:
3.支持向量机 — 解决多分类问题
假设要解决一个K分类问题,即有K个目标类别
方式一:one vs one方式
建立 K(K - 1) / 2 个svm分类器,每个分类器负责K个类别中的两个类别,判断输入样本属于哪个类别
对于一个待预测的样本,使用所有分类器进行分类,最后保留被预测词数最多的类别
例:假设类别有[A,B,C]
X —> SVM(A,B) —> A
X —> SVM(A,C) —> A
X —> SVM(B,C) —> B
最终判断 X —> A
方式二:one vs rest方式
建立K个svm分类器,每个分类器负责划分输入样本属于K个类别中的某一个类别的概率,最后保留预测分值最高的类别
例:假设类别有[A,B,C]
X —> SVM(A,rest) —> 0.1
X —> SVM(B,rest) —> 0.2
X —> SVM(C,rest) —> 0.5
最终判断: X —> C
4.支持向量机的优缺点
优点:
① 少数支持向量决定了最终结果,对异常值不敏感
② 对于样本数量需求较低
③ 可以处理高维度数据
缺点:
① 样本数量过多的时候,计算负担很大
② 多分类任务处理起来比较麻烦
③ 核函数的选取以及参数的选取较为困难
5.代码实现
Ⅰ、加载训练好的模型
Word2Vec.load():加载之前保存的 Word2Vec 模型
参数 | 类型 | 说明 |
---|---|---|
filepath_or_buffer | str 或 file-like object | 模型文件的路径或文件对象。 |
*args | 任意其他参数 | 传递给底层加载机制的其他参数(通常不需要)。 |
**kwargs | 任意关键字参数 | 传递给底层加载机制的其他关键字参数(通常不需要)。 |
#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
model = Word2Vec.load(path)
return model
Ⅱ、加载数据集
open():打开一个文件,并返回一个文件对象,用于读取、写入或其他操作。
参数 | 类型 | 说明 |
---|---|---|
file | str 或 path-like object | 要打开的文件路径。 |
mode | str | 文件打开模式,如 'r' (读取)、'w' (写入)、'a' (追加)等。 |
buffering | int | 缓冲策略。 |
encoding | str | 文件编码方式,如 'utf-8' 。 |
errors | str | 错误处理方式,如 'strict' 、'ignore' 等。 |
其他参数 | — | 根据模式不同,可能有其他参数。 |
json.loads():将 JSON 格式的字符串解析为 Python 对象(如字典、列表等)。
参数 | 类型 | 说明 |
---|---|---|
s | str | 要解析的 JSON 字符串。 |
cls | 可选,json.JSONDecoder 的子类 | 自定义解码器类,默认为 json.JSONDecoder 。 |
object_hook | 可选,函数 | 用于自定义解码特定类型的对象。 |
其他参数 | — | 其他可选参数,如 parse_float 、parse_int 等。 |
append():将一个元素添加到列表的末尾。
参数 | 类型 | 说明 |
---|---|---|
object | 任意类型 | 要添加到列表末尾的对象。 |
join():将序列中的元素连接成一个字符串,元素之间使用指定的分隔符。
参数 | 类型 | 说明 |
---|---|---|
iterable | 可迭代对象 | 要连接的元素序列,如列表、元组等。 |
sep | str | 元素之间的分隔符,默认为空字符串。 |
jieba.lcut():使用 jieba 分词库将输入的字符串切分成词语列表。
参数 | 类型 | 说明 |
---|---|---|
sentence | str | 需要分词的字符串。 |
cut_all | bool | 是否使用全模式分词。True 表示全模式,False 表示精确模式(默认)。 |
HMM | bool | 是否使用 HMM 模型识别新词。True 表示使用,False 表示不使用(默认)。 |
use_paddle | bool | 是否启用 PaddlePaddle 模式进行分词。需要安装 PaddlePaddle 库。True 或 False 。 |
user_dict | str | 用户自定义词典的路径,用于增强分词效果。 |
#加载数据集
def load_sentence(path, model):
sentences = []
labels = []
with open(path, encoding="utf8") as f:
for line in f:
line = json.loads(line)
title, content = line["title"], line["content"]
sentences.append(" ".join(jieba.lcut(title)))
labels.append(line["tag"])
train_x = sentences_to_vectors(sentences, model)
train_y = label_to_label_index(labels)
return train_x, train_y
Ⅲ、 将tag标签转换为类别编号
#tag标签转化为类别标号
def label_to_label_index(labels):
return [LABELS[y] for y in labels]
Ⅳ、 文本向量化
split():将字符串按照指定的分隔符切分成子字符串列表。
参数 | 类型 | 说明 |
---|---|---|
sep | str 或 None | 分隔符,默认为任意空白字符。 |
maxsplit | int | 最大分割次数,默认为 -1,表示不限制。 |
np.zeros():创建一个指定形状和数据类型的全零数组。
参数 | 类型 | 说明 |
---|---|---|
shape | int 或 tuple | 数组的形状。 |
dtype | data-type | 数组元素的数据类型,默认为 float64 。 |
order | 'C' 或 'F' | 内存布局方式,'C' 行优先,'F' 列优先,默认为 'C' 。 |
append():将一个元素添加到列表的末尾。
参数 | 类型 | 说明 |
---|---|---|
object | 任意类型 | 要添加到列表末尾的对象。 |
np.array():创建一个 NumPy 数组。
参数 | 类型 | 说明 |
---|---|---|
object | array_like | 输入数据,可以是列表、元组、嵌套列表等。 |
dtype | data-type | 数组元素的数据类型,默认由输入数据推断。 |
copy | bool | 是否复制输入数据,默认为 True 。 |
ndmin | int | 返回数组的最小维度。 |
其他参数 | — | 根据具体需求,可能有其他参数。 |
model.wv():访问 Word2Vec 模型的词向量(word vectors)。
参数 | 类型 | 说明 |
---|---|---|
word | str | 要获取词向量的单词。 |
vector | bool | 是否返回词向量,默认为 True 。 |
其他参数 | — | 根据具体需求,可能有其他参数。 |
#文本向量化,使用了基于这些文本训练的词向量
def sentences_to_vectors(sentences, model):
vectors = []
for sentence in sentences:
words = sentence.split()
vector = np.zeros(model.vector_size)
for word in words:
try:
vector += model.wv[word]
# vector = np.max([vector, model.wv[word]], axis=0)
except KeyError:
vector += np.zeros(model.vector_size)
vectors.append(vector / len(words))
return np.array(vectors)
Ⅴ、SVM分类器训练
SVC():支持向量分类器(Support Vector Classifier),用于分类任务
参数 | 类型 | 说明 |
---|---|---|
C | float | 正则化参数,控制误分类的惩罚力度。 |
kernel | str 或 callable | 核函数类型,如 'linear' 、'poly' 、'rbf' 、'sigmoid' 等。 |
degree | int | 多项式核函数的度数,默认为 3。 |
gamma | str 或 float | 核函数的系数,'scale' 或 'auto' ,或具体数值。 |
coef0 | float | 核函数中的独立项。 |
其他参数 | — | 根据具体需求,可能有其他参数。 |
SVC对象.fit():训练支持向量分类器模型。
参数 | 类型 | 说明 |
---|---|---|
X | array-like | 训练数据的特征矩阵。 |
y | array-like | 训练数据的目标标签。 |
sample_weight | array-like | 每个样本的权重。 |
其他参数 | — | 根据具体需求,可能有其他参数。 |
SVC对象.predict():使用训练好的支持向量分类器进行预测。
参数 | 类型 | 说明 |
---|---|---|
X | array-like | 需要预测的数据特征矩阵。 |
返回值 | array | 预测的标签。 |
classification_report():生成一个文本报告,展示主要的分类指标,如精确率(precision)、召回率(recall)、F1 分数等。
参数 | 类型 | 说明 |
---|---|---|
y_true | array-like | 真实的目标标签。 |
y_pred | array-like | 预测的目标标签。 |
labels | array-like | 报告中包含的标签列表。 |
target_names | list 或 None | 标签的可读名称。 |
sample_weight | array-like | 每个样本的权重。 |
其他参数 | — | 根据具体需求,可能有其他参数。 |
def main():
model = load_word2vec_model("model.w2v")
train_x, train_y = load_sentence("F:\人工智能NLP\\NLP\Day7_文本分类问题\data\\train_tag_news.json", model)
test_x, test_y = load_sentence("F:\人工智能NLP\\NLP\Day7_文本分类问题\data\\valid_tag_news.json", model)
classifier = SVC()
classifier.fit(train_x, train_y)
y_pred = classifier.predict(test_x)
print(classification_report(test_y, y_pred))
Ⅵ、使用基于词向量的SVM分类器
#!/usr/bin/env python3
#coding: utf-8
#使用基于词向量的分类器
#对比几种模型的指标
import json
import jieba
import numpy as np
from gensim.models import Word2Vec
from sklearn.metrics import classification_report
from sklearn.svm import SVC
from collections import defaultdict
LABELS = {'健康': 0, '军事': 1, '房产': 2, '社会': 3, '国际': 4, '旅游': 5, '彩票': 6, '时尚': 7, '文化': 8, '汽车': 9, '体育': 10, '家居': 11, '教育': 12, '娱乐': 13, '科技': 14, '股票': 15, '游戏': 16, '财经': 17}
#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
model = Word2Vec.load(path)
return model
#加载数据集
def load_sentence(path, model):
sentences = []
labels = []
with open(path, encoding="utf8") as f:
for line in f:
line = json.loads(line)
title, content = line["title"], line["content"]
sentences.append(" ".join(jieba.lcut(title)))
labels.append(line["tag"])
train_x = sentences_to_vectors(sentences, model)
train_y = label_to_label_index(labels)
return train_x, train_y
#tag标签转化为类别标号
def label_to_label_index(labels):
return [LABELS[y] for y in labels]
#文本向量化,使用了基于这些文本训练的词向量
def sentences_to_vectors(sentences, model):
vectors = []
for sentence in sentences:
words = sentence.split()
vector = np.zeros(model.vector_size)
for word in words:
try:
vector += model.wv[word]
# vector = np.max([vector, model.wv[word]], axis=0)
except KeyError:
vector += np.zeros(model.vector_size)
vectors.append(vector / len(words))
return np.array(vectors)
def main():
model = load_word2vec_model("model.w2v")
train_x, train_y = load_sentence("F:\人工智能NLP\\NLP\Day7_文本分类问题\data\\train_tag_news.json", model)
test_x, test_y = load_sentence("F:\人工智能NLP\\NLP\Day7_文本分类问题\data\\valid_tag_news.json", model)
classifier = SVC()
classifier.fit(train_x, train_y)
y_pred = classifier.predict(test_x)
print(classification_report(test_y, y_pred))
if __name__ == "__main__":
main()