自然语言处理01 - AIE54 Day13学习笔记

2024-08-16
25 分钟阅读
包含图片

自然语言处理第一天学习内容,包括数据类型概述、文本预处理、向量化技术、词嵌入和循环神经网络

自然语言处理NLP文本预处理向量化词嵌入RNNLSTMAIE54

自然语言处理01 - AIE54 Day13学习笔记

学习日期:2024-08-16
主讲老师:李晓华
课时:6
文档来源:day13-自然语言处理01.pdf

课程关键词

数据类型 | 自然语言处理概述 | 文本预处理 | 向量化技术 | 循环神经网络 | LSTM


🎯 第一部分:数据类型与自然语言处理概述

1.1 三类数据的本质特征

核心分类:从数据科学视角,所有数据可以分为三大类,每类数据都有其特定的依赖关系和处理方法。

三种数据类型

  1. 表格类数据(Tabular Data)

    • 特征:零维信息,各特征互相独立
    • 适用算法:机器学习和全连接网络
    • 示例:年龄、收入、学历等结构化数据
  2. 图像类数据(Image Data)

    • 特征:二维信息,在两个方向上互相依赖
    • 适用算法:卷积神经网络(CNN)
    • 示例:224×224×3的RGB图像
  3. 时序类数据(Time Series Data)

    • 特征:一维信息,在时间方向上互相依赖
    • 适用算法:循环神经网络(RNN)
    • 示例:文本序列、时间序列数据

1.2 文本数据的特殊性

重要提示:文本属于时序类数据,具有强烈的序列依赖性,词语的顺序直接影响语义理解。

import numpy as np
import pandas as pd

# 表格数据示例 - 各特征独立
tabular_data = pd.DataFrame({
    '年龄': [25, 30, 35],
    '收入': [50000, 60000, 70000],
    '学历': [1, 2, 3]  # 编码后的类别特征
})

# 图像数据示例 - 像素间有空间依赖
image_data = np.random.rand(224, 224, 3)  # 高度×宽度×通道

# 文本数据示例 - 词语间有时序依赖
text_sequence = ["我", "爱", "自然", "语言", "处理"]
print("文本序列的时序依赖性:", text_sequence)

应用场景:理解数据类型是选择合适算法的基础,文本处理需要考虑序列信息的保持。

1.3 自然语言处理的数据科学本质

核心思想

  • 数据驱动:抛开人为制定的语法规则,让数据本身来决策
  • 统计视角:从统计学角度而非语言学角度分析文本
  • 机器学习:通过大量数据训练模型,发现语言的内在规律

数据驱动的示例:通过统计而非规则判断词语重要性

from collections import Counter

def analyze_word_importance(texts):
    """通过词频统计分析词语重要性"""
    all_words = []
    for text in texts:
        all_words.extend(text.split())
    
    word_freq = Counter(all_words)
    print("词频统计结果:")
    for word, freq in word_freq.most_common(10):
        print(f"{word}: {freq} 次")
    
    return word_freq

# 示例文本
sample_texts = [
    "酒店服务很好 房间很干净",
    "酒店位置不错 服务态度好",
    "房间设施齐全 服务周到"
]

word_importance = analyze_word_importance(sample_texts)

🔧 第二部分:自然语言处理通用流程

2.1 文本预处理流程

完整流程

  1. 文本输入 → 2. 分词 → 3. 编码 → 4. 向量化

详细步骤解析

import jieba
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# 步骤1: 文本输入(各种形式的文本)
raw_texts = [
    "你好吗?",
    "2H2 + O2 = 2H2O",
    "今天天气很好"
]

# 步骤2: 分词 - 把文本拆成最小语义单元
def tokenize_text(text):
    """使用jieba进行中文分词"""
    return jieba.lcut(text)

tokenized_texts = [tokenize_text(text) for text in raw_texts]
print("分词结果:", tokenized_texts)

# 步骤3: 编码 - 构建词汇表并分配ID
def build_vocabulary(tokenized_texts):
    """构建全局词汇表"""
    # 添加特殊标记
    vocab = {"<UNK>", "<PAD>", "<SOS>", "<EOS>"}
    for tokens in tokenized_texts:
        vocab.update(tokens)
    
    # 创建双向映射
    token2idx = {token: idx for idx, token in enumerate(vocab)}
    idx2token = {idx: token for token, idx in token2idx.items()}
    return token2idx, idx2token

