上一章:[(89条消息) Machine Learning with Python Cookbook 学习笔记 第5章_五舍橘橘的博客-CSDN博客]
代码笔记仓库(给颗星再走qaq):yy6768/Machine-Learning-with-Python-Cookbook-notebook: 人工智能典型算法笔记 (github.com)
对非结构性文本进行一些基本的清理
常用的函数:
strip
replace
split
clean.py
# 正则表达式模块
import re
# 新建一段文本
text_data = [" Interrobang. By Aishwarya Henriette ",
"Parking And Going. By Karl Gautier",
" Today Is The night. By Jarek Prakash "]
# 除去始末空格
strip_whitespace = [string.strip() for string in text_data]
# Show text
print(strip_whitespace)
# 除去.
remove_periods = [string.replace(".", "") for string in strip_whitespace]
# Show text
print(remove_periods)
# Create function
def capitalizer(string: str) -> str:
return string.upper()
# 全部变成大写
print([capitalizer(string) for string in remove_periods])
# 新建一个正则表达式匹配函数
def replace_letters_with_upper_x(string: str) -> str:
return re.sub(r"[a-zA-Z]", "X", string)
# 运用function
print([replace_letters_with_upper_x(string) for string in remove_periods])
cleanHtml.py
需要安装两个库
Beautiful Soup 4
官网:Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation (beautiful-soup-4.readthedocs.io)
Beautiful Soup 中文文档
教程BeautifulSoup详细使用教程!你学会了吗? - 知乎 (zhihu.com)
conda install bs4
lxml
Python lxml库的安装和使用 (biancheng.net)
conda install lxml
代码:
# Load library
from bs4 import BeautifulSoup
# 创建一些html文本
html = """
Masego Azra"
"""
# 转换 html
soup = BeautifulSoup(html, "lxml")
# 查找div,寻找class为fullname的标签,获得它的文本属性
print(soup.find("div", {"class": "full_name"}).text)
Beautiful Soup 是一个强大的 Python 库,专为抓取 HTML 而设计。
支持原生和第三方解析器
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | Python的内置标准库执行速度适中文档容错能力强 | Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | 速度快文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”) | 速度快唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性以浏览器的方式解析文档生成HTML5格式的文档 | 速度慢不依赖其他库 |
Beautiful Soup四大标签
支持遍历文档树,查找文档树以及CSS选择器三个主要操作
移除标点
removingPunctuation.py
# Load libraries
import unicodedata
import sys
# 文本
text_data = ['Hi!!!! I. Love. This. Song....',
'10000% Agree!!!! #LoveIT',
'Right?!?!']
# 创建一个字典,用于处理标点
punctuation = dict.fromkeys(i for i in range(sys.maxunicode)
if unicodedata.category(chr(i)).startswith('P'))
# For each string, 移除标点
print([string.translate(punctuation) for string in text_data])
把文本分解为单个单词
NLTK——强大的python自然语言工具包,具有强大的文本集操作
nltk包需要安装
conda install nltk
nltk需要下载数据包
# 主动下载
import nltk
nltk.download('punkt')
# 离线下载
# 下载到指定的文件夹里
# http://www.nltk.org/nltk_data/
NTLKExample.py
# Load library
from nltk.tokenize import word_tokenize
# Create text
string = "The science of today is the technology of tomorrow"
# 分词
print(word_tokenize(string))
# Load library
from nltk.tokenize import sent_tokenize
# Create text
string = "The science of today is the technology of tomorrow. Tomorrow is today."
# 分句子
print(sent_tokenize(string))
主动下载+结果
移除去stop words(信息量极少的次,例如‘I’ 'am’等)
仍然需要NLTK库
stopWorkds.py
# Load library
from nltk.corpus import stopwords
# 需要下载
import nltk
nltk.download('stopwords')
# Create word tokens
tokenized_words = ['i',
'am',
'going',
'to',
'go',
'to',
'the',
'store',
'and',
'park']
# Load stop words
stop_words = stopwords.words('english')
# Remove stop words
print([word for word in tokenized_words if word not in stop_words])
# Show stop words
stop_words[:5]
[‘i’, ‘me’, ‘my’, ‘myself’, ‘we’]
PorterStemmer
stemmingWords.py
# Load library
from nltk.stem.porter import PorterStemmer
# Create word tokens
tokenized_words = ['i', 'am', 'humbled', 'by', 'this', 'traditional', 'meeting']
# 创建 stemmer
porter = PorterStemmer()
# 运用 stemmer
print([porter.stem(word) for word in tokenized_words])
虽然将句子中的词转换为词干可读性较差,但是更容易将句子观察比较
PorterStemmer使用了现在非常常用的Porter算法来删除一些常见的前缀和后缀生成stemming
定义一个类
class Stemmer
{ private char[] b;
private int i, /* b中的元素位置(偏移量) */
i_end, /* 要抽取词干单词的结束位置 */
j, k;
private static final int INC = 50;
/* 随着b的大小增加数组要增长的长度(防止溢出) */
public Stemmer()
{ b = new char[INC];
i = 0;
i_end = 0;
}
}
字符串处理add
/**
* 增加一个字符到要存放待处理的单词的数组。添加完字符时,
* 可以调用stem(void)方法来进行抽取词干的工作。
*/
public void add(char ch)
{ if (i == b.length)
{ char[] new_b = new char[i+INC];
for (int c = 0; c < i; c++) new_b[c] = b[c];
b = new_b;
}
b[i++] = ch;
}
/** 增加wLen长度的字符数组到存放待处理的单词的数组b。
*/
public void add(char[] w, int wLen)
{ if (i+wLen >= b.length)
{ char[] new_b = new char[i+wLen+INC];
for (int c = 0; c < i; c++) new_b[c] = b[c];
b = new_b;
}
for (int c = 0; c < wLen; c++) b[i++] = w[c];
}
一系列辅助函数
总结下来就是判断发音类,判断类型类和操作类,核心函数应该是m()返回辅音序列的个数
cons(i):参数i:int型;返回值bool型。当i为辅音时,返回真;否则为假。
m()
:返回值:int型。表示单词b介于0和j之间辅音序列的个度。现假设c代表辅音序列,而v代表元音序列。<…>表示任意存在。于是有如下定义;
vowelinstem():返回值:bool型。从名字就可以看得出来,表示单词b介于0到i之间是否存在元音。
doublec(j):参数j:int型;返回值bool型。这个函数用来表示在j和j-1位置上的两个字符是否是相同的辅音。
cvc(i):参数i:int型;返回值bool型。对于i,i-1,i-2位置上的字符,它们是“辅音-元音-辅音”的形式,并且对于第二个辅音,它不能为w、x、y中的一个。这个函数用来处理以e结尾的短单词。比如说cav(e),lov(e),hop(e),crim(e)。但是像snow,box,tray就辅符合条件。
ends(s):参数:String;返回值:bool型。顾名思义,判断b是否以s结尾。
setto(s):参数:String;void类型。把b在(j+1)…k位置上的字符设为s,同时,调整k的大小。
r(s):参数:String;void类型。在m()>0的情况下,调用setto(s)。
// cons(i) 为真 <=> b[i] 是一个辅音
private final boolean cons(int i)
{ switch (b[i])
{ case 'a': case 'e': case 'i': case 'o': case 'u': return false; //aeiou
case 'y': return (i==0) ? true : !cons(i-1);
//y开头,为辅;否则看i-1位,如果i-1位为辅,y为元,反之亦然。
default: return true;
}
}
// m() 用来计算在0和j之间辅音序列的个数。 见上面的说明。 */
private final int m()
{ int n = 0; //辅音序列的个数,初始化
int i = 0; //偏移量
while(true)
{ if (i > j) return n; //如果超出最大偏移量,直接返回n
if (! cons(i)) break; //如果是元音,中断
i++; //辅音移一位,直到元音的位置
}
i++; //移完辅音,从元音的第一个字符开始
while(true)//循环计算vc的个数
{ while(true) //循环判断v
{ if (i > j) return n;
if (cons(i)) break; //出现辅音则终止循环
i++;
}
i++;
n++;
while(true) //循环判断c
{ if (i > j) return n;
if (! cons(i)) break;
i++;
}
i++;
}
}
// vowelinstem() 为真 <=> 0,...j 包含一个元音
private final boolean vowelinstem()
{ int i; for (i = 0; i <= j; i++) if (! cons(i)) return true;
return false;
}
// doublec(j) 为真 <=> j,(j-1) 包含两个一样的辅音
private final boolean doublec(int j)
{ if (j < 1) return false;
if (b[j] != b[j-1]) return false;
return cons(j);
}
/* cvc(i) is 为真 <=> i-2,i-1,i 有形式: 辅音 - 元音 - 辅音
并且第二个c不是 w,x 或者 y. 这个用来处理以e结尾的短单词。 e.g.
cav(e), lov(e), hop(e), crim(e), 但不是
snow, box, tray.
*/
private final boolean cvc(int i)
{ if (i < 2 || !cons(i) || cons(i-1) || !cons(i-2)) return false;
{ int ch = b[i];
if (ch == 'w' || ch == 'x' || ch == 'y') return false;
}
return true;
}
private final boolean ends(String s)
{ int l = s.length();
int o = k-l+1;
if (o < 0) return false;
for (int i = 0; i < l; i++) if (b[o+i] != s.charAt(i)) return false;
j = k-l;
return true;
}
// setto(s) 设置 (j+1),...k 到s字符串上的字符, 并且调整k值
private final void setto(String s)
{ int l = s.length();
int o = j+1;
for (int i = 0; i < l; i++) b[o+i] = s.charAt(i);
k = j+l;
}
private final void r(String s) { if (m() > 0) setto(s); }
然后正式进入六步操作的工作
第一步是否为ed或着ing结尾
可以明显看到麻烦在于分类讨论,有许多诸如sses或者ies这样的结尾很难判断
/* step1() 处理复数,ed或者ing结束的单词。比如:
caresses -> caress
ponies -> poni
ties -> ti
caress -> caress
cats -> cat
feed -> feed
agreed -> agree
disabled -> disable
matting -> mat
mating -> mate
meeting -> meet
milling -> mill
messing -> mess
meetings -> meet
*/
private final void step1()
{ if (b[k] == 's')
{ if (ends("sses")) k -= 2; //以“sses结尾”
else if (ends("ies")) setto("i"); //以ies结尾,置为i
else if (b[k-1] != 's') k--; //两个s结尾不处理
}
if (ends("eed")) { if (m() > 0) k--; } //以“eed”结尾,当m>0时,左移一位
else if ((ends("ed") || ends("ing")) && vowelinstem())
{ k = j;
if (ends("at")) setto("ate"); else
if (ends("bl")) setto("ble"); else
if (ends("iz")) setto("ize"); else
if (doublec(k))//如果有两个相同辅音
{ k--;
{ int ch = b[k];
if (ch == 'l' || ch == 's' || ch == 'z') k++;
}
}
else if (m() == 1 && cvc(k)) setto("e");
}
}
第二步 如果含有元音,并且以y结尾将y改成i
private final void step2() { if (ends("y") && vowelinstem()) b[k] = 'i'; }
第三步 将双后缀的单词映射为单后缀。 和第一步一样需要分类讨论,有很多英语上的特殊情况例如
所以只能一个一个进行判断,但是实际上就是枚举所有类别的双后缀然后转换成原来的模式
/* step3() 将双后缀的单词映射为单后缀。 所以 -ization ( = -ize 加上
-ation) 被映射到 -ize 等等。 注意在去除后缀之前必须确保
m() > 0. */
private final void step3() { if (k == 0) return; switch (b[k-1])
{
case 'a': if (ends("ational")) { r("ate"); break; }
if (ends("tional")) { r("tion"); break; }
break;
case 'c': if (ends("enci")) { r("ence"); break; }
if (ends("anci")) { r("ance"); break; }
break;
case 'e': if (ends("izer")) { r("ize"); break; }
break;
case 'l': if (ends("bli")) { r("ble"); break; }
if (ends("alli")) { r("al"); break; }
if (ends("entli")) { r("ent"); break; }
if (ends("eli")) { r("e"); break; }
if (ends("ousli")) { r("ous"); break; }
break;
case 'o': if (ends("ization")) { r("ize"); break; }
if (ends("ation")) { r("ate"); break; }
if (ends("ator")) { r("ate"); break; }
break;
case 's': if (ends("alism")) { r("al"); break; }
if (ends("iveness")) { r("ive"); break; }
if (ends("fulness")) { r("ful"); break; }
if (ends("ousness")) { r("ous"); break; }
break;
case 't': if (ends("aliti")) { r("al"); break; }
if (ends("iviti")) { r("ive"); break; }
if (ends("biliti")) { r("ble"); break; }
break;
case 'g': if (ends("logi")) { r("log"); break; }
} }
第四步,处理 -ic-,-full,-ness等等后缀。和步骤3有着类似的处理。 也是分类讨论然后替换
private final void step4() { switch (b[k])
{
case 'e': if (ends("icate")) { r("ic"); break; }
if (ends("ative")) { r(""); break; }
if (ends("alize")) { r("al"); break; }
break;
case 'i': if (ends("iciti")) { r("ic"); break; }
break;
case 'l': if (ends("ical")) { r("ic"); break; }
if (ends("ful")) { r(""); break; }
break;
case 's': if (ends("ness")) { r(""); break; }
break;
} }
第五步就是根据m()的统计情况,处理vcvc的情况
private final void step5()
{ if (k == 0) return; switch (b[k-1])
{ case 'a': if (ends("al")) break; return;
case 'c': if (ends("ance")) break;
if (ends("ence")) break; return;
case 'e': if (ends("er")) break; return;
case 'i': if (ends("ic")) break; return;
case 'l': if (ends("able")) break;
if (ends("ible")) break; return;
case 'n': if (ends("ant")) break;
if (ends("ement")) break;
if (ends("ment")) break;
/* element etc. not stripped before the m */
if (ends("ent")) break; return;
case 'o': if (ends("ion") && j >= 0 && (b[j] == 's' || b[j] == 't')) break;
/* j >= 0 fixes Bug 2 */
if (ends("ou")) break; return;
/* takes care of -ous */
case 's': if (ends("ism")) break; return;
case 't': if (ends("ate")) break;
if (ends("iti")) break; return;
case 'u': if (ends("ous")) break; return;
case 'v': if (ends("ive")) break; return;
case 'z': if (ends("ize")) break; return;
default: return;
}
if (m() > 1) k = j;//调用对k赋值
}
第6步很好理解 除去末尾冗余的e
private final void step6()
{ j = k;
if (b[k] == 'e')
{ int a = m();
if (a > 1 || a == 1 && !cvc(k-1)) k--;
}
if (b[k] == 'l' && doublec(k) && m() > 1) k--;
}
标记词性
tagging.py
# Load libraries
from nltk import pos_tag
from nltk import word_tokenize
from sklearn.preprocessing import MultiLabelBinarizer
# 下载
# import nltk
# nltk.download('averaged_perceptron_tagger')
# Create text
text_data = "Chris loved outdoor running"
# 使用训练好的模型处理
text_tagged = pos_tag(word_tokenize(text_data))
# Show parts of speech
print(text_tagged)
# Filter words
print([word for word, tag in text_tagged if tag in ['NN', 'NNS', 'NNP', 'NNPS']])
# 用独热编码将词性统计转化为特征矩阵
# Create text
tweets = ["I am eating a burrito for breakfast",
"Political science is an amazing field",
"San Francisco is an awesome city"]
# Create list
tagged_tweets = []
# 标记每个词的词性
for tweet in tweets:
tweet_tag = pos_tag(word_tokenize(tweet))
tagged_tweets.append([tag for word, tag in tweet_tag])
# Use one-hot 编码
one_hot_multi = MultiLabelBinarizer()
print(one_hot_multi.fit_transform(tagged_tweets))
print(one_hot_multi.classes_)
如果是非专业术语语句,使用 NLTK 的预训练词性标注器是最简单的方法
Brown Corpus是NTLK使用的语料库
NLTK uses the Penn Treebank parts for speech tags, some examples:
tag | Parts of Speech |
---|---|
NNP | Proper noun, singular |
NN | Noun, singular or mass |
RB | Adverb |
VBD | Verb, past tense |
VBG | Verb, gerund or present participle |
JJ | Adjective |
PRP | Personal pronoun |
这里我们使用一个退避 n-gram 标注器,其中 n 是我们在预测词的词性标签时考虑的先前词的数量。首先我们使用 TrigramTagger 考虑前面两个词;如果两个单词不存在,我们“退后”并使用 BigramTagger 考虑前一个单词的标签,最后如果失败,我们只使用 UnigramTagger 查看单词本身。
# Load library
from nltk.corpus import brown
from nltk.tag import UnigramTagger
from nltk.tag import BigramTagger
from nltk.tag import TrigramTagger
# Get some text from the Brown Corpus, broken into sentences
sentences = brown.tagged_sents(categories='news')
# Split into 4000 sentences for training and 623 for testing
train = sentences[:4000]
test = sentences[4000:]
# backoff tagger
unigram = UnigramTagger(train)
bigram = BigramTagger(train, backoff=unigram)
trigram = TrigramTagger(train, backoff=bigram)
# 准确率 :0.8179229731754832
print(trigram.evaluate(test))
统计特定的词出现的次数
使用scikit-learn’s CountVectorizer
countExample.py
# Load library
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
# Create text
text_data = np.array(['I love Brazil. Brazil!',
'Sweden is best',
'Germany beats both'])
# 创建一个特征矩阵包含计数信息
count = CountVectorizer()
bag_of_words = count.fit_transform(text_data)
# 打印
print(bag_of_words)
print(bag_of_words.toarray())
# 展示特征的name get_feature_names即将废弃
print(count.get_feature_names_out())
Bag-of-words models是文本转换成feature最常用的模型
大多数bag-of-words的矩阵都是稀疏矩阵,所以CountVectorizer 的一个很好的特性是默认情况下输出是一个稀疏矩阵
CountVectorizer 带有许多有用的参数,可以轻松创建词袋特征矩阵。首先,虽然默认情况下每个特征都是一个单词,但不一定是这样。相反,我们可以将每个特征设置为两个单词(称为 2-gram)甚至三个单词(3-gram)的组合。
ngram_range 设置我们的 n-gram 的最小和最大大小。 例如,(2,3) 将返回所有 2-gram 和 3-gram。 其次,我们可以使用内置列表或自定义列表的 stop_words 轻松删除低信息填充词。 最后,我们可以使用词汇将我们想要考虑的单词或短语限制在某个单词列表中。 例如,我们可以为仅出现的国家名称创建一个词袋特征矩阵:
# Create feature matrix with arguments
count_2gram = CountVectorizer(ngram_range=(1, 2),
stop_words="english",
vocabulary=['brazil'])
bag = count_2gram.fit_transform(text_data)
# View feature matrix
print(bag.toarray())
# View the 1-grams and 2-grams
print(count_2gram.vocabulary_)
N-Gram是一种基于统计语言模型的算法。它的基本思想是将文本里面的内容按照字节进行大小为N的滑动窗口操作,形成了长度是N的字节片段序列。
自然语言处理中N-Gram模型介绍 - 知乎 (zhihu.com)
weightWords.py
# Load libraries
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
# Create text
text_data = np.array(['I love Brazil. Brazil!',
'Sweden is best',
'Germany beats both'])
# 创建 the tf-idf 特征矩阵
tfidf = TfidfVectorizer()
feature_matrix = tfidf.fit_transform(text_data)
# 展示这个特征矩阵
print(feature_matrix)
# 转换为一般数组
print(feature_matrix.toarray())
# 特征名称
print(tfidf.vocabulary_)
t f − i d f ( t , d ) = t f ( t , d ) ∗ i d f ( t ) tf-idf(t, d) = tf(t,d) * idf(t) tf−idf(t,d)=tf(t,d)∗idf(t)
tf 和 idf 的计算方式有很多变化。 在 scikit-learn 中,tf 只是单词在文档中出现的次数,idf 的计算公式为:
i d f ( t ) = l o g ( 1 + n d 1 + d f ( d , t ) + 1 idf(t) = log(\frac{1 + n_d}{1 + df(d, t}) +1 idf(t)=log(1+df(d,t1+nd)+1
其中 nd 是文档数,df(d,t) 是术语,t 的文档频率(即,该术语出现的文档数)。默认情况下,scikit-learn 然后使用欧几里得范数(L2 范数)对 tf-idf 向量进行归一化。结果值越高,单词对文档越重要
下一章:(93条消息) Machine Learning with Python Cookbook 学习笔记 第7章_五舍橘橘的博客-CSDN博客