机器学习教程 十五-细解卷积神经网络

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

卷积运算

再次引用上一篇里的内容《自己动手做聊天机器人 二十二-神奇算法之人工神经网络》:

卷积英文是convolution(英文含义是:盘绕、弯曲、错综复杂),数学表达是:

上面连续的情形如果不好理解,可以转成离散的来理解,其实就相当于两个多项式相乘,如:(x*x+3*x+2)(2*x+5),计算他的方法是两个多项式的系数分别交叉相乘,最后相加。用一句话概括就是:多项式相乘,相当于系数向量的卷积。

如果再不好理解,我们可以通俗点来讲:卷积就相当于在一定范围内做平移并求平均值。比如说回声可以理解为原始声音的卷积结果,因为回声是原始声音经过很多物体反射回来声音揉在一起。再比如说回声可以理解为把信号分解成无穷多的冲击信号,然后再进行冲击响应的叠加。再比如说把一张图像做卷积运算,并把计算结果替换原来的像素点,可以实现一种特殊的模糊,这种模糊其实是一种新的特征提取,提取的特征就是图像的纹路。总之卷积就是先打乱,再叠加。

下面我们在看上面的积分公式,需要注意的是这里是对τ积分,不是对x积分。也就是说对于固定的x,找到x附近的所有变量,求两个函数的乘积,并求和。

 

卷积神经网络

英文简称CNN,大家并不陌生,因为你可能见过DNN(深度神经网络)、RNN(循环神经网络)。CNN主要应用领域是图像处理,它本质上是一个分类器。

卷积神经网络为什么这么深得人心呢?因为在卷积神经网络的第一层就是特征提取层,也就是不需要我们自己做特征提取的工作,而是直接把原始图像作为输入,这带来了很大的便利,归根结底还是归功于卷积运算的神奇。

那么第一层是怎么利用卷积运算做特征提取的呢?我们还是通过图像处理的例子来说明。参考生物学的视觉结构,当人眼观察一个事物的时候,并不是每个视神经细胞感知所有看到的“像素”,而是一个神经细胞负责一小块视野,也就是说假设看到的全部视野是1000像素,而神经细胞有10个,那么一个神经细胞就负责比1000/10得到的平均值大一圈的范围,也就是200像素,一个细胞负责200个像素,10个细胞一共是2000个像素,大于1000个像素,说明有重叠。这和上面卷积运算的原理很像。用一张图来表示如下:

 

什么是卷积核

先看下面这张图,这是计算5*5矩阵中间的3*3部分的卷积值

绿色部分是一个5*5的矩阵,标橙的部分说明正在进行卷积计算,×1表示算上这个单元的值,×0表示不计算,这样得出的结果1×1+1×0+1×1+0×0+1×1+1×0+0×1+0×0+1×1=4,这样计算出了第一个元素的卷积

我们继续让这个橙色部分移动并计算,最终会得到如下结果:

那么这里的橙色(标记×1或×0)的矩阵(一般都是奇数行奇数列)就叫做卷积核,即

1 0 1
0 1 0
1 0 1

卷积计算实际上是一种对图像元素的矩阵变换,是提取图像特征的方法,多种卷积核可以提取多种特征。每一种卷积核生成的图像都叫做一个通道,这回也就理解了photoshop中“通道”的概念了吧

一个卷积核覆盖的原始图像的范围(上面就是5*5矩阵范围)叫做感受野(receptive field),这个概念来自于生物学

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

 

多层卷积

利用一次卷积运算(哪怕是多个卷积核)提取的特征往往是局部的,难以提取出比较全局的特征,因此需要在一层卷积基础上继续做卷积计算 ,这也就是多层卷积,例如下面这个示意图:

这实际上有四层卷积、三层池化、加上一层全连接,经过这些计算后得出的特征再利用常规的机器学习分类算法(如soft-max)做分类训练。上面这个过程是一个真实的人脸识别的卷积神经网络。

 

池化

上面讲到了池化,池化是一种降维的方法。按照卷积计算得出的特征向量维度大的惊人,不但会带来非常大的计算量,而且容易出现过拟合,解决过拟合的办法就是让模型尽量“泛化”,也就是再“模糊”一点,那么一种方法就是把图像中局部区域的特征做一个平滑压缩处理,这源于局部图像一些特征的相似性(即局部相关性原理)。

具体做法就是对卷积计算得出的特征在局部范围内算出一个平均值(或者取最大值、或者取随机采样值)作为特征值,那么这个局部范围(假如是10*10),就被压缩成了1*1,压缩了100倍,这样虽然更“模糊”了,但是也更“泛化”了。通过取平均值来池化叫做平均池化,通过取最大值来池化叫做最大池化。

 

卷积神经网络训练过程

上面讲解了卷积神经网络的原理,那么既然是深度学习,要学习的参数在哪里呢?

上面我们讲的卷积核中的因子(×1或×0)其实就是需要学习的参数,也就是卷积核矩阵元素的值就是参数值。一个特征如果有9个值,1000个特征就有900个值,再加上多个层,需要学习的参数还是比较多的。

和多层神经网络(见我的另外一篇文章《机器学习教程 十二-神经网络模型的原理》)一样,为了方便用链式求导法则更新参数,我们设计sigmoid函数作为激活函数,我们同时也发现卷积计算实际上就是多层神经网络中的Wx矩阵乘法,同时要加上一个偏执变量b,那么前向传到的计算过程就是:

如果有更多层,计算方法相同

因为是有监督学习,所以模型计算出的y'和观察值y之间的偏差用于更新模型参数,反向传导的计算方法参考《机器学习教程 十二-神经网络模型的原理》中的反向传导算法:

参数更新公式是:

偏导计算公式是:

其中a的计算公式是:

残差δ的计算公式是:

上面是输出层残差的推导公式和计算方法,下面是隐藏层残差的推导公式和计算方法

机器学习教程 十四-利用tensorflow做手写数字识别

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

什么是tensorflow

tensor意思是张量,flow是流。

张量原本是力学里的术语,表示弹性介质中各点应力状态。在数学中,张量表示的是一种广义的“数量”,0阶张量就是标量(比如:0、1、2……),1阶张量就是向量(比如:(1,3,4)),2阶张量就是矩阵,本来这几种形式是不相关的,但是都归为张量,是因为他们同时满足一些特性:1)可以用坐标系表示;2)在坐标变换中遵守同样的变换法则;3)有着相同的基本运算(如:加、减、乘、除、缩放、点积、对称……)

那么tensorflow可以理解为通过“流”的形式来处理张量的一种框架,是由google开发并开源,已经应用于google大脑项目开发

 

