Python 智能项目:6~10(3)

简介: Python 智能项目:6~10(3)

Python 智能项目:6~10(2)https://developer.aliyun.com/article/1426936

要注意的一件事是,在android:id选项中,在 XML 文件中声明了用户和移动应用核心逻辑相互交互所通过的变量。 例如,用户提供的电影评论将由Review变量处理,如下面所示的 XML 文件中所定义:

android:id="@+id/Review"

Android 应用的核心逻辑

Android 应用的核心逻辑是处理用户请求以及传递的数据,然后将结果发送回用户。 作为此移动应用的一部分,核心逻辑将接受用户提供的电影评论,处理原始数据,并将其转换为经过训练的 LSTM 模型可以进行推理的格式。 Java 中的OnClickListener函数用于监视用户是否已提交处理请求。 在将输入直接输入经过优化的经过训练的 LSTM 模型进行推理之前,需要将提供的电影评论中的每个单词更改为其索引。 除了优化的 protobuf 模型之外,还为此目的存储了单词及其对应索引的字典。 TensorFlowInferenceInterface 方法用于对训练后的模型进行推理。 优化的 protobuf 模型和单词词典及其对应的索引存储在assets文件夹中。 总而言之,应用的核心逻辑执行的任务如下:

  1. 将索引字典中的单词加载到WordToIndHashMap中。 词间索引字典是在训练模型之前,在文本的预处理过程中从分词器派生的。
  2. 使用OnClickListener方法监视用户是否已提交电影评论以进行推断。
  3. 如果已提交电影评论,则将从与 XML 关联的Review变量中读取评论。 该评论会通过删除标点符号等内容进行清理,然后拆分为单词。 使用HashMap函数WordToInd将这些单词中的每个单词转换为相应的索引。 这些索引构成我们 TensorFlow 模型的InputVec输入,以进行推断。 输入向量长度为1000; 因此,如果评论的字数少于1000,则向量的开头将填充零。
  4. 下一步,使用TensorFlowInferenceInterface函数创建mInferenceInterface对象,将优化的 protobuf 模型(扩展名为.pb)从assets文件夹加载到内存中。 与原始模型一样,需要定义要参考的 TensorFlow 模型的输入节点和输出节点。 对于我们的模型,它们定义为INPUT_NODEOUTPUT_NODE,它们分别包含 TensorFlow 输入占位符的名称和输出情感概率的操作。 mInferenceInterface 对象的feed方法用于将InputVec值分配给的INPUT_NODE 模型,而mInferenceInterfacerun方法执行OUTPUT_NODE。 最后,使用mInferenceInterfacefetch方法来填充对浮点变量value_的推断结果。
  5. 情感分数(情感为正的概率)乘以五将转换为等级。 然后通过ratingBar 变量将其馈送到 Android 应用用户界面。

Java 中的移动应用的核心逻辑如下:

package com.example.santanu.abc;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.RatingBar;
import android.widget.TextView;
import android.widget.Button;
import android.widget.EditText;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import org.tensorflow.contrib.android.TensorFlowInferenceInterface;
public class MainActivity extends AppCompatActivity {
    private TensorFlowInferenceInterface mInferenceInterface;
    private static final String MODEL_FILE = "file:///android_asset/optimized_model.pb";
    private static final String INPUT_NODE = "inputs/X";
    private static final String OUTPUT_NODE = "positive_sentiment_probability";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Create references to the widget variables
        final TextView desc = (TextView) findViewById(R.id.desc);
        final Button submit = (Button) findViewById(R.id.submit);
        final EditText Review = (EditText) findViewById(R.id.Review);
        final TextView score = (TextView) findViewById(R.id.score);
        final RatingBar ratingBar = (RatingBar) findViewById(R.id.ratingBar);
        //String filePath = "/home/santanu/Downloads/Mobile_App/word2ind.txt";
        final Map<String,Integer> WordToInd = new HashMap<String,Integer>();
        //String line;
        //reader = new BufferedReader(new InputStreamReader(getAssets().open("word2ind.txt")));
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(
                    new InputStreamReader(getAssets().open("word_ind.txt")));
            // do reading, usually loop until end of file reading
            String line;
            while ((line = reader.readLine()) != null)
            {
                String[] parts = line.split("\n")[0].split(",",2);
                if (parts.length >= 2)
                {
                    String key = parts[0];
                    //System.out.println(key);
                    int value = Integer.parseInt(parts[1]);
                    //System.out.println(value);
                    WordToInd.put(key,value);
                } else
                {
                    //System.out.println("ignoring line: " + line);
                }
            }
        } catch (IOException e) {
            //log the exception
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    //log the exception
                }
            }
        }
        //line = reader.readLine();
        // Create Button Submit Listener
        submit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Read Values
                String reviewInput = Review.getText().toString().trim();
                System.out.println(reviewInput);
                String[] WordVec = reviewInput.replaceAll("[^a-zA-z0-9 ]", "").toLowerCase().split("\\s+");
                System.out.println(WordVec.length);
                int[] InputVec = new int[1000];
                // Initialize the input
                for (int i = 0; i < 1000; i++) {
                    InputVec[i] = 0;
                }
                // Convert the words by their indices
                int i = 1000 - 1 ;
                for (int k = WordVec.length -1 ; k > -1 ; k--) {
                    try {
                        InputVec[i] = WordToInd.get(WordVec[k]);
                        System.out.println(WordVec[k]);
                        System.out.println(InputVec[i]);
                    }
                    catch (Exception e) {
                        InputVec[i] = 0;
                    }
                    i = i-1;
                }
                if (mInferenceInterface == null) {
                    AssetManager assetManager = getAssets();
                    mInferenceInterface = new TensorFlowInferenceInterface(assetManager,MODEL_FILE);
                }
                float[] value_ = new float[1];
                mInferenceInterface.feed(INPUT_NODE,InputVec,1,1000);
                mInferenceInterface.run(new String[] {OUTPUT_NODE}, false);
                System.out.println(Float.toString(value_[0]));
                mInferenceInterface.fetch(OUTPUT_NODE, value_);
                double scoreIn;
                scoreIn = value_[0]*5;
                double ratingIn = scoreIn;
                String stringDouble = Double.toString(scoreIn);
                score.setText(stringDouble);
                ratingBar.setRating((float) ratingIn);
            }
        });
    }
}

