30天吃掉tensorflow2 1_3学习笔记

1-3,文本数据建模流程范例

 

一,准备数据

imdb数据集的目标是根据电影评论的文本内容预测评论的情感标签。

训练集有20000条电影评论文本,测试集有5000条电影评论文本,其中正面评论和负面评论都各占一半。

文本数据预处理较为繁琐,包括中文切词(本示例不涉及),构建词典,编码转换,序列填充,构建数据管道等等。

在tensorflow中完成文本数据预处理的常用方案有两种,第一种是利用tf.keras.preprocessing中的Tokenizer词典构建工具和tf.keras.utils.Sequence构建文本数据生成器管道。

第二种是使用tf.data.Dataset搭配.keras.layers.experimental.preprocessing.TextVectorization预处理层。

第一种方法较为复杂,其使用范例可以参考以下文章。

https://zhuanlan.zhihu.com/p/67697840

第二种方法为TensorFlow原生方式,相对也更加简单一些。

教学项目里介绍的是第二种方法。

代码:

import numpy as np

import pandas as pd

from matplotlib import pyplot as plt

import tensorflow as tf

from tensorflow.keras import models,layers,preprocessing,optimizers,losses,metrics

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization

import re,string



train_data_path = "./data/imdb/train.csv"

test_data_path =  "./data/imdb/test.csv"



MAX_WORDS = 10000  # 仅考虑最高频的10000个词

MAX_LEN = 200  # 每个样本保留200个词的长度

BATCH_SIZE = 20





#构建管道

def split_line(line):

    arr = tf.strings.split(line,"\t")

    label = tf.expand_dims(tf.cast(tf.strings.to_number(arr[0]),tf.int32),axis = 0)

    text = tf.expand_dims(arr[1],axis = 0)

    return (text,label)



ds_train_raw =  tf.data.TextLineDataset(filenames = [train_data_path]) \

   .map(split_line,num_parallel_calls = tf.data.experimental.AUTOTUNE) \

   .shuffle(buffer_size = 1000).batch(BATCH_SIZE) \

   .prefetch(tf.data.experimental.AUTOTUNE)



ds_test_raw = tf.data.TextLineDataset(filenames = [test_data_path]) \

   .map(split_line,num_parallel_calls = tf.data.experimental.AUTOTUNE) \

   .batch(BATCH_SIZE) \

   .prefetch(tf.data.experimental.AUTOTUNE)





#构建词典

def clean_text(text):

    lowercase = tf.strings.lower(text)

    stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')

    cleaned_punctuation = tf.strings.regex_replace(stripped_html,

         '[%s]' % re.escape(string.punctuation),'')

    return cleaned_punctuation



vectorize_layer = TextVectorization(

    standardize=clean_text,

    split = 'whitespace',

    max_tokens=MAX_WORDS-1, #有一个留给占位符

    output_mode='int',

    output_sequence_length=MAX_LEN)



ds_text = ds_train_raw.map(lambda text,label: text)

vectorize_layer.adapt(ds_text)

print(vectorize_layer.get_vocabulary()[0:100])
#单词编码

ds_train = ds_train_raw.map(lambda text,label:(vectorize_layer(text),label)) \

    .prefetch(tf.data.experimental.AUTOTUNE)

ds_test = ds_test_raw.map(lambda text,label:(vectorize_layer(text),label)) \

.prefetch(tf.data.experimental.AUTOTUNE)

 

tf.strings.split这个方法,效果和普通的split差不多,只不过在普通split方法的基础上添加了shape和dtype

测试代码:

line="123,\t456\t789"

arr = tf.strings.split(line,"\t")

print(arr)

结果:

这里\t是tab的转义字符,为了把文本标签和内容区分开来,实际在python里复制粘贴显示的时候是空格,但直接用空格替代会报错。说明转义字符还是复制过来了,只不过显示和空格一样

观察样本可以发现,对于正面评价的样本,第一个字符是1,对于其余样本则是0

 

tf.expand_dims则是对张量进行维度扩张,axis是指定插入的维度,在这里使用是为了将数据从[a,b,c]的标量形式变成[1,a][2,b][3,c]的张量