tensorflow安装

sudo pip install https://storage.googleapis.com/tensorflow/mac/tensorflow-0.9.0-py2-none-any.whl

不同平台找对应的whl包

可能遇到的问题:

发现无法import tensorflow,问题在于protobuf版本不对,必须先卸载掉,再安装tensorflow,这样会自动安装3.0版本的protobuf

sudo pip uninstall protobuf
sudo brew remove protobuf260
sudo pip install --upgrade https://storage.googleapis.com/tensorflow/mac/tensorflow-0.9.0-py2-none-any.whl

 

手写数字数据集获取

http://yann.lecun.com/exdb/mnist/可以下载手写数据集,http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz和http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz,下载解压后发现不是图片格式,而是自己特定的格式,为了说明这是什么样的数据,我写了一段程序来显示这些数字:

/************************
 * author: lcsays
 * date:   2016-08-02
 * brief:  read MNIST data
 ************************/
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
unsigned char *lables = NULL;
/**
 * All the integers in the files are stored in the MSB first (high endian) format
 */
void copy_int(uint32_t *target, unsigned char *src)
{
    *(((unsigned char*)target)+0) = src[3];
    *(((unsigned char*)target)+1) = src[2];
    *(((unsigned char*)target)+2) = src[1];
    *(((unsigned char*)target)+3) = src[0];
}
int read_lables()
{
    FILE *fp = fopen("./train-labels-idx1-ubyte", "r");
    if (NULL == fp)
    {
        return -1;
    }
    unsigned char head[8];
    fread(head, sizeof(unsigned char), 8, fp);
    uint32_t magic_number = 0;
    uint32_t item_num = 0;
    copy_int(&magic_number, &head[0]);
    // magic number check
    assert(magic_number == 2049);
    copy_int(&item_num, &head[4]);
    uint64_t values_size = sizeof(unsigned char) * item_num;
    lables = (unsigned char*)malloc(values_size);
    fread(lables, sizeof(unsigned char), values_size, fp);
    fclose(fp);
    return 0;
}
int read_images()
{
    FILE *fp = fopen("./train-images-idx3-ubyte", "r");
    if (NULL == fp)
    {
        return -1;
    }
    unsigned char head[16];
    fread(head, sizeof(unsigned char), 16, fp);
    uint32_t magic_number = 0;
    uint32_t images_num = 0;
    uint32_t rows = 0;
    uint32_t cols = 0;
    copy_int(&magic_number, &head[0]);
    // magic number check
    assert(magic_number == 2051);
    copy_int(&images_num, &head[4]);
    copy_int(&rows, &head[8]);
    copy_int(&cols, &head[12]);
    uint64_t image_size = rows * cols;
    uint64_t values_size = sizeof(unsigned char) * images_num * rows * cols;
    unsigned char *values = (unsigned char*)malloc(values_size);
    fread(values, sizeof(unsigned char), values_size, fp);
    for (int image_index = 0; image_index < images_num; image_index++)
    {
        // print the label
        printf("=========================================  %d  ======================================\n", lables[image_index]);
        for (int row_index = 0; row_index < rows; row_index++)
        {
            for (int col_index = 0; col_index < cols; col_index++)
            {
                // print the pixels of image
                printf("%3d", values[image_index*image_size+row_index*cols+col_index]);
            }
            printf("\n");
        }
        printf("\n");
    }
    free(values);
    fclose(fp);
    return 0;
}
int main(int argc, char *argv[])
{
    if (-1 == read_lables())
    {
        return -1;
    }
    if (-1 == read_images())
    {
        return -1;
    }
    return 0;
}

下载并解压出数据集文件train-images-idx3-ubyte和train-labels-idx1-ubyte放到源代码所在目录后,编译并执行:

gcc -o read_images read_images.c
./read_images

 

展示出来的效果如下:

 

一共有60000个图片,从代码可以看出数据集里存储的实际就是图片的像素

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

 

softmax模型

我们在《机器学习教程 十三-用scikit-learn做逻辑回归》中介绍了逻辑回归模型。逻辑回归是用于解决二类分类问题(使用sigmoid函数),而softmax模型是逻辑回归模型的扩展,用来解决多类分类问题。

softmax意为柔和的最大值,也就是如果某个zj大于其他z,那么这个映射的分量就逼近于1,其他的分量就逼近于0,从而将其归为此分类,多个分量对应的就是多分类,数学形式和sigmoid不同,如下:

它的特点是,所有的softmax加和为1,其实它表示的是一种概率,即x属于某个分类的概率。

在做样本训练时,这里的xi计算方法是:

其中W是样本特征的权重,xj是样本的特征值,bi是偏置量。

详细来说就是:假设某个模型训练中我们设计两个特征,他们的值分别是f1和f2,他们对于第i类的权重分别是0.2和0.8,偏置量是1,那么

xi=f1*0.2+f2*0.8+1

如果所有的类别都计算出x的值,如果是一个训练好的模型,那么应该是所属的那个类别对应的softmax值最大

softmax回归算法也正是基于这个原理,通过大量样本来训练这里的W和b,从而用于分类的

 

tensorflow的优点

tensorflow会使用外部语言计算复杂运算来提高效率,但是不同语言之间的切换和不同计算资源之间的数据传输耗费很多资源,因此它使用图来描述一系列计算操作,然后一起传给外部计算,最后结果只传回一次,这样传输代价最低,计算效率最高

举个例子:

import tensorflow as tf
x = tf.placeholder(tf.float32, [None, 784])

这里的x不是一个实际的x,而是一个占位符,也就是一个描述,描述成了二维浮点型,后面需要用实际的值来填充,这就类似于printf("%d", 10)中的占位符%d,其中第一维是None表示可无限扩张,第二维是784个浮点型变量

如果想定义可修改的张量,可以这样定义:

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

其中W的维度是[784, 10],b的形状是[10]

有了这三个变量,我们可以定义我们的softmax模型:

y = tf.nn.softmax(tf.matmul(x,W) + b)

这虽然定义,但是没有真正的进行计算,因为这只是先用图来描述计算操作

其中matmul是矩阵乘法,因为x的维度是[None, 784],W的维度是[784, 10],所以矩阵乘法得出的是[None, 10],这样可以和向量b相加

softmax函数会计算出10维分量的概率值,也就是y的形状是[10]

 

数字识别模型实现

基于上面定义的x、W、b,和我们定义的模型:

y = tf.nn.softmax(tf.matmul(x,W) + b)

我们需要定义我们的目标函数,我们以交叉熵(衡量预测用于描述真相的低效性)为目标函数,让它达到最小:

