第十一课:TensorFlow文本处理实战——让机器”读懂”人话

2519818a3ad9efd1410175ea1cfc2737

 

 

开场白:从图像到文字,换个战场

上节课咱们把图片处理得明明白白,有同学在群里问:”老师,我想做个情感分析模型,文本怎么喂给TensorFlow?直接把句子传进去吗?”

哈,要是能这么简单就好了。机器只认数字,你给它一句”今天天气真好”,它完全不知道该咋办。今天这节课,就讲讲怎么把文字变成机器能理解的数字。

第一部分:为什么文本处理这么麻烦

文字和图片的本质区别

图片好处理,像素值本来就是数字,RGB三个通道,直接能喂进模型。但文字不一样:

  • 离散性:每个字、每个词都是独立的符号
  • 顺序性:词的顺序决定意思,”我打他”和”他打我”完全不同
  • 语义性:同一个词在不同上下文里意思不同
  • 噪声多:标点、表情符号、错别字、网络用语

举个例子,这三句话:

1. "这个手机真不错!"
2. "这个手机真不错?"
3. "这个手机真不 错!"

人一眼就能看出情感不同,但对机器来说,这都是字符的组合,得教它怎么区分。

文本预处理要解决的问题

原始文本数据就像刚从田里挖出来的土豆,得洗干净、削皮、切块,才能下锅。文本预处理也是这个道理:

常见的”脏数据”

  • 大小写混乱:”TensorFlow”、”tensorflow”、”TENSORFLOW”
  • 标点符号:”你好!”、”你好。”、”你好…”
  • 停用词:”的”、”了”、”是”这些高频但没啥信息量的词
  • 特殊字符:emoji、HTML标签、URL链接
  • 格式问题:多余的空格、换行符

我之前做过一个微博情感分析项目,原始数据里emoji就有上百种,还有各种火星文、缩写。不处理的话,模型学出来就是一团乱麻。

第二部分:TensorFlow文本处理的工具箱

核心模块一览

TensorFlow给咱们准备了几套工具:

import tensorflow as tf

# 1. 基础字符串操作
tf.strings.lower()           # 转小写
tf.strings.upper()           # 转大写
tf.strings.split()           # 分词
tf.strings.length()          # 字符串长度
tf.strings.regex_replace()   # 正则替换

# 2. 文本向量化(重点!)
from tensorflow.keras.layers import TextVectorization

# 3. 数据集操作
tf.data.TextLineDataset      # 从文件读取文本

# 4. 高级处理(需要额外安装)
import tensorflow_text as tf_text

实战:基础字符串操作

先从简单的开始,看看怎么处理字符串:

# 创建一些测试数据
sentences = tf.constant([
    "TensorFlow 真好用!",
    "我爱深度学习",
    "NLP 自然语言处理"
])

# 1. 转小写(英文部分)
lower_sentences = tf.strings.lower(sentences)
print(lower_sentences)
# 输出: ['tensorflow 真好用!', '我爱深度学习', 'nlp 自然语言处理']

# 2. 按空格分词
words = tf.strings.split(sentences)
print(words)
# 输出: [['TensorFlow', '真好用!'], ['我爱深度学习'], ['NLP', '自然语言处理']]

# 3. 计算长度(字符数)
lengths = tf.strings.length(sentences)
print(lengths)
# 输出: [16, 6, 12] (包含标点和空格)

注意个坑tf.strings.split()默认按空格分,中文没有空格怎么办?这就需要专门的中文分词工具,后面会讲。

正则表达式清洗文本

处理复杂文本,正则表达式是利器:

import re
import string

def clean_text(text):
    """
    清洗文本的通用函数
    """
    # 1. 转小写
    text = tf.strings.lower(text)
    
    # 2. 移除标点符号
    # string.punctuation 是所有英文标点的集合
    pattern = '[%s]' % re.escape(string.punctuation)
    text = tf.strings.regex_replace(text, pattern, ' ')
    
    # 3. 移除多余空格(把连续空格替换成单个空格)
    text = tf.strings.regex_replace(text, '\s+', ' ')
    
    # 4. 去掉首尾空格
    text = tf.strings.strip(text)
    
    return text

# 测试一下
dirty_text = tf.constant([
    "  Hello,  World!!!  ",
    "TensorFlow   is    AWESOME!!!",
])
clean = clean_text(dirty_text)
print(clean)
# 输出: ['hello world', 'tensorflow is awesome']