需要注意的要点之一是,为了将包添加到依赖项中,我们可能需要编辑该应用的build.gradle文件:

org.tensorflow:tensorflow-android:1.7.0

测试移动应用

我们将使用以下两部电影的评论测试该移动应用: 《阿凡达》和《星际穿越》。 《阿凡达》电影评论来自这里,其内容如下:

“看着《阿凡达》,我感觉与 1977 年看到《星球大战》时的感觉差不多。那是另一部我充满不确定性的电影。詹姆斯·卡梅隆的电影一直是毫无疑问的超前嗡嗡声的主题,就像他的《泰坦尼克号》一样。再次,他只是通过制作一部非凡的电影而使怀疑者们保持沉默,好莱坞仍然至少有一个人知道如何花费 2.5 亿美元,或者明智地花费 3 亿美元。

“阿凡达》不仅是一种令人震撼的娱乐活动,而且还是一项技术突破。它具有鲜明的绿色和反战信息。它注定要发动一场邪教。它包含如此直观的细节, 像《指环王》一样,它发明了一种新的语言 Na’vi,尽管我很仁慈地怀疑这种语言可以被人类甚至青少年使用,它创造了新的电影明星。 在那些电影中,您觉得必须跟上对话的步伐。”

审阅者给电影评分为 4/5,而移动应用的评分为 4.8/5,如以下屏幕截图所示(“图 7.4”):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GX13pnWB-1681654125435)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/edef4e5f-45e7-40d0-8e72-9d9f44e37b38.png)]

图 7.4。 电影 Avatar 的移动应用评论评分

同样,我们将评估应用为电影《星际穿越》提供的评分,并从这里获取评论。 评论如下:

“星际大片代表了导演兼导演克里斯托弗·诺兰(Christopher Nolan)所期待的更多激动人心,发人深省,视觉上灿烂的电影制作人,即使其知识渊博超出了人们的理解范围。”

该影片在烂番茄上的平均评分为 7/10,如果将其缩放为 5,则得分为 3.5/5,而移动应用预测的评分为 3.37,如下图所示(“图 7.5”):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4W7WIOI-1681654125436)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e35227a8-c487-43ee-934b-08186678ae3a.png)]

图 7.5。 电影《星际穿越》的移动应用评论评分

正如您在前面的两个插图中看到的那样,移动电影评论分级应用在为电影评论提供合理的分级方面做得很好。

总结

完成本章后,读者应该对如何使用 TensorFlow 的移动功能在 Android 应用中部署深度学习模型有一个清晰的认识。 本章所涉及的技术和实现细节将对读者有所帮助,帮助他们构建智能 Android 移动应用并以有趣的方式扩展它们。 该项目的详细代码位于这里

在下一章中,我们将构建一个用于客户服务的对话式 AI 聊天机器人。 我们非常期待你的参与。

八、用于客户服务的会话式 AI 聊天机器人

会话式聊天机器人最近因其在增强客户体验方面的作用而大肆宣传。 现代企业已经开始在几个不同的过程中使用聊天机器人的功能。 由于对话式 AI 的广泛接受,填写表格或通过互联网发送信息的繁琐任务变得更加简化。 对话型聊天机器人的理想品质之一是,它应该能够在当前上下文中响应用户请求。 对话型聊天机器人系统中的参与者分别是用户和机器人。 使用对话型聊天机器人有很多优点,如下表所示:

  • 个性化帮助:为所有客户创建个性化体验可能是一项繁琐的任务,但如果不这样做,则会使企业蒙受损失。 对话型聊天机器人是向每个客户提供个性化体验的便捷替代方法。
  • 全天候支持:使用客户服务代表 24/7 的费用很高。 在非工作时间使用聊天机器人提供客户服务消除了雇用额外客户代表的麻烦。
  • 一致性响应:聊天机器人提供的响应可能是一致的,而不同客户服务代表对相同问题的响应可能会有所不同。 如果客户对客户服务代表提供的答案不满意,则无需多次拨打电话。
  • 耐心:虽然客户服务代表在与客户打交道时可能会失去耐心,但这对于聊天机器人来说是不可能的。
  • 查询记录:与人类客户服务代表相比,聊天机器人在查询记录方面效率更高。