其中y'是实际分布,y是预测的分布,即:

y_ = tf.placeholder("float", [None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

利用梯度下降法优化上面定义的Variable:

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

其中0.01是学习速率,也就是每次对变量做多大的修正

 

按照上面的思路,最终实现的代码digital_recognition.py如下:

 

# coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding( "utf-8" )
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_string('data_dir', './', 'Directory for storing data')
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

x = tf.placeholder(tf.float32, [None, 784]) W = tf.Variable(tf.zeros([784,10])) b = tf.Variable(tf.zeros([10])) y = tf.nn.softmax(tf.matmul(x,W) + b) y_ = tf.placeholder(“float”, [None,10]) cross_entropy = -tf.reduce_sum(y_*tf.log(y)) train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) init = tf.initialize_all_variables() sess = tf.InteractiveSession() sess.run(init) for i in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels}))

运行效果如下:

[root@mymac $] python digital_recognition.py
Extracting ./train-images-idx3-ubyte.gz
Extracting ./train-labels-idx1-ubyte.gz
Extracting ./t10k-images-idx3-ubyte.gz
Extracting ./t10k-labels-idx1-ubyte.gz
0.9039

 

解释一下

flags.DEFINE_string('data_dir', './', 'Directory for storing data')

表示我们用当前目录作为训练数据的存储目录,如果我们没有提前下好训练数据和测试数据,程序会自动帮我们下载到./

mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

这句直接用库里帮我们实现好的读取训练数据的方法,无需自行解析

for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

这几行表示我们循环1000次,每次从训练样本里选取100个样本来做训练,这样我们可以修改配置来观察运行速度

最后几行打印预测精度,当调整循环次数时可以发现总训练的样本数越多,精度就越高

机器学习教程 十三-用scikit-learn做逻辑回归

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

二类分类问题

逻辑回归最广泛的应用就是二类分类,我们以脏话判别为例来利用逻辑回归,对一句话做脏话分析判断

输入样本如下:

是脏话:fuck you

是脏话:fuck you all

不是脏话:hello everyone

 

我们来预测以下两句话是否是脏话:

fuck me
hello boy

 

# coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding( "utf-8" )
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model.logistic import LogisticRegression
X = []
# 前三行作为输入样本
X.append("fuck you")
X.append("fuck you all")
X.append("hello everyone")
# 后两句作为测试样本
X.append("fuck me")
X.append("hello boy")
# y为样本标注
y = [1,1,0]
vectorizer = TfidfVectorizer()
# 取X的前三句作为输入做tfidf转换
X_train = vectorizer.fit_transform(X[:-2])
# 取X的后两句用上句生成的tfidf做转换
X_test = vectorizer.transform(X[-2:])
# 用逻辑回归模型做训练
classifier = LogisticRegression()
classifier.fit(X_train, y)
# 做测试样例的预测
predictions = classifier.predict(X_test)
print predictions

 

输出结果如下:

[1 0]

判断成:

是脏话:fuck me

不是脏话:hello boy

 

自己动手做聊天机器人 二十二-神奇算法之人工神经网络

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

人工神经网络

人工神经网络是借鉴了生物神经网络的工作原理形成的一种数学模型,有关人工神经网络的原理、公式推导以及训练过程请见我的文章《机器学习教程 十二-神经网络模型的原理》

 

神奇用法之一

我们这样来设计我们的神经网络:由n个输入特征得出与输入特征几乎相同的n个结果,这样训练出的隐藏层可以得到意想不到的信息。

比如,在信息检索领域,我们需要通过模型训练来得出合理的排序模型,那么输入的特征可能有:文档质量、文档点击历史、文档前链数目、文档锚文本信息……,为了能找出这些特征中隐藏的信息,我们把隐藏层的神经元数目设置的少于输入特征的数目,经过大量样本的训练出能还原原始特征的模型,这样相当于我们用少于输入特征数目的信息还原出了全部特征,表面上是一种压缩,实际上通过这种方式就可以发现某些特征之间存在隐含的相关性,或者有某种特殊的关系。

同样的,我们还可以让隐藏层中的神经元数目多余输入特征的数目,这样经过训练得出的模型还可以展示出特征之间某种细节上的关联,比如我们对图像识别做这样的模型训练,在得出的隐藏层中能展示出多种特征之间的细节信息,如鼻子一定在嘴和眼睛中间。

这种让输出和输入一致的用法就是传说中的自编码算法。

 

神奇用法之二

人工神经网络模型通过多层神经元结构建立而成,每一层可以抽象为一种思维过程,经过多层思考,最终得出结论。举一个实际的例子:识别美女图片

按照人的思维过程,识别美女图片要经过这样的判断:1)图片类别(人物、风景……);2)图片人物性别(男、女、其他……);3)相貌如何(美女、恐龙、5分……)

那么在人工神经网络中,这个思考过程可以抽象成多个层次的计算:第一层计算提取图片中有关类别的特征,比如是否有形如耳鼻口手的元素,是否有形如蓝天白云绿草地的元素;第二层提取是否有胡须、胸部、长发以及面部特征等来判断性别;第三层提取五官、肤质、衣着等信息来确定颜值。为了让神经网络每一层有每一层专门要做的事情,需要在每一层的神经元中添加特殊的约束条件才能做到。人类的大脑是经过上亿年进化而成的,它的功能深不可及,某些效率也极高,而计算机在某些方面效率比人脑要高很多,两种结合起来一切皆有可能。

这种通过很多层提取特定特征来做机器学习的方法就是传说中的深度学习。

 

神奇用法之三

讲述第三种用法之前我们先讲一下什么是卷积运算。卷积英文是convolution(英文含义是:盘绕、弯曲、错综复杂),数学表达是:

 

数学上不好理解,我们可以通俗点来讲:卷积就相当于在一定范围内做平移并求平均值。比如说回声可以理解为原始声音的卷积结果,因为回声是原始声音经过很多物体反射回来声音揉在一起。再比如说回声可以理解为把信号分解成无穷多的冲击信号,然后再进行冲击响应的叠加。再比如说把一张图像做卷积运算,并把计算结果替换原来的像素点,可以实现一种特殊的模糊,这种模糊其实是一种新的特征提取,提取的特征就是图像的纹路。总之卷积就是先打乱,再叠加。

下面我们在看上面的积分公式,需要注意的是这里是对τ积分,不是对x积分。也就是说对于固定的x,找到x附近的所有变量,求两个函数的乘积,并求和。

下面回归正题,在神经网络里面,我们设计每个神经元计算输出的公式是卷积公式,这样相当于神经网络的每一层都会输出一种更高级的特征,比如说形状、脸部轮廓等。这种神经网络叫做卷积神经网络。

