深度学习(九):自然语言处理

1 自然语言处理(NLP)

NLP 任务希望能做到以下三件事:
1、标记文本区域(如词性标注、情感分类或者命名实体识别)
2、链接两个以上的文本区域(识别表示同一个实体的名词短语或代词)
3、填补基于上下文的信息空缺(例如用完形填空的形式补齐单词缺失的部分)

2 监督NLP学习

1、由于神经网络只能将输入的数字映射到输出的数字,所以NLP的第一步是将文本转换为数字形式
2、将文本转换为数字的目标可以看作:使输入和输出之间的相关性对神经网络来说更明显。这将有助于更快的训练和更高的泛化
3、通常的做法是创建一个矩阵,其中每一行(向量)对应于每个电影评论,每一列表示评论汇中是否包含词汇表中的特定单词。在对应的位置插入1,剩余的位置插入0。这种存储形式称为 one-hot 编码,是二进制数据最常见的编码格式


3 引入嵌入层

1、下面是预测情绪的神经网络。第一层表示数据集(layer_0),然后是线性层(weights_0_1)和 relu 层(layer_1),之后是另一个线性层(weights_1_2),再后是输出层,也就是预测层。有点像之前的堆叠神经网络
2、可以使用一个嵌入层来替换第一个线性层(weights_0_1),从而快速切入layer_1
3、和一个只有1和0的向量进行乘积操作,在数学上等价于对矩阵中的几行进行求和(如下面 A*v 的例子)。对数据量更少的矩阵行进行求和要快得多



处理IMDB影评数据代码

import numpy as np
import sys
np.random.seed(1)
#https://github.com/iamtrask/grokking-deep-learning
f = open('reviews.txt')
raw_reviews = f.readlines()
f.close()
#https://github.com/iamtrask/grokking-deep-learning
f= open('labels.txt')
raw_labels = f.readlines()
f.close()

#[{'apple', 'orange', 'banana'}, {'apple', 'banana'}, {'orange', 'banana'}]
tokens = list(map(lambda x:set(x.split(" ")),raw_reviews))

vocab = set()
for sent in tokens:
    for word in sent:
        if(len(word)>0):
            vocab.add(word)
vocab = list(vocab)

word2index = {}
for i,word in enumerate(vocab):
    word2index[word] = i

#维度是(25000,影评单词相对应vocab中的索引)
input_dataset = list()
for sent in tokens:
    sent_indices = list()
    for word in sent:
        try:
            sent_indices.append(word2index[word])
        except:
            ""
    input_dataset.append(list(set(sent_indices)))

target_dataset = list()
for label in raw_labels:
    if label in raw_labels:
        if label == 'positive\n':
            target_dataset.append(1)
        else:
            target_dataset.append(0)

def sigmoid(x):
    return 1/(1+np.exp(-x))

alpha,iterations = (0.01,2)
hidden_size = 100 

#vocab 影评中单词的数量大概是7w多,hidden_size是100
weights_0_1 = 0.2*np.random.random((len(vocab),hidden_size)) - 0.1
#维度是(100,1)
weights_1_2 = 0.2*np.random.random((hidden_size,1)) - 0.1

correct,total = (0,0)
for iter in range(iterations):
    #选择前2.4万条记录进行训练
    for i in range(len(input_dataset)-1000):
        x,y = (input_dataset[i],target_dataset[i])
        #weight_0_1 只筛选input_dataset有对应值的行,并按列进行求和
        #维度(1,hidden_size),sigmoid激活函数,转化为0-1的概率
        layer_1 = sigmoid(np.sum(weights_0_1[x],axis=0))
        layer_2 = sigmoid(np.dot(layer_1,weights_1_2))

        #预测值和真值比较
        layer_2_delta = layer_2 - y
        #反向传播
        layer_1_delta = layer_2_delta.dot(weights_1_2.T)
        #校正weights
        weights_0_1[x] -= layer_1_delta * alpha
        #np.outer 函数计算两个向量的外积,即返回一个矩阵,矩阵的元素是两个输入向量中每个元素的乘积
        weights_1_2 -= np.outer(layer_1,layer_2_delta) * alpha

        if (np.abs(layer_2_delta) <0.5):
            correct += 1
        total += 1
        if(i %10 == 9):
            progress = str(i/float(len(input_dataset)))
            sys.stdout.write('\rIter:'+str(iter)\
                             +' Progress:'+progress[2:4]\
                                +'.' +progress[4:6]\
                                     +'% Training Accuracy:'\
                                         + str(correct/float(total)) + '%' )
    print()

