首页 > 其他分享 >《自然语言处理入门》读书笔记

《自然语言处理入门》读书笔记

时间:2022-12-12 17:01:21浏览次数:77  
标签:__ word 入门 dictionary 读书笔记 text dic return 自然语言


第一章,新手上路

1.1自然语言与编程语言

  1. 词汇量
  2. 结构化:
  3. 歧义性:
  4. 容错性;
  5. 易变性
  6. 简略性

1.2自然语言处理的层次

《自然语言处理入门》读书笔记_数据集

  • 文本:语音(语音识别),图像(光学符号识别),和文本。
  • 词法分析
  • 中文分词(将文本分割为有意义的词语,将字序列分割为次序列),
  • 词性标注(确定每个词语的类别和浅层的歧义消除),
  • 命名实体识别(较长的专有名词)
  • 信息抽取:根据单词和词性,抽取一部分有用的信息
  • 文本分类与文本聚类
  • 文本分类:一段话的语义,邮件是否是垃圾邮件,即把文档分类整理,即为文本分类,
  • 文本聚类:把相识的文档归档在一起,或者排除重复的文档,
  • 句法分析:根据句子主谓宾结构,分析描述句子意思,用于问答系统和搜索引擎。
  • 语义分析和篇章分析:相比较语法分析,语义分析侧重于语义而非语法,
  • 包括词义消歧(确定一个词在语境中的含义,不是简单词性),
  • 语义角色标注(标注句子中的谓语和其他成分的关系)
  • 语义依存分析(分析句子中词语之间的语义关系)
  • 其他任务
  • 自动问答
  • 自动摘要
  • 机器翻译
  • 信息检索(IR)是区别于自然语言处理的独立学科,IR的目标是查询信息,而NLP的目标是理解语言。

1.3 自然语言处理的流派

《自然语言处理入门》读书笔记_数据集_02

《自然语言处理入门》读书笔记_自然语言处理_03

1.4 机器学习

机器学习不直接编程却能赋予计算机提高能力的方法,及计算机通过某项任务的经验数据提高在该项任务上的能力。让机器学会算法的算法,把机器学习算法称为元算法,把机器学到的算法成为模型。

模型:

模型是对现实问题的数学抽象,由一个假设函数和一系列参数构成,可以表示:

《自然语言处理入门》读书笔记_数据集_04

自变量X为一个特征向量。用来表示一个对象的特征。

特征:

指事物特点转化后的数值,把事物特征通过数值类型来提取,称为特征提取

名字的特征提取:

“沈雁冰”特征提取

特征序号

特征条件

特征值

1

是否含有“雁"

1

2

是否含有”冰“

1

沈雁冰的可以表示为二维向量

《自然语言处理入门》读书笔记_List_05


假设根据名字判断性别的模型为

《自然语言处理入门》读书笔记_数据集_04

《自然语言处理入门》读书笔记_数据集_07

 中,

《自然语言处理入门》读书笔记_人工智能_08

表示特征值,比如娜是女性特征,磊是男性特征。

不需要通过字来写特征,定义一套模板来提取特征,比如通过定义

《自然语言处理入门》读书笔记_List_09

变量,遍历多个不同的名字,获取属于名字的所有模板,

《自然语言处理入门》读书笔记_人工智能_10

是通过名字生成,不是自己去分析统计得到,自动提取特征的模板称为特征模板。

设计特征模板称为特征工程。特征越多,参数就多,模型就越复杂,模型的复杂度与数据集匹配。

数据集:

供元算法学习的Demo称为样本(习题),多个样本称为数据集,在自然语言处理领域称为语料库,数据集的种类很多。

常用的数据集:

  1. MNIST (手写数字识别):
  2. ImageNet(图像识别):
  3. TREC(信息检索):
  4. SQuAD(自动问答):
  5. Europrl(机器翻译):

监督学习:

当习题有标准答案的时候,此时的学习算法称为监督学习,监督学习算法在机器做完题之后,会纠正模型错误,