继续深入主题,在自然语言中,我们知道较近的上下文词语之间存在一定的相关性,由于标点、特殊词等的分隔使得在传统自然语言处理中会脱离词与词之间的关联,结果丢失了一部分重要信息,利用卷积神经网络完全可以做多元(n-gram)的计算,不会损失自然语言中的临近词的相关性信息。这种方法对于语义分析、语义聚类等都有非常好的效果。

这种神奇用法就是传说中的CNN

 

总结

神经网络因为其层次和扩展性的强大,有着非常多的神奇用法和非常广泛的应用,因为希望聊天机器人能够具有智能,就不得不寻找能够承载智能的方法,神经网络是其中一个,沿着这个网络,让我们继续探索。

机器学习教程 十二-神经网络模型的原理

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

人工神经网络

人工神经网络又叫神经网络,是借鉴了生物神经网络的工作原理形成的一种数学模型。下面是一张生物神经元的图示:

生物神经网络就是由大量神经元构成的网络结构如下图:

生物的神经网络是通过神经元、细胞、触电等结构组成的一个大型网络结构,用来帮助生物进行思考和行动等。那么人们就想到了电脑是不是也可以像人脑一样具有这种结构,这样是不是就可以思考了?

类似于神经元的结构,人工神经网络也是基于这样的神经元组成:

这里面的x1、x2、x3是输入值,中间的圆就像是神经元,经过它的计算得出hw,b(x)的结果作为神经元的输出值。

由这样的神经元组成的网络就是人工神经网络:

其中橙色的圆都是用来计算hw,b(x)的,纵向我们叫做层(Layer),每一层都以前一层为输入,输出的结果传递给下一层

 

这样的结构有什么特别的吗?

如果我们把神经网络看做一个黑盒,那么x1、x2、x3是这个黑盒的输入X,最右面的hw,b(x)是这个黑盒的输出Y,按照之前几篇机器学习的文章可以知道:这可以通过一个数学模型来拟合,通过大量训练数据来训练这个模型,之后就可以预估新的样本X应该得出什么样的Y

但是使用普通的机器学习算法训练出的模型一般都比较肤浅,就像是生物的进化过程,如果告诉你很久以前地球上只有三叶虫,现在地球上有各种各样的生物,你能用简单的模型来表示由三叶虫到人类的进化过程吗?不能。但是如果模拟出中间已知的多层隐藏的阶段(低等原始生物、无脊椎动物、脊椎动物、鱼类、两栖类、爬行动物、哺乳动物、人类时代)就可以通过海量的训练数据模拟出

也可以类比成md5算法的实现,给你无数个输入字符串和它的md5值,你能用肤浅的算法推出md5的算法吗?不能。因为md5的计算是一阶段一阶段的,后一阶段的输入依赖前一阶段的结果,无法逆推。但是如果已知中间几个阶段,只是不知道这几个阶段的参数,那么可以通过海量数据训练出来。

以上说明了神经网络结构的特别之处:通过较深的多个层次来模拟真实情况,从而构造出最能表达真实世界的模型,它的成本就是海量的训练数据和巨大的计算量。

 

神经网络模型的数学原理

每一个神经元的数学模型是:

其中的矩阵向量乘法

表示的就是输入多个数据的加权求和,这里的b(也就是上面图中的+1)是截距值,用来约束参数值,就像是一个向量(1,2,3)可以写成(2,4,6)也可以写成(10,20,30),那么我们必须取定一个值,有了截距值就可以限定了

其中f叫做激活函数,激活函数的设计有如下要求:1)保证后期计算量尽量小;2)固定取值范围;3)满足某个合理的分布。常用的激活函数是sigmond函数和双曲正切函数(tanh):

sigmond函数:

双曲正切函数(tanh):

这两个函数显然满足2)固定取值范围;3)满足某个合理的分布,那么对于1)保证后期计算量尽量小这个要求来说,他们的好处在于:

sigmond函数的导数是:

tanh函数的导数是:

这会减少非常多的计算量,后面就知道了

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

当计算多层的神经网络时,对于如下三层神经网络来说

我们知道:

其中的

分别表示第2层神经元的输出的第1、2、3个神经元产生的值

这三个值经过第3层最后一个神经元计算后得出最终的输出是:

以上神经网络如果有更多层,那么计算原理相同

我们发现这些神经元的激活函数f是相同的,唯一不同的就是权重W,那么我们做学习训练的目标就是求解这里的W,那么我们如何通过训练获得更精确的W呢?

 

反向传导算法

回想一下前面文章讲过的回归模型,我们也是知道大量训练样本(x,y),未知的是参数W和b,那么我们计算W的方法是:先初始化一个不靠谱的W和b,然后用输入x和W和b预估y,然后根据预估的y和实际的y之间的差距来通过梯度下降法更新W和b,然后再继续下一轮迭代,最终逼近正确的W和b

神经网络算法也一样的道理,使用梯度下降法需要设计一个代价函数:

以上是对于一个(x,y)的代价函数,那么当我们训练很多个样本时:

其中m是样本数,左项是均方差,右项是规则化项,我们的目的就是经过多伦迭代让代价函数最小

我来单独解释一下我对这个规则化项的理解:规则化项的目的是防止过拟合,过拟合的含义就是“太适合这些样本了,导致不适合样本之外的数据,泛化能力低”,规则化项首先增大了代价函数的值,因为我们训练的目的是减小代价函数,所以我们自然就会经过多轮计算逐步减小规则化项,规则化项里面是各个W的平方和,因为∑W=1,所以要想平方和变小,只有让各个W的值尽量相同,这就需要做一个折中,也就是W既要显示出各项权重的不同,又要降低差别,因此这里的λ的值就比较关键了,λ大了权重就都一样了,小了就过拟合了,所以需要根据经验给一个合适的值。

 

具体计算过程

首先我们为W和b初始化一个很小的随机值,然后分别对每个样本经过上面说过的神经网络的计算方法,计算出y的预估值

然后按照梯度下降法对W和b进行更新:

这里面最关键的是偏导的计算方法,对于最终的输出节点来说,代价函数J(W,b)的计算方法比较简单,就是把输出节点的激活值和实际值之间的残差代入J(W,B)公式。而隐藏层里的节点的代价函数该怎么计算呢?

我们根据前向传导(根据输入x计算hW,b(x)的方法)算法来反推:

我们把第l层的第i个节点的残差记作:

因为hW,b(x)=f(Wx),这里面我们把Wx记作z,那么残差表达的其实是z的残差,也就是代价函数关于z的偏导

