文本分类总体上包括8个步骤。数据探索分析-》数据抽取-》文本预处理-》分词-》去除停用词-》文本向量化表示-》分类器-》模型评估.重要python库包括numpy(数组),pandas(用于处理结构化数据),matplotlib(绘制词云,便于直观表示),sklearn(提供大量分类聚类算法库).
1.数据探索分析
(1)获取大量未经过处理的文档,且标记好文档所属类型。
(2)给各个文档分配唯一的Id,并将之前用文字标记的分类类别用离散数字代替。例如 分类标记为[‘正常短信’,‘垃圾短信’],将其离散表示为[0,1].
(3)将Id,文档内容,标记 作为列,样本个数作为行,将这些文档读入一个数组中。形式为:[ [Id1,content1,label1], ...,[Id_n,content_n,label_n] ]
代码示例:
import pandas as pd
data = pd.read_csv(csv文件名,header=None) # 读入csv文件,不读入列名
data.columns = ["Id","Content","Label"]
1.1DataFrame中获取数据的一些方法:
- data.loc[] # 通过字符串索引的方式获取指定行列数据 例如:
data.loc[0:2,"content"] # 获取第0,1,2行的content列 的数据,【注意】:0:2获取的是0,1,2行,这一点和一般的切片不相同
data.loc[[0,2],["content","label"]] # 通过列表指定行列
- data.iloc[] # 通过数字索引方式,用法和数组的一模一样
data["label"] # 获取label列的数据,结果是一维数组
data[["content","label"]] # 结果是 content,label列的所有数据1.2统计不同label出现频率,绘制饼图
data["label"].value_counts() # 获取label这一列数据中各个标记出现次数,结果以series的形式返回
1.2.1绘制饼图
num=data["label"].value_counts()
import matplotlib.pyplot as plt
plt.figure(figsize=(3,3)) # 将画布设置为3*3的正方形
plt.pie(num,labels=["正常","垃圾"]) # 绘制饼图,num是一个series ,series是一个带索引的数组,和字典使用类似。
plt.show()
2.数据抽取
当不同标记的比例不平衡时,需要分层抽样,例如0标记出现72000次,而1标记出现8000次,则此时会产生模型偷懒问题。
data_normal = data.loc[data["label"]==1].sample(1000,random_state=123) #在label为1的所有数据里挑选1000个随机样本
data_bad = data.loc[data["label"]==0].sample(1000,random_state=123) #在label为0的所有数据里挑选1000个随机样本
data_new = pd.contat([data_normal,data_bad],axis=0) # 默认行拼接,所以axis可不写
3.文本预处理
如下图所示,content一项中包含了xxx,以及一些特殊编码字符,以及逗号句号等等的标点符号,这些东西都是无意义字符,需要删除掉
删除这些特殊非中文字符,需要用到正则表达式,正则表达式是爬虫中必不可少的一个知识点,是一个条件表达式,用这个构造的条件表达式所指定的规则在一个指定字符串中检索匹配出符合规则的句子。
import re
afterDeleteSpecialWord=data_new["content"].apply(lambda x:re.sub("[^u4E00-u9FD5]+","",string))
这里的apply表示对这个series数组中的每一个元素(即文档的内容字符串)都执行这个匿名函数x,string是传进来的参数,re.sub 表示 将前面指定的正则表达式"[^u4E00-u9FD5]+"所匹配的字符串(即非中文特殊字符)用""代替。这里的正则表达式"[^u4E00-u9FD5]+":
[]是一个原子列表,^表示非,u4E00-u9FD5中文字符的正则表示,前面加上^则表示非中文字符,[]+表示这个原子列表中的字符可以匹配1次或多次。具体正则表达式的用法网上资源很多,这里不详细解释.
处理完后,标点符号,以及特殊字符都不见了,如下所示:
4.分词,去除停用词
第一步先将之前的content中的内容进行分词,分词后content列的元素是一个列表,比如之前的content列中的元素"我来到北京清华大学计算机学院",执行分词后结果为:["我","来到","北京","清华大学","计算机","学院"]
第二步是去除停用词,首先加载停用词文件,里面存储了N个停用词,然后对第一步中的分词结果进行去除存在于停用词列表中的词.
代码如下:
import jieba # 分词库
with open("stopList.txt","r") as f:
stop=f.read() # 获取的结果是一个大的字符串,其中换行符之类的特殊字符也存在于其中
stop = stop.split() # 按照空格,换行符进行分割,获取停用词列表
stop = [" "]+stop # 由于之前的stop里没有空格,而空格是停用词,所以重新加回空格
jieba.load_userdic(path) # 加载指定路径path里的用户自定义词典
after_segement = afterDeleteSpecialWord.apply(jieba.lcut) # 进行分词
data_after = after_segement.apply(lambda x:[i for i in x if i not in stop]) # 去除停用词
4.1绘制词云
绘制词云是文本分类里对词频的一个直观图像表示,以图像形式呈现,频率高的词汇字体更大,频率小的,字体小。
import matplotlib.pyplot as plt # 画图工具
from wordcloud import WordCloud # 词云工具库
import itertools # 将二维数据压缩为一维数据
pic = plt.imread(picturePath) # 这里picturePath为具体图片所在路径,这里没有指定.这行代码是加载画板的一个背景图片
"""
wc = WordCloud(font_path=r"C:WindowsFonts字体名称",background_color="white",mask=pic) # 生成一个词云对象 ,windows系统中的字体存放在c盘Windows文件夹下的Fonts文件夹中。因为这里统计的都是中文,所以不要选英文字体,而是选择中文字体,右键,属性,如图,为具体的字体名称 """
num = pd.Series(list(itertools.chain(*list(data_after)))).value_counts() # 统计词频
wc.fit_words(num) # 将统计好的词频放进去
plt.imshow(wc)
plt.show()
文本向量化表示
文本向量化表示的含义为:由于我们目前得到的是一个分词结果,是中文,而计算机不能直接将中文作为分类的输入数据,必须将其用数字来表示,那么如何将一个文档用一个数字化的向量表示呢,这就是文本向量化。
常用的向量化表示有词袋模型,词频,TF-IDF,以及考虑上下文的词嵌入。
词袋模型是指,一个文档中出现了的词则该词置1,词库中其他词置0,而不考虑出现次数的多少。一个文档则可以表示成一个N维的0,1向量,N的大小取决于词库的大小。
词频:在词袋模型的基础上,考虑出现词的次数,而不是只要出现了就是1。
TF-IDF:考虑一个词的词频以及逆文档频率,逆文档频率是指该词在所有文档里的稀有程度,该词在所有文档里出现的文档数越少,则该词越稀有,区分度就越高,逆文档频率就越高,逆文档频率=log(所有文档数/(出现该词的文档数+1)),而TF-IDF则=TF*IDF。
在sklearn 中的feature_extraction.text包中有CountVectorizer,TfidfVectorizer直接可以做文本向量化工作,一个是根据词频,一个是TF-IDF。
tmp = data_after.apply(lambda x:" ".join(x)) # 由于谷歌开发的向量化工具仅支持按空格统计,所以之前的列表存放的单词需要转换成一个以空格分隔开的一个大字符串。
cv=CountVectorizer().fit(tmp) # 加载字典,以及需要向量化的文本数据
vector_data = cv.transform(tmp) # 向量化,结果是一个迭代器
vector_array = vector_data.toarray() # 将迭代器转为数组
文本分类
接下来的步骤和一般机器学习分类问题是一模一样的,不多做介绍。已经得到结构化数据vector_array,以及相应的标签label,可以用sklearn的各种训练模型进行训练,测试,模型评估等等。