token2idx, idx2token = build_vocabulary(tokenized_texts)
print(f"词汇表大小: {len(token2idx)}")
print("示例映射:", {k: v for k, v in list(token2idx.items())[:5]})

关键概念

  • 全局字典:每个词都出现且只出现一次,分配唯一ID
  • 特殊标记<UNK>(未知词)、<PAD>(填充)、<SOS>(句首)、<EOS>(句尾)

2.2 分词策略与原则

核心原则

重要:不要从语法角度考虑分词的合理性,要从统计的视角看待分词问题!

两种主要分词方法

import jieba

def character_level_tokenize(text):
    """字符级分词 - 最细粒度"""
    return list(text)

def word_level_tokenize(text):
    """词级分词 - 使用jieba"""
    return jieba.lcut(text)

# 示例对比
sample_text = "北京大学的学生很优秀"

char_tokens = character_level_tokenize(sample_text)
word_tokens = word_level_tokenize(sample_text)

print("字符级分词:", char_tokens)
print("词级分词:", word_tokens)
print(f"字符级词汇量: {len(set(char_tokens))}")
print(f"词级词汇量: {len(set(word_tokens))}")

分词方法比较

  • 字符级:词汇量小,但语义信息有限
  • 词级:语义信息丰富,但词汇量大,存在OOV问题

🎯 第三部分:文本向量化技术

3.1 从非结构化到结构化数据

核心挑战

  • 文本特点:行和列都无法对齐,不能直接套用传统机器学习算法
  • 解决思路:通过向量化技术将文本转换为固定长度的数值向量

结构化转换原理

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

# 原始文本数据(非结构化)
texts = [
    "酒店 服务 很好",
    "房间 很 干净 服务 态度 好",
    "位置 不错 房间 设施 齐全"
]

print("原始文本长度不一:")
for i, text in enumerate(texts):
    print(f"文本{i+1}: {len(text.split())}个词")

3.2 Count向量化(词袋模型)

定义:统计每个词在文档中出现的次数,形成固定长度的特征向量。

核心思想

  • 计数逻辑:重要的事情说三遍,重复越多越重要
  • 稀疏矩阵:大量零值,少量有效数字
  • 词袋模型:丢失词语顺序信息,只保留词频信息
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd

def create_count_vectors(texts):
    """创建Count向量化"""
    # 初始化向量化器
    vectorizer = CountVectorizer(
        token_pattern=r'\b\w+\b',  # 词汇提取模式
        lowercase=False,           # 保持原始大小写
        max_features=1000         # 最大特征数
    )
    
    # 拟合并转换文本
    count_matrix = vectorizer.fit_transform(texts)
    feature_names = vectorizer.get_feature_names_out()
    
    # 转换为DataFrame便于查看
    count_df = pd.DataFrame(
        count_matrix.toarray(),
        columns=feature_names,
        index=[f'文档{i+1}' for i in range(len(texts))]
    )
    
    return count_df, vectorizer

# 示例应用
sample_texts = [
    "酒店 服务 很好 很好",
    "房间 干净 服务 态度",
    "位置 很好 设施 齐全"
]

count_df, count_vectorizer = create_count_vectors(sample_texts)
print("Count向量化结果:")
print(count_df)

# 查看稀疏性
total_elements = count_df.size
zero_elements = (count_df == 0).sum().sum()
sparsity = zero_elements / total_elements * 100
print(f"\n稀疏度: {sparsity:.2f}%")

特点分析

  • 优点:简单直观,计算效率高
  • 缺点:忽略词序,无法区分词语重要性

3.3 TF-IDF向量化

定义:Term Frequency-Inverse Document Frequency,综合考虑词频和逆文档频率。

数学原理

TF-IDF(t, d) = TF(t, d) × IDF(t)

其中:

  • TF(t, d) = 词t在文档d中的出现次数 / 文档d的总词数
  • IDF(t) = log(文档总数 / 包含词t的文档数)

核心思想

  • TF:词在当前文档中的重要性
  • IDF:词在整个语料库中的稀有程度
  • 平衡:既考虑局部重要性,又考虑全局稀有性
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np

def create_tfidf_vectors(texts):
    """创建TF-IDF向量"""
    # 初始化TF-IDF向量化器
    tfidf_vectorizer = TfidfVectorizer(
        token_pattern=r'\b\w+\b',
        lowercase=False,
        max_features=1000,
        smooth_idf=True,  # 平滑IDF
        use_idf=True      # 使用IDF权重
    )
    
    # 拟合并转换
    tfidf_matrix = tfidf_vectorizer.fit_transform(texts)
    feature_names = tfidf_vectorizer.get_feature_names_out()
    
    # 创建DataFrame
    tfidf_df = pd.DataFrame(
        tfidf_matrix.toarray(),
        columns=feature_names,
        index=[f'文档{i+1}' for i in range(len(texts))]
    )
    
    return tfidf_df, tfidf_vectorizer