那么输出层的残差就是:

前一层的残差的推导公式为:

再往前的每一层都按照这个公式计算得出残差,这个过程就叫做反向传导算法

下面在回过头来看我们的更新算法

偏导数的求法如下:

说一下我对这个公式的理解:代价函数对W的偏导表达的是权重(第l层上第i个节点第j个输入)的变化,而这个变化就是一个误差值,误差体现在哪里呢?体现在它影响到的下一层节点的残差δ,那么它对这个残差的影响有多大呢,在于它的输出值a,所以得出这个偏导就是aδ。代价函数对b的偏导原理类似。

现在我们有了所有的a和δ,就可以更新所有的W了,完成了一轮迭代

大家已经注意到了,在计算δ的方法中用到了f'(z),这也就是激活函数的导数,现在明白为什么激活函数设计成sigmond或者tanh了吧?因为他们的导数更容易计算

 

总结一下整个计算过程

1. 初始化W和b为小随机数

2. 遍历所有样本,利用前向传导算法计算出神经网络的每一层输出a和最终的输出值hW,b(x)

3. 利用hW,b(x)和真实值y计算输出层的残差δ

4. 利用反向传导算法计算出所有层所有节点的残差δ

5. 利用每一层每一个节点的a和δ计算代价函数关于W和b的偏导

6. 用得出的偏导来更新权重

7. 返回2进行下一轮迭代直到代价函数不再收敛为止

8. 得到我们的神经网络

参考文献:UFLDL教程

自己动手做聊天机器人 二十一-比TF-IDF更好的隐含语义索引模型是个什么鬼

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

TF-IDF

TF(term frequency),表示一个词在一个文档中出现的频率;IDF(inverse document frequency),表示一个词出现在多少个文档中。

它的思路是这样的:同一个词在短文档中出现的次数和在长文档中出现的次数一样多时,对于短文档价值更大;一个出现概率很低的词一旦出现在文档中,其价值应该大于其他普遍出现的词。

这在信息检索领域的向量模型中做相似度计算非常有效,屡试不爽,曾经是google老大哥发家的必杀技。但是在开发聊天机器人这个事情上看到了它的软肋,那就是它只是考虑独立的词上的事情,并没有任何语义信息在里面,因此我们需要选择加入了语义特征的更有效的信息检索模型。

 

隐含语义索引模型

在TF-IDF模型中,所有词构成一个高维的语义空间,每个文档在这个空间中被映射为一个点,这种方法维数一般比较高而且每个词作为一维割裂了词与词之间的关系。所以为了解决这个问题,我们要把词和文档同等对待,构造一个维数不高的语义空间,每个词和每个文档都是被映射到这个空间中的一个点。用数学来表示这个思想就是说,我们考察的概率即包括文档的概率,也包括词的概率,以及他们的联合概率。

为了加入语义方面的信息,我们设计一个假想的隐含类包括在文档和词之间,具体思路是这样的:

(1)选择一个文档的概率是p(d);

(2)找到一个隐含类的概率是p(z|d);

(3)生成一个词w的概率为p(w|z);

以上是假设的条件概率,我们根据观测数据能估计出来的是p(d, w)联合概率,这里面的z是一个隐含变量,表达的是一种语义特征。那么我们要做的就是利用p(d, w)来估计p(d)、p(z|d)和p(w|z),最终根据p(d)、p(z|d)和p(w|z)来求得更精确的p(w, d),即词与文档之间的相关度。

为了做更精确的估计,设计优化的目标函数是对数似然函数:

L=∑∑n(d, w) log P(d, w)

 

那么如何来通过机器学习训练这些概率呢?首先我们知道:

p(d, w) = p(d) × p(w|d)

p(w|d) = ∑p(w|z)p(z|d)

同时又有:

p(z|d) = p(z)p(d|z)/∑p(z)p(d|z)

那么

p(d, w) =p(d)×∑p(w|z) p(z)p(d|z)/∑p(z)p(d|z)=∑p(z)×p(w|z)×p(d|z)

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

下面我们采取EM算法,EM算法的精髓就是按照最大似然的原理,先随便拍一个分布参数,让每个人都根据分布归类到某一部分,然后根据这些归类来重新统计数目,按照最大似然估计分布参数,然后再重新归类、调参、估计、归类、调参、估计,最终得出最优解

那么我们要把每一个训练数据做归类,即p(z|d,w),那么这个概率值怎么计算呢?

我们先拍一个p(z)、p(d|z)、p(w|z)

然后根据

p(z|d,w)=p(z)p(d|z)p(w|z)/∑p(z)p(d|z)p(w|z),其中分子是一个z,分母是所有的z的和

这样计算出来的值是p(z|d,w)的最大似然估计的概率估计(这是E过程)

然后根据这个估计来对每一个训练样本做归类

根据归类好的数据统计出n(d,w)

然后我再根据以下公式来更新参数

p(z) = 1/R  ∑n(d,w)p(z|d,w)
p(d|z)=∑n(d,w)p(z|d,w) / ∑n(d,w)p(z|d,w),其中分子是一个d的和,分母是所有的d的和,这样计算出来的值是p(d|z)的最大似然估计
p(w|z)=∑n(d,w)p(z|d,w) / ∑n(d,w)p(z|d,w),其中分子是一个w的和,分母是所有的w的和,这样计算出来的值是p(w|z)的最大似然估计

最后重新计算p(z|d,w):

p(z|d,w)=p(z)p(d|z)p(w|z)/∑p(z)p(d|z)p(w|z)

这是M的过程

不断重复上面EM的过程使得对数似然函数最大:

L=∑∑n(d, w) log P(d, w)

通过以上迭代就能得出最终的p(w, d),即词与文档之间的相关度,后面就是利用相关度做检索的过程了

为了得到词词之间的相关度,我们用p(w, d)乘以它的转置,即

p(w,w) = p(w,d)×trans(p(w,d))

当用户查询query的关键词构成词向量Wq, 而文档d表示成词向量Wd,那么query和文档d的相关度就是:

R(query, d) = Wq×p(w,w)×Wd

这样把所有文档算出来的相关度从大到小排序就是搜索的排序结果

 

总结

综上就是隐含语义索引模型的内容,相比TF-IDF来说它加进了语义方面的信息、考虑了词与词之间的关系,是根据语义做信息检索的方法,更适合于研发聊天机器人做语料训练和分析,而TF-IDF更适合于完全基于独立的词的信息检索,更适合于纯文本搜索引擎

自己动手做聊天机器人 二十-语义角色标注的基本方法

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

语义角色

