用神经网络训练一个文本分类器

标签: IT技术 机器学习 | 发表时间:2017-08-10 13:05 | 作者:Anne90
出处:http://blog.jobbole.com

理解聊天机器人的工作原理是非常重要的。聊天机器人内部一个非常重要的组件就是文本分类器。我们看一下文本分类器的神经网络(ANN)的内部工作原理。

1-DpMaU1p85ZSgamwYDkzL-A

多层神经网络

我们将会使用2层网络(1个隐层)和一个“词包”的方法来组织我们的训练数据。文本分类有3个特点:模式匹配、算法、神经网络。虽然使用多项朴素贝叶斯算法的方法非常有效,但是它有3个致命的缺陷:

  • 这个算法输出一个分数而不是一个概率。我们可以使用概率来忽略特定阈值以下的预测结果。这类似于忽略收音机中的噪声。
  • 这个算法从一个样本中学习一个分类中包含什么,而不是一个分类中不包含什么。一个分类中不包含什么的的学习模式往往也很重要。
  • 不成比例的大训练集的分类将会导致扭曲的分类分数,迫使算法相对于分类规模来调整输出分数,这并不理想。

和它“天真”的对手一样,这种分类器并不试图去理解句子的含义,而仅仅对它进行分类。事实上,所谓的“人工智能聊天机器人”并不理解语言,但那是 另一个故事

如果你刚接触人工神经网络,这是它的 工作原理

理解分类算法,请看 这里

我们来逐个分析文本分类器的每个部分。我们将按照以下顺序:

  1. 引用需要的库
  2. 提供训练集
  3. 整理数据
  4. 迭代:编写代码+测试预测结果+调整模型
  5. 抽象

代码在这里,我们使用ipython notebook这个在数据科学项目上非常高效的工具。代码语法是python。

我们首先导入自然语言工具包。我们需要一个可靠的方法将句子切分成词并且将单词词干化处理。

# use natural language toolkit
import nltk
from nltk.stem.lancaster import LancasterStemmer
import os
import json
import datetime
stemmer = LancasterStemmer()

下面是我们的训练集,12个句子属于3个类别(“意图”)。

# 3 classes of training data
training_data = []
training_data.append({"class":"greeting", "sentence":"how are you?"})
training_data.append({"class":"greeting", "sentence":"how is your day?"})
training_data.append({"class":"greeting", "sentence":"good day"})
training_data.append({"class":"greeting", "sentence":"how is it going today?"})

training_data.append({"class":"goodbye", "sentence":"have a nice day"})
training_data.append({"class":"goodbye", "sentence":"see you later"})
training_data.append({"class":"goodbye", "sentence":"have a nice day"})
training_data.append({"class":"goodbye", "sentence":"talk to you soon"})

training_data.append({"class":"sandwich", "sentence":"make me a sandwich"})
training_data.append({"class":"sandwich", "sentence":"can you make a sandwich?"})
training_data.append({"class":"sandwich", "sentence":"having a sandwich today?"})
training_data.append({"class":"sandwich", "sentence":"what's for lunch?"})
print ("%s sentences in training data" % len(training_data))

12 sentences in training data

现在我们可以将数据结构组织为: documentsclasses 和 words.

words = []
classes = []
documents = []
ignore_words = ['?']
# loop through each sentence in our training data
for pattern in training_data:
    # tokenize each word in the sentence
    w = nltk.word_tokenize(pattern['sentence'])
    # add to our words list
    words.extend(w)
    # add to documents in our corpus
    documents.append((w, pattern['class']))
    # add to our classes list
    if pattern['class'] not in classes:
        classes.append(pattern['class'])

# stem and lower each word and remove duplicates
words = [stemmer.stem(w.lower()) for w in words if w not in ignore_words]
words = list(set(words))

# remove duplicates
classes = list(set(classes))

print (len(documents), "documents")
print (len(classes), "classes", classes)
print (len(words), "unique stemmed words", words)

12 documents
3 classes ['greeting', 'goodbye', 'sandwich']
26 unique stemmed words ['sandwich', 'hav', 'a', 'how', 'for', 'ar', 'good', 'mak', 'me', 'it', 'day', 'soon', 'nic', 'lat', 'going', 'you', 'today', 'can', 'lunch', 'is', "'s", 'see', 'to', 'talk', 'yo', 'what']