对于性别识别来讲,数据集每个人名被表示为,

《自然语言处理入门》读书笔记_人工智能_11

 

对于督导学习来讲,如果答案是男性,而函数输出女性。

在    

《自然语言处理入门》读书笔记_数据集_12

  中,已知   

《自然语言处理入门》读书笔记_人工智能_13

 ,要想使  

《自然语言处理入门》读书笔记_数据集_14

  增大到非负值,需要将 

《自然语言处理入门》读书笔记_数据集_15

 的

《自然语言处理入门》读书笔记_List_16

 值增大一些、当多次迭代修改之后,男性的常用字权重就会加大,女性的常用字频率会减小。即这种优先级就会自动求出来。在自然语言处理中,对应每个词的词频。

这种在有标签(答案)的数据集上迭代学习的过程称为训练,训练用的数据集称为训练集,训练的结果是一系列参数(特征权重)或者迭代好的模型。利用模型,我们可以为任意一个名字做性别预测

《自然语言处理入门》读书笔记_人工智能_17

无监督学习:

即告诉题,不告诉答案。无监督学习一般用于聚类和降维,两者都不需要标注数据。

聚类:把相识的文档归档在一起,或者排除重复的文档,根据样本的相似性和簇的粒度决定。

降维:将样本点从高维空间变换到低维空间的过程,机器学习中的高维数据比比皆是,比如性别识别,以常用汉字为特征,就突破2000,如果有

《自然语言处理入门》读书笔记_List_18

个特征,则样本对应

《自然语言处理入门》读书笔记_数据集_19

维空间的点,多出的给假设函数的因变量使用。如果想让样本点可视化,需要降到二维或者三维。

降维算法要求,降维后尽量不损失信息,或者讲,样本在低维空间里每个维度的方差尽量大。

其他的类型的机器学习算法:

半监督学习:从多个不同的模型中提取相同预测的一致结果作为训练样本,扩充训练集。

强化学习:一边预测,一边根据环境情况反馈规划下一次决策。

1.5语料库:

自然语言处理领域的数据集

中文分词语料库:

人工正确切分后的句子集合

词性标注语料库:

句子切分后为每个词语指定一个词性的语料。词性标注集。

命名实体识别语料库

人工标注了文本内部制作者关心的实体名词以及实体类别。

句法分析语料库:

汉语中常用的句法分析语料库有CTB,即分词间语法的联系。

文本分类语料库:

人工标注了所所属分类的文章构成的语料库

语料库建设,即构建语料库的过程,规范制定 -- 人员培训--人工标注

1.6 开源工具:

  1. python接口:https://github.com/hankcs/pyhanlp
  2. java接口:https://github.com/hankcs/HanLP

第二章,词典分词

中文分词:即将一段文本拆分为一系列单词的过程。中文分词分为两类:

  1. 基于词典规则
  2. 基于机器学习

2.1 词典分词:

词的定义

语言学上定义具备独立意义的最小的单位。

词的性质:

齐夫定律:一个单词的词频于它的词频排名成反比,中文如此。

前30个常用词统计

《自然语言处理入门》读书笔记_数据集_20

《自然语言处理入门》读书笔记_System_21

2.2 词典

Hanlp词典:

词典的加载:

python:

from pyhanlp import *


def load_dictionary():
"""
加载HanLP中的mini词库
:return: 一个set形式的词库
"""
IOUtil = JClass('com.hankcs.hanlp.corpus.io.IOUtil')
path = HanLP.Config.CoreDictionaryPath.replace('.txt', '.mini.txt')
dic = IOUtil.loadDictionary([path])
return set(dic.keySet())


if __name__ == '__main__':
dic = load_dictionary()
print(len(dic))
print(list(dic)[0])
85584
倒贴

切分算法:

对于不同的词语选择规则来进行词典查询,常用的规则有正向最长匹配,逆向最长匹配。和双向最长匹配。它都是基于完全切分过程。

完全切分:

找出一段文本中的所有单词。朴素的完全切分算法比较简单,只需要遍历文本中的连续序列,查询改序列是否在文本上即可

def fully_segment(text, dic):
word_list = []
for i in range(len(text)): # i 从 0 到text的最后一个字的下标遍历
for j in range(i + 1, len(text) + 1): # j 遍历[i + 1, len(text)]区间
word = text[i:j] # 取出连续区间[i, j]对应的字符串
if word in dic: # 如果在词典中,则认为是一个词
word_list.append(word)
return word_list


if __name__ == '__main__':
dic = load_dictionary()

print(fully_segment('商品和服务', dic))

正向最长匹配:

对完全切分进行优化,我们需要有意义的词语序列,不是字典中单词构成的列表。考虑单词越长表达的意义越多。定义单词越长,优先级越高,即优先输出字典中最长的词,扫描顺序从前往后,即正向最长匹配。

from tests.book.ch02.utility import load_dictionary


def forward_segment(text, dic):
word_list = []
i = 0
while i < len(text):
longest_word = text[i] # 当前扫描位置的单字
for j in range(i + 1, len(text) + 1): # 所有可能的结尾
word = text[i:j] # 从当前位置到结尾的连续字符串
if word in dic: # 在词典中
if len(word) > len(longest_word): # 并且更长
longest_word = word # 则更优先输出
word_list.append(longest_word) # 输出最长词
i += len(longest_word) # 正向扫描
return word_list


if __name__ == '__main__':
dic = load_dictionary()

print(forward_segment('就读北京大学', dic))
print(forward_segment('研究生命起源', dic))
['就读', '北京大学']
['研究生', '命', '起源']

误差的原因及研究生的优先级大于研究

逆向最长匹配:

from tests.book.ch02.utility import load_dictionary


def backward_segment(text, dic):
word_list = []
i = len(text) - 1
while i >= 0: # 扫描位置作为终点
longest_word = text[i] # 扫描位置的单字
for j in range(0, i): # 遍历[0, i]区间作为待查询词语的起点
word = text[j: i + 1] # 取出[j, i]区间作为待查询单词
if word in dic:
if len(word) > len(longest_word): # 越长优先级越高
longest_word = word
break
word_list.insert(0, longest_word) # 逆向扫描,所以越先查出的单词在位置上越靠后
i -= len(longest_word)
return word_list


if __name__ == '__main__':
dic = load_dictionary()
print(backward_segment('项目的研究', dic))
print(backward_segment('研究生命起源', dic))
['项', '目的', '研究']
['研究', '生命', '起源']

双向最长匹配,

即同时执行正向逆向最长匹配,若词数不同,则返回词数更少的那个,否则返回两者中单字更少的那个,当单字也相同的时候,优先返回逆向最长匹配的结果。来源于语言学汉字的特性,启发式算法(单字词的算法要小于非单字词的算法),

from tests.book.ch02.backward_segment import backward_segment
from tests.book.ch02.forward_segment import forward_segment
from tests.book.ch02.utility import load_dictionary


def count_single_char(word_list: list): # 统计单字成词的个数
return sum(1 for word in word_list if len(word) == 1)


def bidirectional_segment(text, dic):
f = forward_segment(text, dic)
b = backward_segment(text, dic)
if len(f) < len(b): # 词数更少优先级更高
return f
elif len(f) > len(b):
return b
else:
if count_single_char(f) < count_single_char(b): # 单字更少优先级更高
return f
else:
return b # 都相等时逆向匹配优先级更高


if __name__ == '__main__':
dic = load_dictionary()

print(bidirectional_segment('研究生命起源', dic))

效果还没有逆向分词好

速度测评:

词典分词的核心在于速度而不在于精度

python;

《自然语言处理入门》读书笔记_自然语言处理_22

java:

《自然语言处理入门》读书笔记_数据集_23

java代码:

package com.hankcs.book.ch02;

import com.hankcs.hanlp.corpus.io.IOUtil;
import com.hankcs.hanlp.dictionary.CoreDictionary;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
* 《自然语言处理入门》2.3 切分算法