举个栗子:“我昨天吃了一块肉”,按照常规理解“我吃肉”应该是句子的核心,但是对于机器来说“我吃肉”实际上已经丢失了非常多的重要信息,没有了时间,没有了数量。为了让机器记录并提取出这些重要信息,句子的核心并不是“我吃肉”,而是以谓词“吃”为核心的全部信息。

“吃”是谓词,“我”是施事者,“肉”是受事者,“昨天”是事情发生的时间,“一块”是数量。语义角色标注就是要分析出这一些角色信息,从而可以让计算机提取出重要的结构化信息,来“理解”语言的含义。

 

语义角色标注的基本方法

语义角色标注需要依赖句法分析的结果进行,因为句法分析包括短语结构分析、浅层句法分析、依存关系分析,所以语义角色标注也分为:基于短语结构树的语义角色标注方法、基于浅层句法分析结果的语义角色标注方法、基于依存句法分析结果的语义角色标注方法。但无论哪种方法,过程都是:

句法分析->候选论元剪除->论元识别->论元标注->语义角色标注结果

其中论元剪除就是在较多候选项中去掉肯定不是论元的部分

其中论元识别是一个二值分类问题,即:是论元和不是论元

其中论元标注是一个多值分类问题

下面分别针对三种方法分别说明这几个过程的具体方法

 

基于短语结构树的语义角色标注方法

短语结构树是这样的结构:

S——|
|        |
NN    VP
我      |——|
          Vt     NN
          吃     肉

短语结构树里面已经表达了一种结构关系,因此语义角色标注的过程就是依赖于这个结构关系来设计的一种复杂策略,策略的内容随着语言结构的复杂而复杂化,因此我们举几个简单的策略来说明。

首先我们分析论元剪除的策略:

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

因为语义角色是以谓词为中心的,因此在短语结构树中我们也以谓词所在的节点为中心,先平行分析,比如这里的“吃”是谓词,和他并列的是“肉”,明显“肉”是受事者,那么设计什么样的策略能使得它成为候选论元呢?我们知道如果“肉”存在一个短语结构的话,那么一定会多处一个树分支,那么“肉”和“吃”一定不会在树的同一层,因此我们设计这样的策略来保证“肉”被选为候选论元:如果当前节点的兄弟节点和当前节点不是句法结构的并列关系,那么将它作为候选论元。当然还有其他策略不需要记得很清楚,现用现查就行了,但它的精髓就是基于短语结构树的结构特点来设计策略的。

然后就是论元识别过程了。论元识别是一个二值分类问题,因此一定是基于标注的语料库做机器学习的,机器学习的二值分类方法都是固定的,唯一的区别就是特征的设计,这里面一般设计如下特征效果比较好:谓词本身、短语结构树路径、短语类型、论元在谓词的位置、谓词语态、论元中心词、从属类别、论元第一个词和最后一个词、组合特征。

论元识别之后就是论元标注过程了。这又是一个利用机器学习的多值分类器进行的,具体方法不再赘述。

 

基于依存句法分析结果和基于语块的语义角色标注方法

这两种语义角色标注方法和基于短语结构树的语义角色标注方法的主要区别在于论元剪除的过程,原因就是他们基于的句法结构不同。

基于依存句法分析结果的语义角色标注方法会基于依存句法直接提取出谓词-论元关系,这和依存关系的表述是很接近的,因此剪除策略的设计也就比较简单:以谓词作为当前节点,当前节点所有子节点都是候选论元,将当前节点的父节点作为当前节点重复以上过程直至到根节点为止。

基于依存句法分析结果的语义角色标注方法中的论元识别算法的特征设计也稍有不同,多了有关父子节点的一些特征。

有了以上几种语义角色标注方法一定会各有优缺点,因此就有人想到了多种方法相融合的方法,融合的方式可以是:加权求和、插值……,最终效果肯定是更好,就不多说了。

 

多说几句

语义角色标注当前还是不是非常有效,原因有诸多方面,比如:依赖于句法分析的准确性、领域适应能力差。因此不断有新方法来解决这些问题,比如说可以利用双语平行语料来弥补准确性的问题,中文不行英文来,英文不行法语来,反正多多益善,这确实有助于改进效果,但是成本提高了许多。语义角色标注还有一段相当长的路要走,希望学术界研究能不断开花结果吧

自己动手做聊天机器人 十九-机器人是怎么理解“日后再说”的

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

词义消歧

词义消歧是句子和篇章语义理解的基础,是必须解决的问题。任何一种语言都有大量具有多种含义的词汇,中文的“日”,英文的“bank”,法语的“prendre”……。

词义消歧可以通过机器学习的方法来解决。谈到机器学习就会分成有监督和无监督的机器学习。词义消歧有监督的机器学习方法也就是分类算法,即判断词义所属的分类。词义消歧无监督的机器学习方法也就是聚类算法,把词义聚成多类,每一类是一种含义。

 

有监督的词义消歧方法

基于互信息的词义消歧方法

这个方法的名字不好理解,但是原理却非常简单:用两种语言对照着看,比如:中文“打人”对应英文“beat a man”,而中文“打酱油”对应英文“buy some sauce”。这样就知道当上下文语境里有“人”的时候“打”的含义是beat,当上下文语境里有“酱油”的时候“打”的含义是buy。按照这种思路,基于大量中英文对照的语料库训练出来的模型就可以用来做词义消歧了,这种方法就叫做基于“互信息”的词义消歧方法。讲到“互信息”还要说一下它的起源,它来源于信息论,表达的是一个随机变量中包含另一个随机变量的信息量(也就是英文信息中包含中文信息的信息量),假设两个随机变量X、Y的概率分别是p(x), p(y),它们的联合分布概率是p(x,y),那么互信息计算公式是:

I(X; Y) = ∑∑p(x,y)log(p(x,y)/(p(x)p(y)))

以上公式是怎么推导出来的呢?比较简单,“互信息”可以理解为一个随机变量由于已知另一个随机变量而减少的不确定性(也就是理解中文时由于已知了英文的含义而让中文理解更确定了),因为“不确定性”就是熵所表达的含义,所以:

I(X; Y) = H(X) - H(X|Y)

等式后面经过不断推导就可以得出上面的公式,对具体推导过程感兴趣可以百度一下。

那么我们在对语料不断迭代训练过程中I(X; Y)是不断减小的,算法终止的条件就是I(X; Y)不再减小。

基于互信息的词义消歧方法自然对机器翻译系统的效果是最好的,但它的缺点是:双语语料有限,多种语言能识别出歧义的情况也是有限的(比如中英文同一个词都有歧义就不行了)。

 