聊天机器人不是最近才出现的东西,其起源可以追溯到 1950 年代。 在第二次世界大战之后,艾伦·图灵(Alan Turing)开发了图灵测试,以查看人是否可以将人与机器区分开。 多年后的 1966 年,约瑟夫·魏曾鲍姆(Joseph Weizenbaum)开发了一些名为 Eliza 的软件,该软件模仿了心理治疗师的语言。 该工具仍位于这个页面中。

聊天机器人可以执行各种各样的任务,下面的列表中显示了其中的一些任务,以强调其多函数式:

  • 回答有关产品的查询
  • 向客户提供建议
  • 进行句子补全活动
  • 会话聊天机器人
  • 与客户协商价格并参与投标

很多时候,企业很难确定是否需要聊天机器人。 企业是否需要聊天机器人可以通过“图 8.1”中的流程图确定:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iykLYptj-1681654125436)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/aeb9840d-65f5-4676-89a3-63a554915107.png)]

图 8.1:客户参与模型

作为本章的一部分,我们将涵盖以下主题:

  • 聊天机器人架构
  • 用于聊天机器人的 LSTM 序列到序列模型
  • 为 Twitter 支持聊天机器人构建序列到序列模型

技术要求

您将需要具备 Python 3,TensorFlow 和 Keras 的基础知识

可以在 GitHub 上找到本章的代码文件

观看以下视频,查看运行中的代码

聊天机器人架构

聊天机器人的核心组件是其自然语言处理框架。 聊天机器人使用自然语言处理通过通常称为解析的过程来处理呈现给他们的数据。 然后解析解析的用户输入,并根据用户的需求(从输入中解密),将适当的响应发送回用户。 聊天机器人可能需要从知识库和历史交易数据存储中寻求帮助,以适当地处理用户的请求。

聊天机器人可以大致分为以下两类:

  • 基于检索的模型:这些模型通常依赖于查找表或知识库来从预定义的答案集中选择答案。 尽管这种方法看起来很幼稚,但是生产中的大多数聊天机器人都是这种类型的。 当然,从查找表或知识库中选择最佳答案可能会有各种复杂程度。
  • 生成模型:生成模型可即时生成响应,而不是采用基于查找的方法。 它们主要是概率模型或基于机器学习的模型。 直到最近,马尔可夫链大多被用作生成模型。 然而,随着深度学习的最新成功,基于循环神经网络的方法越来越受欢迎。 通常,RSTM 的 LSTM 版本被用作聊天机器人的生成模型,因为它更擅长处理长序列。

基于检索的模型和生成模型都具有各自的优缺点。 由于基于检索的模型从一组固定的答案中进行回答,因此它们无法处理看不见的问题或没有适当预定义响应的请求。 生成模型要复杂得多。 他们可以了解用户输入中的实体并生成类似人的响应。 但是,它们很难训练,并且通常需要更多的数据来训练。 他们还容易犯语法错误,而基于检索的模型则不会犯这些语法错误。

使用 LSTM 的序列到序列模型

序列到序列模型架构非常适合捕获客户输入的上下文,然后基于该上下文生成适当的响应。 “图 8.2”显示了一个序列到序列模型框架,该框架可以像聊天机器人那样回答问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khpNr7EH-1681654125436)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a9129778-213f-4d7d-8f07-04e7c50b78e9.png)]

图 8.2:使用 LSTM 的序列到序列模型

从上图(“图 8.2”)中可以看出,编码器 LSTM 接受单词的输入序列,并将其编码为隐藏状态向量h和单元状态向量c。 向量hc是 LSTM 编码器最后一步的隐藏状态和单元状态。 它们本质上将捕获整个输入句子的上下文。

然后,以hc形式的编码信息作为其初始隐藏状态和单元状态被馈送到解码器 LSTM 。 每个步骤中的解码器 LSTM 尝试预测以当前单词为条件的下一个单词。 这意味着,解码器 LSTM 的每个步骤的输入都是当前字。

为了预测第一个单词,LSTM 将提供一个虚拟的起始关键字,它代表句子的开头。 同样, 虚拟关键字表示句子的结尾,并且一旦预测到该句,就应该停止输出生成。

在训练每个目标词的序列到序列模型的过程中,我们知道apriori是先前的词,这是解码器 LSTM 的输入。 但是,在推理过程中,我们将没有这些目标词,因此我们必须将上一步作为输入。

建立序列到序列模型

我们将用于构建聊天机器人的序列到序列模型的架构将对先前在“图 8.2”中说明的基本序列到序列架构进行一些修改。修改后的架构可以在下图中看到(“图 8.3”):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMtMJJ4E-1681654125436)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e3a27c9d-19a4-4b77-a737-ef6ae8dec474.png)]

图 8.3:序列到序列模型

与其将编码器最后一步的隐藏状态h和单元状态c馈送到解码器 LSTM 的初始隐藏状态和单元状态,我们将隐藏状态h馈入解码器的每个输入步骤。 为了预测目标词w[t]在任何步骤t中,输入是先前的目标词w[t-1]t-1和隐藏状态h

Twitter 上的客户支持

现在,我们对如何使用循环神经网络构建聊天机器人有了一些想法,我们将使用 20 个大品牌对客户发布的推文的客户服务响应来构建聊天机器人。 数据集twcs.zip位于这个页面中。每个推文均由tweet_id标识,并且推文内容位于text字段中。 客户发布的推文可以通过in_response_to_tweet_id字段进行标识。 这应该包含客户推文的空值。 对于客户服务推文,此in_response_to_tweet_id 字段应指向此推文所针对的客户tweet_id