# 示例:比较Count和TF-IDF的差异
extended_texts = [
    "酒店 服务 很好 很好 很好",  # "很好"出现3次
    "房间 干净 服务 态度 很好",  # "很好"出现1次
    "位置 很好 设施 齐全 服务",  # "很好"出现1次
    "价格 合理 环境 很好 推荐"   # "很好"出现1次
]

# Count向量化
count_df, _ = create_count_vectors(extended_texts)
print("Count向量化 - '很好'的权重:")
print(count_df['很好'])

# TF-IDF向量化
tfidf_df, _ = create_tfidf_vectors(extended_texts)
print("\nTF-IDF向量化 - '很好'的权重:")
print(tfidf_df['很好'].round(4))
print("\n分析:TF-IDF降低了高频常见词的权重")

应用场景:文档分类、信息检索、关键词提取等需要考虑词语重要性的任务。


🧠 第四部分:词嵌入(Word Embedding)

4.1 嵌入层的概念与实现

定义:将离散的词汇索引映射到连续的高维向量空间,每个词用一个稠密向量表示。

核心优势

  • 语义相似性:相似词语在向量空间中距离较近
  • 稠密表示:相比稀疏的one-hot编码,信息密度更高
  • 可学习性:通过训练自动学习词语的语义表示
import torch
import torch.nn as nn
import numpy as np

def create_embedding_layer(vocab_size, embedding_dim, padding_idx=None):
    """创建词嵌入层"""
    embedding = nn.Embedding(
        num_embeddings=vocab_size,  # 词汇表大小
        embedding_dim=embedding_dim,  # 嵌入维度
        padding_idx=padding_idx      # 填充词索引
    )
    return embedding

# 示例:构建嵌入层
vocab_size = 10000      # 词汇表大小
embedding_dim = 300     # 嵌入维度(常用值:100, 200, 300)
padding_idx = 0         # 填充词索引

embed_layer = create_embedding_layer(vocab_size, embedding_dim, padding_idx)

print(f"嵌入矩阵形状: {embed_layer.weight.shape}")
print(f"参数数量: {embed_layer.weight.numel():,}")

# 示例输入:批量句子的词索引
batch_sentences = torch.tensor([
    [1, 45, 123, 678, 2],    # 句子1
    [3, 89, 234, 567, 0],    # 句子2(最后一个是填充)
    [12, 56, 789, 0, 0]      # 句子3(后两个是填充)
], dtype=torch.long)

print(f"输入形状: {batch_sentences.shape}")  # [batch_size, seq_len]

# 通过嵌入层
embedded = embed_layer(batch_sentences)
print(f"输出形状: {embedded.shape}")  # [batch_size, seq_len, embedding_dim]

# 查看填充词的嵌入(应该是零向量)
print(f"\n填充词嵌入的前5维: {embedded[1, -1, :5]}")

重要参数解析

  • num_embeddings:词汇表中词的总数
  • embedding_dim:每个词的向量维度
  • padding_idx:填充词的索引,其嵌入向量保持为零

4.2 嵌入层的训练机制

训练原理

  • 查找表:嵌入层本质上是一个可学习的查找表(Look-up Table)
  • 反向传播:通过下游任务的损失函数更新嵌入权重
  • 语义学习:相似上下文中出现的词会学到相似的表示