注意每个单词都是词根并且小写。词根有助于机器将“have”和“having”等同起来。同时我们也不关心大小写。 1-eUedufAl7_sI_QWSEIstZg

我们将训练集中的每个句子转换为词包。

# create our training data
training = []
output = []
# create an empty array for our output
output_empty = [0] * len(classes)

# training set, bag of words for each sentence
for doc in documents:
    # initialize our bag of words
    bag = []
    # list of tokenized words for the pattern
    pattern_words = doc[0]
    # stem each word
    pattern_words = [stemmer.stem(word.lower()) for word in pattern_words]
    # create our bag of words array
    for w in words:
        bag.append(1) if w in pattern_words else bag.append(0)

    training.append(bag)
    # output is a '0' for each tag and '1' for current tag
    output_row = list(output_empty)
    output_row[classes.index(doc[1])] = 1
    output.append(output_row)

# sample training/output
i = 0
w = documents[i][0]
print ([stemmer.stem(word.lower()) for word in w])
print (training[i])
print (output[i])

['how', 'ar', 'you', '?']
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 0]

上面的步骤是文本分类中的一个经典步骤:每个训练句子被转化为一个包含0和1的数组,而不是语料库中包含独特单词的数组。

['how', 'are', 'you', '?']

被词干化为:

['how', 'ar', 'you', '?']

然后转换为输入词包的形式: 1代表单词存在于词包中(忽略问号?)

[0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

输出:第一类

[1, 0, 0]

注意:一个句子可以有多个分类,也可以没有。确保理解上面的内容,仔细阅读代码直到你理解它。

机器学习的第一步是要有干净的数据

1-CcQPggEbLgej32mVF2lalg

接下来我们的学习2层神经网络的核心功能。

如果你是人工神经网络新手,这里是它的工作原理

我们使用 numpy,原因是它可以提供快速的矩阵乘法运算。

1-8SJcWjxz8j7YtY6K-DWxKw

我们使用sigmoid函数对值进行归一化,用其导数来衡量错误率。通过不断迭代和调整,直到错误率低到一个可以接受的值。

下面我们也实现了bag-of-words函数,将输入的一个句子转化为一个包含0和1的数组。这就是转换训练数据,得到正确的转换数据至关重要。

import numpy as np
import time

# compute sigmoid nonlinearity
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output

# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):
    return output*(1-output)

def clean_up_sentence(sentence):
    # tokenize the pattern
    sentence_words = nltk.word_tokenize(sentence)
    # stem each word
    sentence_words = [stemmer.stem(word.lower()) for word in sentence_words]
    return sentence_words

# return bag of words array: 0 or 1 for each word in the bag that exists in the sentence
def bow(sentence, words, show_details=False):
    # tokenize the pattern
    sentence_words = clean_up_sentence(sentence)
    # bag of words
    bag = [0]*len(words)  
    for s in sentence_words:
        for i,w in enumerate(words):
            if w == s: 
                bag[i] = 1
                if show_details:
                    print ("found in bag: %s" % w)

    return(np.array(bag))

def think(sentence, show_details=False):
    x = bow(sentence.lower(), words, show_details)
    if show_details:
        print ("sentence:", sentence, "n bow:", x)
    # input layer is our bag of words
    l0 = x
    # matrix multiplication of input and hidden layer
    l1 = sigmoid(np.dot(l0, synapse_0))
    # output layer
    l2 = sigmoid(np.dot(l1, synapse_1))
    return l2

现在我们对神经网络训练函数进行编码,创造连接权重。别太激动,这主要是矩阵乘法——来自中学数学课堂。

2017-06-03_151025

我们现在准备去构建我们的神经网络 模型,我们将连接权重保存为json文件。

你应该尝试不同的“α”(梯度下降参数),看看它是如何影响错误率。此参数有助于错误调整,并找到最低错误率:

synapse_0 += alpha * synapse_0_weight_update

1-HZ-YQpdBM4hDbh4Q5FcsMA

我们在隐藏层使用了20个神经元,你可以很容易地调整。这些参数将随着于您的训练数据规模的不同而不同,将错误率调整到低于10 ^ – 3是比较合理的。

X = np.array(training)
y = np.array(output)

start_time = time.time()

train(X, y, hidden_neurons=20, alpha=0.1, epochs=100000, dropout=False, dropout_percent=0.2)

elapsed_time = time.time() - start_time
print ("processing time:", elapsed_time, "seconds")

Training with 20 neurons, alpha:0.1, dropout:False 
Input matrix: 12x26    Output matrix: 1x3
delta after 10000 iterations:0.0062613597435
delta after 20000 iterations:0.00428296074919
delta after 30000 iterations:0.00343930779307
delta after 40000 iterations:0.00294648034566
delta after 50000 iterations:0.00261467859609
delta after 60000 iterations:0.00237219554105
delta after 70000 iterations:0.00218521899378
delta after 80000 iterations:0.00203547284581
delta after 90000 iterations:0.00191211022401
delta after 100000 iterations:0.00180823798397
saved synapses to: synapses.json
processing time: 6.501226902008057 seconds

synapse.json文件中包含了全部的连接权重, 这就是我们的模型

1-qYkCgPE3DD26VD-qDwsicA

一旦连接权重已经计算完成,对于分类来说只需要classify()函数了:大约15行代码

备注:如果训练集有变化,我们的模型需要重新计算。对于非常大的数据集,这需要较长的时间。

现在我们可以生成一个句子属于一个或者多个分类的概率了。它的速度非常快,这是因为我们之前定义的think()函数中的点积运算。

# probability threshold
ERROR_THRESHOLD = 0.2
# load our calculated synapse values
synapse_file = 'synapses.json' 
with open(synapse_file) as data_file: 
    synapse = json.load(data_file) 
    synapse_0 = np.asarray(synapse['synapse0']) 
    synapse_1 = np.asarray(synapse['synapse1'])

def classify(sentence, show_details=False):
    results = think(sentence, show_details)

    results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD ] 
    results.sort(key=lambda x: x[1], reverse=True) 
    return_results =[[classes[r[0]],r[1]] for r in results]
    print ("%s n classification: %s" % (sentence, return_results))
    return return_results