创建用于训练聊天机器人的数据

要提取客户发布的所有入站推文,我们需要将具有in_response_to_tweet_id 字段的所有推文作为null提取。 如果in_response_to_tweet_id 字段不为空,则可以通过推文筛选出包含客户服务代表响应的出站文件。 有了入站和出站文件后,我们需要将它们合并到入站文件的tweet_id和出站文件的in_response_to_tweet_id 中。 作为回应,这将为我们提供客户发布的tweets in以及客户服务代表在回复中发布tweets out。 数据创建函数可以编码如下:

def process_data(self,path):
        data = pd.read_csv(path)
       if self.mode == 'train':
            data = pd.read_csv(path)
            data['in_response_to_tweet_id'].fillna(-12345,inplace=True)
            tweets_in =  data[data['in_response_to_tweet_id'] == -12345]
            tweets_in_out = 
            tweets_in.merge(data,left_on=['tweet_id'],right_on=
            ['in_response_to_tweet_id'])
            return tweets_in_out[:self.num_train_records]
        elif self.mode == 'inference':
            return data

将文本标记为单词索引

需要将这些推文标记化并转换为数字,然后才能将其发送到神经网络。 计数向量化器用于确定固定数量的常见单词,这些单词构成了聊天机器人的词汇空间。 我们还引入了三个新标记,分别表示句子的开头(START),句子的结尾(PAD)和任何未知的单词(UNK)。 标记推文的函数如下所示,以供参考:

def tokenize_text(self,in_text,out_text):
        count_vectorizer = CountVectorizer(tokenizer=casual_tokenize, max_features=self.max_vocab_size - 3)
        count_vectorizer.fit(in_text + out_text)
        self.analyzer = count_vectorizer.build_analyzer()
        self.vocabulary = 
        {key_: value_ + 3 for key_,value_ in count_vectorizer.vocabulary_.items()}
        self.vocabulary['UNK'] = self.UNK
        self.vocabulary['PAD'] = self.PAD
        self.vocabulary['START'] = self.START
        self.reverse_vocabulary = 
        {value_: key_ for key_, value_ in self.vocabulary.items()}
        joblib.dump(self.vocabulary,self.outpath + 'vocabulary.pkl')
        joblib.dump(self.reverse_vocabulary,self.outpath + 'reverse_vocabulary.pkl')
        joblib.dump(count_vectorizer,self.outpath + 'count_vectorizer.pkl')
        #pickle.dump(self.count_vectorizer,open(self.outpath + 
        'count_vectorizer.pkl',"wb"))

现在,需要将标记化的单词转换为单词索引,以便可以将它们馈送到循环神经网络,如以下代码所示:

def words_to_indices(self,sent):
        word_indices = 
       [self.vocabulary.get(token,self.UNK) for token in self.analyzer(sent)] + 
        [self.PAD]*self.max_seq_len
        word_indices = word_indices[:self.max_seq_len]
        return word_indices

我们还希望将循环神经网络预测的单词索引转换为单词以形成句子。 此函数可以编码如下:

def indices_to_words(self,indices):
        return ' '.join(self.reverse_vocabulary[id] for id in indices if id != self.PAD).strip()

替换匿名的屏幕名称

在对推文进行标记之前,可能值得将推文中的匿名屏幕名称替换为通用名称,以使响应更好地泛化。 此函数可以编码如下:

def replace_anonymized_names(self,data):
        def replace_name(match):
            cname = match.group(2).lower()
            if not cname.isnumeric():
                return match.group(1) + match.group(2)
            return '@__cname__'
        re_pattern = re.compile('(\W@|^@)([a-zA-Z0-9_]+)')
        if self.mode == 'train':
            in_text = data['text_x'].apply(lambda txt:re_pattern.sub(replace_name,txt))
            out_text = data['text_y'].apply(lambda 
            txt:re_pattern.sub(replace_name,txt))
            return list(in_text.values),list(out_text.values)
        else:
            return map(lambda x:re_pattern.sub(replace_name,x),data)

定义模型

RNN 的 LSTM 版本用于构建序列到序列模型。 这是因为 LSTM 在记住长文本序列中的长期依存关系方面效率更高。 LSTM 架构中的三个门使它能够有效地记住长期序列。 基本的 RNN 无法记住长期依赖关系,因为与其架构相关的梯度问题逐渐消失。

在此模型中,我们使用两个 LSTM。 第一个 LSTM 将输入推文编码为上下文向量。 该上下文向量不过是编码器 LSTM 的最后一个隐藏状态h ∈ R^nn是隐藏状态向量的维。 输入推文x ∈ R^k作为单词索引序列被馈送到编码器 LSTM,k就是输入推文的序列长度。 这些单词索引在馈送到 LSTM 之前已映射到单词嵌入w ∈ R^m。 单词嵌入包含在一个嵌入矩阵中[W ∈ R^(m x N)],其中N表示词汇表中单词的数量。