*/
public class NaiveDictionaryBasedSegmentation
{
public static void main(String[] args) throws IOException
{
// 加载词典
TreeMap<String, CoreDictionary.Attribute> dictionary =
IOUtil.loadDictionary("data/dictionary/CoreNatureDictionary.mini.txt");
System.out.printf("词典大小:%d个词条\n", dictionary.size());
System.out.println(dictionary.keySet().iterator().next());
// 完全切分
System.out.println(segmentFully("就读北京大学", dictionary));
// 正向最长匹配
System.out.println(segmentForwardLongest("就读北京大学", dictionary));
System.out.println(segmentForwardLongest("研究生命起源", dictionary));
System.out.println(segmentForwardLongest("项目的研究", dictionary));
// 逆向最长匹配
System.out.println(segmentBackwardLongest("研究生命起源", dictionary));
System.out.println(segmentBackwardLongest("项目的研究", dictionary));
// 双向最长匹配
String[] text = new String[]{
"项目的研究",
"商品和服务",
"研究生命起源",
"当下雨天地面积水",
"结婚的和尚未结婚的",
"欢迎新老师生前来就餐",
};
for (int i = 0; i < text.length; i++)
{
System.out.printf("| %d | %s | %s | %s | %s |\n", i + 1, text[i],
segmentForwardLongest(text[i], dictionary),
segmentBackwardLongest(text[i], dictionary),
segmentBidirectional(text[i], dictionary)
);
}

evaluateSpeed(dictionary);
}

/**
* 评测速度
*
* @param dictionary 词典
*/
public static void evaluateSpeed(Map<String, CoreDictionary.Attribute> dictionary)
{
String text = "江西鄱阳湖干枯,中国最大淡水湖变成大草原";
long start;
double costTime;
final int pressure = 10000;

System.out.println("正向最长");
start = System.currentTimeMillis();
for (int i = 0; i < pressure; ++i)
{
segmentForwardLongest(text, dictionary);
}
costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("%.2f万字/秒\n", text.length() * pressure / 10000 / costTime);

System.out.println("逆向最长");
start = System.currentTimeMillis();
for (int i = 0; i < pressure; ++i)
{
segmentBackwardLongest(text, dictionary);
}
costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("%.2f万字/秒\n", text.length() * pressure / 10000 / costTime);

System.out.println("双向最长");
start = System.currentTimeMillis();
for (int i = 0; i < pressure; ++i)
{
segmentBidirectional(text, dictionary);
}
costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("%.2f万字/秒\n", text.length() * pressure / 10000 / costTime);
}

/**
* 完全切分式的中文分词算法
*
* @param text 待分词的文本
* @param dictionary 词典
* @return 单词列表
*/
public static List<String> segmentFully(String text, Map<String, CoreDictionary.Attribute> dictionary)
{
List<String> wordList = new LinkedList<String>();
for (int i = 0; i < text.length(); ++i)
{
for (int j = i + 1; j <= text.length(); ++j)
{
String word = text.substring(i, j);
if (dictionary.containsKey(word))
{
wordList.add(word);
}
}
}
return wordList;
}

/**
* 正向最长匹配的中文分词算法
*
* @param text 待分词的文本
* @param dictionary 词典
* @return 单词列表
*/
public static List<String> segmentForwardLongest(String text, Map<String, CoreDictionary.Attribute> dictionary)
{
List<String> wordList = new LinkedList<String>();
for (int i = 0; i < text.length(); )
{
String longestWord = text.substring(i, i + 1);
for (int j = i + 1; j <= text.length(); ++j)
{
String word = text.substring(i, j);
if (dictionary.containsKey(word))
{
if (word.length() > longestWord.length())
{
longestWord = word;
}
}
}
wordList.add(longestWord);
i += longestWord.length();
}
return wordList;
}

/**
* 逆向最长匹配的中文分词算法
*
* @param text 待分词的文本
* @param dictionary 词典
* @return 单词列表
*/
public static List<String> segmentBackwardLongest(String text, Map<String, CoreDictionary.Attribute> dictionary)
{
List<String> wordList = new LinkedList<String>();
for (int i = text.length() - 1; i >= 0; )
{
String longestWord = text.substring(i, i + 1);
for (int j = 0; j <= i; ++j)
{
String word = text.substring(j, i + 1);
if (dictionary.containsKey(word))
{
if (word.length() > longestWord.length())
{
longestWord = word;
break;
}
}
}
wordList.add(0, longestWord);
i -= longestWord.length();
}
return wordList;
}

/**
* 统计分词结果中的单字数量
*
* @param wordList 分词结果
* @return 单字数量
*/
public static int countSingleChar(List<String> wordList)
{
int size = 0;
for (String word : wordList)
{
if (word.length() == 1)
++size;
}
return size;
}

/**
* 双向最长匹配的中文分词算法
*
* @param text 待分词的文本
* @param dictionary 词典
* @return 单词列表
*/
public static List<String> segmentBidirectional(String text, Map<String, CoreDictionary.Attribute> dictionary)
{
List<String> forwardLongest = segmentForwardLongest(text, dictionary);
List<String> backwardLongest = segmentBackwardLongest(text, dictionary);
if (forwardLongest.size() < backwardLongest.size())
return forwardLongest;
else if (forwardLongest.size() > backwardLongest.size())
return backwardLongest;
else
{
if (countSingleChar(forwardLongest) < countSingleChar(backwardLongest))
return forwardLongest;
else
return backwardLongest;
}
}

}

