最近我参加了一个人工智能与自然语言处理的课程,这是第一周的学习笔记。这份笔记不涉及一般知识,全部都是与实践(我在这门课上的作业)有关的总结。
文章目录理论学习人工智能的五种模型:作业1. Rule Based 基于规则的模型2. Probability Based 基于概率的模型
理论学习
人工智能的五种模型:
Rule Based 基于规则的模型
Probability Based 基于概率的模型
Search Based 基于搜索的模型
Mathematical or Analytical Based 基于数学或统计的模型
Machine Learning (Deep Learning) Based 基于机器学习或深度学习的模型
作业
1. Rule Based 基于规则的模型
语言是有语法规则的,比如一个完整的句子一般都有“主谓宾”结构。因此我们可以根据一个规则生成句子(像不像人话再说)。首先我们创建一个规则(以空格分界)和语料库(以 “|” 分界):
simple_grammar = """
sentence => noun_phrase verb_phrase
noun_phrase => Article Adj* noun
Adj* => null | Adj Adj*
verb_phrase => verb noun_phrase
Article => 一个 | 这个
noun => 女人 | 篮球 | 桌子 | 小猫
verb => 看着 | 坐在 | 听着 | 看见
Adj => 蓝色的 | 好看的 | 小小的
"""
这个语法结构是:
名词性结构和动词性结构组成一个句子;
名词性结构由冠词、形容词结构和名词组成;
形容词结构由空、一个形容词或多个形容词组成;
动词性结构由动词和名词组成。
选择词语的时候,我们先按照“|”将字符串拆分,然后使用 random.choice 随机选择一个词。
>>> import random
>>> def adj(): return random.choice('蓝色的 | 好看的 | 小小的'.split('|')).split()[0] # 先拆分字符串,然后随机选择一个词
>>> adj()
'好看的'
然后我们还可以定义一个函数重复取词:
>>> def adj_star():
... return random.choice([lambda : '', lambda : adj() + adj_star()])()
>>> adj_star()
'好看的蓝色的'
>>> adj_star()
'小小的蓝色的好看的好看的'
可以看到,这个函数可以一次读取多次形容词。有两个地方需要说明:
第二行里调用了函数本身,需要使用 lambda 来控制函数的调用,即加了 lambda 以后,只有随机抽到了这部分才会启动。没有 lambda 的话,在选择的时候,无论有没有选取这部分,它都会启动,从而形成一个死循环。
最后的 () 是函数执行符。没有这对括号,函数返回的是它的内存地址:
>>> def adj_star():
... return random.choice([lambda : '', lambda : adj() + adj_star()])
>>> adj_star()
.()>
我们希望程序能够适应语法规则的变化,这样我们就不需要每次变化的时候都重新写程序了。我们以上面的语法和语料库为例,我们希望:程序读到这个库的时候,程序首先解析语法,然后根据解析的语法生成句子。下面是语法解析的代码:
def create_grammer(grammer_str, line_split="\n", split="=>"):
grammer = {}
for line in grammer_str.split(line_split):
if not line.strip():
continue
exp, stmt = line.split(split)
grammer[exp.strip()] = [s.split() for s in stmt.split("|")]
return grammer
解析出来的语法是这样的:
{'sentence': [['noun_phrase', 'verb_phrase']],
'noun_phrase': [['Article', 'Adj*', 'noun']],
'Adj*': [['null'], ['Adj', 'Adj*']],
'verb_phrase': [['verb', 'noun_phrase']],
'Article': [['一个'], ['这个']],
'noun': [['女人'], ['篮球'], ['桌子'], ['小猫']],
'verb': [['看着'], ['坐在'], ['听着'], ['看见']],
'Adj': [['蓝色的'], ['好看的'], ['小小的']]}
然后是生成句子:
from random import choice
def generate(gram, target):
if target not in gram: return target
expaned = [generate(gram, t) for t in choice(gram[target])]
return ''.join([e for e in expaned])
生成的句子如下:
'这个小猫看见这个女人'
有点奇怪,不过意思是这个意思。我们还可以同时生成多个句子:
def generate_n(gram, target, n):
sen = []
for i in range(n):
s = generate(gram, target)
sen.append(s)
return sen
比如一次生成 10 句:
['一个桌子坐在一个女人', '这个蓝色的桌子坐在这个小猫', '一个蓝色的小小的好看的桌子听着一个篮球', '这个小猫看着这个小小的小猫', '这个女人看见一个小猫', '这个蓝色的小猫坐在这个蓝色的小猫', '一个好看的篮球看见一个好看的篮球', '一个蓝色的蓝色的篮球坐在这个篮球', '这个女人听着这个好看的小小的桌子', '一个好看的好看的好看的好看的篮球听着这个篮球']
一看就不是人话…为了从生成的句子里挑选出最接近人类讲的句子,我们就要用到第二个模型:基于概率的模型。
2. Probability Based 基于概率的模型
这个模型的原理是分析一个足够大的语料库,把每个词出现的概率算出来。出现的多意味着人说的概率大,那么计算机生成的句子里出现这个词就越合理。这就是 n-gram 理论。而如果仅统计单个词的出现频率(称为 1-gram )不准确,因为出现概率的统计是忽略位置的,比如“我”作为主语出现的频率可能很高,而在整个语料库中出现的概率可能很低。所以有时候我们会统计两个词一起出现的概率,这称为 2-gram,比如“今天天气不错”可以分成“今天+天气”和“天气+不错”两个个体。同样还有 3-gram、4-gram…
我们使用从豆瓣上爬取的电影评论作为语料库(空间有限,下面仅展示开头一小部分),然后使用 2-gram 分析。
0 吴京意淫到了脑残的地步,看了恶心想吐
1 首映礼看的。太恐怖了这个电影,不讲道理的,完全就是吴京在实现他这个小粉红的英雄梦。各种装备轮...
2 吴京的炒作水平不输冯小刚,但小刚至少不会用主旋律来炒作…吴京让人看了不舒服,为了主旋律而主旋...
3 凭良心说,好看到不像《战狼1》的续集,完虐《湄公河行动》。
4 中二得很
原始数据比较“脏”(即有标点干扰分词),需要清洗,这里我们用正则表达式保留所有文本。
def token(string):
return re.findall("\w+", string)
comment_clean = ["".join(token(str(a))) for a in comment]
清洗之后的文本如下:
['吴京意淫到了脑残的地步看了恶心想吐',
'首映礼看的太恐怖了这个电影不讲道理的完全就是吴京在实现他这个小粉红的英雄梦各种装备轮番上场视物理逻辑于不顾不得不说有钱真好随意胡闹',
'吴京的炒作水平不输冯小刚但小刚至少不会用主旋律来炒作吴京让人看了不舒服为了主旋律而主旋律为了煽情而煽情让人觉得他是个大做作大谎言家729更新片子整体不如湄公河行动1整体不够流畅编剧有毒台词尴尬2刻意做作的主旋律煽情显得如此不合时宜而又多余',
'凭良心说好看到不像战狼1的续集完虐湄公河行动',
'中二得很']
由于中文与英文的结构不同(中文的词语是连在一起的),处理中文语料需要分词。Python 中有一个中文分词模块叫 jieba(结巴?街霸?)。
import jieba
def cut(string):
return list(jieba.cut(string))
TOKEN = []
for i, line in enumerate(comment_clean):
if i % 10000 == 0: print(i)
TOKEN += cut(line)
我们来看一下分词的结果:
['吴京', '意淫', '到', '了', '脑残', '的', '地步', '看', '了', '恶心', '想', '吐', '首映礼', '看', '的', '太', '恐怖', '了', '这个', '电影', '不讲道理', '的', '完全', '就是', '吴京', '在', '实现', '他', '这个', '小', '粉红', '的', '英雄', '梦', '各种', '装备', '轮番', '上场', '视', '物理', '逻辑', '于', '不顾', '不得不', '说', '有钱', '真', '好', '随意', '胡闹', '吴京', '的', '炒作', '水平', '不输', '冯小刚', '但小刚', '至少', '不会', '用', '主旋律', '来', '炒作', '吴京', '让', '人', '看', '了', '不', '舒服', '为了', '主旋律', '而', '主旋律', '为了', '煽情', '而', '煽情', '让', '人', '觉得', '他', '是', '个', '大', '做作', '大', '谎言', '家', '729', '更新', '片>子', '整体', '不如', '湄公河', '行动', '1', '整体', '不够', '流畅']
看起来还不错。下一步是整合相同的词语,顺便统计出现的次数。
from collections import Counter
TOKEN_2_GRAM = [''.join(TOKEN[i:i+2]) for i in range(len(TOKEN[:-1]))]
words_count_2 = Counter(TOKEN_2_GRAM)
定义一个计算概率的函数:
def prob_2(word1, word2):
if word1 + word2 in words_count_2: return words_count_2[word1+word2] / len(TOKEN_2_GRAM)
else:
return 1 / len(TOKEN_2_GRAM)
再定义一个计算一句话出现的可能性:
def get_probability(sentences):
prob = []
for sen in sentences:
words = cut(sen)
sentence_prob = 1
for i, word in enumerate(words[:-1]):
next_ = words[i+1]
probability = prob_2(word, next_)
sentence_prob *= probability
prob.append(sentence_prob)
return prob
最后,把所有函数放在一起,成为一个能够找出的最有可能的句子的函数:
def generate_best(grammer_str, target, n, line_split="\n"):
example_grammer = create_grammer(grammer_str)
sentence_n = generate_n(gram=example_grammer, target=target,n=n)
file = "....csv" # 这里隐去了语料库源
content = pd.read_csv(file)
comment = content["comment"]
comment_clean = ["".join(token(str(a))) for a in comment]
TOKEN = []
for i, line in enumerate(comment_clean):
TOKEN += cut(line)
words_count = Counter(TOKEN)
TOKEN_2_GRAM = [''.join(TOKEN[i:i+2]) for i in range(len(TOKEN[:-1]))]
words_count_2 = Counter(TOKEN_2_GRAM)
prob = get_probability(sentence_n)
sen_prob = zip(sentence_n, prob)
prob = sorted(sen_prob, key=lambda x: x[1], reverse=True)
print(f"The most likely sentence is '{prob[0][0]}' with probability {prob[0][1]} among {n} sentences.")
我们使用一个新的规则和语料库训练模型:
grammer_1 = '''
sentence = 主语结构 谓语结构 宾语结构
主语结构 = 定语 主语 | 主语
谓语结构 = 状语 谓语 | 谓语
宾语结构 = 定语 宾语 | 宾语
定语 = 高大的 | 矮小的 | 巨大的 | 渺小的 | 漂亮的 | 丑陋的 | 红色的 | 饥饿的 | 饱满的 | 可怜的 | 快乐的 | 补水的 | 无奈的
主语 = 我 | 你 | 他 | 她 | 它 | 我们 | 你们 | 他们
状语 = 狠狠地 | 高兴地 | 快速地 | 奇怪地 | 绝对地
谓语 = 跑 | 跳 | 玩耍 | 打 | 打扮 | 践踏 | 鄙视 | 尊重 | 勾引
宾语 = 小狗 | 女孩 | 工人 | 足球 | 电脑 | 手机 | 电话 | 北京 | 大海
'''
试验一下:
>>> generate_best(grammer_str=grammer_1, target="sentence", n=10)
The most likely sentence is '你打饱满的小狗' with probability 6.862749247901574e-23 among 10 sentences.
emmmmmm,听起来还是怪怪的…不过这是万里长征第一步。欢迎大家访问我的 GitHub:https://github.com/vincent507cpu