def demonstrate_embedding_training():
    """演示嵌入层的训练过程"""
    # 创建简单的分类任务来训练嵌入
    
    class SimpleTextClassifier(nn.Module):
        def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes):
            super().__init__()
            self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
            self.fc1 = nn.Linear(embedding_dim, hidden_dim)
            self.fc2 = nn.Linear(hidden_dim, num_classes)
            self.relu = nn.ReLU()
        
        def forward(self, x):
            # x: [batch_size, seq_len]
            embedded = self.embedding(x)  # [batch_size, seq_len, embedding_dim]
            # 简单平均池化
            pooled = embedded.mean(dim=1)  # [batch_size, embedding_dim]
            # 分类层
            hidden = self.relu(self.fc1(pooled))
            output = self.fc2(hidden)
            return output
    
    # 创建模型
    model = SimpleTextClassifier(
        vocab_size=1000,
        embedding_dim=128,
        hidden_dim=64,
        num_classes=2  # 二分类
    )
    
    # 模拟训练数据
    batch_size = 4
    seq_len = 10
    x = torch.randint(1, 100, (batch_size, seq_len))  # 避开padding_idx=0
    y = torch.randint(0, 2, (batch_size,))
    
    # 前向传播
    logits = model(x)
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {logits.shape}")
    
    # 查看嵌入权重的变化
    initial_embedding = model.embedding.weight[1].clone()  # 保存初始权重
    
    # 模拟一次训练步骤
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
    loss = criterion(logits, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    # 检查权重变化
    updated_embedding = model.embedding.weight[1]
    weight_change = torch.norm(updated_embedding - initial_embedding).item()
    
    print(f"\n训练前后嵌入权重变化: {weight_change:.6f}")
    print("嵌入层通过下游任务的监督信号学习语义表示")

demonstrate_embedding_training()

🔄 第五部分:循环神经网络(RNN)

5.1 RNN的基本原理

核心思想

  • 参数共享:在时间维度上共享参数,类似于CNN在空间维度上的参数共享
  • 序列建模:能够处理变长序列,保持历史信息
  • 递归结构:当前时刻的输出依赖于当前输入和前一时刻的隐状态

数学表达式

h_t = tanh(x_t W_ih + h_{t-1} W_hh + b_hh)

其中:

  • h_t:时刻t的隐状态
  • x_t:时刻t的输入
  • W_ih:输入到隐层的权重矩阵
  • W_hh:隐层到隐层的权重矩阵
import torch
import torch.nn as nn

def create_rnn_model(input_size, hidden_size, num_layers=1, batch_first=True):
    """创建RNN模型"""
    rnn = nn.RNN(
        input_size=input_size,      # 输入特征维度
        hidden_size=hidden_size,    # 隐状态维度
        num_layers=num_layers,      # RNN层数
        batch_first=batch_first,    # 批次维度在前
        bidirectional=False         # 是否双向
    )
    return rnn

# 创建RNN示例
input_size = 512      # 词嵌入维度
hidden_size = 256     # 隐状态维度
batch_size = 2        # 批量大小
seq_len = 10          # 序列长度

rnn = create_rnn_model(input_size, hidden_size)

# 准备输入数据
# 格式:[batch_size, seq_len, input_size] (当batch_first=True时)
x = torch.randn(batch_size, seq_len, input_size)
print(f"输入形状: {x.shape}")

# 初始隐状态
# 格式:[num_layers, batch_size, hidden_size]
h0 = torch.zeros(1, batch_size, hidden_size)
print(f"初始隐状态形状: {h0.shape}")

# 前向传播
output, hidden = rnn(x, h0)
print(f"输出形状: {output.shape}")  # [batch_size, seq_len, hidden_size]
print(f"最终隐状态形状: {hidden.shape}")  # [num_layers, batch_size, hidden_size]

RNN的特点

  • 优点:能处理序列数据,理论上可以记住任意长的历史信息
  • 缺点:梯度消失问题,难以学习长期依赖关系

5.2 LSTM(长短期记忆网络)

核心创新:通过门控机制解决RNN的梯度消失问题,能够有效学习长期依赖关系。

三个门控机制

  • 遗忘门:决定丢弃哪些信息
  • 输入门:决定存储哪些新信息
  • 输出门:决定输出哪些信息
def create_lstm_model(input_size, hidden_size, num_layers=1, batch_first=True, bidirectional=False):
    """创建LSTM模型"""
    lstm = nn.LSTM(
        input_size=input_size,
        hidden_size=hidden_size,
        num_layers=num_layers,
        batch_first=batch_first,
        bidirectional=bidirectional,
        dropout=0.1 if num_layers > 1 else 0  # 多层时添加dropout
    )
    return lstm

# 创建LSTM示例
lstm = create_lstm_model(input_size=512, hidden_size=256, num_layers=2)

# 准备输入
batch_size, seq_len, input_size = 2, 15, 512
x = torch.randn(batch_size, seq_len, input_size)

# LSTM需要两个初始状态:隐状态和细胞状态
num_layers = 2
h0 = torch.zeros(num_layers, batch_size, 256)  # 隐状态
c0 = torch.zeros(num_layers, batch_size, 256)  # 细胞状态

print(f"输入形状: {x.shape}")
print(f"初始隐状态形状: {h0.shape}")
print(f"初始细胞状态形状: {c0.shape}")

# 前向传播
output, (hn, cn) = lstm(x, (h0, c0))

print(f"输出形状: {output.shape}")
print(f"最终隐状态形状: {hn.shape}")
print(f"最终细胞状态形状: {cn.shape}")

# 双向LSTM示例
bilstm = create_lstm_model(
    input_size=512,
    hidden_size=256,
    bidirectional=True
)

# 双向LSTM的隐状态维度会翻倍
h0_bi = torch.zeros(2, batch_size, 256)  # 2 = num_layers * 2 (双向)
c0_bi = torch.zeros(2, batch_size, 256)

output_bi, (hn_bi, cn_bi) = bilstm(x, (h0_bi, c0_bi))
print(f"\n双向LSTM输出形状: {output_bi.shape}")  # hidden_size * 2

LSTM的优势

  • 长期记忆:细胞状态可以携带长距离信息
  • 选择性记忆:门控机制决定记住或遗忘信息
  • 梯度流动:避免梯度消失,训练更稳定

5.3 实际应用示例

让我们用真实的酒店评论数据演示完整的文本分类流程:

import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import jieba
from collections import Counter

class TextClassificationModel(nn.Module):
    """基于LSTM的文本分类模型"""
    
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, num_layers=2):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
        self.lstm = nn.LSTM(
            embedding_dim,
            hidden_dim,
            num_layers,
            batch_first=True,
            bidirectional=True,
            dropout=0.3
        )
        self.fc = nn.Linear(hidden_dim * 2, output_dim)  # *2 for bidirectional
        self.dropout = nn.Dropout(0.5)
    
    def forward(self, x):
        # x: [batch_size, seq_len]
        embedded = self.embedding(x)  # [batch_size, seq_len, embedding_dim]
        # LSTM
        lstm_out, (hidden, _) = self.lstm(embedded)
        # 使用最后一个时刻的输出
        # 取最后一层的前向和后向隐状态
        # hidden: [num_layers*2, batch_size, hidden_dim]
        forward_hidden = hidden[-2, :, :]  # 前向
        backward_hidden = hidden[-1, :, :]  # 后向
        final_hidden = torch.cat([forward_hidden, backward_hidden], dim=1)
        # 分类层
        output = self.dropout(final_hidden)
        output = self.fc(output)
        return output