基于贝叶斯分类器的消歧方法

提到贝叶斯那么一定少不了条件概率,这里的条件指的就是上下文语境这个条件,任何多义词的含义都是跟上下文语境相关的。假设语境(context)记作c,语义(semantic)记作s,多义词(word)记作w,那么我要计算的就是多义词w在语境c下具有语义s的概率,即:

p(s|c)

那么根据贝叶斯公式:

p(s|c) = p(c|s)p(s)/p(c)

我要计算的就是p(s|c)中s取某一个语义的最大概率,因为p(c)是既定的,所以只考虑分子的最大值:

s的估计=max(p(c|s)p(s))

因为语境c在自然语言处理中必须通过词来表达,也就是由多个v(词)组成,那么也就是计算:

max(p(s)∏p(v|s))

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

下面就是训练的过程了:

p(s)表达的是多义词w的某个语义s的概率,可以统计大量语料通过最大似然估计求得:

p(s) = N(s)/N(w)

p(v|s)表达的是多义词w的某个语义s的条件下出现词v的概率,可以统计大量语料通过最大似然估计求得:

p(v|s) = N(v, s)/N(s)

训练出p(s)和p(v|s)之后我们对一个多义词w消歧的过程就是计算(p(c|s)p(s))的最大概率的过程

 

无监督的词义消歧方法

完全无监督的词义消歧是不可能的,因为没有标注是无法定义是什么词义的,但是可以通过无监督的方法来做词义辨识。无监督的词义辨识其实也是一种贝叶斯分类器,和上面讲到的贝叶斯分类器消歧方法不同在于:这里的参数估计不是基于有标注的训练预料,而是先随机初始化参数p(v|s),然后根据EM算法重新估计这个概率值,也就是对w的每一个上下文c计算p(c|s),这样可以得到真实数据的似然值,回过来再重新估计p(v|s),重新计算似然值,这样不断迭代不断更新模型参数,最终得到分类模型,可以对词进行分类,那么有歧义的词在不同语境中会被分到不同的类别里。

仔细思考一下这种方法,其实是基于单语言的上下文向量的,那么我们进一步思考下一话题,如果一个新的语境没有训练模型中一样的向量怎么来识别语义?

这里就涉及到向量相似性的概念了,我们可以通过计算两个向量之间夹角余弦值来比较相似性,即:

cos(a,b) = ∑ab/sqrt(∑a^2∑b^2)

 

机器人是怎么理解“日后再说”的

回到最初的话题,怎么让机器人理解“日后再说”,这本质上是一个词义消歧的问题,假设我们利用无监督的方法来辨识这个词义,那么就让机器人“阅读”大量语料进行“学习”,生成语义辨识模型,这样当它听到这样一则对话时:

有一位老嫖客去找小姐,小姐问他什么时候结账啊。嫖客说:“钱的事情日后再说。”就开始了,完事后,小姐对嫖客说:“给钱吧。”嫖客懵了,说:“不是说日后再说吗?”小姐说:“是啊,你现在不是已经日后了吗?”

辨识了这里的“日后再说”的词义后,它会心的笑了

自己动手做聊天机器人 十八-神奇算法之句法分析树的生成

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

句法分析

先来解释一下句法分析。句法分析分为句法结构分析和依存关系分析。

句法结构分析也就是短语结构分析,比如提取出句子中的名次短语、动词短语等,最关键的是人可以通过经验来判断的短语结构,那么怎么由机器来判断呢?

(有关依存关系分析的内容,具体可以看《自己动手做聊天机器人 十二-教你如何利用强大的中文语言技术平台做依存句法和语义依存分析》)

 

句法分析树

样子如下:

         -吃(v)-

|                      |

我(rr)            肉(n)

 

句法结构分析基本方法

分为基于规则的分析方法和基于统计的分析方法。基于规则的方法存在很多局限性,所以我们采取基于统计的方法,目前最成功的是基于概率上下文无关文法(PCFG)。基于PCFG分析需要有如下几个要素:终结符集合、非终结符集合、规则集。

相对于先叙述理论再举实例的传统讲解方法,我更倾向于先给你展示一个简单的例子,先感受一下计算过程,然后再叙述理论,这样会更有趣。

例子是这样的:我们的终结符集合是:∑={我, 吃, 肉,……},这个集合表示这三个字可以作为句法分析树的叶子节点,当然这个集合里还有很多很多的词

我们的非终结符集合是:N={S, VP, ……},这个集合表示树的非页子节点,也就是连接多个节点表达某种关系的节点,这个集合里也是有很多元素

我们的规则集:R={

NN->我    0.5

Vt->吃     1.0

NN->肉   0.5

VP->Vt NN    1.0

S->NN VP 1.0

……

}

这里的句法规则符号可以参考词性标注,后面一列是模型训练出来的概率值,也就是在一个固定句法规则中NN的位置是“我”的概率是0.5,NN推出“肉”的概率是0.5,0.5+0.5=1,也就是左部相同的概率和一定是1。不知道你是否理解了这个规则的内涵

再换一种方法解释一下,有一种句法规则是:

S——|

|        |

NN    VP

          |——|

          Vt     NN

其中NN的位置可能是“我”,也可能是“肉”,是“我”的概率是0.5,是“肉”的概率是0.5,两个概率和必为1。其中Vt的位置一定是“吃”,也就是概率是1.0……。这样一说是不是就理解了?

 

规则集里实际上还有很多规则,只是列举出会用到的几个

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

以上的∑、N、R都是经过机器学习训练出来的数据集及概率,具体训练方法下面我们会讲到

 

那么如何根据以上的几个要素来生成句法分析树呢?

(1)“我”

词性是NN,推导概率是0.5,树的路径是“我”

(2)“吃”

词性是Vt,推导概率是1.0,树的路径是“吃”

(3)“肉”

词性是NN,概率是0.5,和Vt组合符合VP规则,推导概率是0.5*1.0*1.0=0.5,树的路径是“吃肉”

NN和VP组合符合S规则,推导概率是0.5*0.5*1.0=0.25,树的路径是“我吃肉”

 

所以最终的树结构是:

S——|

|        |

NN    VP

我      |——|

          Vt     NN

          吃     肉

 

上面的例子是比较简单的,实际的句子会更复杂,但是都是通过这样的动态规划算法完成的

提到动态规划算法,就少不了“选择”的过程,一句话的句法结构树可能有多种,我们只选择概率最大的那一种作为句子的最佳结构,这也是“基于概率”上下文无关文法的名字起源。

 