第二个 LSTM 用作解码器。 它试图将编码器 LSTM 创建的上下文向量h解码为有意义的响应。 作为此方法的一部分,我们在每个时间步中将上下文向量与前一个单词一起馈入以生成当前单词。 在第一步中,我们没有任何先前的词可用于条件 LSTM,因此我们使用智能体START词开始从解码器 LSTM 生成词序列的过程。 在推理过程中,我们在当前时间步输入前一个单词的方式与训练过程中使用的方法不同。 在训练中,由于我们在每个时间步都知道apriori之前的单词,因此相应地输入它们没有任何问题。 但是,在推理期间,由于我们在当前时间步上没有实际的前一个单词,因此会反馈前一个时间步上的预测单词。 每个时间步t的隐藏状态h'[t]在最终的最大 softmax N之前通过几个全连接层馈送。 在此 softmax 层中获得最大概率的单词是时间步长的预测单词。 然后将这个字输入到下一步的输入,即解码器 LSTM 的步骤t + 1

Keras 中的TimeDistributed函数允许在解码器 LSTM 的每个时间步长获得预测的有效实现,如以下代码所示:

def define_model(self):
        # Embedding Layer
        embedding = Embedding(
            output_dim=self.embedding_dim,
            input_dim=self.max_vocab_size,
            input_length=self.max_seq_len,
            name='embedding',
        )
        # Encoder input
        encoder_input = Input(
            shape=(self.max_seq_len,),
            dtype='int32',
            name='encoder_input',
        )
        embedded_input = embedding(encoder_input)
        encoder_rnn = LSTM(
            self.hidden_state_dim,
            name='encoder',
            dropout=self.dropout
        )
        # Context is repeated to the max sequence length so that the same context 
        # can be feed at each step of decoder
        context = RepeatVector(self.max_seq_len)(encoder_rnn(embedded_input))
        # Decoder    
        last_word_input = Input(
            shape=(self.max_seq_len,),
            dtype='int32',
            name='last_word_input',
        )
        embedded_last_word = embedding(last_word_input)
        # Combines the context produced by the encoder and the last word uttered as 
        inputs
        # to the decoder.
        decoder_input = concatenate([embedded_last_word, context],axis=2)
        # return_sequences causes LSTM to produce one output per timestep instead of 
        one at the
        # end of the intput, which is important for sequence producing models.
        decoder_rnn = LSTM(
            self.hidden_state_dim,
            name='decoder',
            return_sequences=True,
            dropout=self.dropout
        )
        decoder_output = decoder_rnn(decoder_input)
        # TimeDistributed allows the dense layer to be applied to each decoder output    
       per timestep
        next_word_dense = TimeDistributed(
            Dense(int(self.max_vocab_size/20),activation='relu'),
            name='next_word_dense',
        )(decoder_output)
        next_word = TimeDistributed(
            Dense(self.max_vocab_size,activation='softmax'),
            name='next_word_softmax'
        )(next_word_dense)
        return Model(inputs=[encoder_input,last_word_input], outputs=[next_word])

训练模型的损失函数

对模型进行分类交叉熵损失训练,以预测解码器 LSTM 的每个时间步中的目标单词。 任何步骤中的分类交叉熵损失都将遍及词汇表的所有单词,并且可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FxwknbbO-1681654125437)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/81b811e5-db95-4cf3-9d09-b9a0c7bff88e.png)]

标签[y[i]], i = 1 -> N代表目标单词的单热编码版本。 仅对应于实际单词的标签为1; 其余为0。 项Pi表示实际目标单词是由i索引的单词的概率。 为了获得每个输入/输出推特对的总损失C,我们需要对解码器 LSTM 的所有时间步长上的损失求和。 由于词汇量可能会变得很大,因此在每个时间步骤中为目标标签创建单热编码向量y[t] = [y[i]], i = 1 -> n会很昂贵。 sparse_categorical_crossentropy损失在这里变得非常有益,因为我们不需要将目标单词转换为单热编码向量,而只需输入目标单词的索引作为目标标签即可。

训练模型

该模型可以使用 Adam 优化器进行训练,因为它可靠地提供了稳定的收敛性。 由于 RNN 容易出现梯度问题(尽管这对于 LSTM 来说不是问题),因此,如果梯度太大,则最好将其剪裁。 可以使用 Adam 优化器和sparse_categorical_crossentropy定义和编译给定的模型,如以下代码块所示:

def create_model(self):
        _model_ = self.define_model()
        adam = Adam(lr=self.learning_rate,clipvalue=5.0)
        _model_.compile(optimizer=adam,loss='sparse_categorical_crossentropy')
        return _model_

现在我们已经研究了所有基本函数,可以将训练函数编码如下:

def train_model(self,model,X_train,X_test,y_train,y_test):
        input_y_train = self.include_start_token(y_train)
        print(input_y_train.shape)
        input_y_test = self.include_start_token(y_test)
        print(input_y_test.shape)
        early = EarlyStopping(monitor='val_loss',patience=10,mode='auto')
        checkpoint = 
        ModelCheckpoint(self.outpath + 's2s_model_' + str(self.version) + 
        '_.h5',monitor='val_loss',verbose=1,save_best_only=True,mode='auto')
        lr_reduce =
        ReduceLROnPlateau(monitor='val_loss',factor=0.5, patience=2, verbose=0, 
         mode='auto')
        model.fit([X_train,input_y_train],y_train, 
              epochs=self.epochs,
              batch_size=self.batch_size, 
              validation_data=[[X_test,input_y_test],y_test], 
              callbacks=[early,checkpoint,lr_reduce], 
              shuffle=True)
        return model