字典树

匹配算法的瓶颈之一在与如何判断集合是否含有字符串,如果用有序集合的话(TreeMap)复杂度为  

《自然语言处理入门》读书笔记_人工智能_24

 ,

《自然语言处理入门》读书笔记_List_18

为词典的大小,如果用散列表的话(HashMap,dict),即空间还时间的方法。

字符串集合常用字典树(trie树,前缀树)存储

字典树即将词语视为root到某一节点的一条路径,并在词尾点上做标记。如果能走到特殊标记的节点,说明在该集合中。否则不存在。

不光是集合,字典树也可以实现映射,需要将相应的值悬挂在键的终点节点。当词典的大小为

《自然语言处理入门》读书笔记_List_18

时,最坏情况下的任然是

《自然语言处理入门》读书笔记_人工智能_24

实际速度比二分法要快,前缀匹配是递进的过程,算法不必比较支符串的前缀。

字典数的节点实现

我们约定用值为None表示节点不对应词语,这样就不能插入值为None 的键了,节点的实现代码:

class Node(object):
# 数据结构,构造方法。
# 指向下一个节点的字典
# 当前节点的值
def __init__(self, value) -> None:
self._children = {}
self._value = value

# 添加子节点
def _add_child(self, char, value, overwrite=False):
child = self._children.get(char)
if child is None:
child = Node(value)
self._children[char] = child
elif overwrite:
child._value = value
return child

在添加节点中,会先检查已经存在的字符char对应的child,如果不存在就直接插入子节点,如果存在,根据标识确定是否要覆盖。

字典树的增删改查的实现

class Trie(Node):
def __init__(self) -> None:
super().__init__(None)

def __contains__(self, key):
return self[key] is not None

def __getitem__(self, key):
state = self
for char in key:
state = state._children.get(char)
if state is None:
return None
return state._value

def __setitem__(self, key, value):
state = self
for i, char in enumerate(key):
if i < len(key) - 1:
state = state._add_child(char, None, False)
else:
state = state._add_child(char, value, True)

删改查其实是一回事,都是查询,删除操作无非是将终点设置成None,修改即设置成其他的值。

从确定有限状态机自动机(DFA)来讲,每个节点都是一个状态,状态表示当前,已经查询的的前缀

从父节点到子节点的移动过程可以看做是一次状态的转移,当某个字符进行状态转移的时候,会询问该字符父节点与子节点的映射关系,如果父节点有满足条件的边,则进行状态转移,否则失败,当完成了全部的状态转移,我们询问最后一个状态是否为终点状态。