def prepare_text_data(texts, max_vocab_size=10000, max_seq_len=100):
    """准备文本数据"""
    # 分词
    tokenized_texts = [jieba.lcut(text) for text in texts]
    
    # 构建词汇表
    all_tokens = []
    for tokens in tokenized_texts:
        all_tokens.extend(tokens)
    token_counts = Counter(all_tokens)
    vocab = ['<PAD>', '<UNK>'] + [token for token, _ in token_counts.most_common(max_vocab_size-2)]
    token2idx = {token: idx for idx, token in enumerate(vocab)}
    
    # 文本转换为索引序列
    def text_to_indices(tokens):
        indices = [token2idx.get(token, token2idx['<UNK>']) for token in tokens]
        # 截断或填充到固定长度
        if len(indices) > max_seq_len:
            indices = indices[:max_seq_len]
        else:
            indices.extend([0] * (max_seq_len - len(indices)))  # 0是<PAD>的索引
        return indices
    
    indexed_texts = [text_to_indices(tokens) for tokens in tokenized_texts]
    return indexed_texts, token2idx, vocab

# 示例:使用酒店评论数据
sample_comments = [
    "酒店服务很好,房间干净整洁,位置优越",
    "前台服务态度差,房间设施陈旧,不推荐",
    "性价比不错,早餐丰富,交通便利",
    "房间太小,隔音效果差,影响休息"
]

sample_labels = [1, 0, 1, 0]  # 1: 正面, 0: 负面

# 数据预处理
indexed_texts, token2idx, vocab = prepare_text_data(sample_comments, max_seq_len=50)
vocab_size = len(vocab)

print(f"词汇表大小: {vocab_size}")
print(f"示例索引序列长度: {len(indexed_texts[0])}")

# 创建模型
model = TextClassificationModel(
    vocab_size=vocab_size,
    embedding_dim=128,
    hidden_dim=64,
    output_dim=2,  # 二分类
    num_layers=2
)

# 模型参数统计
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)

print(f"\n模型总参数: {total_params:,}")
print(f"可训练参数: {trainable_params:,}")