这个函数在实际项目里很实用,我一般会根据具体任务微调——比如做情感分析时,感叹号可能是有用的信息,就不能全删掉。

第三部分:文本向量化——核心中的核心

什么是向量化

简单说,就是把文字转成数字。有几种常见方式:

1. 词袋模型(Bag of Words) 把每个词映射成一个数字,句子就是一串数字序列。

句子:"我 爱 机器 学习"
词汇表:{"我":1, "爱":2, "机器":3, "学习":4}
向量化:[1, 2, 3, 4]

2. One-Hot编码 每个词是一个向量,只有对应位置是1,其他都是0。

词汇表大小:10000
"机器" → [0, 0, 0, ..., 1, ..., 0](第3位是1)

缺点:太稀疏了,10000维的向量里只有一个1。

3. 词嵌入(Embedding) 把每个词映射到一个低维的稠密向量,比如100维。

"机器" → [0.23, -0.15, 0.88, ..., 0.42](100个数)

这是目前主流方法,后面会详细讲。

TextVectorization——TensorFlow的瑞士军刀

这个层能一键完成大部分向量化工作:

from tensorflow.keras.layers import TextVectorization

# 创建向量化层
vectorizer = TextVectorization(
    max_tokens=10000,              # 词汇表大小(最多记住10000个词)
    output_mode='int',             # 输出整数索引
    output_sequence_length=50      # 统一序列长度(短的补零,长的截断)
)

# 准备训练数据
text_data = [
    "我爱深度学习",
    "TensorFlow 真好用",
    "自然语言处理很有趣",
    "机器学习改变世界",
]

# 构建词汇表(这一步叫 adapt)
vectorizer.adapt(text_data)

# 向量化一个句子试试
test_sentence = "我爱TensorFlow"
vectorized = vectorizer(test_sentence)
print(vectorized)
# 输出类似:[5, 12, 3, 0, 0, 0, ..., 0](后面补零到50位)

几个关键参数的含义

  1. max_tokens:词汇表容量
    • 太小:很多词会变成”未知词”
    • 太大:模型参数多,训练慢
    • 经验值:英文5000-20000,中文10000-50000
  2. output_mode:输出格式
    • 'int':输出词的索引,最常用
    • 'binary':多热编码(词出现就是1)
    • 'count':词频统计
    • 'tf-idf':加权词频
  3. output_sequence_length:序列长度
    • 看你的数据,大部分句子有多长
    • 短了会截断信息,长了浪费计算

查看构建的词汇表

# 获取词汇表
vocab = vectorizer.get_vocabulary()
print("词汇表大小:", len(vocab))
print("前20个词:", vocab[:20])

# 输出类似:
# 词汇表大小: 15
# 前20个词: ['', '[UNK]', '学习', 'tensorflow', '我爱', ...]

注意:

  • 第0位永远是空字符串(用来padding)
  • 第1位是[UNK](unknown,表示未知词)
  • 后面按词频从高到低排列

不同output_mode的区别

来个对比实验:

# 准备数据
texts = ["机器学习", "深度学习", "机器学习很有趣"]

# 模式1:int(索引)
vec_int = TextVectorization(max_tokens=10, output_mode='int', output_sequence_length=5)
vec_int.adapt(texts)
print("int模式:", vec_int("机器学习"))
# 输出:[2, 3, 0, 0, 0]

# 模式2:binary(多热)
vec_binary = TextVectorization(max_tokens=10, output_mode='binary', output_sequence_length=5)
vec_binary.adapt(texts)
print("binary模式:", vec_binary("机器学习"))
# 输出:[0, 0, 1, 1, 0, 0, 0, 0, 0, 0](位置2和3是1)

# 模式3:count(词频)
vec_count = TextVectorization(max_tokens=10, output_mode='count', output_sequence_length=5)
vec_count.adapt(texts)
print("count模式:", vec_count("机器学习机器"))
# 输出:[0, 0, 2, 1, 0, 0, 0, 0, 0, 0]("机器"出现2次)

实际用哪个?

  • 做Embedding + LSTM/Transformer → 用'int'
  • 简单分类任务 → 用'binary''tf-idf'
  • 传统机器学习(如朴素贝叶斯)→ 用'count'

第四部分:处理中文的特殊问题

中文分词的坑