train_model函数的开头,我们创建input_y_traininput_y_test,它们分别是y_trainy_test的副本,并从它们移了一个时间步,以便它们可以用作解码器每个时间步的前一个单词的输入。 这些移位序列的第一个单词是START关键字,它在解码器 LSTM 的第一时间步输入。 include_start_token定制工具函数如下:

def include_start_token(self,Y):
    print(Y.shape)
    Y = Y.reshape((Y.shape[0],Y.shape[1]))
    Y = np.hstack((self.START*np.ones((Y.shape[0],1)),Y[:, :-1]))
    return Y

回到训练函数train_model,我们看到如果10周期的损失没有减少,可以使用EarlyStopping回调工具启用提前停止。 类似地,如果误差没有在两个周期内减少,则ReduceLROnPlateau回调会将现有学习率降低一半(0.5)。 只要误差在某个周期减少,就会通过ModelCheckpoint回调保存模型。

从模型生成输出响应

训练完模型后,我们要使用它来生成给定输入鸣叫的响应。 可以通过以下步骤完成此操作:

  1. 用通用名称替换输入推文中的匿名屏幕名称。
  2. 将修改后的输入推文转换为单词索引。
  3. 将单词索引输入到编码器 LSTM,将START关键字输入到解码器 LSTM,以生成第一个预测的单词。 从下一步开始,输入上一个时间步的预测单词,而不是START关键字。
  4. 继续执行此操作,直到预测到句子结尾关键字。 我们用PAD表示了这一点。
  5. 查看逆词汇词典,从预测的单词索引中获取单词。

以下代码中提供了respond_to_input函数,该函数可以在给定输入鸣叫的情况下完成生成输出序列的工作:

def respond_to_input(self,model,input_sent):
        input_y = self.include_start_token(self.PAD * np.ones((1,self.max_seq_len)))
        ids = np.array(self.words_to_indices(input_sent)).reshape((1,self.max_seq_len))
        for pos in range(self.max_seq_len -1):
            pred = model.predict([ids, input_y]).argmax(axis=2)[0]
            #pred = model.predict([ids, input_y])[0]
            input_y[:,pos + 1] = pred[pos]
        return self.indices_to_words(model.predict([ids,input_y]).argmax(axis=2)[0])

全部放在一起

综上所述,main函数可以定义为具有两个流程:一个用于训练,另一个用于推理。 即使在训练函数中,我们也会对输入的推文序列生成一些响应,以检查我们对模型的训练程度。 以下代码显示main函数供参考:

def main(self):
        if self.mode == 'train':
            X_train, X_test, y_train, y_test,test_sentences = self.data_creation()
            print(X_train.shape,y_train.shape,X_test.shape,y_test.shape)
            print('Data Creation completed')
            model = self.create_model()
            print("Model creation completed")
            model = self.train_model(model,X_train,X_test,y_train,y_test)
            test_responses = self.generate_response(model,test_sentences)
            print(test_sentences)  
            print(test_responses)
            pd.DataFrame(test_responses).to_csv(self.outpath + 
            'output_response.csv',index=False)
        elif self.mode == 'inference':
            model = load_model(self.load_model_from)
            self.vocabulary = joblib.load(self.vocabulary_path)
            self.reverse_vocabulary = joblib.load(self.reverse_vocabulary_path)
            #nalyzer_file = open(self.analyzer_path,"rb")
            count_vectorizer = joblib.load(self.count_vectorizer_path)
            self.analyzer = count_vectorizer.build_analyzer()
            data = self.process_data(self.data_path)
            col = data.columns.tolist()[0]
            test_sentences = list(data[col].values)
            test_sentences = self.replace_anonymized_names(test_sentences)
            responses = self.generate_response(model,test_sentences)
            print(responses)
            responses.to_csv(self.outpath + 'responses_' + str(self.version) + 
            '_.csv',index=False)

调用训练

可以通过运行带有多个参数的chatbot.py(请参见 GitHub 中此项目的代码)模块来调用训练,如以下命令所示:

python chatbot.py --max_vocab_size 50000 --max_seq_len 30 --embedding_dim 100 --hidden_state_dim 100 --epochs 80 --batch_size 128 --learning_rate 1e-4 --data_path /home/santanu/chatbot/data/twcs.csv --outpath /home/santanu/chatbot/ --dropout 0.3 --mode train --num_train_records 50000 --version v1

以下是一些重要的论据,以及它们的描述和用于调用聊天机器人序列到序列模型的训练的使用值:

参数 说明 用于训练的值
max_vocab_size 词汇中的单词数 50,000
max_seq_len 应被限制到 LSTM 的推文的最大长度 30
hidden_state_dim LSTM 的隐藏状态h的大小 100
embedding_dim 词嵌入的维度 100
learning rate 用于优化程序的起始学习率 0.0001
dropout 用于正则化目的的丢弃 0.3
Mode 训练/推理 对于训练,请使用train;对于推理,请使用inference

对某些输入推文的推断结果

训练后的序列到序列模型响应于某些输入推文生成了一些相关的推文。 以下命令显示了示例推断命令。 data_path参数包含测试输入推文的位置:

python chatbot.py --max_vocab_size 50000 --max_seq_len 30 --embedding_dim 100 --hidden_state_dim 100  --data_path /home/santanu/chatbot/data/test.csv --outpath /home/santanu/chatbot/ --dropout 0.3 --mode inference --version v1 --load_model_from /home/santanu/chatbot/s2s_model_v1_.h5  --vocabulary_path /home/santanu/chatbot/vocabulary.pkl --reverse_vocabulary_path /home/santanu/chatbot/reverse_vocabulary.pkl --count_vectorizer_path /home/santanu/chatbot/count_vectorizer.pkl

推断结果如下表所示:

Tweet in Tweet out
@sprintcare is the worst customer service. @__cname__ Hey there! Can you dm us your account’s email address? We’ll take a look backstage / dn https://t.co/ldfdzrinat.
@__cname__ Y’all lie about your great connection. 5 bars LTE, still won’t load something. Smh. @__cname__ We apologize for the trouble. Please send us a dm with your email address so we can connect.
@__cname__ Whenever I contact customer support, they tell me I have shortcode enabled on my account, but I have never in the 4 years I’ve tried https://t.co/0G98RtNxPK. @__cname__ Hi there, we are sorry to hear this. Please send us a dm with your email address and phone number so we can look into this for.
Actually that’s a broken link you sent me and incorrect information https://t.co/V4yfrHR8VI. @__cname__ Hi, we are sorry to hear this. Please dm us your contact info and we’ll be happy to help. ^ kk
Yo @Ask_Spectrum, your customer service reps are super nice— but imma start trippin if y’all don’t get my service going! @__cname__ We apologize for the trouble. Please send us a dm with your email address so we can connect.
My picture on @Ask_Spectrum pretty much every day. Why should I pay $171 per month? https://t.co/U6ptkQa5Ik. @__cname__ Hi, we are sorry to hear this. Please dm us your contact info and we’ll be happy to help. ^ kk
Somebody from @VerizonSupport please help me �������� I’m having the worst luck with your customer service. @__cname__ We apologize for the trouble. Please dm us your contact info and phone number so we can assist.
@VerizonSupport My friend is without internet we need to play video games together please our skills diminish every moment without internetz. @__cname__ Hey there! Can you dm us your account’s email address? we’ll take a look backstage / dn https://t.co/ldfdzrinat
@__cname__ tried to pay a bill for 60 days. No service, rude CS, and several transfers. Look up my equipment # and give me an acct #! @__cname__ Hi, we are sorry to hear this. Please dm us your contact info and we’ll be happy to help. ^ kk
@__cname__ is the worst ISP I’ve ever had. @__cname__ Hey there! Can you dm us your account’s email address? We’ll take a look backstage / dn https://t.co/ldfdzrinat
@__cname__ fix your app it won’t even open. @__cname__ Hi there! We don’t have any info on this issue. Please send us a dm with your name and phone number and the full service address.

总结

现在,我们到本章的结尾。 看完本章中说明的与聊天机器人和序列到序列模型相关的各种概念之后,读者现在应该能够构建自己的聊天机器人实现并以有趣的方式对其进行扩展。 众所周知,序列到序列模型不仅适用于聊天机器人,还适用于整个自然语言处理领域,例如机器翻译。 本章的代码位于 GitHub

在下一章中,我们将使用强化学习来使赛车学习如何独立驾驶。 我们非常期待你的参与。

九、使用强化学习的自主无人驾驶汽车

在过去的几年中,增强学习已经真正兴起,在增强学习中,智能体通过与环境的交互来学习决策。 这是当今人工智能和机器学习中最热门的主题之一,并且这一领域的研究正在快速发展。 在强化学习RL)中,智能体将他们的行动和经验转化为学习,以便将来做出更好的决策。

增强学习不属于有监督或无监督的机器学习范式,因为它本身就是一个领域。 在监督学习中,我们尝试学习一个映射F: X → Y,将输入X映射到输出Y,而在强化学习中,智能体学习通过反复试验采取最佳行动。 当业务代表执行任务出色时,将分配奖励,而当业务代表执行不好时,则要支付罚款。 智能体试图吸收这些信息,并学会在类似的情况下不重复这些错误。 智能体所处的这些条件称为状态。 “图 9.1”显示了强化学习框架中环境中智能体的交互作用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Coxp8g0-1681654125437)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9d2d1715-a781-4b00-9312-09d3cd27cc1b.png)]

图 9.1:智能体与环境交互的图示

技术要求

您将需要具备 Python 3,TensorFlow,Keras 和 OpenCV 的基础知识。

可以在 GitHub 上找到本章的代码文件

观看以下视频,查看运行中的代码

马尔可夫决策过程

任何强化学习问题都可以看作是马尔可夫决策过程,我们在第 1 章“基于人工智能的系统基础”中进行了简要介绍。 为了您的利益,我们将再次详细研究。 在马尔可夫决策过程中,我们有一个与环境交互的主体。 在任何给定的情况下,t智能体处于多种状态之一:s[t] = s ∈ S。 根据主体的动作a[t] = a ∈ A处于状态s[t]具有新状态s[t + 1] = s' ∈ S。 在这里,S表示智能体可能会暴露的所有状态,而A则表示智能体可以参与的可能动作。

您现在可能想知道智能体如何采取行动。 应该是随机的还是基于启发式的? 好吧,这取决于智能体与相关环境的交互程度。 在初始阶段,智能体可能会采取随机行动,因为他们不了解环境。 但是,一旦智能体与环境进行了足够的交互(基于奖励和惩罚),智能体就会了解在给定状态下采取哪种适当的措施。 类似于人们倾向于采取有益于长期奖励的行动一样,RL 智能体也采取行动,最大限度地提高了长期奖励。