# 测试前向传播
x = torch.tensor(indexed_texts, dtype=torch.long)
y = torch.tensor(sample_labels, dtype=torch.long)

with torch.no_grad():
    logits = model(x)
    print(f"\n输入形状: {x.shape}")
    print(f"输出形状: {logits.shape}")
    print(f"预测概率: {torch.softmax(logits, dim=1)}")

📈 第六部分:总结与进阶方向

6.1 核心知识点回顾

文本处理流程

  1. 分词:字符级 vs 词级,统计视角优于语法视角
  2. 向量化:Count → TF-IDF → Embedding,从稀疏到稠密
  3. 序列建模:RNN → LSTM → Transformer,处理序列依赖关系

关键技术对比

技术优点缺点适用场景
Count向量简单快速忽略词序,稀疏文档分类
TF-IDF考虑词重要性仍然稀疏,无语义信息检索
Word Embedding稠密,有语义需要训练深度学习NLP
RNN处理序列梯度消失短序列任务
LSTM长期依赖计算复杂长序列任务

6.2 实践建议

学习路径

  1. 基础巩固:熟练掌握文本预处理和向量化技术
  2. 代码实践:完成酒店评论分类项目
  3. 模型调优:尝试不同的超参数和网络结构
  4. 进阶学习:学习Attention机制和Transformer架构

常见问题与解决方案

# 常见问题1:内存不足
def create_data_loader(texts, labels, batch_size=32, shuffle=True):
    """创建数据加载器,避免内存问题"""
    class TextDataset(Dataset):
        def __init__(self, texts, labels):
            self.texts = texts
            self.labels = labels
        
        def __len__(self):
            return len(self.texts)
        
        def __getitem__(self, idx):
            return torch.tensor(self.texts[idx], dtype=torch.long), \
                   torch.tensor(self.labels[idx], dtype=torch.long)
    
    dataset = TextDataset(texts, labels)
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle)
    return dataloader

# 常见问题2:序列长度不一致
def pad_sequences(sequences, max_len=None, pad_value=0):
    """序列填充函数"""
    if max_len is None:
        max_len = max(len(seq) for seq in sequences)
    
    padded = []
    for seq in sequences:
        if len(seq) >= max_len:
            padded.append(seq[:max_len])
        else:
            padded.append(seq + [pad_value] * (max_len - len(seq)))
    return padded

# 常见问题3:梯度爆炸
def train_with_gradient_clipping(model, dataloader, epochs=10):
    """带梯度裁剪的训练函数"""
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    criterion = nn.CrossEntropyLoss()
    
    for epoch in range(epochs):
        for batch_idx, (data, target) in enumerate(dataloader):
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            
            # 梯度裁剪
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
            optimizer.step()
            
            if batch_idx % 10 == 0:
                print(f'Epoch: {epoch}, Batch: {batch_idx}, Loss: {loss.item():.4f}')
    
    print("学习完成!继续探索更高级的NLP技术吧!")

🎯 关键要点总结

✅ 最佳实践

  1. 数据驱动思维:从统计角度而非语法角度分析文本
  2. 序列建模重要性:文本具有时序依赖性,需要合适的序列模型
  3. 向量化技术选择:根据任务需求选择合适的向量化方法
  4. 模型架构设计:LSTM能有效处理长序列依赖关系

🔍 深入理解

  1. 文本预处理:分词、编码、向量化是NLP的基础步骤
  2. 词嵌入技术:稠密向量表示比稀疏表示更有语义信息
  3. 循环神经网络:RNN和LSTM是处理序列数据的重要工具
  4. 梯度问题:LSTM通过门控机制解决梯度消失问题

📈 进阶方向

  • Attention机制:学习注意力机制和Transformer架构
  • 预训练模型:BERT、GPT等大规模预训练模型
  • 多模态学习:文本与图像、音频的结合
  • 实际应用:情感分析、机器翻译、问答系统等

学习心得

这节课深入学习了自然语言处理的核心技术:

  1. 数据科学视角:从数据类型分类的角度理解文本处理的特殊性
  2. 技术演进:从简单的词频统计到复杂的神经网络模型
  3. 实践导向:通过完整的代码示例理解每个技术环节
  4. 问题解决:学会处理NLP中的常见技术问题

下一步学习计划

  • 深入学习Attention机制和Transformer架构
  • 实践BERT等预训练模型的使用
  • 完成完整的NLP项目(情感分析、文本分类等)
  • 探索多模态学习和实际应用场景