英文有天然的空格分隔,中文没有。”我爱机器学习”该怎么分?

错误分法:"我 爱 机 器 学 习"(按字符分,丢失词义)
正确分法:"我 爱 机器学习"("机器学习"是一个词)

TensorFlow自带的工具对中文支持不好,得借助专业分词工具。

方案一:jieba分词(推荐)

import jieba

def chinese_tokenize(text):
    """
    使用jieba进行中文分词
    """
    # jieba.cut返回生成器,转成列表
    words = list(jieba.cut(text))
    # 用空格连接,变成类似英文的格式
    return ' '.join(words)

# 测试
text = "我爱自然语言处理技术"
tokenized = chinese_tokenize(text)
print(tokenized)
# 输出:"我 爱 自然语言 处理 技术"

# 然后可以用TextVectorization处理
corpus = [
    "我爱自然语言处理",
    "深度学习很有趣",
    "TensorFlow真好用"
]
corpus_tokenized = [chinese_tokenize(text) for text in corpus]

vectorizer = TextVectorization(max_tokens=1000)
vectorizer.adapt(corpus_tokenized)

方案二:字符级建模(简单粗暴)

不分词,直接把每个字当作一个token:

vectorizer = TextVectorization(
    max_tokens=5000,  # 常用汉字约3500个
    output_mode='int',
    split='character'  # 按字符分割(这是关键)
)

corpus = ["我爱学习", "深度学习"]
vectorizer.adapt(corpus)
print(vectorizer("我爱"))
# 输出:每个字一个索引

优缺点对比

方法 优点 缺点 适用场景
jieba分词 保留词义,效果好 需要额外安装,速度稍慢 通用NLP任务
字符级 简单,不需要分词 丢失词级信息,序列更长 短文本分类

我的经验:如果是新闻分类、情感分析这种,用jieba;如果是短信过滤、关键词提取,字符级也够用。

第五部分:构建完整的文本处理流水线

从原始文本到模型输入

真实项目里,文本处理是一个流水线,不是单独一步。来看个完整例子:

def build_text_pipeline(file_path, batch_size=32, is_training=True):
    """
    构建端到端的文本数据处理流水线
    """
    # 1. 从文件加载文本(每行一条)
    dataset = tf.data.TextLineDataset(file_path)
    
    # 2. 清洗文本
    def clean(text):
        text = tf.strings.lower(text)
        text = tf.strings.regex_replace(text, '[^a-zA-Z\s]', '')
        return text
    
    dataset = dataset.map(clean, num_parallel_calls=tf.data.AUTOTUNE)
    
    # 3. 构建词汇表(只对训练集做)
    if is_training:
        vectorizer.adapt(dataset.take(10000))  # 用前10000条构建词汇表
    
    # 4. 向量化
    dataset = dataset.map(vectorizer, num_parallel_calls=tf.data.AUTOTUNE)
    
    # 5. 批处理
    dataset = dataset.batch(batch_size)
    
    # 6. 预取(提速关键)
    dataset = dataset.prefetch(tf.data.AUTOTUNE)
    
    return dataset

# 使用
train_ds = build_text_pipeline("train.txt", is_training=True)
val_ds = build_text_pipeline("val.txt", is_training=False)

把向量化嵌入模型

更优雅的做法是把TextVectorization当作模型的第一层:

# 定义向量化层
vectorizer = TextVectorization(
    max_tokens=10000,
    output_sequence_length=100
)
vectorizer.adapt(train_texts)  # 提前构建词汇表