测试代码

line="1       The first one meant victory. This one means defeat. It takes place in a Bolivia"

arr = tf.strings.split(line,"\t")

label = tf.expand_dims(tf.cast(tf.strings.to_number(arr[0]), tf.int32), axis=0)

text = tf.expand_dims(arr[1], axis=0)

print("arr",arr)

print("label",label)

print("text",text)

结果:

之后就是用tf.data.TextLineDataset()结合map方法和之前的处理函数读取数据。

关于textlinedataset的参考:https://blog.csdn.net/weixin_45342712/article/details/95480530

 

接下来是构建词典这部分的代码

def clean_text(text):

    lowercase = tf.strings.lower(text)

    stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')

    cleaned_punctuation = tf.strings.regex_replace(stripped_html,

         '[%s]' % re.escape(string.punctuation),'')

    return cleaned_punctuation

lowercase是将文本里的大写字母转化成小写

tf.strings.regex_replace是为了将html里的空标签转化成空格

tf.strings.regex_replace(stripped_html,'[%s]' % re.escape(string.punctuation),'')这一段是为了去除所有的标点符号

特殊文本去除方法参考:

https://blog.csdn.net/m0_38102647/article/details/83657792

 

[%s]是正则表达式,其中%表示在所有行中进行处理

s表示查找、替换功能,格式是:s/查找的内容/替换的内容/选项

参考:https://zhidao.baidu.com/question/252394378.html

re.escape是对字符串中所有可能被解释为正则运算符的字符进行转义re.escape(string.punctuation)里存放的是所有的标点符号

   接下来是文本向量化这个部分,构建向量化层(用于做文本向量化处理)

vectorize_layer = TextVectorization(

    standardize=clean_text,

    split = 'whitespace',

    max_tokens=MAX_WORDS-1, #有一个留给占位符

    output_mode='int',

    output_sequence_length=MAX_LEN)

   实验代码:

ds_text = ds_train_raw.map(lambda text,label: text)

for each in ds_text:

print(each)

结果:

ds_text = ds_train_raw.map(lambda text,label: text)这里之所以要这么写,是因为ds_train_raw里不仅包含了label还包含了text,但是这里只需要text就够了,因此通过匿名函数的方式将返回的text和label只取text。这里的text和label是之前ds_train_raw调用之前的splitline函数所返回的结果

 

实际上如果展开来写就是这样

for each in ds_train_raw:

    text,label=each

    print(text)

 

将文本向量化,其实就是转化为数字,这样才能放进神经网络里运算

实验代码:

 ds_train = ds_train_raw.map(lambda text,label:(vectorize_layer(text),label)) \

    .prefetch(tf.data.experimental.AUTOTUNE)

ds_test = ds_test_raw.map(lambda text,label:(vectorize_layer(text),label)) \

    .prefetch(tf.data.experimental.AUTOTUNE)

for each in ds_train:

    text,label=each

    print(text)

结果:

二,定义模型

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

教学项目里选择使用继承Model基类构建自定义模型。

# 演示自定义模型范例,实际上应该优先使用Sequential或者函数式API

tf.keras.backend.clear_session()



class CnnModel(models.Model):

    def __init__(self):

        super(CnnModel, self).__init__()

       

    def build(self,input_shape):

        self.embedding = layers.Embedding(MAX_WORDS,7,input_length=MAX_LEN)

        self.conv_1 = layers.Conv1D(16, kernel_size= 5,name = "conv_1",activation = "relu")

        self.pool_1 = layers.MaxPool1D(name = "pool_1")

        self.conv_2 = layers.Conv1D(128, kernel_size=2,name = "conv_2",activation = "relu")

        self.pool_2 = layers.MaxPool1D(name = "pool_2")

        self.flatten = layers.Flatten()

        self.dense = layers.Dense(1,activation = "sigmoid")

        super(CnnModel,self).build(input_shape)

   

    def call(self, x):

        x = self.embedding(x)

        x = self.conv_1(x)

        x = self.pool_1(x)

        x = self.conv_2(x)

        x = self.pool_2(x)

        x = self.flatten(x)

        x = self.dense(x)

        return(x)

   

    # 用于显示Output Shape

    def summary(self):

        x_input = layers.Input(shape = MAX_LEN)

        output = self.call(x_input)

        model = tf.keras.Model(inputs = x_input,outputs = output)

        model.summary()

   