Java代码实现

《自然语言处理入门》读书笔记_人工智能_28

首子散列其余二分的字典树

散列函数,用于将对象转换为整数,散列函数满足,对象相同,则散列值相同,如果做到对象不同,散列值不同则为完美散列。

对于Python内置的dict来讲,实际用的是字符串的散列函数,在64位系统上,返回64位整数,但unicode字符总共才136690个,远远小于

《自然语言处理入门》读书笔记_List_29

,这就导致两个字符相邻,散列值差很多。Java的散列相对友好一些,Java使用UTF-16,每个字符可以映射为16位不重复的整数。所有散列函数输出区间为 

《自然语言处理入门》读书笔记_数据集_30

 内的正整数,

具体做法是,创建一个65535的数组,将子节点按对应的哈希值索引存放到内存中,这样每次状态转移,都是直接寻址。

但是这种做法内存指数膨胀,当词典中词语长度为

《自然语言处理入门》读书笔记_List_31

时,则字段数

标签:__,word,入门,dictionary,读书笔记,text,dic,return,自然语言
From: https://blog.51cto.com/u_13474506/5931187

相关文章

  • 《Spring Cloud 微服务架构进阶》读书笔记
    前页随着DevOps和以Docker为主的容器技术的发展,云原生应用架构和微服务变得流行起来。云原生包含的内容很多,如DevOps、持续交付、微服务、敏捷等第一章,微服务架构介......
  • docker容器入门
    docker镜像操作docker简单镜像操作#查询镜像dockersearchmysql#查看镜像列表-a显示所有dockerimages-a#拉取镜像如果不加版本号,默认使用lastsdockerpullmy......
  • 读书笔记(二)——《被讨厌的勇气》
    世界是简单的,人生也是简单的人是可以改变的,人是可以获得乐趣的开篇——引言阿德勒的哲学从我的理解上来看,我理解了“世界是简单的,人生也是简单”,这个和我的世界观差不多。......
  • 读书笔记(三)——11.26
    文章目录​​一、早冥读写跑​​​​二、《刻意练习》​​​​三、《人类简史》​​​​四、《活法》​​一、早冥读写跑阅读感悟游戏心态,不要让事情本身舒服了,你的情绪和......
  • 《深入理解Java虚拟机》读书笔记
    第一部分,走进Java第二部分,自动内存管理机制:第二章:Java内存区域与内存溢出异常一,Java中,虚拟机自动管理内存机制,不在需要为每一个new操作去写配对的delete和free操作,不容易......
  • 《Maven实战》读书笔记
    嗯,实训的时候第一次接触maven,感觉好方便,嗯,所以假期的时候刷这本书,好累呀,是心里不大舒服,加油,未来。                       ......
  • 《java8高级应用与开发》读书笔记(二)
    写在前面本笔记涉及内容:类加载、反射、枚举、注解、国际化、格式化类加载:是指将类的class文件读入内存,并为之创建一个Java.lang.class对象。即当线程使用任何一个类时,系统都......
  • 《云原生入门级开发者认证》学习笔记之云原生基础设施之容器技术
    写在前面嗯,报了考试,整理课堂笔记记忆学习的原因:虽然考了​​CKA​​​,了解了一些​​K8s​​相关的知识但是对​​云原生​​整个体系一直都很模糊希望对云原生有一个......
  • Anaconda安装和入门(超级简单详细的安装步骤)
    简介Anaconda是为独立从业者开发的工具包,可让您使用数以千计的开源包和库,它是一种开源、灵活的解决方案,它提供了以跨平台方式构建、分发、安装、更新和管理软件的实用......
  • 一些NLP的入门资料参考
    本站整理了一些NLP的入门资料参考,建议初学者看看。需要复制链接在浏览器里打开。1.通过kaggle比赛学习机器学习文本分类方法https://zhuanlan.zhihu.com/p/34899693?utm_med......