correct,total = (0,0)
for i in range(len(input_dataset)-1000,len(input_dataset)):
    x = input_dataset[i]
    y = target_dataset[i]

    layer_1 = sigmoid(np.sum(weights_0_1[x],axis=0))
    layer_2 = sigmoid(np.dot(layer_1,weights_1_2))

    if(np.abs(layer_2 -y) < 0.5):
        correct += 1
    total += 1
print("Test Accuracy:" + str(correct/float(total)))

复习一下外积的计算

4 神经网络学到了什么

1、两层神经网络学到的信息是:输入数据和目标数据之间的关联
2、含有隐藏层的神经网络:隐藏层是将前一层的数据点分为 n 个小组(其中n是隐藏层中的神经元数量)。每个隐藏的神经元接受一个数据点,并尝试回答这个数据点(每条影评)是否属于我这个小组。作为隐藏层,它搜索对输入来说有用的分组方式
3、有用的分组满足下面两个条件:(1)分组必须对输出标签的预测有用(2)分组结果是你所关心的数据中某种实际出现的现象,那么分组是有用的。例如一个神经元可以识别“可怕”和“不可怕”,可以识别“糟糕”和“不糟糕”就是有用的分组
4、隐藏层从根本上意味着对前一层的数据进行分组。在更小的颗粒度来看,每个神经元将数据点分类为属于或者不属于该组。在更高层次来看,如果两个数据点同时属于许多相同的组,它们就是相似的
5、具有类似预测功能的单词应该属于类似的组。与“good”相连的三项粗体权重构成了“good”这一单词的嵌入表达。它反映了“good”这个词属于每个分组(隐藏层神经元)的可能。具有类似预测能力的单词具有类似的单词嵌入表达(权重值)

5 输出嵌入表达(权重)相近的单词和神经元的含义

1、weights_0_1 的每行代表每个单词到每个隐藏神经元的权重。想要找到与目标单词类似的词,只需将两个条目的向量进行比较即可。可以用简单的欧式距离比较两个单词的权重
2、神经元的意义完全基于所预测的目标标签。例如与“beautiful”最相似的单词是“freedom”,但对于预测影评是正面和负面来说,这些词具有相同的意义。神经网络中的每一件事的推导都基于关联抽象,都致力于试图正确的做出预测

from collections import Counter
import math

def similar(target='beautiful'):
    target_index = word2index[target]
    socores = Counter()
    for word,index in word2index.items():
        raw_difference = weights_0_1[index] - weights_0_1[target_index]
        squared_difference = raw_difference * raw_difference
        scores[word] = -math.sqrt(sum(squared_difference))
    return scores.most_common(10)

print(similar('beautiful'))

print(similar('terrible'))

6 完形填空

1、关于Word2Vec的背景知识参考:https://zhuanlan.zhihu.com/p/99397850
2、关于negative sampling的背景知识参考:https://zhuanlan.zhihu.com/p/456088223
3、这个是利用上下文去预测目标单词的例子,采用了负采样的方法
4、与上一个预测影评是正面和负面的例子相比,这次与 ‘terrible’ 聚类到一起是基于在同一个短语中出现的可能性(与情绪无关)
5、由此得出的结论是,即使上面的两个案例(预测目标词汇,预测情绪正负)中的网络在相同的数据上训练,其架构非常相似,我们依旧可以通过更改网络要预测的内容,来影响网络在权重允许的范围内能够学到的内容。这个选择想让网络学习内容的过程称为“智能定位(intelligence targeting)”

import sys,random,math
from collections import Counter
import numpy as np

np.random.seed(1)
random.seed(1)

f = open('reviews.txt')
raw_reviews = f.readlines()
f.close()

#[{'apple', 'orange', 'banana'}, {'apple', 'banana'}, {'orange', 'banana'}]
tokens = list(map(lambda x:set(x.split(" ")),raw_reviews))


wordcnt = Counter()
for sent in tokens:
    for word in sent:
        wordcnt[word] -= 1 
#对word按照从大到小进行排列,但是因为是负数,其实是按照从小到大进行排列
vocab = list(set(map(lambda x:x[0],wordcnt.most_common())))

word2index = {}
for i,word in enumerate(vocab):
    word2index[word]=i

#准备输入数据集
concatenated = list()
input_dataset = list()

for sent in tokens:
    sent_indices = list()
    for word in sent:
        try:
            sent_indices.append(word2index[word])
            concatenated.append(word2index[word])
        except:
            ""
    #将每个词转换为索引,并将所有索引构建成 input_dataset 数据集
    input_dataset.append(sent_indices)
