{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":19018,"databundleVersionId":2703900,"sourceType":"competition"},{"sourceId":11650,"sourceType":"datasetVersion","datasetId":8327},{"sourceId":320111,"sourceType":"datasetVersion","datasetId":134715}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Contents\n\nIn this Notebook I will start with the very Basics of RNN's and Build all the way to latest deep learning architectures to solve NLP problems. It will cover the Following:\n* Simple RNN's\n* Word Embeddings : Definition and How to get them\n* LSTM's\n* GRU's\n* BI-Directional RNN's\n* Encoder-Decoder Models (Seq2Seq Models)\n* Attention Models\n* Transformers - Attention is all you need\n* BERT\n","metadata":{}},{"cell_type":"code","source":"import numpy as np  # 数值计算基础库，用于处理多维数组和矩阵运算\nimport pandas as pd  # 数据分析库，提供DataFrame结构处理表格数据 也可以读取CSV file I/O (e.g. pd.read_csv)\nfrom tqdm import tqdm  # 进度条显示工具，用于可视化循环进度\nfrom sklearn.model_selection import train_test_split\nimport tensorflow as tf  # Kaggle服务器已经装好环境，本地运行需要下载GPU版本的Tensorflow，配置环境\n# tensorflow是一个深度学习框架\n\nfrom keras.models import Sequential  # Sequential 是最简单的模型容器，按顺序堆叠各层，用于快速搭建线性堆叠型网络。\nfrom keras.layers import LSTM, GRU, SimpleRNN  # 这就是我们要训练的神经元，用于处理序列数据的神经元\n# SimpleRNN：基础循环层，存在梯度消失问题 \n# LSTM：长短期记忆网络，通过门控机制解决长期依赖问题 \n# GRU：门控循环单元，LSTM的简化版（合并了遗忘门和输入门）\n\n# Dense、Activation、Dropout 现在都在 keras.layers 里，改成这样：\nfrom keras.layers import Dense, Activation, Dropout\n# Dense：全连接层，用于序列模型的最后几层，将 RNN 输出映射到目标维度。\n# Activation：激活函数层，可单独指定如 Activation('relu')、Activation('softmax')。\n# Dropout：在训练时随机丢弃一定比例的神经元，防止过拟合。\n\nfrom keras.layers import Embedding  # 词嵌入向量，词嵌入层（将离散符号映射为稠密向量）\nfrom keras.layers import BatchNormalization  # 批标准化层，加速训练并稳定收敛\nfrom tensorflow.keras.utils import to_categorical   # np_utils 提供一些工具函数，比如 to_categorical 将整数标签转成 one-hot 编码。\nfrom sklearn import preprocessing, decomposition, model_selection, metrics, pipeline\nfrom keras.layers import GlobalMaxPooling1D, Conv1D, MaxPooling1D, Flatten, Bidirectional, SpatialDropout1D\n# Conv1D + MaxPooling1D：一维卷积和池化层，可提取局部 n-gram 特征\n# GlobalMaxPooling1D：对整个时间步取最大值，常用于提炼最显著特征\n# Flatten：把多维张量展开为一维\n# Bidirectional：将 RNN 包装为双向 RNN，同时考虑正向和反向上下文\n# SpatialDropout1D：在时间步维度上丢弃整条特征通道，防过拟合\n\nfrom tensorflow.keras.preprocessing import sequence\nfrom tensorflow.keras.preprocessing.text import Tokenizer # keras.preprocessing是处理文本的包，sequence, text是处理具体功能的模块\nfrom keras.callbacks import EarlyStopping  # 调用于在验证集指标（如 loss）不再改善时提前终止训练，防止过拟合并节省计算资源\nfrom sklearn.metrics import accuracy_score\n\n# 可视化工具链\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n%matplotlib inline\nfrom plotly import graph_objs as go\nimport plotly.express as px\nimport plotly.figure_factory as ff\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:51:24.218457Z","iopub.execute_input":"2025-06-24T22:51:24.218750Z","iopub.status.idle":"2025-06-24T22:51:40.258042Z","shell.execute_reply.started":"2025-06-24T22:51:24.218730Z","shell.execute_reply":"2025-06-24T22:51:40.257430Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#检测硬件环境，设置分布式策略 \ntry:\n    # 尝试检测是否存在 TPU（张量处理单元），适用于 Google Cloud / Kaggle 等平台\n    # 如果 TPU 存在，将返回一个 TPUClusterResolver 对象（自动识别环境变量）\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    # 如果没有 TPU（本地运行或无 TPU 支持），则设为 None\n    tpu = None\n\n# 如果检测到 TPU，进行初始化并创建适用于 TPU 的分布式策略\nif tpu:\n    tf.config.experimental_connect_to_cluster(tpu)  # 连接到 TPU 集群\n    tf.tpu.experimental.initialize_tpu_system(tpu)  # 初始化 TPU 系统（分配资源）\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)  # 创建基于 TPU 的分布式训练策略\nelse:\n    # 如果没有 TPU，则默认使用 CPU 或单 GPU 的策略\n    strategy = tf.distribute.get_strategy()  # 适用于单机多 GPU 或 CPU 的默认策略\n\n# 打印当前同步训练的副本数（例如 TPU 上通常是 8 个）\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:51:47.216163Z","iopub.execute_input":"2025-06-24T22:51:47.216809Z","iopub.status.idle":"2025-06-24T22:51:47.222112Z","shell.execute_reply.started":"2025-06-24T22:51:47.216784Z","shell.execute_reply":"2025-06-24T22:51:47.221355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Read Data 读取数据","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-toxic-comment-train.csv') #读取训练集，使用的competitions下的一个数据集\nvalidation = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/validation.csv')#读取验证集\ntest = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/test.csv') #读取测试集\n#在右边的input模块 Add Input可以添加本地数据集\n#调用 Pandas 的 read_csv() 函数，把位于指定路径的 CSV 文件读入成一个 DataFrame 对象，赋值给变量 train，validation，test","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:51:52.385012Z","iopub.execute_input":"2025-06-24T22:51:52.385310Z","iopub.status.idle":"2025-06-24T22:51:56.099999Z","shell.execute_reply.started":"2025-06-24T22:51:52.385289Z","shell.execute_reply":"2025-06-24T22:51:56.099320Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We will drop the other columns and approach this problem as a Binary Classification Problem and also we will have our exercise done on a smaller subsection of the dataset(only 12000 data points) to make it easier to train the models","metadata":{}},{"cell_type":"code","source":"train.drop(['severe_toxic','obscene','threat','insult','identity_hate'],axis=1,inplace=True)\n#去除一些不需要的列并在原数据集上修改，axis=1代表在列方向上删除","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:00.908778Z","iopub.execute_input":"2025-06-24T22:52:00.909042Z","iopub.status.idle":"2025-06-24T22:52:00.938601Z","shell.execute_reply.started":"2025-06-24T22:52:00.909022Z","shell.execute_reply":"2025-06-24T22:52:00.937995Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train.loc[:12000,:] #使用loc索引，只使用前12000行的数据\ntrain.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:03.756431Z","iopub.execute_input":"2025-06-24T22:52:03.756713Z","iopub.status.idle":"2025-06-24T22:52:03.763376Z","shell.execute_reply.started":"2025-06-24T22:52:03.756691Z","shell.execute_reply":"2025-06-24T22:52:03.762693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\ndf = pd.DataFrame({\n    'comment_text': ['a','b','c'],\n    'toxic': [0,1,0],\n    'severe_toxic':[0,0,0],\n    'obscene':[1,0,1]\n})\n# 丢掉不需要的列\ndf.drop(['severe_toxic','obscene'], axis=1, inplace=True)\n# 只取前 2 行（示例中原来有 3 行）\ndf = df.loc[:1, :]\n\n\noutput：\ncomment_text\ttoxic\na\t              0\nb\t              1","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head() # 查看前几行","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T11:59:15.644320Z","iopub.execute_input":"2025-06-20T11:59:15.644698Z","iopub.status.idle":"2025-06-20T11:59:15.656975Z","shell.execute_reply.started":"2025-06-20T11:59:15.644667Z","shell.execute_reply":"2025-06-20T11:59:15.656040Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We will check the maximum number of words that can be present in a comment , this will help us in padding later","metadata":{}},{"cell_type":"code","source":"train['comment_text'].apply(lambda x:len(str(x).split())).max()\n#取出 train DataFrame 中名为 comment_text 的这一列，得到一个 Series，其中每个元素都是一条评论（或空值／NaN）\n#对于这一列的每一行作为一个x变成字符串，然后依据空格进行切分，并对每一行进行计数，并在所有返回的词数中取最大值，得到训练集中那条最长评论的词数\n#将当前元素 x 强制转成字符串的好处是如果有缺失值（NaN），str(x) 会得到 'nan'，避免报错","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:09.144173Z","iopub.execute_input":"2025-06-24T22:52:09.144476Z","iopub.status.idle":"2025-06-24T22:52:09.201670Z","shell.execute_reply.started":"2025-06-24T22:52:09.144456Z","shell.execute_reply":"2025-06-24T22:52:09.201065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\ns = pd.Series([\"hello world\", \"\", None])\n# len(str(x).split()) 依次为：2, 0, 1 (‘None’→'None'→['None'])\nmax_len = s.apply(lambda x: len(str(x).split())).max()\n# max_len == 2","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"roc_auc 分数是衡量模型分类能力的核心指标，特别适合二分类不平衡任务。如果训练的是有毒评论分类器，这个指标比准确率更稳健，因为准确率可能被多数类掩盖。","metadata":{}},{"cell_type":"code","source":"def roc_auc(predictions,target): \n    #roc_auc：函数名，意指“Receiver Operating Characteristic Area Under Curve”\n    #predictions是模型对每个样本输出的“正类”概率或得分（浮点数数组）。target是对应的真实二分类标签（0 或 1 的整数或布尔数组）。\n    '''\n    This methods returns the AUC Score when given the Predictions\n    and Labels\n    '''\n    #计算 ROC 曲线点\n    fpr, tpr, thresholds = metrics.roc_curve(target, predictions)\n    roc_auc = metrics.auc(fpr, tpr)#False Positive Rate（假正例率） True Positive Rate（召回率）\n    return roc_auc\n    #可以试一试也可以用后面的准确率","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:14.604482Z","iopub.execute_input":"2025-06-24T22:52:14.605125Z","iopub.status.idle":"2025-06-24T22:52:14.610464Z","shell.execute_reply.started":"2025-06-24T22:52:14.605091Z","shell.execute_reply":"2025-06-24T22:52:14.609628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\ny_true = [0,1,1,0]\ny_score = [0.2,0.8,0.6,0.4]\nfpr, tpr, _ = metrics.roc_curve(y_true, y_score)\n# fpr=[0. ,0.5,1. ], tpr=[0.,1.,1.]\nauc = metrics.auc(fpr, tpr)\n# auc == 1.0 AUC = 面积 = 1.0（完美分类）","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Data Preparation 准备训练数据","metadata":{}},{"cell_type":"code","source":"#将训练集中的评论文本和标签按比例随机拆分成训练子集和验证子集\nxtrain, xvalid, ytrain, yvalid = train_test_split(train.comment_text.values, train.toxic.values, #.values 把 Series 转成 NumPy 数组\n                                                  stratify=train.toxic.values, \n                                                  random_state=42, #随机种子，保证每次拆分结果一致，方便复现实验。\n                                                  test_size=0.2, shuffle=True)#在拆分前先打乱样本顺序，默认即为 True\n\n#stratify=train.toxic.values表示分层抽样 保证训练集与验证集里面的标签比例分布一致，也就是说数据集不能完全随机，避免类别失衡影响模型评估。\n#数据集中标签分布比例是2比8则分割得到的训练集和测试集中标签比例也是2比8","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:20.936332Z","iopub.execute_input":"2025-06-24T22:52:20.936892Z","iopub.status.idle":"2025-06-24T22:52:20.947363Z","shell.execute_reply.started":"2025-06-24T22:52:20.936871Z","shell.execute_reply":"2025-06-24T22:52:20.946636Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"验证集与测试集：\n验证集用于调参与模型选择，测试集用于最终评估模型性能\n验证集\t用于调整模型结构、超参数、早停等\t每轮训练后都用\n测试集\t模型最终评估\t模型训练完之后    只能使用一次\n而测试集要等到你训练完所有模型、决定哪个最优之后才用来“一锤定音”，评估模型在真实未知数据上的泛化能力\n如果你现在用测试集：\n1.相当于“偷看了考卷”来调模型，导致测试集变成伪训练数据\n2.模型在测试集上表现看起来很好，但其实是在“过拟合测试集”","metadata":{}},{"cell_type":"code","source":"# using keras tokenizer here\ntoken = Tokenizer(num_words=None) #使用词嵌入模块Tokenizer，初始化一个分词器\n#text 对应 keras.preprocessing.text，提供了 Tokenizer、text_to_word_sequence、one_hot 等文本处理工具  注意是一种工具而不是文本\n#num_words=None 表示不过滤词频，Tokenizer 会收录训练语料里出现的所有不同单词。\n#如果你希望只保留出现最频繁的前 N 个词，可将 num_words=N，这样在后续把词转成索引时，会忽略索引 ≥ N 的词\nmax_len = 1500 \n#后面 pad_sequences 会把所有序列统一做或截到长度 max_len前面那句 train['comment_text'].apply(...).max() 得到最大词数）\n#！！训练深度机器学习模型时硬性要求输入的数据长度一样，如果不一样的话会自行截断影响数据的完整性\n\ntoken.fit_on_texts(list(xtrain) + list(xvalid)) #将训练集 xtrain 和验证集 xvalid 的文本合并，统计所有单词的频率并构建词典\nxtrain_seq = token.texts_to_sequences(xtrain) #将每条文本（每条样本）转换为对应的整数序列（根据 word_index 映射）。\n#若 xtrain = [\"cat sat\", \"the cat\"]，word_index = {\"cat\":1, \"sat\":2, \"the\":3}，则输出：xtrain_seq = [[1, 2], [3, 1]]\nxvalid_seq = token.texts_to_sequences(xvalid)\n\n#RNN/LSTM/GRU 模型要求每个批次的输入长度一致，必须把所有序列填充到相同长度！！！\n#zero pad the sequences 填补长度为1500 将所有需要序列用0填补到1500长度\nxtrain_pad = sequence.pad_sequences(xtrain_seq, maxlen=max_len)\nxvalid_pad = sequence.pad_sequences(xvalid_seq, maxlen=max_len)\n#序列长度小于 maxlen 时，默认在前面（padding='pre'）用 0 填充；\n#序列长度超出 maxlen 时，默认在前面（truncating='pre'）截掉多余部分。\n#可以设置 padding='post'、truncating='post'，让填充/截断都在末尾进行\n\nword_index = token.word_index \n#把 Tokenizer 生成的词典映射保存到变量 word_index，方便后续要：\n #查看某个词的索引：word_index.get(\"toxic\")\n #构建 Embedding 层时指定 vocab_size = len(word_index) + 1（加 1 是因为索引从 1 开始，0 保留给填充用）\n #导出词典做分析或反向映射（索引→词）时，也需要它","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:52:31.594092Z","iopub.execute_input":"2025-06-24T22:52:31.594666Z","iopub.status.idle":"2025-06-24T22:52:32.652001Z","shell.execute_reply.started":"2025-06-24T22:52:31.594645Z","shell.execute_reply":"2025-06-24T22:52:32.651433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：Tokenizer 构建词典 → 序列化\ntexts = [\"cat sat\", \"the cat sat\"]\ntoken = Tokenizer()\ntoken.fit_on_texts(texts)\n# word_index = {'sat':1, 'cat':2, 'the':3}\nseqs = token.texts_to_sequences(texts)\n# => [[2,1], [3,2,1]]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：pad_sequences 填充/截断\nseqs = [[2,1], [3,2,1]]\npadded = pad_sequences(seqs, maxlen=4)\n# 默认 pre-padding → [[0,0,2,1], [0,3,2,1]]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(word_index) #字典里不同的词的个数","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:26:49.211670Z","iopub.execute_input":"2025-06-24T06:26:49.212250Z","iopub.status.idle":"2025-06-24T06:26:49.216556Z","shell.execute_reply.started":"2025-06-24T06:26:49.212226Z","shell.execute_reply":"2025-06-24T06:26:49.216001Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Simple RNN  \nRecurrent Neural Network(RNN) are a type of Neural Network where the output from previous step are fed as input to the current step. In traditional neural networks, all the inputs and outputs are independent of each other, but in cases like when it is required to predict the next word of a sentence, the previous words are required and hence there is a need to remember the previous words. Thus RNN came into existence, which solved this issue with the help of a Hidden Layer.\n递归神经网络 （RNN） 是一种神经网络，其中上一步的输出作为当前步骤的输入。在传统的神经网络中，所有的输入和输出都是相互独立的，但在需要预测句子的下一个单词的情况下，需要前面的单词，因此需要记住前面的单词。因此，RNN 应运而生，它在 Hidden Layer 的帮助下解决了这个问题。\n\n","metadata":{}},{"cell_type":"code","source":"# A simpleRNN without any pretrained embeddings and one dense layer\nmodel = Sequential()#初始化一个线性堆叠的神经网络容器，它将各层按照添加顺序线性堆叠，刚创建时 model 为空，尚未配置输入／输出\nmodel.add(Embedding(len(word_index) + 1,300,input_length=max_len))\n#第一层 嵌入层 用于把词索引映射到稠密向量空间，是 NLP 中常用的“词嵌入”组件，在字典长度上加1（因为还用0填补），每个单词的维度是300（自己设置），第三个参数设置每个样本里面序列的长度是多少（1500）\nmodel.add(SimpleRNN(100))\n#第二层 简单RNN层 100个RNN（有记忆功能的普通神经元）100为隐藏状态维度即每个时间步输出100维向量\nmodel.add(Dense(1, activation='sigmoid')) \n#第三层 全连接输出层  每一个RNN有一条边连接到 Dense里面的一个神经元 输出维度为1即一个神经元的输出并且将输出使用Sigmoid函数压缩到（0,1）\nmodel.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) #对模型进行其他设置\n# 损失函数binary_crossentropy是二分类交叉熵损失，适用于正负样本的二分类任务\n# 优化器adam自适应矩估计优化器，结合了RMSProp和动量法的优点     上课讲过！\n#评估指标accuracy是分类准确率（正确预测数/总样本数）\nmodel.summary()\n#第一层 none 还没有数据这是一个空模型 数据长度 数据个数  最后是本层有多少个参数","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T15:35:54.361232Z","iopub.execute_input":"2025-06-20T15:35:54.362101Z","iopub.status.idle":"2025-06-20T15:35:57.561768Z","shell.execute_reply.started":"2025-06-20T15:35:54.362063Z","shell.execute_reply":"2025-06-20T15:35:57.560793Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"词嵌入层，这个层构建了一个二维表，行表示单词，列表示单词的嵌入（映射的向量）。比如本例子中，行数是43497，列数是300，则是一个43497*300的二维表\n该表中参数是学习出来的，初始化为随机值，通过交叉熵损失，优化参数，逐步降低该损失值。\nsimpleRNN是我们课堂上所讲的RNN的具体实现，参数100表示该层RNN中有100个RNN神经元，注意的是，每个RNN神经元在t时刻的隐藏状态会传递给\nt+1时刻所有的RNN神经元，所以参数量计算：300*100（输入到神经元）+100*100（隐状态到隐状态）+100*1（偏置）=0100\n每一时刻给一个300维的单词 同时传给100个神经元（全连接） 100个神经元有100个隐藏状态","metadata":{}},{"cell_type":"code","source":"#对模型进行训练\nmodel.fit(xtrain_pad, ytrain, epochs=5, batch_size=64) #前12000条数据然后按比例切割，Multiplying by Strategy to run on TPU's\n#xtrain_pad：形状 (N_train, max_len) 的整数序列矩阵，表示训练集的所有样本（已做填充）\n#ytrain：形状 (N_train,) 的标签向量（0 或 1）\n#每个 epoch 会把训练集拆成若干个 batch，依次喂入模型，计算损失并通过 Adam 优化器更新所有可训练参数，也就是说每进行一个epoch，就会重新划分batch\n#每一个深度机器学习都需要换分batch后再输入（有点类似于K折交叉验证的划分，K折交叉验证只适用于简单的运算）","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T15:36:19.350016Z","iopub.execute_input":"2025-06-20T15:36:19.350411Z","iopub.status.idle":"2025-06-20T15:49:03.829665Z","shell.execute_reply.started":"2025-06-20T15:36:19.350377Z","shell.execute_reply":"2025-06-20T15:49:03.828889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"###在验证集上的效果\nscores = model.predict(xvalid_pad)# 输出的是概率返回一个形状 (N_valid, 1) 的数组 scores，每个值在 [0,1] 之间，表示是类别1的概率\npredicted_labels = (scores > 0.5).astype(int)  # 转为0/1标签，固定阈值0.5，将概率转为类别标签；适用于类别平衡数据，不平衡时需调整阈值\nprint(predicted_labels[:10])  # 输出前10个预测标签","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T15:54:37.622861Z","iopub.execute_input":"2025-06-20T15:54:37.623229Z","iopub.status.idle":"2025-06-20T15:54:45.209302Z","shell.execute_reply.started":"2025-06-20T15:54:37.623198Z","shell.execute_reply":"2025-06-20T15:54:45.208362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"accuracy = accuracy_score(yvalid, predicted_labels)#验证准确率，来自 sklearn.metrics，比较真实标签 yvalid 与预测标签 predicted_labels，返回准确率（正确预测占总样本比例）\nprint(f\"Accuracy: {accuracy:.4f}\")  # 输出格式化为4位小数\n#print(\"Auc: %.2f%%\" % (roc_auc(scores,yvalid))) \n#可选 也可以用前面定义的 roc_auc 函数计算 ROC AUC，AUC 能反映模型在不同阈值下的整体区分能力，更适合不均衡数据集的评估","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T15:54:58.339905Z","iopub.execute_input":"2025-06-20T15:54:58.340579Z","iopub.status.idle":"2025-06-20T15:54:58.346540Z","shell.execute_reply.started":"2025-06-20T15:54:58.340547Z","shell.execute_reply":"2025-06-20T15:54:58.345582Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores_model = []#创建一个空列表 scores_model，用于存储不同模型的评估结果\nscores_model.append({'Model': 'SimpleRNN','AUC_Score': accuracy})#将当前 SimpleRNN 的“准确率”或“AUC”以字典形式添加进去方便比较不同模型的性能","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T15:55:00.562222Z","iopub.execute_input":"2025-06-20T15:55:00.563084Z","iopub.status.idle":"2025-06-20T15:55:00.567435Z","shell.execute_reply.started":"2025-06-20T15:55:00.563051Z","shell.execute_reply":"2025-06-20T15:55:00.566599Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n**Tokenization**  \n  加载 GloVe 预训练词向量，并将其构造成一个字典 embeddings_index，为之后构建 Embedding 层服务。  \n  GloVe 提供了每个词（如 “apple”, “politics”, “love”）的一个 固定长度（如 300 维）向量表示，可以用来初始化神经网络的词嵌入层。\n  Embedding 是 LSTM 输入的前置环节，RNN/LSTM/GRU 不能直接处理词（字符串），它们只能处理数字（向量）。所以我们要先把每个词转换为向量——这一步叫“词嵌入”，用 Embedding 层实现。   \n  预训练词向量 有两个优势：语义更丰富（来自 840B 词汇的训练）训练更快，性能更稳（不从随机初始化开始）尤其是当训练数据不是非常大（如 IMDB、Jigsaw 这种几十万或几十万条以下），GloVe 等预训练词向量能提供强大的 外部知识补充，提升模型的表现。  \n**但为什么RNN、Transformer不需要加载 GloVe呢？**  \n传统的 RNN 示例中确实很多不加载预训练的 GloVe 向量，直接使用 Embedding() 层并随机初始化。这不是说它不需要词向量，而是它用了随机词向量并在训练中一起学习，而不是加载预训练的 GloVe。  \nRNN的代码：model.add(Embedding(input_dim=vocab_size, output_dim=128, input_length=maxlen))，没有指定 weights= 参数，就会自动随机初始化词向量（比如高斯分布），然后模型训练时，会一起学习这些词向量的参数。  ","metadata":{}},{"cell_type":"code","source":"xtrain_seq[:1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:53:23.258252Z","iopub.execute_input":"2025-06-24T22:53:23.258511Z","iopub.status.idle":"2025-06-24T22:53:23.263495Z","shell.execute_reply.started":"2025-06-24T22:53:23.258494Z","shell.execute_reply":"2025-06-24T22:53:23.262747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load the GloVe vectors in a dictionary:Glove是一个词向量的方法\nembeddings_index = {} #右边的文件是一个模型训练好了训练好的词向量从里面调出来我自己需要的单词的词嵌入相当于自己可以不用做词向量化\nf = open('/kaggle/input/glove840b300dtxt/glove.840B.300d.txt','r',encoding='utf-8') # 打开包含 GloVe 向量的文件（已提前上传到 Kaggle），设置编码为 utf-8\n#glove.840B.300d.txt 是一个 大规模预训练词向量文件：包含约 21 亿个词的训练数据（Common Crawl 语料库）；词向量维度是 300（即每个词映射成一个 300 维实数向量）；文件大小接近 2.1GB，里面大约有 220万个单词的词向量。\nfor line in tqdm(f): ## tqdm 用于显示加载进度条\n    #逐行读取文件，每行格式如下： word val1 val2 val3 ... val300   nginx apple 0.123 -0.456 0.678 ... -0.999\n    values = line.split(' ') ## 用空格分割这一行，得到一个列表\n    word = values[0] # 提取词\n    coefs = np.asarray([float(val) for val in values[1:]])  # 把词向量转换为 numpy 的浮点型数组\n    embeddings_index[word] = coefs  # 存入字典，key 为词，value 为对应的 300 维向量\nf.close()\n\nprint('Found %s word vectors.' % len(embeddings_index))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:53:26.703834Z","iopub.execute_input":"2025-06-24T22:53:26.704103Z","iopub.status.idle":"2025-06-24T22:56:20.871462Z","shell.execute_reply.started":"2025-06-24T22:53:26.704083Z","shell.execute_reply":"2025-06-24T22:56:20.870816Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(embeddings_index.get(\"toxic\"))  # 应该是一个 300 长度的 array\nprint(embeddings_index.get(\"unseenword\"))  # 如果返回 None，说明词向量中没有该词","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:56:42.639913Z","iopub.execute_input":"2025-06-24T22:56:42.640406Z","iopub.status.idle":"2025-06-24T22:56:42.646773Z","shell.execute_reply.started":"2025-06-24T22:56:42.640383Z","shell.execute_reply":"2025-06-24T22:56:42.646014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 创建一个空字典，用于存储每个单词及其对应的词向量（即 word -> vector 的映射）把调出来的放到一个矩阵中\nembedding_matrix = np.zeros((len(word_index) + 1, 300))\n#word_index 是前面用 Tokenizer 建好的词→索引映射，键是词，值是索引（从 1 开始）；加 1 是为了给索引 0（用于填充 pad_sequences 时产生的 0）也预留一行 ；矩阵的列数等于 GloVe 词向量的维度\n#这就是要把预训练好的 300 维向量放入的“容器”，后面会传给 Keras 的 Embedding 层作为初始权重\nfor word, i in tqdm(word_index.items()): #遍历词典中的每个词及其索引\n    #包裹后可以看到遍历进度条，尤其当词典几万甚至十几万条时，能直观了解加载进度。\n    embedding_vector =embeddings_index.get(word) #从预训练向量字典中取出该词的向量\n    if embedding_vector is not None:\n        embedding_matrix[i] = embedding_vector\n    #如果该词在 GloVe 词表里有预训练向量，就返回长度为 300 的 ndarray\n#重新创建一个矩阵的目的：1. Keras的Embedding层接口要求 2. 索引对齐 3.效率与优化：每个 batch 上千、上万个词，每步都要并行地拿出它们的向量。一次性把所有词的向量预载到一个大矩阵里，让底层高效地做 GPU/CPU 的并行查表。","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:56:58.304276Z","iopub.execute_input":"2025-06-24T22:56:58.304934Z","iopub.status.idle":"2025-06-24T22:56:58.464800Z","shell.execute_reply.started":"2025-06-24T22:56:58.304912Z","shell.execute_reply":"2025-06-24T22:56:58.464219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(embedding_matrix.shape)\nprint(np.sum(embedding_matrix))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:57:02.241021Z","iopub.execute_input":"2025-06-24T22:57:02.241474Z","iopub.status.idle":"2025-06-24T22:57:02.257944Z","shell.execute_reply.started":"2025-06-24T22:57:02.241453Z","shell.execute_reply":"2025-06-24T22:57:02.257398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\n词汇索引\t词\t原始词向量 (embeddings_index)\t   填入 embedding_matrix\n0\t   pad  \t  —\t                        [0.0, 0.0, 0.0] (预留给填充)\n1\t   cat  [0.1, 0.2, 0.3]              \t[0.1, 0.2, 0.3]\n2\t   dog\t[0.4, 0.5, 0.6]               \t[0.4, 0.5, 0.6]\n3\t elephant 未在 embeddings_index 中\t    [0.0, 0.0, 0.0] (保持默认)\n\n结果矩阵：\n     dim1  dim2  dim3\npad   0.0   0.0   0.0\ncat   0.1   0.2   0.3\ndog   0.4   0.5   0.6\nelephant 0.0 0.0 0.0\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LSTM's\n\n## Basic Overview\n\nSimple RNN's were certainly better than classical ML algorithms and gave state of the art results, but it failed to capture long term dependencies that is present in sentences . So in 1998-99 LSTM's were introduced to counter to these drawbacks.\n简单的 RNN 肯定比经典的 ML 算法要好,而且能给出最先进的结果,但它无法捕获句子中存在的长期依赖性,因此在 1998-99 年引入了 LSTM 来克服这些缺点。","metadata":{}},{"cell_type":"code","source":"#构建一个使用 预训练 GloVe 词向量的LSTM模型来处理文本分类任务\nwith strategy.scope(): #建立LSTM模型\n    #分布式训练策略下创建模型，如使用 Kaggle 或 Google Colab 上的 TPU 或多个 GPU 时常用\n    \n    # A simple LSTM with glove embeddings and one dense layer\n    model = Sequential ()#使用 Keras 的 Sequential API 按顺序堆叠模型层。\n#第1层：词嵌入层（Embedding）\n    model.add(Embedding(len(word_index) + 1,  # 总词数（包括 index=0 的 pad token）\n                     300,# 每个词的向量维度（GloVe 300d）\n                     weights=[embedding_matrix],#权重是单词的向量直接用不需要训练 使用准备好的 GloVe 词向量矩阵\n                     input_shape=(max_len,), # 每条输入文本被填充/截断后的长度（如1500）\n                     trainable=False))#不需要训练，不更新这些词向量\n    #算法示例：将输入的整数序列（如 [3, 5, 1, 99]）转换成向量序列（如 [[v3], [v5], [v1], [v99]]），其中每个 v 是一个 300维的向量。\n#第2层：LSTM 层\n    model.add(LSTM(100, dropout=0.3, recurrent_dropout=0.3))#随机去除百分之30防止过拟合\n    #100为 LSTM 的隐藏单元数（每个时间步输出一个100维向量） #对输入的 dropout（防止过拟合）#对隐藏状态的 dropout（LSTM 的“记忆”部分也加点随机性）\n    # dropout表示每一步输入和记忆有 30% 的概率被丢弃，这有助于防止过拟合。\n#第3层：输出层   \n    model.add(Dense(1, activation='sigmoid'))\n    #Dense(1)：输出一个标量值（通常是概率）,使用·sigmoid函数，将输出变成 [0, 1] 之间的概率，代表为toxic=1的概率\n  \n    model.compile(loss='binary_crossentropy',  # 二分类交叉熵损失函数，适用于 toxic / non-toxic、positive / negative 这类任务\n                  optimizer='adam', # Adam 是一种自适应学习率的优化器\n                  metrics=['accuracy']) # 训练时输出准确率\n    \nmodel.summary()\n#打印模型的结构，包括每一层的名称、输出形状、参数数量等，便于检查网络结构是否搭建正确。","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:43:12.721544Z","iopub.execute_input":"2025-06-24T06:43:12.722277Z","iopub.status.idle":"2025-06-24T06:43:13.022777Z","shell.execute_reply.started":"2025-06-24T06:43:12.722250Z","shell.execute_reply":"2025-06-24T06:43:13.022245Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"参数计算解释：  \n1.词嵌入层：  \n13049100=（word_index）里一共有 43,496 个不同词+索引0）x300（向量维度）词嵌入层需要存储所有词的向量表示，共 (V+1) 个词，每个词一个 D 维向量，所以总参数量就是行数×列数  \n2.LSTM 层：  \nLSTM 比 SimpleRNN 多了“门”机制，一共 4 套权重（分别对应输入门、遗忘门、输出门、候选隐藏状态门）每一“门”或“候选”都有三部分可训练参数：输入权重 𝑊即把当前输入映射到门的权重；循环权重U即把上一步隐藏状态映射到门；偏置b即每个隐藏单元都有一个偏置  160400=(W+U+b）x4= （300×100+100×100+100）x4\n输入权重W 把输入序列的每一个维度（300）映射到隐藏状态的每一个维度（100），所以矩阵大小是 300×100。\n循环权重𝑈 把上一个时间步的隐藏状态（100 维）映射到本时间步同样 100 维，矩阵大小是 100×100。\n偏置b 每个隐藏单元有一个偏置，共100个。  \n3.Dense层：  \nDense 层要把前一层的所有 H 个特征，映射到 O 个输出，所以需要一个 H×O 的矩阵 即HxO  \n\nTrainable params = LSTM + Dense = 160,400 + 101 = 160,501  \n\nNon‑trainable params = Embedding = 13,049,100\n","metadata":{}},{"cell_type":"code","source":"model.fit(xtrain_pad, ytrain, epochs=5, batch_size=64*strategy.num_replicas_in_sync)\n#训练集中每64个数据为一组共150batch，每一个batch送入模型就更新一次参数然后再重新打包（epoch） 所有的神经网络都是要分batch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:00:16.501867Z","iopub.execute_input":"2025-06-20T16:00:16.502193Z","iopub.status.idle":"2025-06-20T16:34:01.651580Z","shell.execute_reply.started":"2025-06-20T16:00:16.502167Z","shell.execute_reply":"2025-06-20T16:34:01.650700Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores = model.predict(xvalid_pad) #使用你刚刚训练好的 LSTM 模型，对验证集 xvalid_pad 进行预测,返回的是一个概率值（Sigmoid 函数输出的结果，范围在 [0, 1] 之间），表示每个样本属于“toxic”（有毒）的置信度\npredicted_labels = (scores > 0.5).astype(int)  # 转为0/1标签,逻辑判断，若概率大于0.5，返回 True，否则 False  #将布尔值转为整数，True → 1, False → 0\n# 因为模型最后一层用了 sigmoid，表示“为 toxic 的概率”，默认二分类的判断阈值是 0.5\naccuracy = accuracy_score(yvalid, predicted_labels) #计算预测标签 predicted_labels 与验证集真实标签 yvalid 之间的准确率\nprint(f\"Accuracy: {accuracy:.4f}\")  # 输出格式化为4位小数","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:34:30.959889Z","iopub.execute_input":"2025-06-20T16:34:30.960232Z","iopub.status.idle":"2025-06-20T16:35:02.864904Z","shell.execute_reply.started":"2025-06-20T16:34:30.960203Z","shell.execute_reply":"2025-06-20T16:35:02.863856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores_model.append({'Model': 'LSTM','AUC_Score': accuracy}) \n#将当前 LSTM 的“准确率”或“AUC”以字典形式添加进去方便比较不同模型的性能","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:35:12.291166Z","iopub.execute_input":"2025-06-20T16:35:12.292062Z","iopub.status.idle":"2025-06-20T16:35:12.296369Z","shell.execute_reply.started":"2025-06-20T16:35:12.292023Z","shell.execute_reply":"2025-06-20T16:35:12.295356Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# GRU's\nGRU（Gated Recurrent Unit，门控循环单元） 是 Cho 等人在 2014 年提出的一种 RNN 变体，它结合了 LSTM 的“门”机制，但结构更简洁。\n核心思想：用两个门——重置门（reset gate）和更新门（update gate）——来决定如何在每个时间步上融合新输入与历史信息。\n相比 LSTM，GRU 没有单独的记忆单元（cell state），也只用了两个门而非三个，参数更少。\n因此，GRU 较 LSTM 少了约 25% 的参数量，训练和推理都更轻量。\n\n三者的比较：\nSimpleRNN：最基础、最轻量，但难以捕捉长距离依赖。\n\nLSTM：强大的长期依赖建模能力，参数多、速度慢。\n\nGRU：在两者中取得平衡——门结构简洁、参数量少、训练快、性能接近 LSTM。\n","metadata":{},"attachments":{"1a26923d-81af-4667-9494-be011de95a1a.png":{"image/png":"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"}}},{"cell_type":"code","source":"with strategy.scope(): #表示在指定的分布式策略（如 TPU、多个 GPU）作用域内创建并编译模型，才能正确在多设备上运行\n    # GRU with glove embeddings and two dense layers 只不过把LSTM换成了GRU\n     model = Sequential() #使用 Keras 的 Sequential API，按顺序堆叠各层，适合单输入单输出的线性模型\n#第一层：词嵌入层 Embedding层\n     model.add(Embedding(len(word_index) + 1, #词表大小（包括 0 填充索引）\n                     300, #GloVe 预训练向量的维度\n                     weights=[embedding_matrix], #初始化为事先构建的 (V+1,300) 矩阵\n                     input_length=max_len, #每条序列长度，为1500\n                     trainable=False))\n     model.add(SpatialDropout1D(0.3))\n    #类似普通的 Dropout，但在时间步维度上丢弃整个特征通道，即「同一通道」在所有时间步都丢弃或保留。0.3 表示以 30% 的概率将每个通道丢弃，有助防止过拟合，并对序列模型更有效。\n#第二层：GRU 层\n     model.add(GRU(300)) #设置隐藏单元数为 300；GRU 会在每个时间步上维护 300 维的隐藏状态只返回序列最后一步的隐藏状态，输出张量形状为 (batch_size, 300)\n#第三层：Dense输出层\n     model.add(Dense(1, activation='sigmoid'))#Dense(1)：一个神经元，输出一个标量。activation='sigmoid'：Sigmoid 激活函数，将输出映射到 [0,1]，作为“toxic”概率。\n#编译模型\n     model.compile(loss='binary_crossentropy', optimizer='adam',metrics=['accuracy'])   \n    #loss='binary_crossentropy'：二分类交叉熵损失函数。\n    #optimizer='adam'：自适应学习率优化器。\n    #metrics=['accuracy']：训练和评估时计算并显示准确率\nmodel.summary()\n#会展示每一层的名称、输出形状和参数量","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:35:15.750934Z","iopub.execute_input":"2025-06-20T16:35:15.751678Z","iopub.status.idle":"2025-06-20T16:35:15.991210Z","shell.execute_reply.started":"2025-06-20T16:35:15.751645Z","shell.execute_reply":"2025-06-20T16:35:15.990268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"参数计算解释：  \n1.词嵌入层（Embedding）：  \n词表大小：43,496 + 1 = 43,497  \n词向量维度：300（GloVe）  \n所以参数量：43,497 × 300 = 13,049,100  \n该层为每个词分配一个固定的 300 维向量，总共需存储 43,497 个词的向量。  \n2.SpatialDropout1D 层：  \n该层不引入任何可训练参数，仅用于在训练时进行“特征通道级别”的随机丢弃。  \n参数量：0  \n它仅在前向传播中作用于数据，不参与反向传播参数学习。  \n3.GRU 层（Gated Recurrent Unit）   \n输入维度：300（来自 Embedding 层）输出隐藏单元数：300 GRU 包含 3 个“门”：重置门、更新门、新候选状态（reset, update, new）  \n参数 = 3 × [(输入维度 × 输出维度) + (输出维度 × 输出维度) + 输出维度]  \n     = 3 × [(300 × 300) + (300 × 300) + 300]  \n     = 3 × [90,000 + 90,000 + 300]  \n     = 3 × 180,300  \n     = 540,900  \n每个门（如更新门）都包含三部分参数：输入权重 W、循环权重 U、偏置项 b。GRU 有三组门，因此乘以 3。  \n4.Dense 输出层：  \n输入维度：300（GRU 输出）输出维度：1（用于二分类）  \n参数量计算：  \n参数 = 输入维度 × 输出维度 + 偏置  \n     = 300 × 1 + 1 = 301  \n全连接层将 300 维输入映射为一个标量输出，再通过 sigmoid 映射为 [0, 1] 概率。\n\nTrainable 总参数\tGRU + Dense = 540,900 + 301\t541,201  \nNon-trainable 参数\t词嵌入层\t13,049,100  \n\n\n","metadata":{}},{"cell_type":"code","source":"model.fit(xtrain_pad, ytrain, epochs=5, batch_size=64*strategy.num_replicas_in_sync)\n#在 TensorFlow 的分布式策略（如 MirroredStrategy 或 TPUStrategy）下，模型副本会被“同构地”复制到多张 GPU / 多个 TPU 核心上。\n#num_replicas_in_sync 返回正在并行训练的副本（replica）数目 # 64代表的是“每个副本”上的 局部 batch size\n#在训练的过程中主要的流程：1.数据分批（划分为若干个全局batch，每个全局batch又在后台拆分到副本）2.前向传播（Forward Pass） 3.计算损失（Loss） 4.反向传播 5.梯度聚合与参数更新 6.指标记录","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:35:20.615978Z","iopub.execute_input":"2025-06-20T16:35:20.616327Z","iopub.status.idle":"2025-06-20T16:57:42.386011Z","shell.execute_reply.started":"2025-06-20T16:35:20.616295Z","shell.execute_reply":"2025-06-20T16:57:42.385229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores = model.predict(xvalid_pad) #对验证集输入做前向推断，得到每条样本属于正类（toxic）的概率\npredicted_labels = (scores > 0.5).astype(int)  # 以 0.5 为阈值，把概率转换成 0 / 1 的离散标签。\naccuracy = accuracy_score(yvalid, predicted_labels) #计算预测标签与真实标签之间的分类准确率\nprint(f\"Accuracy: {accuracy:.4f}\")  # 输出格式化为4位小数","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:57:46.370962Z","iopub.execute_input":"2025-06-20T16:57:46.371827Z","iopub.status.idle":"2025-06-20T16:58:15.301292Z","shell.execute_reply.started":"2025-06-20T16:57:46.371795Z","shell.execute_reply":"2025-06-20T16:58:15.300295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores_model.append({'Model': 'GRU','AUC_Score': accuracy})\n#将当前 GRU 模型在验证集上的性能记录到 scores_model 列表中，字典包含两对键值：标明模型名称， 存储刚计算出的准确率","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T16:58:31.347447Z","iopub.execute_input":"2025-06-20T16:58:31.348155Z","iopub.status.idle":"2025-06-20T16:58:31.352414Z","shell.execute_reply.started":"2025-06-20T16:58:31.348119Z","shell.execute_reply":"2025-06-20T16:58:31.351427Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Bi-Directional RNN's\nBi-Directional RNN 是一种改进的循环神经网络结构，它在处理序列数据时：\n同时考虑前向（正序）和后向（逆序）两个方向的信息。使用两个独立的 RNN/LSTM/GRU：一个从左到右（正向），一个从右到左（反向）然后将两个方向的输出拼接或合并，作为当前时间步的最终表示。  \n在自然语言处理任务中，某个词的意义往往不仅依赖前文，还依赖后文。\n\n\n","metadata":{}},{"cell_type":"code","source":"with strategy.scope(): #在指定的分布式策略（如多 GPU/TPU）下构建并编译模型，确保并行训练时各副本同步\n    # A simple bidirectional LSTM with glove embeddings and one dense layer\n    model = Sequential() #用 Keras 的线性堆叠容器，按添加顺序逐层构建网络\n#词嵌入层\n    model.add(Embedding(len(word_index) + 1,\n                     300,\n                     weights=[embedding_matrix], #用预训练好的 embedding_matrix 初始化\n                     input_shape=(max_len,),\n                     trainable=False))\n#双向LSTM\n    model.add(Bidirectional(LSTM(300, dropout=0.3, recurrent_dropout=0.3)))\n   #dropout=0.3：输入层丢弃 30% recurrent_dropout=0.3：循环状态丢弃 30%\n#输出层\n    model.add(Dense(1,activation='sigmoid')) #单神经元 + Sigmoid，将前面提取的特征映射为 [0,1] 区间概率，用于二分类。\n    model.compile(loss='binary_crossentropy', optimizer='adam',metrics=['accuracy'])\n    \n    \nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:57:09.019075Z","iopub.execute_input":"2025-06-24T22:57:09.019835Z","iopub.status.idle":"2025-06-24T22:57:12.131110Z","shell.execute_reply.started":"2025-06-24T22:57:09.019812Z","shell.execute_reply":"2025-06-24T22:57:12.130409Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"参数计算解释：  \n1. 词嵌入层（Embedding）：  \n词表大小：43,496 + 1 = 43,497  \n词向量维度：300（GloVe）  \n参数量：43,497 × 300 = 13,049,100  \n该层为每个词分配一个固定的 300 维向量，总共需存储 43,497 个词的向量。  \n2. 双向 LSTM 层（Bidirectional）：\n本质上是两个方向各一层 LSTM（forward + backward），每层 LSTM 单元数 300。  \n单个 LSTM 的参数计算：  \n单向 LSTM 参数=4×[(D×H)+(H×H)+H]=4×[300×300+300×300+300]x2=1,442,400  \n3. 输出层（Dense）：  \n输入维度：600（双向 LSTM 合并后前向 300 + 后向 300）  \n输出维度：1（用于二分类）  \n参数量：600×1+1=601  \n全连接层将 600 维输入映射为一个标量输出，再通过 sigmoid 得到概率。    \nTrainable params = 双向 LSTM + Dense = 1,442,400 + 601 = 1,443,001  \nNon‑trainable params = Embedding = 13,049,100  ","metadata":{}},{"cell_type":"code","source":"#model.fit(xtrain_pad, ytrain, epochs=5, batch_size=64*strategy.num_replicas_in_sync)#启动模型训练\n#训练轮数为 5，即完整扫过训练集 5 次。（等同于新版本中的 epochs=5）\n#全局 batch size = 64 × 副本数，确保每个设备各自处理 64 条样本，随后自动聚合梯度进行同步更新\n####报错：典型的显存（或内存）不足导致的。双向 LSTM 模型太大、输入序列太长或 batch 太大，以至于一次前向/反向传播无法在可用设备上分配足够的内存必须逐步调小模型规模或 batch size，就能避免 ResourceExhaustedError ，并找到性能与资源使用的平衡点\nmodel.fit(xtrain_pad, ytrain, epochs=5, batch_size=16*strategy.num_replicas_in_sync)#启动模型训练","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T22:57:17.835507Z","iopub.execute_input":"2025-06-24T22:57:17.835791Z","execution_failed":"2025-06-25T01:26:03.074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores = model.predict(xvalid_pad) #启动模型训练对验证集输入 xvalid_pad 做前向推断，得到每条样本为正类（toxic）的概率数组\npredicted_labels = (scores > 0.5).astype(int)  # 以 0.5 为阈值，将概率转换成 0/1 的离散标签。\naccuracy = accuracy_score(yvalid, predicted_labels) #用 sklearn 的 accuracy_score 计算预测标签与真实标签 yvalid 之间的准确率\nprint(f\"Accuracy: {accuracy:.4f}\")  # 输出格式化为4位小数","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores_model.append({'Model': 'Bi-directional LSTM','AUC_Score': accuracy})\n#把当前模型的名称和准确率封装成字典，添加到 scores_model 列表中","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores_model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T17:07:21.356285Z","iopub.execute_input":"2025-06-20T17:07:21.356663Z","iopub.status.idle":"2025-06-20T17:07:21.362756Z","shell.execute_reply.started":"2025-06-20T17:07:21.356629Z","shell.execute_reply":"2025-06-20T17:07:21.361788Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Seq2Seq Model Architecture  \nSeq2Seq is a many to many RNN architecture where the input is a sequence and the output is also a sequence (where input and output sequences can be or cannot be of different lengths). This architecture is used in a lot of applications like Machine Translation, text summarization, question answering etc  \nSeq2Seq 是一种多对多 RNN 架构,输入是一个串行,输出也是一个串行(输入和输出串行可以或不能具有不同的长度)。这 种架构用于许多应用进程,如机器翻译、文本摘要、问答等。","metadata":{}},{"cell_type":"code","source":"'''from keras.layers import Input, LSTM, Dense, RepeatVector, TimeDistributed, Attention\nfrom keras.models import Model\n\n# 1. 定义超参\nEMBED_DIM   = 300   # GloVe 维度\nENC_UNITS   = 128   # Encoder 隐藏单元\nDEC_UNITS   = 128   # Decoder 隐藏单元\nMAX_LEN     = max_len  # 500 或你前面设定的长度\nVOCAB_SIZE  = len(word_index) + 1\n\n# 2. 构建输入层\nencoder_inputs = Input(shape=(MAX_LEN,), name=\"encoder_inputs\")   # (batch, timesteps)\ndecoder_inputs = Input(shape=(MAX_LEN,), name=\"decoder_inputs\")   # 同长度，分类任务可全 0\n\n# 3. 词嵌入层（共享）\nembedding_layer = Embedding(\n    input_dim   = VOCAB_SIZE,\n    output_dim  = EMBED_DIM,\n    weights     = [embedding_matrix],\n    input_shape = (MAX_LEN,),\n    trainable   = False,\n    name        = \"shared_embedding\"\n)\n\n# 4. Encoder\nenc_embed = embedding_layer(encoder_inputs)                        # (batch, MAX_LEN, EMBED_DIM)\nencoder_lstm = LSTM(ENC_UNITS, return_state=True, name=\"encoder_lstm\")\n_, state_h, state_c = encoder_lstm(enc_embed)                     # 只保留最后时刻的状态\n\n# 5. Decoder\n# 5.1 先用 RepeatVector 把 context 向量广播到每个时间步\ncontext = RepeatVector(MAX_LEN, name=\"repeat_vector\")([state_h, state_c])\ndec_embed = embedding_layer(decoder_inputs)                        # (batch, MAX_LEN, EMBED_DIM)\n\n# 5.2 拼接 context 与 decoder embed\nfrom keras.layers import Concatenate\ndec_concat = Concatenate(name=\"encoder_decoder_concat\")([context, dec_embed])\n\n# 5.3 Decoder LSTM 返回全部时刻的输出\ndecoder_lstm = LSTM(DEC_UNITS, return_sequences=True, name=\"decoder_lstm\")\ndec_outputs = decoder_lstm(dec_concat)                            # (batch, MAX_LEN, DEC_UNITS)\n\n# 6. Attention（可选）\n# 6.1 计算 attention 权重\nattention_layer = Attention(name=\"attention_layer\")\nattn_out = attention_layer([dec_outputs, dec_outputs])            # 自注意力示例\n\n# 7. 时间分布的全连接 + 池化得到分类向量\ntd_dense = TimeDistributed(Dense(64, activation=\"relu\"), name=\"td_dense\")(attn_out)\nfrom keras.layers import GlobalMaxPooling1D\npooled = GlobalMaxPooling1D(name=\"global_max_pool\")(td_dense)     # (batch, 64)\n\n# 8. 输出层\noutput = Dense(1, activation=\"sigmoid\", name=\"classifier\")(pooled)\n\n# 9. 定义并编译模型\nmodel_seq2seq = Model(inputs=[encoder_inputs, decoder_inputs], outputs=output, name=\"seq2seq_classifier\")\nmodel_seq2seq.compile(\n    loss      = \"binary_crossentropy\",\n    optimizer = \"adam\",\n    metrics   = [\"accuracy\"]\n)\n\n# 10. 查看结构\nmodel_seq2seq.summary()\n\n# 11. 训练\n# 对 decoder_inputs，我们这里简单用全零输入（或可同 X_train_pad）\nzero_decoder_input = np.zeros_like(X_train_pad)\nhistory_seq2seq = model_seq2seq.fit(\n    [X_train_pad, zero_decoder_input], y_train,\n    epochs          = 5,\n    batch_size      = 64,\n    validation_data = ([X_valid_pad, np.zeros_like(X_valid_pad)], y_valid),\n    callbacks       = [EarlyStopping(monitor=\"val_loss\", patience=2, restore_best_weights=True)]\n)\n\n# 12. 评估并记录\nscores = model_seq2seq.predict([X_valid_pad, np.zeros_like(X_valid_pad)])\npreds  = (scores > 0.5).astype(int)\nacc = accuracy_score(y_valid, preds)\nprint(f\"Seq2Seq 模型验证集准确率: {acc:.4f}\")\nscores_model.append({'Model': 'Seq2Seq', 'AUC_Score': acc})'''","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"1. 更丰富的时序建模  \n\n普通的单向或双向 RNN/LSTM 只在编码阶段捕捉依赖。Seq2Seq+Attention 按时序多次“解码”，有助于模型学到更复杂的时序特征。  \n\n2. 引入注意力机制  \n\n注意力让模型能“软对齐”序列内部的重要位置，提升信息聚焦能力，特别是在长序列上优势明显。  \n\n3. 分类任务的变体探索  \n\n虽然解码端不需要真正生成新词，但借助解码过程可以模拟一种“动态阅读—总结—再阅读” 的架构，往往能挖掘出隐性特征。    \n\n这样一个 Seq2Seq+Attention 的分类器，就能在情感分析、毒性检测等任务中，充分利用时序和注意力的优势，往往在长文本上比单纯的 RNN/LSTM 表现更稳健。","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# Visualization of Results obtained from various Deep learning models\nresults = pd.DataFrame(scores_model).sort_values(by='AUC_Score',ascending=False)\n#将之前收集的 scores_model（列表里每项是 {'Model':…, 'AUC_Score':…}）转成 Pandas DataFrame\n#然后按 'AUC_Score' 从大到小排序，得到各模型性能的排行榜\nresults.style.background_gradient(cmap='Blues')\n#给这个表格添加“蓝色渐变”背景，高分单元格颜色更深，直观突出性能更好的模型","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.io as pio\n# 在 Kaggle 中常用的渲染器\npio.renderers.default = \"iframe_connected\"\n#用 Plotly 的 Funnelarea（漏斗图）将模型名称和对应分数映射到面积大小\nfig = go.Figure(go.Funnelarea(\n    text =results.Model,\n    values = results.AUC_Score,\n    title = {\"position\": \"top center\", \"text\": \"Funnel-Chart of Sentiment Distribution\"}\n    ))\nfig.show(renderer=\"kaggle\")      # 在 Kaggle Notebook 上\n#text 表示每个扇区的标签（模型名），values 表示面积大小（AUC 得分）；title 设置图表标题并居中显示","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Transformers : Attention is all you need\nTransformer 是一种基于自注意力机制的并行化序列模型，通过将输入映射为查询（Q）、键（K）、值（V）三个向量，并计算它们的加权组合来捕捉全局依赖；多头注意力进一步让模型从多个子空间学习不同的关系，再结合位置编码补偿其对序列顺序的无意识。对于文本分类，我们通常只用其编码器部分，将分词后的文本输入 Transformer，取 [CLS]（或池化）输出接一个全连接层，用 Sigmoid/Softmax 得到有毒／无毒的预测概率；这种结构既能高效并行训练，又能长程依赖建模，是当前 NLP 任务中的主流方案。","metadata":{}},{"cell_type":"code","source":"#1️⃣ 加载依赖与工具包\nimport os #Python 标准库，用于文件路径、环境变量等操作\nimport tensorflow as tf #加载 TensorFlow，NLP 模型训练与数据管道构建的核心框架\nfrom tensorflow.keras.layers import Dense, Input\nfrom tensorflow.keras.optimizers import Adam #加载 Adam 优化器，用于参数更新\nfrom tensorflow.keras.models import Model #Keras 函数式 API 的模型基类\nfrom tensorflow.keras.callbacks import ModelCheckpoint #训练时保存最佳模型的回调\nfrom kaggle_datasets import KaggleDatasets #Kaggle 提供的工具，用于获取竞赛数据集的 GCS 路径\nimport transformers #Hugging Face 的 Transformers 库，用于加载预训练 Transformer 模型和 Tokenizer\nfrom tokenizers import BertWordPieceTokenizer #Hugging Face 的快速分词工具，基于 WordPiece 算法","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T17:11:00.254591Z","iopub.execute_input":"2025-06-20T17:11:00.254965Z","iopub.status.idle":"2025-06-20T17:11:00.260487Z","shell.execute_reply.started":"2025-06-20T17:11:00.254933Z","shell.execute_reply":"2025-06-20T17:11:00.259521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2️⃣ 加载数据\n\ntrain1 = pd.read_csv(\"/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-toxic-comment-train.csv\")\nvalid = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/validation.csv')\ntest = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/test.csv')\nsub = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/sample_submission.csv')\n#train1、valid、test、sub 分别对应训练集、验证集、测试集和提交模板","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T17:11:03.853832Z","iopub.execute_input":"2025-06-20T17:11:03.854468Z","iopub.status.idle":"2025-06-20T17:11:05.667205Z","shell.execute_reply.started":"2025-06-20T17:11:03.854432Z","shell.execute_reply":"2025-06-20T17:11:05.666456Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Tokenization\n\n构建高性能 Tokenizer","metadata":{}},{"cell_type":"markdown","source":"为什么要用 Hugging Face 的 Tokenizer？  \n1. 子词级分词（WordPiece/BPE）  \n\nHugging Face Tokenizer（如 DistilBertTokenizer、BertWordPieceTokenizer）采用   WordPiece 或 BPE 算法，把未知新词切分成已有子词，有效处理海量长尾词汇和拼写变体。  \n\n2. 与预训练模型一致  \n\n它使用的词表和分词逻辑，和你加载的预训练模型（如 distilbert-base-multilingual-cased）一模一样，保证编码→模型→解码的输入输出保持一致。  \n\n3. 高速 C++ 实现    \n\nHugging Face 的 tokenizers 库底层用 Rust/C++ 写成，支持批量、并行化分词，比纯 Python 分词快几十倍，显著缩短大型语料预处理时间。  \n\n4. 自动处理特殊 Token  \n\n一键插入 [CLS]、[SEP]、[PAD]、[UNK] 等特殊标记，并生成对应的 attention mask、token type ids，简化了下游代码。  ","metadata":{}},{"cell_type":"code","source":"# First load the real tokenizer\ntokenizer = transformers.DistilBertTokenizer.from_pretrained('distilbert-base-multilingual-cased')\n#从 Hugging Face Hub 加载与 distilbert-base-multilingual-cased 模型一致的 Python Tokenizer，实现词到 ID 的映射\n# Save the loaded tokenizer locally 将词表文件（vocab.txt）保存到当前工作目录，供快速分词器加载\ntokenizer.save_pretrained('.') \n# Reload it with the huggingface tokenizers library\nfast_tokenizer = BertWordPieceTokenizer('vocab.txt', lowercase=False)\nfast_tokenizer\n#用 Hugging Face Tokenizers 库的 C++ 实现，加载同一份词表，性能更优；\n\nlowercase=False 保持多语言大小写敏感","metadata":{"trusted":true,"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#3️⃣ 文本预处理：Tokenizer 编码函数\n#定义批量编码函数\ndef fast_encode(texts, tokenizer, chunk_size=256, maxlen=512):\n    \"\"\"\n    Encoder for encoding the text into sequence of integers for BERT Input\n    将输入的文本列表（texts）通过指定的 tokenizer 批量编码为固定长度的整数序列（Token IDs），\n    并支持分块处理以优化内存使用。\n    \"\"\"\n    #将文本列表批量分块（chunk_size），高效地转换为固定长度的 Token ID 矩阵 (N, maxlen)\n    tokenizer.enable_truncation(max_length=maxlen)\n    tokenizer.enable_padding(max_length=maxlen) #保证每条序列长度固定，超长截断、补短填 0\n    all_ids = []\n    #一次性对一个文本块进行编码，返回多个 Encoding 对象，每个 .ids 属性即 ID 列表\n    for i in tqdm(range(0, len(texts), chunk_size)):\n        text_chunk = texts[i:i+chunk_size].tolist()\n        encs = tokenizer.encode_batch(text_chunk)\n        all_ids.extend([enc.ids for enc in encs])\n    \n    return np.array(all_ids)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T17:11:11.855917Z","iopub.execute_input":"2025-06-20T17:11:11.856801Z","iopub.status.idle":"2025-06-20T17:11:11.862852Z","shell.execute_reply.started":"2025-06-20T17:11:11.856766Z","shell.execute_reply":"2025-06-20T17:11:11.861857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\n假设 texts = [\"hi world\", \"bert model\", \"foo\"]\nchunk_size=2, maxlen=5\n词表（简化）映射： {\"hi\":10,\"world\":11,\"bert\":20,\"model\":21,\"foo\":30}\ntokenizer.encode_batch → 对每条文本生成 ids 列表，并做截断/填充：\n文本\t  原始 Token IDs \t截断/填充到长度 5 → enc.ids\n\"hi world\"\t[10,11] \t[0,0,10,11,0]\n\"bert model\"[20,21]\t    [0,0,20,21,0]\n\"foo\"\t     [30]\t     [0,0,0,30,0] \n\n返回：一个 NumPy 数组，形状 (3,5)：\n[[ 0,  0, 10, 11,  0],\n [ 0,  0, 20, 21,  0],\n [ 0,  0,  0, 30,  0]]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#4️⃣ 执行分词编码 + 标签准备\nfrom tokenizers import BertWordPieceTokenizer\n# 然后再执行编码\nx_train = fast_encode(train1.comment_text.astype(str), fast_tokenizer, maxlen=MAX_LEN)\nx_train = fast_encode(train1.comment_text.astype(str), fast_tokenizer, maxlen=MAX_LEN)\nx_valid = fast_encode(valid.comment_text.astype(str), fast_tokenizer, maxlen=MAX_LEN)\nx_test = fast_encode(test.content.astype(str), fast_tokenizer, maxlen=MAX_LEN)\n\ny_train = train1.toxic.values\ny_valid = valid.toxic.values\n#将训练、验证、测试文本分别转为 Token ID 数组 x_train, x_valid, x_test。y_trainy_valid 为对应的二分类标签（0/1）NumPy 数组","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-20T17:11:48.589914Z","iopub.execute_input":"2025-06-20T17:11:48.590632Z","iopub.status.idle":"2025-06-20T17:11:48.631030Z","shell.execute_reply.started":"2025-06-20T17:11:48.590594Z","shell.execute_reply":"2025-06-20T17:11:48.629767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#5️⃣ 配置训练参数和 TF 数据管道\nAUTO = tf.data.experimental.AUTOTUNE\n#让 tf.data 在执行 dataset.prefetch() 或 dataset.map() 等操作时，自动并行化和调度输入数据的读取与预处理，以最大化 GPU/CPU 利用率、减少 I/O 瓶颈\n# Configuration\nEPOCHS = 3 #训练时将完整遍历训练集的次数设为 3\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync #在分布式训练下的全局 batch size。16 是每个设备（GPU/TPU 副本）上的本地 batch\nMAX_LEN = 192 #Transformer 模型输入序列的最大长度","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#训练集\ntrain_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((x_train, y_train)) #从 NumPy 数组创建 Dataset，每个元素是 (x_i, y_i) 或 x_i\n    .repeat() #训练集无限重复，配合 steps_per_epoch 控制结束\n    .shuffle(2048) #随机打乱 2048 条样本，提升泛化！\n    .batch(BATCH_SIZE) #按全局 batch size 批量化，配合分布式策略\n    .prefetch(AUTO)\n)\n#验证集\nvalid_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((x_valid, y_valid))\n    .batch(BATCH_SIZE)\n    .cache() #对验证集做缓存，加速多轮评估\n    .prefetch(AUTO)\n)\n#测试集\ntest_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices(x_test)\n    .batch(BATCH_SIZE)\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#6️⃣ 构建 Transformer 模型\ndef build_model(transformer, max_len=512):\n    \"\"\"\n    function for training the BERT model\n    \"\"\"\n    input_word_ids = Input(shape=(max_len,), dtype=tf.int32, name=\"input_word_ids\") \n    ###输入 (input_word_ids)为 (batch_size, max_len) 的整数张量，表示批量文本的Token ID序列。\n\n    sequence_output = transformer(input_word_ids)[0] ###对于大多数HuggingFace的Transformer模型（如BertModel、TFDistilBertModel），\n    ###输出是一个元组：outputs[0]: 取每条序列第 0 个位置（[CLS]）的向量，作为序列表示，所有Token的上下文向量（形状 (batch_size, max_len, hidden_dim)）\n\n    cls_token = sequence_output[:, 0, :] ### # 取每个样本的第0个Token（[CLS]）\n    ###在BERT预训练中，[CLS] Token的向量被设计为聚合整个序列的信息。分类任务中，通常仅用 [CLS] 向量作为分类头的输入。\n    out = Dense(1, activation='sigmoid')(cls_token)\n    \n    model = Model(inputs=input_word_ids, outputs=out)\n    model.compile(Adam(lr=1e-5), loss='binary_crossentropy', metrics=['accuracy'])\n    #使用学习率 1×10−5 的 Adam 微调预训练模型\n    return model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#算例：\n假设 max_len=5，batch size = 2\ninput_word_ids 张量示例：\n[[101, 2003, 1037, 102,   0],   # [CLS] this a [SEP] [PAD]\n [101, 7592, 2088, 102,   0]]   # [CLS] hello world [SEP] [PAD]\ntransformer(input_word_ids)[0]\n输出 sequence_output 形状 (2, 5, H)，其中 H 是隐藏维度（比如 768）。\n例如：\nsequence_output = tf.random.normal((2,5,768))\ncls_token = sequence_output[:,0,:]\n取第 0 位置 [CLS] 的向量，形状 (2,768)：\n[[…768-dim…],  # 样本1的CLS向量\n […768-dim…]]  # 样本2的CLS向量\nout = Dense(1, activation='sigmoid')(cls_token)\n对 (2,768) 的输入做全连接 + Sigmoid，输出 (2,1) 的概率数组：\n[[0.12],  # 样本1属于正类的概率\n [0.87]]  # 样本2的概率\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Starting Training\n\nIf you want to use any another model just replace the model name in transformers._____ and use accordingly","metadata":{}},{"cell_type":"code","source":"#7️⃣ 加载预训练 Transformer 并构建模型\nwith strategy.scope():\n    transformer_layer = (\n        transformers.TFDistilBertModel #在线下载并加载预训练权重\n        .from_pretrained('distilbert-base-multilingual-cased')\n    )\n    model = build_model(transformer_layer, max_len=MAX_LEN)\nmodel.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_steps = x_train.shape[0] // BATCH_SIZE\ntrain_history = model.fit(\n    train_dataset,\n    steps_per_epoch=n_steps, #每轮训练需执行的批次数，使一次 epoch 刚好消费完整训练集\n    validation_data=valid_dataset, #指定验证集进行每轮后评估\n    epochs=EPOCHS\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#8️⃣ 模型训练（train + fine-tune）\n#额外在验证集上进一步 fine‑tune，通常用于领域自适应\nn_steps = x_valid.shape[0] // BATCH_SIZE\ntrain_history_2 = model.fit(\n    valid_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    epochs=EPOCHS*2\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#9️⃣ 预测并提交\nsub['toxic'] = model.predict(test_dataset, verbose=1) #对测试集做分布式推断，得到概率向量\nsub.to_csv('submission.csv', index=False) #将提交模板中 toxic 列填入预测结果并导出 CSV","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**总体流程说明**  \n阶段\t     说明  \n数据加载\t加载训练/验证/测试集  \n文本编码\t使用 BertWordPieceTokenizer 将文本转为 Token IDs  \n模型构建\t使用预训练 DistilBERT + 分类头（Sigmoid）构建模型  \n数据准备\t使用 tf.data 构建训练/验证数据管道  \n模型训练\t在分布式设备（GPU/TPU）上进行训练、微调  \n推理与提交\t用模型在测试集预测，生成提交结果  ","metadata":{}}]}