数学上,智能体尝试为每个状态动作对s ∈ S, a ∈ A学习 Q 值Q(s, a)。 对于给定状态s[t],RL 智能体选择动作a,该动作给出最大 Q 值。 智能体采取的动作a[t]可以表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmxOd8kX-1681654125437)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/cd87a708-3a74-4d2e-9d4c-4327e5649d5c.png)]

一旦智能体在状态s[t]采取行动a[t],新状态s[t + 1]会呈现给智能体来处理。 这个新状态s[t + 1]通常不是确定性的,通常表示为当前状态s[t]和动作a[t]的条件概率分布。这些概率称为状态转移概率,可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EHzuGDhh-1681654125437)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3ff176e3-0511-454b-b3d2-1cae8974d94b.png)]

每当智能体在状态s[t]采取行动a[t]并达到新状态s[t + 1]时,即时奖励会奖励给智能体,可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MEu2yqq-1681654125438)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/fd483030-7af9-4f3f-90ca-321cbcf08493.png)]

现在,我们拥有定义马尔可夫决策过程所需的一切。 马尔可夫决策过程是一个系统,其特征在于以下四个特征:

  • 一组状态S
  • 一组动作A
  • 一组奖励R
  • 状态转移概率P(s[t + 1] = s' | s[t] = s, a[t] = a)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9dNuGgxl-1681654125438)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/069318ed-1b83-4093-8484-4209e97c18f5.png)]

图 9.2:具有三个状态的马尔可夫决策过程的图示

学习 Q 值函数

对于 RL 智能体做出决定,重要的是智能体学习 Q 值函数。 可以通过贝尔曼方程迭代地学习 Q 值函数。 当智能体开始与环境交互时,它以随机状态s[0]和每个状态动作对的 Q 值的随机状态开始。 智能体的动作在某种程度上也是随机的,因为它没有状态 Q 值来做出明智的决策。 对于每个采取的行动,环境将根据哪个智能体开始建立 Q 值表并随着时间的推移而改善而返回奖励。

在任何暴露状态s[t]处于迭代状态t时,智能体会采取行动a[t],以最大化其长期回报。 Q 表保存长期奖励值,因此选择的a[t]将基于以下启发式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1TMcEmJ-1681654125438)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/aaa2e551-be26-48e5-92f2-f4c6eb370dbc.png)]

Q 值表也通过迭代t进行索引,因为智能体只能查看到目前为止的 Q 表构建,随着智能体与环境的交互作用会越来越大。

根据动作a[t],环境呈现给智能体奖励r[t]和新状态s[t + 1]。 智能体将更新 Q 表,以使其长期预期总收益最大化。 长期奖励r'[t]可以写成如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9s4FKw9d-1681654125438)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/823779f3-1c49-4d21-85ad-8bdc5c069e63.png)]

在这里,γ是折扣因子。 如我们所见,长期奖励结合了即时奖励r[t]和基于所展示的下一状态s[t + 1]的累积未来奖励。

根据计算出的长期奖励,状态动作对(s[t], a[t])的现有 Q 值更新如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIZYQ4gm-1681654125438)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c1ed9edd-cd0e-46df-9288-f79abf7ed4e5.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aiMBXRgH-1681654125439)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/58c4371f-9b27-4fba-bc1e-8f9d5203dec6.png)]

Python 智能项目:6~10(4)https://developer.aliyun.com/article/1426938

相关文章
|
12天前
|
机器学习/深度学习 数据采集 供应链
Python实现深度学习模型:智能库存管理系统
【10月更文挑战第5天】 Python实现深度学习模型:智能库存管理系统
62 9
|
8天前
|
机器学习/深度学习 数据采集 数据可视化
Python 数据分析:从零开始构建你的数据科学项目
【10月更文挑战第9天】Python 数据分析:从零开始构建你的数据科学项目
22 2
|
11天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能数据隐私保护
使用Python实现深度学习模型:智能数据隐私保护 【10月更文挑战第3天】
42 0
|
10天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能质量检测与控制
使用Python实现深度学习模型:智能质量检测与控制 【10月更文挑战第8天】
103 62
使用Python实现深度学习模型:智能质量检测与控制
|
3天前
|
机器学习/深度学习 传感器 存储
使用 Python 实现智能地震预警系统
使用 Python 实现智能地震预警系统
88 61
|
7天前
|
机器学习/深度学习 TensorFlow 调度
使用Python实现深度学习模型:智能能源消耗预测与管理
使用Python实现深度学习模型:智能能源消耗预测与管理
78 30
|
1天前
|
机器学习/深度学习 数据采集 消息中间件
使用Python实现智能火山活动监测模型
使用Python实现智能火山活动监测模型
12 1
|
3天前
|
JSON 搜索推荐 API
Python的web框架有哪些?小项目比较推荐哪个?
【10月更文挑战第15天】Python的web框架有哪些?小项目比较推荐哪个?
12 1
|
5天前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型:智能天气预测与气候分析
使用Python实现深度学习模型:智能天气预测与气候分析
75 3
|
4天前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型:智能海洋监测与保护
使用Python实现深度学习模型:智能海洋监测与保护
25 1