
开场白:从图像到文字,换个战场
上节课咱们把图片处理得明明白白,有同学在群里问:”老师,我想做个情感分析模型,文本怎么喂给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位)
几个关键参数的含义:
- max_tokens:词汇表容量
- 太小:很多词会变成”未知词”
- 太大:模型参数多,训练慢
- 经验值:英文5000-20000,中文10000-50000
- output_mode:输出格式
'int':输出词的索引,最常用'binary':多热编码(词出现就是1)'count':词频统计'tf-idf':加权词频
- 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)
)
这种写法的好处:
- 预处理成为模型的一部分
- 保存模型时,预处理逻辑一起保存
- 部署时不需要单独处理文本
第六部分:高级技巧——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短文本,要求:
- 移除@用户名
- 移除#话题标签
- 移除URL链接
- 移除emoji表情
- 转小写
提示:
import re
def clean_tweet(text):
# 你的代码
pass
# 测试数据
tweets = [
"@user Check out this link: https://example.com #AI #ML 😊",
"TensorFlow is AWESOME!!! 🚀🚀🚀"
]
练习2:对比不同向量化方式的效果
任务:用同一份数据(可以用IMDB数据集),分别尝试:
output_mode='int'+ Embeddingoutput_mode='tf-idf'+ Denseoutput_mode='binary'+ Dense
比较三种方式的训练速度和最终准确率。
练习3:实现一个简单的垃圾邮件分类器
任务:
- 找一个垃圾邮件数据集(比如Kaggle上的SMS Spam Collection)
- 构建完整的预处理流水线
- 训练一个分类模型
- 在测试集上评估
要求达到95%以上准确率。
第十部分:课后总结
今天这节课覆盖了不少东西,重点回顾一下:
核心概念:
- 文本不能直接喂给模型,必须转成数字
- 预处理包括:清洗、分词、向量化
- 向量化有多种方式:索引、多热、TF-IDF、嵌入
关键工具:
tf.strings:基础字符串操作TextVectorization:一站式向量化解决方案tensorflow_text:高级分词和处理
实战技巧:
- 中文需要额外分词(jieba或字符级)
- 把向量化层嵌入模型,简化部署
- 用
tf.data构建高效流水线 - 词汇表大小和序列长度要根据数据调整
下节课预告:我们要进入模型训练的核心部分——损失函数和优化器。会讲不同任务该选什么损失函数,学习率怎么调,怎么避免过拟合等等。
记得把今天的练习做完,尤其是垃圾邮件分类那个,下节课会用到。
有问题随时群里问,咱们下节课见!
原创文章,作者:自动驾驶编程扛把子,如若转载,请注明出处:https://www.key-iot.cn/zj/programming/1635.html