model = CnnModel()

model.build(input_shape =(None,MAX_LEN))

model.summary()

这种定义模型方法是最复杂的一种,但是自由度高

首先是出现了Eembedding层,embedding存在的意义是降维

https://www.cnblogs.com/zf-blog/p/10929077.html

之后就是卷积+池化+卷积+池化+一维化输出

三,训练模型

训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。此处是通过自定义训练循环训练模型

#打印时间分割线

@tf.function

def printbar():

    today_ts = tf.timestamp()%(24*60*60)

   

    hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24)

    minite = tf.cast((today_ts%3600)//60,tf.int32)

    second = tf.cast(tf.floor(today_ts%60),tf.int32)

   

    def timeformat(m):

        if tf.strings.length(tf.strings.format("{}",m))==1:

            return(tf.strings.format("0{}",m))

        else:

            return(tf.strings.format("{}",m))

   

    timestring = tf.strings.join([timeformat(hour),timeformat(minite),

                timeformat(second)],separator = ":")

tf.print("=========="*8+timestring)



optimizer = optimizers.Nadam()

loss_func = losses.BinaryCrossentropy()



train_loss = metrics.Mean(name='train_loss')

train_metric = metrics.BinaryAccuracy(name='train_accuracy')



valid_loss = metrics.Mean(name='valid_loss')

valid_metric = metrics.BinaryAccuracy(name='valid_accuracy')





@tf.function

def train_step(model, features, labels):

    with tf.GradientTape() as tape:

        predictions = model(features,training = True)

        loss = loss_func(labels, predictions)

    gradients = tape.gradient(loss, model.trainable_variables)

    optimizer.apply_gradients(zip(gradients, model.trainable_variables))



    train_loss.update_state(loss)

    train_metric.update_state(labels, predictions)

   



@tf.function

def valid_step(model, features, labels):

    predictions = model(features,training = False)

    batch_loss = loss_func(labels, predictions)

    valid_loss.update_state(batch_loss)

    valid_metric.update_state(labels, predictions)





def train_model(model,ds_train,ds_valid,epochs):

    for epoch in tf.range(1,epochs+1):

       

        for features, labels in ds_train:

            train_step(model,features,labels)



        for features, labels in ds_valid:

            valid_step(model,features,labels)

       

        #此处logs模板需要根据metric具体情况修改

        logs = 'Epoch={},Loss:{},Accuracy:{},Valid Loss:{},Valid Accuracy:{}'

       

        if epoch%1==0:

            printbar()

            tf.print(tf.strings.format(logs,

            (epoch,train_loss.result(),train_metric.result(),valid_loss.result(),valid_metric.result())))

            tf.print("")

       

        train_loss.reset_states()

        valid_loss.reset_states()

        train_metric.reset_states()

        valid_metric.reset_states()



train_model(model,ds_train,ds_test,epochs = 6)

其中with tf.GradientTape() as tape是自动求导的api,

参考:https://blog.csdn.net/forrest97/article/details/105913952

optimizer.apply_gradients部分其实就是根据梯度对神经网络里的参数进行变动,尝试让loss不断变小

参考:https://www.cnblogs.com/marsggbo/p/10056057.html

valid_step部分则是对当前的网络进行评估以更新loss

四,评估模型

通过自定义训练循环训练的模型没有经过编译,无法直接使用model.evaluate(ds_valid)方法


 

def evaluate_model(model,ds_valid):

    for features, labels in ds_valid:

         valid_step(model,features,labels)

    logs = 'Valid Loss:{},Valid Accuracy:{}'

    tf.print(tf.strings.format(logs,(valid_loss.result(),valid_metric.result())))

   

    valid_loss.reset_states()

    train_metric.reset_states()

    valid_metric.reset_states()

之后的保存模型和使用和之前几乎一样

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值