上面的计算过程总结起来就是:设W={ω1ω2ω3……}表示一个句子,其中的ω表示一个词(word),利用动态规划算法计算非终结符A推导出W中子串ωiωi+1ωi+2……ωj的概率,假设概率为αij(A),那么有如下递归公式:

αij(A)=P(A->ωi)

αij(A)=∑∑P(A->BC)αik(B)α(k+1)j(C)

以上两个式子好好理解一下其实就是上面“我吃肉”的计算过程

 

以上过程理解了之后你一定会问,这里面最关键的的非终结符、终结符以及规则集是怎么得来的,概率又是怎么确定的?下面我们就来说明

 

 

句法规则提取方法与PCFG的概率参数估计

这部分就是机器学习的知识了,有关机器学习可以参考《机器学习教程

首先我们需要大量的树库,也就是训练数据。然后我们把树库中的句法规则提取出来生成我们想要的结构形式,并进行合并、归纳等处理,最终得到上面∑、N、R的样子。其中的概率参数计算方法是这样的:

先给定参数为一个随机初始值,然后采用EM迭代算法,不断训练数据,并计算每条规则使用次数作为最大似然计算得到概率的估值,这样不断迭代更新概率,最终得出的概率可以认为是符合最大似然估计的精确值。

 

总结一下

句法分析树生成算法是基于统计学习的原理,根据大量标注的语料库(树库),通过机器学习算法得出非终结符、终结符、规则集及其概率参数,然后利用动态规划算法生成每一句话的句法分析树,在句法分析树生成过程中如果遇到多种树结构,选择概率最大的那一种作为最佳句子结构

自己动手做聊天机器人 十七-让机器做词性自动标注的具体方法

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

何为词性

常说的词性包括:名、动、形、数、量、代、副、介、连、助、叹、拟声。但自然语言处理中要分辨的词性要更多更精细,比如:区别词、方位词、成语、习用语、机构团体、时间词等,多达100多种。

汉语词性标注最大的困难是“兼类”,也就是一个词在不同语境中有不同的词性,而且很难从形式上识别。

 

词性标注过程

为了解决词性标注无法达到100%准确的问题,词性标注一般要经过“标注”和“校验”两个过程,第一步“标注”根据规则或统计的方法做词性标注,第二步“校验”通过一致性检查和自动校对等方法来修正。

 

词性标注的具体方法

词性标注具体方法包括:基于统计模型的方法、基于规则的方法和两者结合的方法。下面我们分别来介绍。

 

基于统计模型的词性标注方法

提到基于统计模型,势必意味着我们要利用大量已经标注好的语料库来做训练,同时要先选择一个合适的训练用的数学模型,《自己动手做聊天机器人 十五-一篇文章读懂拿了图灵奖和诺贝尔奖的概率图模型》中我们介绍了概率图模型中的隐马尔科夫模型(HMM)比较适合词性标注这种基于观察序列来做标注的情形。语言模型选择好了,下面要做的就是基于语料库来训练模型参数,那么我们模型参数初值如何设置呢?这里面就有技巧了

 

隐马尔可夫模型参数初始化的技巧

模型参数初始化是在我们尚未利用语料库之前用最小的成本和最接近最优解的目标来设定初值。HMM是一种基于条件概率的生成式模型,所以模型参数是生成概率,那么我们不妨就假设每个词的生成概率就是它所有可能的词性个数的倒数,这个是计算最简单又最有可能接近最优解的生成概率了。每个词的所有可能的词性是我们已经有的词表里标记好的,这个词表的生成方法就比较简单了,我们不是有已经标注好的语料库嘛,很好统计。那么如果某个词在词表里没有呢?这时我们可以把它的生成概率初值设置为0。这就是隐马尔可夫模型参数初始化的技巧,总之原则就是用最小的成本和最接近最优解的目标来设定初值。一旦完成初始值设定后就可以利用前向后向算法进行训练了。

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

基于规则的词性标注方法

规则就是我们既定好一批搭配关系和上下文语境的规则,判断实际语境符合哪一种则按照规则来标注词性。这种方法比较古老,适合于既有规则,对于兼词的词性识别效果较好,但不适合于如今网络新词层出不穷、网络用语新规则的情况。于是乎,有人开始研究通过机器学习来自动提取规则,怎么提取呢?不是随便给一堆语料,它直接来生成规则,而是根据初始标注器标注出来的结果和人工标注的结果的差距,来生成一种修正标注的转换规则,这是一种错误驱动的学习方法。基于规则的方法还有一个好处在于:经过人工校总结出的大量有用信息可以补充和调整规则库,这是统计方法做不到的。

 

统计方法和规则方法相结合的词性标注方法

统计方法覆盖面比较广,新词老词通吃,常规非常规通吃,但对兼词、歧义等总是用经验判断,效果不好。规则方法对兼词、歧义识别比较擅长,但是规则总是覆盖不全。因此两者结合再好不过,先通过规则排歧,再通过统计标注,最后经过校对,可以得到正确的标注结果。在两者结合的词性标注方法中,有一种思路可以充分发挥两者优势,避免劣势,就是首选统计方法标注,同时计算计算它的置信度或错误率,这样来判断是否结果是否可疑,在可疑情况下采用规则方法来进行歧义消解,这样达到最佳效果。

 

词性标注的校验

做完词性标注并没有结束,需要经过校验来确定正确性以及修正结果。

第一种校验方法就是检查词性标注的一致性。一致性指的是在所有标注的结果中,具有相同语境下同一个词的标注是否都相同,那么是什么原因导致的这种不一致呢?一种情况就是这类词就是兼类词,可能被标记为不同词性。另一种情况是非兼类词,但是由于人工校验或者其他原因导致标记为不同词性。达到100%的一致性是不可能的,所以我们需要保证一致性处于某个范围内,由于词数目较多,词性较多,一致性指标无法通过某一种计算公式来求得,因此可以基于聚类和分类的方法,根据欧式距离来定义一致性指标,并设定一个阈值,保证一致性在阈值范围内。

第二种校验方法就是词性标注的自动校对。自动校对顾名思义就是不需要人参与,直接找出错误的标注并修正,这种方法更适用于一个词的词性标注通篇全错的情况,因为这种情况基于数据挖掘和规则学习方法来做判断会相对比较准确。通过大规模训练语料来生成词性校对决策表,然后根据这个决策表来找通篇全错的词性标注并做自动修正。

 

总结

词性标注的方法主要有基于统计和基于规则的方法,另外还包括后期校验的过程。词性标注是帮助计算机理解语言含义的关键,有了词性标注,我们才可以进一步确定句法和语义,才有可能让机器理解语言的含义,才有可能实现聊天机器人的梦想