# 构建模型
model = tf.keras.Sequential([
    # 第一层就是向量化
    vectorizer,
    
    # 词嵌入层
    tf.keras.layers.Embedding(
        input_dim=10000,   # 词汇表大小
        output_dim=128,    # 嵌入维度
        mask_zero=True     # 自动处理padding
    ),
    
    # 后续层
    tf.keras.layers.LSTM(64),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# 编译
model.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# 训练时直接喂原始文本!
model.fit(
    train_texts,     # 原始字符串
    train_labels,
    epochs=10,
    validation_data=(val_texts, val_labels)
)

这种写法的好处

  1. 预处理成为模型的一部分
  2. 保存模型时,预处理逻辑一起保存
  3. 部署时不需要单独处理文本

第六部分:高级技巧——tensorflow_text

为什么需要tensorflow_text

tf.strings只能做基础操作,复杂任务(比如BERT分词、N-gram生成)得靠tensorflow_text

安装:

pip install tensorflow-text

子词分词(Subword Tokenization)

这是BERT、GPT用的方法,能处理未登录词(out-of-vocabulary):

import tensorflow_text as tf_text

# 使用WordPiece分词器(BERT用的)
tokenizer = tf_text.BertTokenizer(
    vocab_lookup_table="vocab.txt",  # 词汇表文件
    token_out_type=tf.int32
)

text = ["自然语言处理", "TensorFlow"]
tokens = tokenizer.tokenize(text)
print(tokens)
# 输出:子词的索引

子词的优势

  • “unhappiness” → [“un”, “happiness”]
  • “苹果手机” → [“苹果”, “手”, “机”]

即使没见过”unhappiness”这个词,模型也能从”un”和”happiness”推断意思。

N-gram生成

有时候需要考虑词组,而不是单个词:

# 生成2-gram
text = tf.constant(["机器 学习 很 有趣"])
tokens = tf.strings.split(text)

ngrams = tf_text.ngrams(tokens, 2, separator=' ')
print(ngrams)
# 输出:[['机器 学习', '学习 很', '很 有趣']]

这在做文本分类时很有用,”不好”和”不 好”可能是不同的信息。

第七部分:实战案例——IMDB情感分析

完整流程演示

用真实数据集走一遍完整流程:

# 1. 加载IMDB数据集
(train_texts, train_labels), (test_texts, test_labels) = \
    tf.keras.datasets.imdb.load_data(num_words=None)

# IMDB数据集是索引,需要转回文本
word_index = tf.keras.datasets.imdb.get_word_index()
reverse_word_index = {v: k for k, v in word_index.items()}

def decode_review(encoded_review):
    return ' '.join([reverse_word_index.get(i - 3, '?') for i in encoded_review])

train_texts = [decode_review(text) for text in train_texts]
test_texts = [decode_review(text) for text in test_texts]

# 2. 创建向量化层
max_features = 10000
sequence_length = 250

vectorizer = TextVectorization(
    max_tokens=max_features,
    output_sequence_length=sequence_length,
    output_mode='int'
)

# 只用训练数据构建词汇表!
train_ds = tf.data.Dataset.from_tensor_slices(train_texts)
vectorizer.adapt(train_ds.batch(128))

# 3. 构建模型
model = tf.keras.Sequential([
    vectorizer,
    tf.keras.layers.Embedding(max_features, 128),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

model.summary()

# 4. 训练
model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy']
)

history = model.fit(
    train_texts,
    train_labels,
    epochs=10,
    batch_size=32,
    validation_split=0.2
)

# 5. 评估
loss, accuracy = model.evaluate(test_texts, test_labels)
print(f"测试集准确率: {accuracy:.4f}")

预测新评论

def predict_sentiment(text):
    """
    预测评论情感
    """
    # 模型已经包含向量化,直接输入原始文本
    prediction = model.predict([text])[0][0]
    
    if prediction > 0.5:
        return f"正面评价 (置信度: {prediction:.2%})"
    else:
        return f"负面评价 (置信度: {1-prediction:.2%})"

# 测试
print(predict_sentiment("This movie is amazing!"))
print(predict_sentiment("Waste of time, terrible plot."))

第八部分:常见问题答疑

Q1:词汇表大小怎么选?

A:看数据规模和任务。

经验公式:

  • 小数据集(<10万条):5000-10000
  • 中等数据集(10万-100万):10000-30000
  • 大数据集(>100万):30000-50000

实验方法:

# 统计不同max_tokens的覆盖率
for max_tokens in [5000, 10000, 20000]:
    vectorizer = TextVectorization(max_tokens=max_tokens)
    vectorizer.adapt(train_texts)
    vocab = vectorizer.get_vocabulary()
    print(f"{max_tokens}词汇表,覆盖{len(vocab)}个unique词")

Q2:序列长度怎么定?

A:分析你的数据分布。

import numpy as np

# 统计文本长度
lengths = [len(text.split()) for text in train_texts]
print(f"平均长度: {np.mean(lengths):.1f}")
print(f"中位数: {np.median(lengths):.1f}")
print(f"90分位: {np.percentile(lengths, 90):.1f}")
print(f"99分位: {np.percentile(lengths, 99):.1f}")

# 一般选择覆盖90-95%样本的长度

我一般选择覆盖90%样本的长度,太长的就截断。毕竟极端长文本可能本身就是噪声。

Q3:未登录词(OOV)怎么处理?

A:有几种策略。

方案1:用[UNK]标记(默认)

vectorizer = TextVectorization(max_tokens=1000)
vectorizer.adapt(["机器学习", "深度学习"])
print(vectorizer(["强化学习"]))  # "强化"会变成[UNK]

方案2:增大词汇表

vectorizer = TextVectorization(max_tokens=50000)  # 加大容量

方案3:使用子词分词(推荐)

# 用BPE或WordPiece,把未知词拆成已知的子词

Q4:中英文混合文本怎么处理?

A:分别处理后拼接,或统一字符级处理。

def preprocess_mixed(text):
    """
    处理中英文混合文本
    """
    # 1. 英文转小写
    text = text.lower()
    
    # 2. 用jieba分词(会自动保留英文单词)
    words = jieba.cut(text)
    
    # 3. 用空格连接
    return ' '.join(words)

text = "我用TensorFlow做NLP"
print(preprocess_mixed(text))
# 输出: "我 用 tensorflow 做 nlp"

Q5:如何处理超长文本(比如长文章)?

A:有几种方案。

方案1:滑动窗口

def chunk_text(text, chunk_size=512, overlap=50):
    """
    把长文本分成重叠的块
    """
    words = text.split()
    chunks = []
    for i in range(0, len(words), chunk_size - overlap):
        chunk = ' '.join(words[i:i+chunk_size])
        chunks.append(chunk)
    return chunks

方案2:层次化处理

# 1. 先按句子分割
# 2. 每个句子单独编码
# 3. 句子级别再做一层aggregation

方案3:截断重要部分

# 只取前512个词 + 后512个词
def truncate_smart(text, max_len=512):
    words = text.split()
    if len(words) <= max_len:
        return text
    # 取开头和结尾
    head = words[:max_len//2]
    tail = words[-max_len//2:]
    return ' '.join(head + ['...'] + tail)

第九部分:实践练习(作业时间)

练习1:构建自己的文本清洗函数

任务:写一个函数,处理Twitter短文本,要求:

  1. 移除@用户名
  2. 移除#话题标签
  3. 移除URL链接
  4. 移除emoji表情
  5. 转小写

提示:

import re

def clean_tweet(text):
    # 你的代码
    pass

# 测试数据
tweets = [
    "@user Check out this link: https://example.com #AI #ML 😊",
    "TensorFlow is AWESOME!!! 🚀🚀🚀"
]

练习2:对比不同向量化方式的效果

任务:用同一份数据(可以用IMDB数据集),分别尝试:

  1. output_mode='int' + Embedding
  2. output_mode='tf-idf' + Dense
  3. output_mode='binary' + Dense

比较三种方式的训练速度和最终准确率。

练习3:实现一个简单的垃圾邮件分类器

任务

  1. 找一个垃圾邮件数据集(比如Kaggle上的SMS Spam Collection)
  2. 构建完整的预处理流水线
  3. 训练一个分类模型
  4. 在测试集上评估

要求达到95%以上准确率。

第十部分:课后总结

今天这节课覆盖了不少东西,重点回顾一下:

核心概念

  1. 文本不能直接喂给模型,必须转成数字
  2. 预处理包括:清洗、分词、向量化
  3. 向量化有多种方式:索引、多热、TF-IDF、嵌入

关键工具

  • tf.strings:基础字符串操作
  • TextVectorization:一站式向量化解决方案
  • tensorflow_text:高级分词和处理

实战技巧

  • 中文需要额外分词(jieba或字符级)
  • 把向量化层嵌入模型,简化部署
  • tf.data构建高效流水线
  • 词汇表大小和序列长度要根据数据调整

下节课预告:我们要进入模型训练的核心部分——损失函数和优化器。会讲不同任务该选什么损失函数,学习率怎么调,怎么避免过拟合等等。

记得把今天的练习做完,尤其是垃圾邮件分类那个,下节课会用到。

有问题随时群里问,咱们下节课见!

原创文章,作者:自动驾驶编程扛把子,如若转载,请注明出处:https://www.key-iot.cn/zj/programming/1635.html

Like (0)
自动驾驶编程扛把子的头像自动驾驶编程扛把子
Previous 2025年12月8日
Next 5天前

相关推荐