classify("sudo make me a sandwich")
classify("how are you today?")
classify("talk to you tomorrow")
classify("who are you?")
classify("make me some lunch")
classify("how was your lunch today?")
print()
classify("good day", show_details=True)

  sudo make me a sandwich 
 [['sandwich', 0.99917711814437993]]
  how are you today? 
 [['greeting', 0.99864563257858363]]
  talk to you tomorrow 
 [['goodbye', 0.95647479275905511]]
  who are you? 
 [['greeting', 0.8964283843977312]]
  make me some lunch 
 [['sandwich', 0.95371924052636048]]
  how was your lunch today? 
 [['greeting', 0.99120883810944971], ['sandwich', 0.31626066870883057]]

你可以用其它语句、不同概率来试验几次,也可以添加训练数据来改进/扩展当前的模型。尤其注意用很少的训练数据就得到稳定的预测结果。

有一些句子将会产生多个预测结果(高于阈值)。你需要给你的程序设定一个合适的阈值。并非所有的文本分类方案都是相同的: 一些预测情况比其他预测需要更高的置信水平

最后这个分类结果展示了一些内部的细节:

found in bag: good
found in bag: day
sentence: **good day** 
 bow: [0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
good day 
 [['greeting', 0.99664077655648697]]

从这个句子的词包中可以看到,有两个单词和我们的词库是匹配的。同时我们的神经网络从这些 0 代表的非匹配词语中学习了。

如果提供一个仅仅有一个常用单词 ‘a’ 被匹配的句子,那我们会得到一个低概率的分类结果A:

found in bag: a
sentence: **a burrito! **
 bow: [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
a burrito! 
 [['sandwich', 0.61776860634647834]]

现在你已经掌握了构建聊天机器人的一些基础知识结构,它能处理大量不同的意图,并且对于有限或者海量的训练数据都能很好的适配。想要为某个意图添加一个或者多个响应实在轻而易举,就不必多讲了。

Enjoy!

用神经网络训练一个文本分类器,首发于 文章 - 伯乐在线

相关 [神经网络 训练 文本] 推荐:

用神经网络训练一个文本分类器

- - 文章 – 伯乐在线
理解聊天机器人的工作原理是非常重要的. 聊天机器人内部一个非常重要的组件就是文本分类器. 我们看一下文本分类器的神经网络(ANN)的内部工作原理. 我们将会使用2层网络(1个隐层)和一个“词包”的方法来组织我们的训练数据. 文本分类有3个特点:模式匹配、算法、神经网络. 虽然使用多项朴素贝叶斯算法的方法非常有效,但是它有3个致命的缺陷:.

用神经网络“训练”谷歌翻译,机器翻译实现颠覆性突破

- - 钛媒体:网罗天下创新事
钛媒体注:昨日,谷歌在 ArXiv.org 上发表论文《Google`s Neural Machine Translation System: Bridging the Gap between Human and Machine Translation》介绍谷歌的神经机器翻译系统(GNMT),当日钛媒体作者机器之心就对该论文进行了摘要翻译并推荐到网站(www.jiqizhixin.com)上.

BP神经网络的Java实现

- - ITeye博客
课程作业要求实现一个BPNN. 此前只用Matlab实现过,这次尝试使用Java实现了一个. 关于BPNN的原理,就不赘述了.  为了验证正确性,我写了一个测试用例,目的是对于任意的整数(int型),BPNN在经过训练之后,能够准确地判断出它是奇数还是偶数,正数还是负数. System.out.println("训练完毕,下面请输入一个任意数字,神经网络将自动判断它是正数还是复数,奇数还是偶数.

神经网络与用户行为

- - 博客园_新闻
英文原文: Neurology & User Behavior: What We Know. 流量和转化率是我们衡量一个网站是否成功的两个重要指标. 网站转化率就是指用户进行了相应目标行动的访问次数(成交人数)与总访问次数的比率. 这里所指的相应的行动可以是用户登录、用户注册、用户订阅、用户下载、用户购买等一些列用户行为,因此网站转化率是一个广义的概念.

研究人员首次用DNA构造人工神经网络

- Robi - Solidot
加州理工研究人员首次用DNA构造出人工神经网络,类似一个简化的大脑. 此前他们曾研制过一个能计算平方根的DNA逻辑门. 研究人员112种不同的DNA链组成四个相互联系的人工神经元,它会玩一个猜心术的游戏. 研究人员和这个试管中的神经网络玩了27次游戏,每次提供的线索都不相同,而它每次都猜对了. 研究人员表示,具有人工智能的生化系统,可以在医药,化学以及生物领域带来不可估量的应用.

自组织增量学习神经网络-SOINN

- 金文 - 丕子
自组织增量学习神经网络(Self-organizing incremental neural network, SOINN)实现包括学习、记忆、联想、推理、常识等方面的研究,最终目的是实现能够模拟人类大脑的供智能机械使用的通用型智能信息处理系统——人工脑. a. 基于SOINN的监督学习、非监督学习、半监督学习算法研究.

动态神经网络工具包DyNet:比Theano和TensorFlow更快

- - IT瘾-tuicool
近日,来自卡内基梅隆大学、日本奈良先端科学技术大学、Google DeepMind、以色列巴伊兰大学、艾伦人工智能研究所、IBM T.J. Watson 研究中心、澳大利亚墨尔本大学、约翰·霍普金斯大学、谷歌、华盛顿大学、微软和英国爱丁堡大学的研究者共同发表了一篇重磅论文《DyNet: The Dynamic Neural Network Toolkit》,正式介绍了动态神经网络工具包 DyNet;该工具包也已在 GitHub 上开源:http://github.com/clab/dynet.

Leetcode 编程训练

- - 酷 壳 - CoolShell.cn
Leetcode这个网站上的题都是一些经典的公司用来面试应聘者的面试题,很多人通过刷这些题来应聘一些喜欢面试算法的公司,比如:Google、微软、Facebook、Amazon之类的这些公司,基本上是应试教育的功利主义. 我做这些题目的不是为了要去应聘这些公司,而是为了锻炼一下自己的算法和编程能力.

微软宣布其所有语音翻译服务已全部使用深层神经网络技术

- - 微软亚洲研究院
近日,在 微软Connect();//2016大会上,Microsoft Translator宣布所有微软语音翻译服务已全部使用最先进的深层神经网络技术,其中,包括如 Skype Translator实时语音翻译和Microsoft Translator移动端应用等使用微软语音翻译服务的应用程序.

零基础入门深度学习(5) - 循环神经网络 - 作业部落 Cmd Markdown 编辑阅读器

- -
零基础入门深度学习(5) - 循环神经网络. 无论即将到来的是大数据时代还是人工智能时代,亦或是传统行业使用人工智能在云上处理大数据的时代,作为一个有理想有追求的程序员,不懂深度学习(Deep Learning)这个超热的技术,会不会感觉马上就out了. 现在救命稻草来了,《零基础入门深度学习》系列文章旨在讲帮助爱编程的你从零基础达到入门级水平.