#concatenated 是所有评论中词索引的平铺版本,用于负采样。这里没有去重,能展现上下文和目标词之间的关系
concatenated = np.array(concatenated)
#随机打乱输入数据集
random.shuffle(input_dataset)

alpha,iterations = (0.05,2)

#negative: 负采样的数量为5
hidden_size,window,negative = (50,2,5)

#词到隐藏层的权重,维度为 len(vocab) x hidden_size,表示每个词与隐藏层之间的连接
#维度(25000,50)
weights_0_1 = (np.random.rand(len(vocab),hidden_size)-0.5) * 0.2

#隐藏层到输出层的权重,初始化为零
weights_1_2 = np.random.rand(len(vocab),hidden_size)*0

layer_2_target = np.zeros(negative + 1)
#array([1., 0., 0., 0., 0., 0.])
layer_2_target[0] = 1

def similar(target='beautiful'):
    target_index = word2index[target]
    scores = Counter()
    for word,index in word2index.items():
        raw_difference = weights_0_1[index] - weights_0_1[target_index]
        squared_difference = raw_difference * raw_difference
        scores[word] = -math.sqrt(sum(squared_difference))
    return scores.most_common(10)

def sigmoid(x):
    return 1/(1+np.exp(-x))

for rev_i,review in enumerate(input_dataset*iterations):
    #review 是每条评论的index
    for target_i in range(len(review)):
        #target_samples 包含了当前目标词(review[target_i])和从 concatenated 中随机采样的负样本。负样本是随机选择的与目标词无关的词
        #np.random.rand(negative) 生成一个长度为 negative 的随机浮点数组,表示负样本的数量
        #通过乘以 len(concatenated) 并转化为整数来随机选择词汇表中的词索引
        #负样本的目的是帮助模型区分相关词和不相关的词,从而学习到更有意义的词向量
        target_samples = [review[target_i]] + list(concatenated[(np.random.rand(negative)*len(concatenated)).astype('int').tolist()])
        #left_context 获取目标词之前的上下文词,范围是 [target_i - window, target_i),确保索引不小于 0
        #window 是上下文窗口的大小,表示考虑多少个单词作为上下文
        left_context = review[max(0,target_i - window):target_i]
        #right_context 获取目标词之后的上下文词,范围是 (target_i + 1, target_i + window),确保索引不超过句子长度
        right_context = review[target_i+1:min(len(review),target_i+window)]


        #weights_0_1 是输入层到隐藏层的权重矩阵。weights_0_1[left_context + right_context] 获取上下文词的词向量
        #np.mean(..., axis=0) 对上下文词的词向量进行平均,得到一个表示上下文信息的单一向量(layer_1)
        # mean instead of sum, interesting
        # context words w/o target word
        layer_1 = np.mean(weights_0_1[left_context+right_context],axis=0)

        #weights_1_2 是隐藏层到输出层的权重矩阵。weights_1_2[target_samples] 获取负样本和正样本的词向量
        layer_2 = sigmoid(layer_1.dot(weights_1_2[target_samples].T))

        layer_2_delta = layer_2 - layer_2_target #array([1., 0., 0., 0., 0., 0.]) 第一个单词是目标单词的概率为1,负采样的概率为0
        layer_1_delta = layer_2_delta.dot(weights_1_2[target_samples])

        weights_0_1[left_context+right_context] -= layer_1_delta * alpha
        weights_1_2[target_samples] -= np.outer(layer_2_delta,layer_1)*alpha

    if(rev_i % 250 == 0):
        sys.stdout.write('\rProgress:'+str(rev_i/float(len(input_dataset)*iterations)) + "  " + str(similar('terrible')))
    sys.stdout.write('\rProgress:'+str(rev_i/float(len(input_dataset)*iterations)))

print(similar('terrible'))

7 损失函数的意义

1、除了通过控制输入和预测目标值,还可以通过调整网络测量误差的方式、网络层的参数数量和类型,以及应用的正则化类型来改变网络的学习过程。这些所有技术都属于构建损失函数的范畴
2、损失函数决定了神经网络学到的知识。误差函数更正式的术语是损失函数或目标函数(这三个短语全部是可以互换的)
3、两个神经网络可以有相同的初始权重值,在相同的数据集上训练,但最终由于选择了不同的损失函数,而学到截然不同的模式

8 单词类比

1、完形填空的任务能够为词汇创建有趣的嵌入表达,这种现象称为 单词类比,意味着你可以基于不同单词的嵌入向量,对它们进行基本的代数运算。如 king 的向量,减去 man 的向量,加上 woman 的向量,然后搜索最相似的向量,最相似的向量通常是 queen

9 参考资料

《深度学习图解》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容