From 6b0133bee491f4efd0bc1ae0e964989bde8cea04 Mon Sep 17 00:00:00 2001 From: WangJianing <851019059@qq.com> Date: Sun, 12 Sep 2021 18:05:27 +0800 Subject: [PATCH 1/3] wjn-pr --- NLPAlgo/MetaFineTuning/README.md | 123 +++ NLPAlgo/MetaFineTuning/bert.py | 355 +++++++ NLPAlgo/MetaFineTuning/classifier.py | 252 +++++ NLPAlgo/MetaFineTuning/config.py | 106 ++ .../MetaFineTuning/convert_tf_ckpt_to_of.py | 90 ++ .../data/k-shot-cross/g1/16-42/dev.csv | 96 ++ .../g1/16-42/ofrecord/dev/dev.of_record-0 | Bin 0 -> 56821 bytes .../g1/16-42/ofrecord/train/train.of_record-0 | Bin 0 -> 56788 bytes .../g1/16-42/ofrecord/train/weight.npy | Bin 0 -> 3494 bytes .../data/k-shot-cross/g1/16-42/train.csv | 96 ++ .../data/k-shot-single/SST-2/16-42/dev.csv | 32 + .../SST-2/16-42/ofrecord/dev/dev.of_record-0 | Bin 0 -> 18872 bytes .../16-42/ofrecord/eval/eval.of_record-0 | Bin 0 -> 516877 bytes .../16-42/ofrecord/train/train.of_record-0 | Bin 0 -> 18978 bytes .../data/k-shot-single/SST-2/16-42/test.csv | 872 +++++++++++++++ .../data/k-shot-single/SST-2/16-42/train.csv | 32 + .../MetaFineTuning/data_utils/data_process.py | 268 +++++ .../data_utils/generate_feature.py | 164 +++ .../data_utils/task_processors.py | 990 ++++++++++++++++++ NLPAlgo/MetaFineTuning/data_utils/utils.py | 319 ++++++ NLPAlgo/MetaFineTuning/finetuning.py | 293 ++++++ NLPAlgo/MetaFineTuning/images/fine_tuning.png | Bin 0 -> 59560 bytes .../images/meta_fine_tuning.png | Bin 0 -> 59560 bytes NLPAlgo/MetaFineTuning/log.py | 50 + NLPAlgo/MetaFineTuning/meta_finetuning.py | 281 +++++ NLPAlgo/MetaFineTuning/preprocess.py | 277 +++++ NLPAlgo/MetaFineTuning/tokenization.py | 467 +++++++++ NLPAlgo/MetaFineTuning/util.py | 190 ++++ .../\345\221\275\344\273\244.txt" | 12 + NLPAlgo/VanillaKowledgeDistillation/README.md | 45 + .../images/kd_student.png | Bin 0 -> 90939 bytes .../images/kd_teacher.png | Bin 0 -> 98019 bytes NLPAlgo/VanillaKowledgeDistillation/main.py | 231 ++++ NLPAlgo/VanillaKowledgeDistillation/model.py | 309 ++++++ NLPAlgo/VanillaKowledgeDistillation/util.py | 187 ++++ README.md | 5 + 36 files changed, 6142 insertions(+) create mode 100644 NLPAlgo/MetaFineTuning/README.md create mode 100644 NLPAlgo/MetaFineTuning/bert.py create mode 100644 NLPAlgo/MetaFineTuning/classifier.py create mode 100644 NLPAlgo/MetaFineTuning/config.py create mode 100644 NLPAlgo/MetaFineTuning/convert_tf_ckpt_to_of.py create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/dev.csv create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/ofrecord/dev/dev.of_record-0 create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/ofrecord/train/train.of_record-0 create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/ofrecord/train/weight.npy create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/train.csv create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/dev.csv create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/dev/dev.of_record-0 create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/eval/eval.of_record-0 create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/train/train.of_record-0 create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/test.csv create mode 100644 NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/train.csv create mode 100644 NLPAlgo/MetaFineTuning/data_utils/data_process.py create mode 100644 NLPAlgo/MetaFineTuning/data_utils/generate_feature.py create mode 100644 NLPAlgo/MetaFineTuning/data_utils/task_processors.py create mode 100644 NLPAlgo/MetaFineTuning/data_utils/utils.py create mode 100644 NLPAlgo/MetaFineTuning/finetuning.py create mode 100644 NLPAlgo/MetaFineTuning/images/fine_tuning.png create mode 100644 NLPAlgo/MetaFineTuning/images/meta_fine_tuning.png create mode 100644 NLPAlgo/MetaFineTuning/log.py create mode 100644 NLPAlgo/MetaFineTuning/meta_finetuning.py create mode 100644 NLPAlgo/MetaFineTuning/preprocess.py create mode 100644 NLPAlgo/MetaFineTuning/tokenization.py create mode 100644 NLPAlgo/MetaFineTuning/util.py create mode 100644 "NLPAlgo/MetaFineTuning/\345\221\275\344\273\244.txt" create mode 100644 NLPAlgo/VanillaKowledgeDistillation/README.md create mode 100644 NLPAlgo/VanillaKowledgeDistillation/images/kd_student.png create mode 100644 NLPAlgo/VanillaKowledgeDistillation/images/kd_teacher.png create mode 100644 NLPAlgo/VanillaKowledgeDistillation/main.py create mode 100644 NLPAlgo/VanillaKowledgeDistillation/model.py create mode 100644 NLPAlgo/VanillaKowledgeDistillation/util.py diff --git a/NLPAlgo/MetaFineTuning/README.md b/NLPAlgo/MetaFineTuning/README.md new file mode 100644 index 0000000..ee5b0ca --- /dev/null +++ b/NLPAlgo/MetaFineTuning/README.md @@ -0,0 +1,123 @@ +## Meta Fine-tuning +Oneflow实现[Meta Fine-tuning(MFT)](https://aclanthology.org/2020.emnlp-main.250.pdf "Meta Fine-tuning(MFT)")算法 + +--- +## MFT概述: +在预训练语言模型的微调阶段,运用Meta learning的思想,在多个相似的domain(或task)之间学习meta knowledge,并将预训练语言模型迁移到通用的领域空间中。简单地来讲,MFT主要分为三个阶段: +- 先通过预训练语言模型获得每个domain的prototypical embedding,并计算prototypical score; +- 引入领域对抗的思想,进行meta fine-tuning,将同属于同一个domain(task)的数据通过N-way K +-shot法进行采样,并混合起来;然后进行学习domain(task)之间的通用知识; +- 对于domain(task)内的每个具体的任务,分别进行标准fine-tuning + + +## 数据获取 +本部分将采用Oneflow静态框架实现MFT算法,数据采用MR、CR、SST-2(情感分析二分类任务),数据格式为: +> [text]\t[domain/task name]\t[label] + +例如 +> it 's a stale , overused cocktail using the same olives since 1962 as garnish . SST-2 0 + +## 数据文件分布说明 + +```shell +data +├── k-shot-cross // 表示多个domain或task混合,用于Meta Fine-tuning阶段的训练和验证 +│   └── g1 // 第1组domain/task +│   └── 16-42 // k取16,random seed为42 +│   └── ofrecord // ofrecord文件 +│   ├── train // 训练集 +│   │   ├── train.of_record-0 // 训练集 +│   │   └── weight.npy // 训练集每个样本的prototypical score、 +│   ├── dev // 验证集 +│   │   └── dev.of_record-0 // 验证集 +│   ├── train.csv // 训练集原始文件 +│   └── dev.csv // 验证集原始文件 +└── k-shot-single // 表示某个domain或task,用于标准Fine-tuning阶段的训练、验证和测试 + └── SST-2 // 以SST-2数据集为例 + └── 16-42 // k取16,random seed为42 + └── ofrecord // ofrecord文件 + ├── train // 训练集 + │   └── train.of_record-0 // 训练集 + ├── dev // 验证集 + │   └── dev.of_record-0 // 验证集 + ├── test // 测试集 + │   └── test.of_record-0 // 测试集 + ├── train.csv // 训练集原始文件 + ├── dev.csv // 验证集原始文件 + └── test.scv // 测试集原始文件 +``` + + +## 实验设置 + +#### Step1:首先获取训练样本中每个样本的prototypical score: +运行preprocess.py,在预训练的BERT模型上(uncased_L-12_H-768_A-12_oneflow),获取BERT的最后一层隐向量,并获得训练集的prototype embedding,以及各个样本的prototypical score,并保存到磁盘中; + +```shell +python3 preprocess.py \ + --task_name g1 \ + --model_load_dir uncased_L-12_H-768_A-12_oneflow \ # 基于Oneflow的BERT模型 + --data_dir data/k-shot-cross/g1/16-42 \ + --num_epochs 4 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 96 \ + --dev_example_num 96 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --resave_ofrecord +``` +执行完后,将会在相应的 `data/k-shot-cross/g1/16-42` 目录下生成ofrecord目录。 + +#### Step2:其次进行Meta Fine-tuning + +运行meta_finetuning.py,在预训练的BERT模型上(uncased_L-12_H-768_A-12_oneflow),对cross-domain/task进行微调: +```shell +python3 meta_finetuning.py \ + --task_name g1 \ + --model_load_dir uncased_L-12_H-768_A-12_oneflow \ + --data_dir data/k-shot-cross/g1/16-42 \ + --num_epochs 63 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 96 \ + --dev_example_num 96 \ + --batch_size_per_device 4 \ + --dev_batch_size_per_device 2 \ + --dev_every_step_num 100 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --learning_rate 5e-5 \ + --resave_ofrecord +``` +例如如下图,SST-2、MR、CR三个domain组成g1数据集,均为二分类任务,每个domain的class有16个样本,总共有96个样本,训练集和验证集均为96个样本,meta fine-tuning后验证集最高准确率为78.125%。微调后,将获得meta learner(oneflow模型保存在“output/model_save-2021-06-15-08:54:42/snapshot_best_mft_model_g1_dev_0.78125”中) + +![Meta Fine-tuning实验截图](images/meta_fine_tuning.png) + + +#### Step3:最后执行标准Fine-tuning + +加载Step2生成的模型文件(例如`output/model_save-2021-06-15-08:54:42/snapshot_best_mft_model_g1_dev_0.7083333333333334`),执行标准微调 + +```shell +python3 finetuning.py \ + --task_name sst-2 \ + --model_load_dir output/model_save-2021-06-15-08:54:42/snapshot_best_mft_model_g1_dev_0.7083333333333334 \ + --data_dir data/k-shot-single/SST-2/16-42 \ + --num_epochs 64 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 32 \ + --dev_example_num 32 \ + --eval_example_num 872 \ + --batch_size_per_device 2 \ + --dev_batch_size_per_device 2 \ + --eval_batch_size_per_device 2 \ + --dev_every_step_num 50 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --learning_rate 1e-5 \ + --resave_ofrecord +``` + +例如如下图,选择模型snapshot_best_mft_model_g1_dev_0.78125,选择SST-2数据集(训练集32个样本,测试集872个样本),然后执行标准微调,最高验证集准确率为68.75%,保存对应的模型后,在测试集上准确率为87.50%。 +![Fine-tuning实验截图](images/fine_tuning.png) + + diff --git a/NLPAlgo/MetaFineTuning/bert.py b/NLPAlgo/MetaFineTuning/bert.py new file mode 100644 index 0000000..8dd580f --- /dev/null +++ b/NLPAlgo/MetaFineTuning/bert.py @@ -0,0 +1,355 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import oneflow as flow +import oneflow.core.common.data_type_pb2 as data_type_util +import oneflow.core.operator.op_conf_pb2 as op_conf_util +import math + +# BERT的基础模型 +class BertBackbone(object): + + def __init__(self, + input_ids_blob, # 句子id序列 [batch_size, sequence_length] + input_mask_blob, + token_type_ids_blob, + vocab_size, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02): + + with flow.scope.namespace("bert"): + with flow.scope.namespace("embeddings"): + # 输出句子token对应的embedding,以及embedding table + (self.embedding_output_, self.embedding_table_) = _EmbeddingLookup( + input_ids_blob=input_ids_blob, + vocab_size=vocab_size, + embedding_size=hidden_size, # 词向量维度 + initializer_range=initializer_range, + word_embedding_name="word_embeddings") + # 累加type embedding或position embedding + self.embedding_output_ = _EmbeddingPostprocessor( + input_blob=self.embedding_output_, + seq_length=seq_length, + embedding_size=hidden_size, + use_token_type=True, + token_type_ids_blob=token_type_ids_blob, + token_type_vocab_size=type_vocab_size, + token_type_embedding_name="token_type_embeddings", + use_position_embeddings=True, + position_embedding_name="position_embeddings", + initializer_range=initializer_range, + max_position_embeddings=max_position_embeddings, + dropout_prob=hidden_dropout_prob, + ) + with flow.scope.namespace("encoder"): + attention_mask_blob = _CreateAttentionMaskFromInputMask( # 生成mask矩阵 + input_mask_blob, from_seq_length=seq_length, to_seq_length=seq_length) + addr_blob = _CreateAddrFromAttentionMask( # 将mask矩阵进行转换 + attention_mask_blob, from_seq_length=seq_length, to_seq_length=seq_length) + self.all_encoder_layers_ = _TransformerModel( # Transformer模型(默认12层) + input_blob=self.embedding_output_, + addr_blob=addr_blob, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + intermediate_act_fn=GetActivation(hidden_act), + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + initializer_range=initializer_range, + do_return_all_layers=True) + self.sequence_output_ = self.all_encoder_layers_[-1] # Transformer最后一层输出向量 + + def embedding_output(self): return self.embedding_output_ + def all_encoder_layers(self): return self.all_encoder_layers_ + def sequence_output(self): return self.sequence_output_ + def embedding_table(self): return self.embedding_table_ + def get_layer_embedding(self, index): # 获得指定层的embedding + assert index >= 0 and index < self.all_encoder_layers_.shape[0] + return self.all_encoder_layers_[index] + +# oneflow初始化函数加载器 +def CreateInitializer(std): + return flow.truncated_normal(std) # 截断的产生正态分布的随机数,即随机数与均值的差值若大于两倍的标准差,则重新生成 + +def _Gelu(in_blob): + return flow.math.gelu(in_blob) + +# Transformer模型 +def _TransformerModel(input_blob, + addr_blob, + seq_length, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + intermediate_act_fn=_Gelu, + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + initializer_range=0.02, + do_return_all_layers=False): + + assert hidden_size % num_attention_heads == 0 + attention_head_size = int(hidden_size / num_attention_heads) + input_width = hidden_size + prev_output_blob = flow.reshape(input_blob, (-1, input_width)) + all_layer_output_blobs = [] + for layer_idx in range(num_hidden_layers): # Transformer包含若干层 + with flow.scope.namespace("layer_%d"%layer_idx): + layer_input_blob = prev_output_blob + with flow.scope.namespace("attention"): + with flow.scope.namespace("self"): + attention_output_blob = _AttentionLayer( + from_blob=layer_input_blob, + to_blob=layer_input_blob, + addr_blob=addr_blob, + num_attention_heads=num_attention_heads, + size_per_head=attention_head_size, + attention_probs_dropout_prob=attention_probs_dropout_prob, + initializer_range=initializer_range, + do_return_2d_tensor=True, + from_seq_length=seq_length, + to_seq_length=seq_length) + with flow.scope.namespace("output"): + attention_output_blob = _FullyConnected( + attention_output_blob, + input_size=num_attention_heads * attention_head_size, + units=hidden_size, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + attention_output_blob = _Dropout(attention_output_blob, hidden_dropout_prob) + attention_output_blob = attention_output_blob + layer_input_blob # 残差 + attention_output_blob = _LayerNorm(attention_output_blob, hidden_size) + with flow.scope.namespace("intermediate"): + if callable(intermediate_act_fn): + act_fn = op_conf_util.kNone + else: + act_fn = intermediate_act_fn + intermediate_output_blob = _FullyConnected( + attention_output_blob, + input_size=num_attention_heads * attention_head_size, + units=intermediate_size, + activation=act_fn, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + if callable(intermediate_act_fn): + intermediate_output_blob = intermediate_act_fn(intermediate_output_blob) + with flow.scope.namespace("output"): + layer_output_blob = _FullyConnected( + intermediate_output_blob, + input_size=intermediate_size, + units=hidden_size, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + layer_output_blob = _Dropout(layer_output_blob, hidden_dropout_prob) + layer_output_blob = layer_output_blob + attention_output_blob + layer_output_blob = _LayerNorm(layer_output_blob, hidden_size) + prev_output_blob = layer_output_blob + all_layer_output_blobs.append(layer_output_blob) + + input_shape = (-1, seq_length, hidden_size) + if do_return_all_layers: + final_output_blobs = [] + for layer_output_blob in all_layer_output_blobs: + final_output_blob = flow.reshape(layer_output_blob, input_shape) + final_output_blobs.append(final_output_blob) + return final_output_blobs + else: + final_output_blob = flow.reshape(prev_output_blob, input_shape) + return [final_output_blob] + +def _AttentionLayer(from_blob, + to_blob, + addr_blob, + num_attention_heads=1, + size_per_head=512, + query_act=op_conf_util.kNone, + key_act=op_conf_util.kNone, + value_act=op_conf_util.kNone, + attention_probs_dropout_prob=0.0, + initializer_range=0.02, + do_return_2d_tensor=False, + batch_size=None, + from_seq_length=None, + to_seq_length=None): + + def TransposeForScores(input_blob, num_attention_heads, seq_length, width): + output_blob = flow.reshape(input_blob, [-1, seq_length, num_attention_heads, width]) + output_blob = flow.transpose(output_blob, perm=[0, 2, 1, 3]) + return output_blob + + from_blob_2d = flow.reshape(from_blob, [-1, num_attention_heads * size_per_head]) + to_blob_2d = flow.reshape(to_blob, [-1, num_attention_heads * size_per_head]) + + query_blob = _FullyConnected( + from_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=query_act, + name="query", + weight_initializer=CreateInitializer(initializer_range)) + + key_blob = _FullyConnected( + to_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=key_act, + name="key", + weight_initializer=CreateInitializer(initializer_range)) + + value_blob = _FullyConnected( + to_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=value_act, + name="value", + weight_initializer=CreateInitializer(initializer_range)) + + query_blob = TransposeForScores(query_blob, num_attention_heads, from_seq_length, size_per_head) + key_blob = TransposeForScores(key_blob, num_attention_heads, to_seq_length, size_per_head) + + attention_scores_blob = flow.matmul(query_blob, key_blob, transpose_b=True) + attention_scores_blob = attention_scores_blob * (1.0 / math.sqrt(float(size_per_head))) + + attention_scores_blob = attention_scores_blob + addr_blob + attention_probs_blob = flow.nn.softmax(attention_scores_blob) + attention_probs_blob = _Dropout(attention_probs_blob, attention_probs_dropout_prob) + + value_blob = flow.reshape(value_blob, [-1, to_seq_length, num_attention_heads, size_per_head]) + value_blob = flow.transpose(value_blob, perm=[0, 2, 1, 3]) + context_blob = flow.matmul(attention_probs_blob, value_blob) + context_blob = flow.transpose(context_blob, perm=[0, 2, 1, 3]) + + if do_return_2d_tensor: + context_blob = flow.reshape(context_blob, [-1, num_attention_heads * size_per_head]) + else: + context_blob = flow.reshape(context_blob, [-1, from_seq_length, num_attention_heads * size_per_head]) + return context_blob + +def _FullyConnected(input_blob, input_size, units, activation=None, name=None, + weight_initializer=None): + weight_blob = flow.get_variable( + name=name + '-weight', + shape=[input_size, units], + dtype=input_blob.dtype, + initializer=weight_initializer) + bias_blob = flow.get_variable( + name=name + '-bias', + shape=[units], + dtype=input_blob.dtype, + initializer=flow.constant_initializer(0.0)) + output_blob = flow.matmul(input_blob, weight_blob) + output_blob = flow.nn.bias_add(output_blob, bias_blob) + return output_blob + +def _Dropout(input_blob, dropout_prob): + if dropout_prob == 0.0: + return input_blob + return flow.nn.dropout(input_blob, rate=dropout_prob) + + +def _LayerNorm(input_blob, hidden_size): + return flow.layers.layer_norm(input_blob, name='LayerNorm', begin_norm_axis=-1, begin_params_axis=-1) + + +def _CreateAttentionMaskFromInputMask(to_mask_blob, from_seq_length, to_seq_length): + output = flow.cast(to_mask_blob, dtype=flow.float) # 将指定的张量转换为对应的类型 + output = flow.reshape(output, [-1, 1, to_seq_length]) # [batch_size, 1, seq_len] + zeros = flow.constant(0.0, dtype=flow.float, shape=[from_seq_length, to_seq_length]) + output = zeros + output # 广播机制相加 + return output + + +def _CreateAddrFromAttentionMask(attention_mask_blob, from_seq_length, to_seq_length): + attention_mask_blob = flow.reshape(attention_mask_blob, [-1, 1, from_seq_length, to_seq_length]) + attention_mask_blob = flow.cast(attention_mask_blob, dtype=flow.float) + addr_blob = (attention_mask_blob - 1.0) * 10000.0 # mask为1的值变为0,mask为0的值变为-10000(非常小的数) + return addr_blob + +# 添加额外的embedding信息 +def _EmbeddingPostprocessor(input_blob, + seq_length, + embedding_size, + use_token_type=False, + token_type_ids_blob=None, + token_type_vocab_size=16, + token_type_embedding_name="token_type_embeddings", + use_position_embeddings=True, + position_embedding_name="position_embeddings", + initializer_range=0.02, + max_position_embeddings=512, + dropout_prob=0.1): + output = input_blob + + if use_token_type: # 如果使用token type embedding,使用预先加载或初始化的type embedding table + assert token_type_ids_blob is not None + token_type_table = flow.get_variable(name=token_type_embedding_name, + shape=[token_type_vocab_size, embedding_size], + dtype=input_blob.dtype, + initializer=CreateInitializer(initializer_range)) + token_type_embeddings = flow.gather(params=token_type_table, indices=token_type_ids_blob, axis=0) + output = output + token_type_embeddings # 按照BERT原文,直接加和即可 + + if use_position_embeddings: + position_table = flow.get_variable(name=position_embedding_name, + shape=[1, max_position_embeddings, embedding_size], + dtype=input_blob.dtype, + initializer=CreateInitializer(initializer_range)) + assert seq_length <= max_position_embeddings + if seq_length != max_position_embeddings: + position_table = flow.slice(position_table, begin=[None, 0, 0], size=[None, seq_length, -1]) + output = output + position_table + + output = _LayerNorm(output, embedding_size) + output = _Dropout(output, dropout_prob) + + return output + +# 创建EmbeddingLookup功能 +def _EmbeddingLookup(input_ids_blob, + vocab_size, + embedding_size=128, + initializer_range=0.02, + word_embedding_name="word_embeddings"): + # 创建初始化(或获取实现已经定义加载的)的embedding table + embedding_table = flow.get_variable(name=word_embedding_name, shape=[vocab_size, embedding_size], + dtype=flow.float, + initializer=CreateInitializer(initializer_range)) + output = flow.gather(params=embedding_table, indices=input_ids_blob, axis=0) + return output, embedding_table # 输出句子对应的embedding,以及整个embedding table + +# 获取激活函数(oneflow对象) +def GetActivation(name): + if name == 'linear': + return None + elif name == 'relu': + return flow.math.relu + elif name == 'tanh': + return flow.math.tanh + elif name == 'gelu': + return flow.math.gelu + else: + raise Exception("unsupported activation") + diff --git a/NLPAlgo/MetaFineTuning/classifier.py b/NLPAlgo/MetaFineTuning/classifier.py new file mode 100644 index 0000000..4b86d13 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/classifier.py @@ -0,0 +1,252 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import oneflow as flow +import bert as bert_util +import oneflow.core.operator.op_conf_pb2 as op_conf_util + +### Meta Fine-tuning +def MFTBERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + input_domain, + vocab_size, + input_weight, + layer_indexes=[-1], + num_domains=3, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + lambda_=0.1, # 两个loss的权重 + replace_prob=None, + get_output=False, # add by wjn,当为True时,表示只获取BERT的embedding +): + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + if get_output: + last_layer_output = backbone.sequence_output() # [bz, len, dim] + # indices = flow.tensor([0]) + # cls_output = flow.gather(last_layer_output, indices=indices, axis=1) # [bz, dim] 取CLS对应的embedding + cls_output = flow.math.reduce_mean(last_layer_output, axis=1) + # print('cls_output.shape=', cls_output.shape) + return cls_output + + # Meta Fine-tuning: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + ## Meta Fine-tuing: Corrupted Domain Classification loss + corrupted_loss = _AddCorruptedDomainCLSLoss(backbone.all_encoder_layers_, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range) + + # 对corrupted_loss进行加权求和 + corrupted_loss = flow.math.multiply(corrupted_loss, input_weight) + + return cls_loss + lambda_ * corrupted_loss, logit_blob + # return cls_loss, logit_blob + + + +### standard Fine-tuning +def BERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + vocab_size, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + replace_prob=None, +): + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + + # Meta Fine-tuning: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + return cls_loss, logit_blob + + + + +# BERT的CLS token进行分类 +def PooledOutput(sequence_output, hidden_size, initializer_range): + with flow.scope.namespace("bert-pooler"): + first_token_tensor = flow.slice( # 切片操作,提取每个样本的第一个token([CLS])对应的表征向量 + sequence_output, [None, 0, 0], [None, 1, -1]) + first_token_tensor = flow.reshape( + first_token_tensor, [-1, hidden_size]) + pooled_output = bert_util._FullyConnected( # 添加一个全连接层 + first_token_tensor, + input_size=hidden_size, + units=hidden_size, + weight_initializer=bert_util.CreateInitializer(initializer_range), + name="dense", + ) + pooled_output = flow.math.tanh(pooled_output) + return pooled_output + + +def _AddClassficationLoss(input_blob, label_blob, hidden_size, label_num, initializer_range, + scope_name='classification'): + with flow.scope.namespace(scope_name): + output_weight_blob = flow.get_variable( + name="output_weights", + shape=[label_num, hidden_size], + dtype=input_blob.dtype, + # initializer=bert_util.CreateInitializer(initializer_range), + initializer=flow.random_normal_initializer( + mean=0.0, stddev=initializer_range, seed=None, dtype=None) + ) + output_bias_blob = flow.get_variable( + name="output_bias", + shape=[label_num], + dtype=input_blob.dtype, + initializer=flow.constant_initializer(0.0), + ) + logit_blob = flow.matmul( # output_weight_blob先转置,再与input_bob相乘 + input_blob, output_weight_blob, transpose_b=True) # [batch_size, label_num] + logit_blob = flow.nn.bias_add(logit_blob, output_bias_blob) + # 获得每个样本的loss [batch_size] + pre_example_loss = flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=logit_blob, labels=label_blob + ) + loss = pre_example_loss + # print('loss.shape=', loss.shape) # [bz, ] + loss = flow.math.reduce_mean(loss) + # print('loss.shape=', loss.shape) # [1, ] + return loss, pre_example_loss, logit_blob + + + + + +def _AddCorruptedDomainCLSLoss(input_blob, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range, + scope_name='corrupted_domain_classification'): + ''' + input_blob: [layer_num, batch_size, seq_length, hidden_size] + ''' + # print('len(input_blob)=', len(input_blob)) + domain_logits = dict() + total_domain_loss = 0. + with flow.scope.namespace(scope_name): + domain_embedded_matrix = flow.get_variable("domain_projection", [num_domains, hidden_size], + initializer=flow.truncated_normal_initializer(stddev=0.02)) + domain_embedded = flow.gather(params=domain_embedded_matrix, indices=input_domain, axis=0) + domain_embedded = flow.reshape(domain_embedded, shape=[-1, hidden_size]) + # print('domain_embedded.shape=', domain_embedded.shape) # [bz, dim] + for layer_index in layer_indexes: + content_tensor = flow.math.reduce_mean(input_blob[layer_index], axis=1) + content_tensor_with_domains = domain_embedded + content_tensor + # print('content_tensor.shape=', content_tensor.shape) # [bz, dim] + domain_weights = flow.get_variable("domain_weights", [num_domains, hidden_size], initializer=flow.truncated_normal_initializer(stddev=0.02)) + domain_bias = flow.get_variable("domain_bias", [num_domains], initializer=flow.zeros_initializer()) + # print('content_tensor_with_domains.shape=', content_tensor_with_domains.shape) # [bz, dim] + current_domain_logits = flow.matmul(content_tensor_with_domains, domain_weights, transpose_b=True) + current_domain_logits = flow.nn.bias_add(current_domain_logits, domain_bias) + + # domain_logits["domain_logits_"+str(layer_index)] = current_domain_logits + + # 计算当前layer对应的loss + shuffle_domain_labels = flow.random.shuffle(input_domain) # 随机生成错误的domain标签 + shuffle_domain_labels = flow.reshape(shuffle_domain_labels, shape=[-1]) + # print('shuffle_domain_labels.shape=', shuffle_domain_labels.shape) # [bz] + shuffle_domain_labels = flow.squeeze(shuffle_domain_labels) + # one_hot_labels = flow.one_hot(shuffle_domain_labels, depth=num_domains, dtype=flow.float32) + # print('current_domain_logits.shape=', current_domain_logits.shape) # [bz, domain_num] + # print('one_hot_labels.shape=', one_hot_labels.shape) + domain_loss = flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=current_domain_logits, labels=shuffle_domain_labels + ) + total_domain_loss += domain_loss + total_domain_loss = total_domain_loss / len(layer_indexes) + return total_domain_loss diff --git a/NLPAlgo/MetaFineTuning/config.py b/NLPAlgo/MetaFineTuning/config.py new file mode 100644 index 0000000..a0b43fb --- /dev/null +++ b/NLPAlgo/MetaFineTuning/config.py @@ -0,0 +1,106 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +from datetime import datetime + + +def str_list(x): + return x.split(',') + +def int_list(x): + return list(map(int, x.split(','))) + +def float_list(x): + return list(map(float, x.split(','))) + +def str2bool(v): + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Unsupported value encountered.') + +def get_parser(parser=None): + + parser = argparse.ArgumentParser(description="flags for bert") + + parser.add_argument('--do_train', type=str2bool, nargs='?', const=True, help='train or not') + parser.add_argument('--do_eval', type=str2bool, nargs='?', const=True, help='eval or not') + # resouce + parser.add_argument("--model", type=str, default='BERT Pretrain') + parser.add_argument("--gpu_num_per_node", type=int, default=1) + parser.add_argument('--num_nodes', type=int, default=1, + help='node/machine number for training') + parser.add_argument('--node_ips', type=str_list, default=['192.168.1.13', '192.168.1.14'], + help='nodes ip list for training, devided by ",", length >= num_nodes') + parser.add_argument("--ctrl_port", type=int, default=50051, help='ctrl_port for multinode job') + + # train + parser.add_argument("--learning_rate", type=float, default=1e-5, help="Learning rate") + parser.add_argument("--weight_decay_rate", type=float, default=0.01, help="weight decay rate") + parser.add_argument("--warmup_proportion", type=float, default=0.1) + parser.add_argument('--use_fp16', type=str2bool, nargs='?', default='False', const=True, + help='use use fp16 or not') + parser.add_argument('--use_xla', type=str2bool, nargs='?', const=True, + help='Whether to use use xla') + + # log and resore/save + parser.add_argument("--loss_print_every_n_iter", type=int, default=10, required=False, + help="print loss every n iteration") + parser.add_argument("--model_save_every_n_iter", type=int, default=10000, required=False, + help="save model every n iteration",) + parser.add_argument("--model_save_dir", type=str, + default="./output/model_save-{}".format(str(datetime.now().strftime("%Y-%m-%d-%H:%M:%S"))), + required=False, help="model save directory") + parser.add_argument("--save_last_snapshot", type=str2bool, default=False, required=False, + help="save model snapshot for last iteration") + parser.add_argument("--model_load_dir", type=str, default=None, help="model load directory") + parser.add_argument("--log_dir", type=str, default="./output", help="log info save directory") + + # bert backbone + parser.add_argument('--do_lower_case', type=str2bool, nargs='?', const=True, default='True') + parser.add_argument("--seq_length", type=int, default=512) + parser.add_argument("--max_predictions_per_seq", type=int, default=80) + parser.add_argument("--num_hidden_layers", type=int, default=12) + parser.add_argument("--num_attention_heads", type=int, default=12) + parser.add_argument("--max_position_embeddings", type=int, default=512) + parser.add_argument("--type_vocab_size", type=int, default=2) + parser.add_argument("--vocab_size", type=int, default=30522) + parser.add_argument("--attention_probs_dropout_prob", type=float, default=0.1) + parser.add_argument("--hidden_dropout_prob", type=float, default=0.1) + parser.add_argument("--hidden_size_per_head", type=int, default=64) + + return parser + + +def print_args(args): + print("=".ljust(66, "=")) + print("Running {}: num_gpu_per_node = {}, num_nodes = {}.".format( + args.model, args.gpu_num_per_node, args.num_nodes)) + print("=".ljust(66, "=")) + for arg in vars(args): + print("{} = {}".format(arg, getattr(args, arg))) + print("-".ljust(66, "-")) + print("Time stamp: {}".format( + str(datetime.now().strftime("%Y-%m-%d-%H:%M:%S")))) + + +if __name__ == '__main__': + parser = get_parser() + args = parser.parse_args() + print_args(args) diff --git a/NLPAlgo/MetaFineTuning/convert_tf_ckpt_to_of.py b/NLPAlgo/MetaFineTuning/convert_tf_ckpt_to_of.py new file mode 100644 index 0000000..edae13a --- /dev/null +++ b/NLPAlgo/MetaFineTuning/convert_tf_ckpt_to_of.py @@ -0,0 +1,90 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +"""Convert tensorflow checkpoint to oneflow snapshot""" + +import re +import argparse +import tensorflow as tf +import numpy as np +import os + +parser = argparse.ArgumentParser() + +## Required parameters +parser.add_argument("--tf_checkpoint_path", + default = None, + type = str, + required = True, + help = "Path the TensorFlow checkpoint path.") +parser.add_argument("--of_dump_path", + default = None, + type = str, + required = True, + help = "Path to the output OneFlow model.") + +#args = parser.parse_args() +args, unknown = parser.parse_known_args() +print(args) + +# parse unknown arguments for extra weights +extra_weights = {} +for u in unknown: + w = u.split("=") + assert len(w) == 2 + if len(w) == 2: + extra_weights[w[0]] = float(w[1]) + + +def _write_blob(folder, blob): + os.makedirs(folder, exist_ok=True) + filename = os.path.join(folder, "out") + f = open(filename, 'wb') + f.write(blob.tobytes()) + f.close() + print(filename, blob.shape) + +def _SaveWeightBlob2File(blob, folder): + _write_blob(folder, blob) + + for weight, default_value in extra_weights.items(): + d = np.full_like(blob, default_value) + _write_blob(folder + weight, d) + +def convert(): + path = args.tf_checkpoint_path + init_vars = tf.train.list_variables(path) + for name, shape in init_vars: + array = tf.train.load_variable(path, name) + + sep = name.rfind('/') + blob_name = name[sep + 1:] + op_name = name[:sep].replace('/', '-') + + if blob_name == "kernel": + blob_name = "weight" + elif blob_name in ['adam_m', 'adam_v']: + print("find m, v weights") + + folder_name = op_name+"-"+blob_name + folder = os.path.join(args.of_dump_path, folder_name) + #print("saved to:", folder) + + _SaveWeightBlob2File(array, folder) + + +if __name__ == "__main__": + convert() + diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/dev.csv b/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/dev.csv new file mode 100644 index 0000000..c936c03 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/dev.csv @@ -0,0 +1,96 @@ +the screenwriters dig themselves in deeper every time they toss logic and science into what is essentially a `` dungeons and dragons '' fantasy with modern military weaponry ... SST-2 0 +a depressingly retrograde , ` post-feminist ' romantic comedy that takes an astonishingly condescending attitude toward women . SST-2 0 +with a tighter editorial process and firmer direction this material could work , especially since the actresses in the lead roles are all more than competent , but as is , personal velocity seems to be idling in neutral . SST-2 0 +it 's a stale , overused cocktail using the same olives since 1962 as garnish . SST-2 0 +as it is , it 's too long and unfocused . SST-2 0 +if festival in cannes nails hard - boiled hollywood argot with a bracingly nasty accuracy , much about the film , including some of its casting , is frustratingly unconvincing . SST-2 0 +a banal , virulently unpleasant excuse for a romantic comedy . SST-2 0 +`` abandon '' will leave you wanting to abandon the theater . SST-2 0 +the appeal of the vulgar , sexist , racist humour went over my head or -- considering just how low brow it is -- perhaps it snuck under my feet . SST-2 0 +maybe there 's a metaphor here , but figuring it out would n't make trouble every day any better . SST-2 0 +to the filmmakers , ivan is a prince of a fellow , but he comes across as shallow and glib though not mean-spirited , and there 's no indication that he 's been responsible for putting together any movies of particular value or merit . SST-2 0 +there 's an excellent 90-minute film here ; unfortunately , it runs for 170 . SST-2 0 +too predictably , in fact . SST-2 0 +in his role of observer of the scene , lawrence sounds whiny and defensive , as if his life-altering experiences made him bitter and less mature . SST-2 0 +it sounds like another clever if pointless excursion into the abyss , and that 's more or less how it plays out . SST-2 0 +... a cinematic disaster so inadvertently sidesplitting it 's worth the price of admission for the ridicule factor alone . SST-2 0 +in an effort , i suspect , not to offend by appearing either too serious or too lighthearted , it offends by just being wishy-washy . mr 0 +a loquacious and dreary piece of business . mr 0 +the film is hampered by its predictable plot and paper-thin supporting characters . mr 0 +has the feel of an unedited personal journal . mr 0 +a faster paced family flick . upper teens may get cynical . smaller numbered kidlets will enjoy . mr 0 +nair just doesn't have the necessary self-control to guide a loose , poorly structured film through the pitfalls of incoherence and redundancy . mr 0 +an unintentionally surreal kid's picture . . . in which actors in bad bear suits enact a sort of inter-species parody of a vh1 behind the music episode . mr 0 +high drama , disney-style - a wing and a prayer and a hunky has-been pursuing his castle in the sky . mr 0 +solondz may be convinced that he has something significant to say , but he isn't talking a talk that appeals to me . mr 0 +plays like a volatile and overlong w magazine fashion spread . mr 0 + . . . grows decidedly flimsier with its many out-sized , out of character and logically porous action set pieces . mr 0 +the only way to tolerate this insipid , brutally clueless film might be with a large dose of painkillers . mr 0 +her film is like a beautiful food entrée that isn't heated properly , so that it ends up a bit cold and relatively flavorless . mr 0 +brainless , but enjoyably over-the-top , the retro gang melodrama , deuces wild represents fifties teen-gang machismo in a way that borders on rough-trade homo-eroticism . mr 0 +don't be fooled by the impressive cast list - eye see you is pure junk . mr 0 +this is for the most part a useless movie , even with a great director at the helm . mr 0 +also , some other mp3 players such as the nitrus allow you to play wma ( windows media audio ) files , whereas the ipod does not . cr 0 +also , not liking the gel case . cr 0 +the only problem is the size of the keys . cr 0 +i already knew something was wrong , because much of the spam i 've seen in the past six months was from resellers of norton products . cr 0 +my last simpler nokia started right up and was immediately ready for use . cr 0 +sometimes , various applications crash . cr 0 +yeah - this program protects your computer all right - by locking you off the internet . cr 0 +it was immediately noticeable that nis slows down my computer ( pent4 , 256 ram ) . cr 0 +the os is ab it confusing at 1st , but within a few days you will pass this learning curve ! . cr 0 +it 's a rip off if one buys norton internet security and antivirus . cr 0 +the buttons on the phone are small , even for my small fingertips , but you get used to them rather quickly . cr 0 +and , when you think about it , it 's insulting that norton requires its customers to jump through these hoops -- anyone who can provide proof of purchase , whether on the phone or online ( what if your computer is down ? . ) , should have equal priority . cr 0 +the screen is easily scratched but if you have the warranty you should be able to swap it out . cr 0 +they have the worst service of any of the major companies . cr 0 +i 'm probably the only person that dislikes the itunes software . cr 0 +it has an equalizer , but the iriver h10-20gb has a lot more presets than the ipod . cr 0 +it 's about issues most adults have to face in marriage and i think that 's what i liked about it -- the real issues tucked between the silly and crude storyline . SST-2 1 +... spellbinding fun and deliciously exploitative . SST-2 1 +arteta paints a picture of lives lived in a state of quiet desperation . SST-2 1 +hey , happy ! SST-2 1 +i 'll go out on a limb . SST-2 1 +an enjoyable feel-good family comedy regardless of race . SST-2 1 +the power of shanghai ghetto , a documentary by dana janklowicz-mann and amir mann , rests in the voices of men and women , now in their 70s , who lived there in the 1940s . SST-2 1 +it is a strength of a documentary to disregard available bias , especially as temptingly easy as it would have been with this premise . SST-2 1 +while the story 's undeniably hard to follow , iwai 's gorgeous visuals seduce . SST-2 1 +it 's not particularly well made , but since i found myself howling more than cringing , i 'd say the film works . SST-2 1 +there is a certain sense of experimentation and improvisation to this film that may not always work , but it is nevertheless compelling . SST-2 1 +from its invitingly upbeat overture to its pathos-filled but ultimately life-affirming finale , martin is a masterfully conducted work . SST-2 1 +this chicago has hugely imaginative and successful casting to its great credit , as well as one terrific score and attitude to spare . SST-2 1 +nettelbeck ... has a pleasing way with a metaphor . SST-2 1 +the best thing about the movie is its personable , amusing cast . SST-2 1 +a terrific insider look at the star-making machinery of tinseltown . SST-2 1 +neatly constructed thriller . mr 1 +a bittersweet drama about the limbo of grief and how truth-telling can open the door to liberation . mr 1 +anyone who's ever suffered under a martinet music instructor has no doubt fantasized about what an unhappy , repressed and twisted personal life their tormentor deserved . these people are really going to love the piano teacher . mr 1 +in the disturbingly involving family dysfunctional drama how i killed my father , french director anne fontaine delivers an inspired portrait of male-ridden angst and the emotional blockage that accompanies this human condition mr 1 +by turns gripping , amusing , tender and heart-wrenching , laissez-passer has all the earmarks of french cinema at its best . mr 1 +" "" spider-man is better than any summer blockbuster we had to endure last summer , and hopefully , sets the tone for a summer of good stuff . if you're a comic fan , you can't miss it . if you're not , you'll still have a good time . """ mr 1 +mordantly funny and intimately knowing . . . mr 1 +in some ways , lagaan is quintessential bollywood . except it's much , much better . mr 1 +a gracious , eloquent film that by its end offers a ray of hope to the refugees able to look ahead and resist living in a past forever lost . mr 1 +eight legged freaks is clever and funny , is amused by its special effects , and leaves you feeling like you've seen a movie instead of an endless trailer . mr 1 +it's truly awful and heartbreaking subject matter , but one whose lessons are well worth revisiting as many times as possible . mr 1 +a virtual roller-coaster ride of glamour and sleaze . mr 1 +talk to her is so darned assured , we have absolutely no idea who the main characters are until the film is well under way -- and yet it's hard to stop watching . mr 1 +the best of the pierce brosnan james bond films to date . mr 1 +every once in a while , a movie will come along that turns me into that annoying specimen of humanity that i usually dread encountering the most - the fanboy mr 1 +in a summer of clones , harvard man is something rare and riveting : a wild ride that relies on more than special effects . mr 1 +i love the fact i can carry it in my shirt or pants pocket and forget about it . cr 1 +the zen plays mp3 , wma and wave formats , which makes it more versatile than the ipod . cr 1 +we keep the diaper champ in our daughter 's nursery and her room definitely does not smell like dirty diapers . cr 1 +creative media source is very easy to use once you get the hang of it , and the file transfers are fast . cr 1 +with bluetooth off the phone last almost twice as long . cr 1 +it was a very quick setup and installation , in fact the disc that it comes with pretty much makes sure you cant mess it up . cr 1 +"i recently had to run a 3/4 "" straight bit through hard maple to make a deep dado and was amazed that this machine didn 't even slow down ." cr 1 +i would highly recommend that you do this if you want to learn something , but if you are not interested in learning this stuff and just want a working wireless router , just get the wrt54g . cr 1 +overall it is a great value . cr 1 +small size . cr 1 +the stand that comes with it is very useful to prop it up on the desk . cr 1 +the phone can use any midi audio file as a ringtone . cr 1 +i was using the cheaper pail . . . and it worked ok until the opening device fell apart . cr 1 +4 . the build is just right . cr 1 +another great feature for me anyway is the ability to load . wav sounds as ringers . cr 1 +comes with a 16 mb compact flash and one rechargable battery the charging unit , included , is fast and small . cr 1 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/ofrecord/dev/dev.of_record-0 b/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/ofrecord/dev/dev.of_record-0 new file mode 100644 index 0000000000000000000000000000000000000000..8517ad6a86e1daa62e76c9a54674404cdcfaf459 GIT binary patch literal 56821 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 Pmoi(=SYb0&>VW@0v!NmF?z*_@>Z;%>_q*%8 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 G_Wlo7H>i*R literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/train.csv b/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/train.csv new file mode 100644 index 0000000..198bdb0 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data/k-shot-cross/g1/16-42/train.csv @@ -0,0 +1,96 @@ +unfortunately , carvey 's rubber-face routine is no match for the insipid script he has crafted with harris goldberg . SST-2 0 +bean drops the ball too many times ... hoping the nifty premise will create enough interest to make up for an unfocused screenplay . SST-2 0 +the story itself is actually quite vapid . SST-2 0 +rife with nutty cliches and far too much dialogue . SST-2 0 +where this was lazy but enjoyable , a formula comedy redeemed by its stars , that is even lazier and far less enjoyable . SST-2 0 +a dull , simple-minded and stereotypical tale of drugs , death and mind-numbing indifference on the inner-city streets . SST-2 0 +things really get weird , though not particularly scary : the movie is all portent and no content . SST-2 0 +one lousy movie . SST-2 0 +reign of fire never comes close to recovering from its demented premise , but it does sustain an enjoyable level of ridiculousness . SST-2 0 +gussied up with so many distracting special effects and visual party tricks that it 's not clear whether we 're supposed to shriek or laugh . SST-2 0 +no , even that 's too committed . SST-2 0 +it 's difficult to imagine that a more confused , less interesting and more sloppily made film could possibly come down the road in 2002 . SST-2 0 +` god help us , but capra and cooper are rolling over in their graves . ' SST-2 0 +inconsequential road-and-buddy pic . SST-2 0 +a crass and insulting homage to great films like some like it hot and the john wayne classics . SST-2 0 +limps along on a squirm-inducing fish-out-of-water formula that goes nowhere and goes there very , very slowly . SST-2 0 +director brian levant , who never strays far from his sitcom roots , skates blithely from one implausible situation to another , pausing only to tie up loose ends with more bows than you'll find on a french poodle . mr 0 +you can see where big bad love is trying to go , but it never quite gets there . mr 0 +appropriately cynical social commentary aside , #9 never quite ignites . mr 0 +directed in a flashy , empty sub-music video style by a director so self-possessed he actually adds a period to his first name mr 0 +as david letterman and the onion have proven , the worst of tragedies can be fertile sources of humor , but lawrence has only a fleeting grasp of how to develop them . mr 0 +an unsuccessful attempt at a movie of ideas . mr 0 +director-chef gabriele muccino keeps it fast -- zippy , comin' at ya -- as if fearing that his film is molto superficiale . mr 0 +the film tries to touch on spousal abuse but veers off course and becomes just another revenge film . mr 0 +black-and-white and unrealistic . mr 0 +though it draws several decent laughs , it's low-cal woody at best . mr 0 +i found the movie as divided against itself as the dysfunctional family it portrays . mr 0 +it gets the details of its time frame right but it completely misses its emotions . mr 0 +if high crimes were any more generic it would have a universal product code instead of a title . mr 0 +weaves a spell over you , with its disturbingly close-up look at damaged psyches and its subtle undercurrents of danger . but its awkward structure keeps breaking the spell . mr 0 +the movie doesn't generate a lot of energy . it is dark , brooding and slow , and takes its central idea way too seriously . mr 0 +it's so badly made on every level that i'm actually having a hard time believing people were paid to make it . mr 0 +the volume level of the phone is not all that good . cr 0 +- no voice activated dialing ( what were they thinking cr 0 +my only gripe about the hardware is the buttons . cr 0 +the only problem i had was a few components didn 't quite load correctly upon startup , but a reboot fixed the problem . cr 0 +it takes a while to learn how to use it right , and at this point i am able to make about 70% of my calls using it . cr 0 +"we constantly got "" windows virtual memory low "" error messages popping up foll wed by ridiculous slowdowns and/or crashes ." cr 0 +it 's important that people know the following : first , the interface is slow . cr 0 +"i tried it again this morning and it said i didn 't have the required "" permissions "" and lacked proper administrator status ." cr 0 +it would not hang up on calls . cr 0 +basically the 6600 is a bundle of over-hyped features you don 't and won 't need ( if you do get yourself a pda or pocket pc ) , none of them work as they should , and the basic phone functions and ergonomics are less than average . cr 0 +i really regret signing up with t-mobile , but i 'm stuck with them for the next 6 months of the contract . cr 0 +the battery does not last 12 hours . cr 0 +a bit disappoint on that ! . cr 0 +ring tones only come with crazy songs and annoying rings , there is only one ring that sounds close to a regular ring . cr 0 +if you currently own the 2003 version , and are looking to upgrade to the 2004 version , don 't . cr 0 +what disappoints me the most that a software like that is not cheap gets zero support form symantec , if you want support , they 'll charge you a fee ! . cr 0 +(cuarón has) created a substantive movie out of several cliched movie structures : the road movie , the coming-of-age movie , and the teenage sex comedy . SST-2 1 +a true-blue delight . SST-2 1 +zhuangzhuang creates delicate balance of style , text , and subtext that 's so simple and precise that anything discordant would topple the balance , but against all odds , nothing does . SST-2 1 +`` home movie '' is a sweet treasure and something well worth your time . SST-2 1 +an uncluttered , resonant gem that relays its universal points without lectures or confrontations . ' SST-2 1 +blessed with a searing lead performance by ryan gosling (murder by numbers) , the movie is powerful and provocative . SST-2 1 +gives an intriguing twist to the french coming-of-age genre . SST-2 1 +delivers roughly equal amounts of beautiful movement and inside information . SST-2 1 +(clooney 's) debut can be accused of being a bit undisciplined , but it has a tremendous , offbeat sense of style and humor that suggests he was influenced by some of the filmmakers who have directed him , especially the coen brothers and steven soderbergh . SST-2 1 +an escapist confection that 's pure entertainment . SST-2 1 +like a tarantino movie with heart , alias betty is richly detailed , deftly executed and utterly absorbing . SST-2 1 +fans of critics ' darling band wilco will marvel at the sometimes murky , always brooding look of i am trying to break your heart . SST-2 1 +the film is delicately narrated by martin landau and directed with sensitivity and skill by dana janklowicz-mann . SST-2 1 +ozpetek 's effort has the scope and shape of an especially well-executed television movie . SST-2 1 +great over-the-top moviemaking if you 're in a slap-happy mood . SST-2 1 +narc is a no-bull throwback to 1970s action films . SST-2 1 +as quiet , patient and tenacious as mr . lopez himself , who approaches his difficult , endless work with remarkable serenity and discipline . mr 1 +stanley kwan has directed not only one of the best gay love stories ever made , but one of the best love stories of any stripe . mr 1 +the film has a kind of hard , cold effect . mr 1 +ash wednesday is not edward burns' best film , but it is a good and ambitious film . and it marks him as one of the most interesting writer/directors working today . mr 1 +this sci-fi techno-sex thriller starts out bizarre and just keeps getting weirder . mr 1 +it's a talking head documentary , but a great one . mr 1 +that rare documentary that incorporates so much of human experience -- drama , conflict , tears and surprise -- that it transcends the normal divisions between fiction and nonfiction film . mr 1 +jeffs has created a breathtakingly assured and stylish work of spare dialogue and acute expressiveness . mr 1 +'it's better to go in knowing full well what's going to happen , but willing to let the earnestness of its execution and skill of its cast take you down a familiar road with a few twists . cynics need not apply . ' mr 1 +it works its magic with such exuberance and passion that the film's length becomes a part of its fun . mr 1 +it may scream low budget , but this charmer has a spirit that cannot be denied . mr 1 +as a girl-meets-girl romantic comedy , kissing jessica steinis quirky , charming and often hilarious . yet it's not quite the genre-busting film it's been hyped to be because it plays everything too safe . mr 1 +the film is a fierce dance of destruction . its flame-like , roiling black-and-white inspires trembling and gratitude . mr 1 +very amusing , not the usual route in a thriller , and the performances are odd and pixilated and sometimes both . mr 1 + . . . a story we haven't seen on the big screen before , and it's a story that we as americans , and human beings , should know . mr 1 +a compelling allegory about the last days of germany's democratic weimar republic . mr 1 +the zen micro ( mine 's black ) has a lot going for it . cr 1 +i hooked the linksys wireless router up to a broadband connection which is running at a maximum of 700 plus kilobytes on the download e ! . cr 1 +overall this is a slightly better than average phone . cr 1 +has the click wheel and the ability to scan through thousands of songs and organizes them great . cr 1 +the product i rate five stars , cr 1 +i also use the phone as my modem via bluetooth to my apple power book ! . cr 1 +) no locked-in proprietary business like apple ; . this baby plays wmas too . cr 1 +while i like the performance of the phone in every regard , i would buy another one solely upon the apparent indestructibility of it . cr 1 +i also own a retail store and this little guy easily plugs into my aux slot in the store 's sound system . cr 1 +my experience with installation was quite good . cr 1 +it took me about an hour to set up a wireless environment in her townhouse and she hasn 't called me yet with any problems . cr 1 +first of all , my calls were loud and clear , unlike with sprint . cr 1 +with the champ all you had to do was drop the dirty diaper into the opening and flip the lid . cr 1 +likewise , i 've heard norton 2004 professional version is fine too . cr 1 +"while i have not had the "" privilege "" , as many call it , of owning an ipod , based on the experience of friends who have had some misfortune with their ipod ( and subsequently purchased the zen touch or zen micro ) , i can pretty much rest assured that my decision to purchase the zen micro was one of the more informed ones ." cr 1 +upon opening the box i was confronted with this small cute little mp3 player . cr 1 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/dev.csv b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/dev.csv new file mode 100644 index 0000000..ed43332 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/dev.csv @@ -0,0 +1,32 @@ +i 'm left slightly disappointed that it did n't . SST-2 0 +this is an exercise not in biography but in hero worship . SST-2 0 +true to its animatronic roots : ... as stiff , ponderous and charmless as a mechanical apparatus ... ` the country bears ' should never have been brought out of hibernation . SST-2 0 +terrible . SST-2 0 +will undoubtedly play well in european markets , where mr. besson is a brand name , and in asia , where ms. shu is an institution , but american audiences will probably find it familiar and insufficiently cathartic . SST-2 0 +all too familiar ... basically the sort of cautionary tale that was old when ` angels with dirty faces ' appeared in 1938 . SST-2 0 +but i had a lot of problems with this movie . SST-2 0 +dong never pushes for insights beyond the superficial tensions of the dynamic he 's dissecting , and the film settles too easily along the contours of expectation . SST-2 0 +parents beware ; this is downright movie penance . SST-2 0 +this is a movie that starts out like heathers , then becomes bring it on , then becomes unwatchable . SST-2 0 +taken as a whole , the tuxedo does n't add up to a whole lot . SST-2 0 +it still feels like a prison stretch . SST-2 0 +for all of its insights into the dream world of teen life , and its electronic expression through cyber culture , the film gives no quarter to anyone seeking to pull a cohesive story out of its 2 1\/2 - hour running time . SST-2 0 +unfortunately , it 's also not very good . SST-2 0 +very stupid and annoying . SST-2 0 +purports to be a hollywood satire but winds up as the kind of film that should be the target of something deeper and more engaging . SST-2 0 +a film with a great premise but only a great premise . SST-2 1 +there are scenes of cinematic perfection that steal your heart away . SST-2 1 +the characters are complex and quirky , but entirely believable as the remarkable ensemble cast brings them to life . SST-2 1 +not only a coming-of-age story and cautionary parable , but also a perfectly rendered period piece . SST-2 1 +with this masterful , flawless film , (wang) emerges in the front ranks of china 's now numerous , world-renowned filmmakers . SST-2 1 +when a movie has stuck around for this long , you know there 's something there . SST-2 1 +it may ... work as a jaunt down memory lane for teens and young adults who grew up on televised scooby-doo shows or reruns . SST-2 1 +you wo n't have any trouble getting kids to eat up these veggies . SST-2 1 +i 'm not a fan of the phrase ` life affirming ' because it usually means ` schmaltzy , ' but real women have curves truly is life affirming . SST-2 1 +the auteur 's ear for the way fears and slights are telegraphed in the most blithe exchanges gives the film its lingering tug . SST-2 1 +`` the best disney movie since the lion king '' SST-2 1 +` yes , that 's right : it 's forrest gump , angel of death . ' SST-2 1 +spiderman rocks SST-2 1 +satin rouge is not a new , or inventive , journey , but it 's encouraging to see a three-dimensional , average , middle-aged woman 's experience of self-discovery handled with such sensitivity . SST-2 1 +it 's no surprise that as a director washington demands and receives excellent performances , from himself and from newcomer derek luke . SST-2 1 +not many movies have that kind of impact on me these days . SST-2 1 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/dev/dev.of_record-0 b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/dev/dev.of_record-0 new file mode 100644 index 0000000000000000000000000000000000000000..99dfc856e667e40b62c9ae2e69c77bf727347ef9 GIT binary patch literal 18872 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/eval/eval.of_record-0 b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/eval/eval.of_record-0 new file mode 100644 index 0000000000000000000000000000000000000000..f4694c9846239c20dc65bbb99571c6aecf5908ea GIT binary patch literal 516877 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
    YHFq$I!I6zAN~Hs?sFl-*-w@#rKzlRx{$1~BUC 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/train/train.of_record-0 b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/ofrecord/train/train.of_record-0 new file mode 100644 index 0000000000000000000000000000000000000000..f3720cbe3484a926b0c6f4a76a259fa65b69c841 GIT binary patch literal 18978 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 WtgC^AQ}1chNgJNs2%SFPuJ}J-G1EE# literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/test.csv b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/test.csv new file mode 100644 index 0000000..c346f21 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/test.csv @@ -0,0 +1,872 @@ +the movie 's relatively simple plot and uncomplicated morality play well with the affable cast . SST-2 1 +you do n't have to know about music to appreciate the film 's easygoing blend of comedy and romance . SST-2 1 +a must-see for the david mamet enthusiast and for anyone who appreciates intelligent , stylish moviemaking . SST-2 1 +a rarity among recent iranian films : it 's a comedy full of gentle humor that chides the absurdity of its protagonist 's plight . SST-2 1 +martin and barbara are complex characters -- sometimes tender , sometimes angry -- and the delicate performances by sven wollter and viveka seldahl make their hopes and frustrations vivid . SST-2 1 +it is great summer fun to watch arnold and his buddy gerald bounce off a quirky cast of characters . SST-2 1 +that is a compliment to kuras and miller . SST-2 1 +although laced with humor and a few fanciful touches , the film is a refreshingly serious look at young women . SST-2 1 +with tightly organized efficiency , numerous flashbacks and a constant edge of tension , miller 's film is one of 2002 's involvingly adult surprises . SST-2 1 +exquisitely nuanced in mood tics and dialogue , this chamber drama is superbly acted by the deeply appealing veteran bouquet and the chilling but quite human berling . SST-2 1 +the weight of the piece , the unerring professionalism of the chilly production , and the fascination embedded in the lurid topic prove recommendation enough . SST-2 1 +it confirms fincher 's status as a film maker who artfully bends technical know-how to the service of psychological insight . SST-2 1 +the movie is n't just hilarious : it 's witty and inventive , too , and in hindsight , it is n't even all that dumb . SST-2 1 +bleakly funny , its characters all the more touching for refusing to pity or memorialize themselves . SST-2 1 +... with `` the bourne identity '' we return to the more traditional action genre . SST-2 1 +good car chases , great fight scenes , and a distinctive blend of european , american and asian influences . SST-2 1 +deliriously funny , fast and loose , accessible to the uninitiated , and full of surprises SST-2 1 +it provides the grand , intelligent entertainment of a superior cast playing smart people amid a compelling plot . SST-2 1 +having had the good sense to cast actors who are , generally speaking , adored by the movie-going public , khouri then gets terrific performances from them all . SST-2 1 +hardly a masterpiece , but it introduces viewers to a good charitable enterprise and some interesting real people . SST-2 1 +a giggle a minute . SST-2 1 +not the kind of film that will appeal to a mainstream american audience , but there is a certain charm about the film that makes it a suitable entry into the fest circuit . SST-2 1 +it gets onto the screen just about as much of the novella as one could reasonably expect , and is engrossing and moving in its own right . SST-2 1 +it is amusing , and that 's all it needs to be . SST-2 1 +miller is playing so free with emotions , and the fact that children are hostages to fortune , that he makes the audience hostage to his swaggering affectation of seriousness . SST-2 1 +all-in-all , the film is an enjoyable and frankly told tale of a people who live among us , but not necessarily with us . SST-2 1 +unlike the speedy wham-bam effect of most hollywood offerings , character development -- and more importantly , character empathy -- is at the heart of italian for beginners . SST-2 1 +a smart , witty follow-up . SST-2 1 +the best film about baseball to hit theaters since field of dreams . SST-2 1 +a gorgeous , witty , seductive movie . SST-2 1 +a swashbuckling tale of love , betrayal , revenge and above all , faith . SST-2 1 +like mike is a winner for kids , and no doubt a winner for lil bow wow , who can now add movies to the list of things he does well . SST-2 1 +audrey tatou has a knack for picking roles that magnify her outrageous charm , and in this literate french comedy , she 's as morning-glory exuberant as she was in amélie . SST-2 1 +one of those energetic surprises , an original that pleases almost everyone who sees it . SST-2 1 +the story and structure are well-honed . SST-2 1 +the film is quiet , threatening and unforgettable . SST-2 1 +a warm but realistic meditation on friendship , family and affection . SST-2 1 +reign of fire looks as if it was made without much thought -- and is best watched that way . SST-2 1 +if steven soderbergh 's ` solaris ' is a failure it is a glorious failure . SST-2 1 +for anyone unfamiliar with pentacostal practices in general and theatrical phenomenon of hell houses in particular , it 's an eye-opener . SST-2 1 +lovely and poignant . SST-2 1 +a grimly competent and stolid and earnest military courtroom drama . SST-2 1 +feature debuter d.j. caruso directs a crack ensemble cast , bringing screenwriter tony gayton 's narcotics noir to life . SST-2 1 +for the most part , it 's a work of incendiary genius , steering clear of knee-jerk reactions and quick solutions . SST-2 1 +it 's fun lite . SST-2 1 +the emotions are raw and will strike a nerve with anyone who 's ever had family trauma . SST-2 1 +vera 's three actors -- mollà , gil and bardem -- excel in insightful , empathetic performances . SST-2 1 +cq 's reflection of artists and the love of cinema-and-self suggests nothing less than a new voice that deserves to be considered as a possible successor to the best european directors . SST-2 1 +a working class `` us vs. them '' opera that leaves no heartstring untugged and no liberal cause unplundered . SST-2 1 +stephen rea , aidan quinn , and alan bates play desmond 's legal eagles , and when joined by brosnan , the sight of this grandiloquent quartet lolling in pretty irish settings is a pleasant enough thing , ` tis . SST-2 1 +has a lot of the virtues of eastwood at his best . SST-2 1 +thanks to haynes ' absolute control of the film 's mood , and buoyed by three terrific performances , far from heaven actually pulls off this stylistic juggling act . SST-2 1 +a big , gorgeous , sprawling swashbuckler that delivers its diversions in grand , uncomplicated fashion . SST-2 1 +one of the best films of the year with its exploration of the obstacles to happiness faced by five contemporary individuals ... a psychological masterpiece . SST-2 1 +the movie has an infectious exuberance that will engage anyone with a passing interest in the skate\/surf culture , the l.a. beach scene and the imaginative (and sometimes illegal) ways kids can make a playground out of the refuse of adults . SST-2 1 +the volatile dynamics of female friendship is the subject of this unhurried , low-key film that is so off-hollywood that it seems positively french in its rhythms and resonance . SST-2 1 +one of the smartest takes on singles culture i 've seen in a long time . SST-2 1 +worth watching for dong jie 's performance -- and for the way it documents a culture in the throes of rapid change . SST-2 1 +intriguing documentary which is emotionally diluted by focusing on the story 's least interesting subject . SST-2 1 +writer\/director joe carnahan 's grimy crime drama is a manual of precinct cliches , but it moves fast enough to cover its clunky dialogue and lapses in logic . SST-2 1 +belongs to daniel day-lewis as much as it belongs to martin scorsese ; it 's a memorable performance in a big , brassy , disturbing , unusual and highly successful film . SST-2 1 +it will grip even viewers who are n't interested in rap , as it cuts to the heart of american society in an unnerving way . SST-2 1 +more romantic , more emotional and ultimately more satisfying than the teary-eyed original . SST-2 1 +the movie is beautiful to behold and engages one in a sense of epic struggle -- inner and outer -- that 's all too rare in hollywood 's hastier productions . SST-2 1 +few films capture so perfectly the hopes and dreams of little boys on baseball fields as well as the grown men who sit in the stands . SST-2 1 +displaying about equal amounts of naiveté , passion and talent , beneath clouds establishes sen as a filmmaker of considerable potential . SST-2 1 +the movie achieves as great an impact by keeping these thoughts hidden as ... (quills) did by showing them . SST-2 1 +a pleasant enough romance with intellectual underpinnings , the kind of movie that entertains even as it turns maddeningly predictable . SST-2 1 +but it still jingles in the pocket . SST-2 1 +my thoughts were focused on the characters . SST-2 1 +at a time when half the so-called real movies are little more than live-action cartoons , it 's refreshing to see a cartoon that knows what it is , and knows the form 's history . SST-2 1 +as a first-time director , paxton has tapped something in himself as an actor that provides frailty with its dark soul . SST-2 1 +exciting and direct , with ghost imagery that shows just enough to keep us on our toes . SST-2 1 +on the heels of the ring comes a similarly morose and humorless horror movie that , although flawed , is to be commended for its straight-ahead approach to creepiness . SST-2 1 +an entertaining , colorful , action-filled crime story with an intimate heart . SST-2 1 +falls neatly into the category of good stupid fun . SST-2 1 +so unassuming and pure of heart , you ca n't help but warmly extend your arms and yell ` safe ! ' SST-2 1 +a strangely compelling and brilliantly acted psychological drama . SST-2 1 +you 'll gasp appalled and laugh outraged and possibly , watching the spectacle of a promising young lad treading desperately in a nasty sea , shed an errant tear . SST-2 1 +a celebration of quirkiness , eccentricity , and certain individuals ' tendency to let it all hang out , and damn the consequences . SST-2 1 +the subtle strength of `` elling '' is that it never loses touch with the reality of the grim situation . SST-2 1 +seldahl 's barbara is a precise and moving portrait of someone whose world is turned upside down , first by passion and then by illness . SST-2 1 +just as moving , uplifting and funny as ever . SST-2 1 +generally , clockstoppers will fulfill your wildest fantasies about being a different kind of time traveler , while happily killing 94 minutes . SST-2 1 +slick piece of cross-promotion . SST-2 1 +my wife is an actress is an utterly charming french comedy that feels so american in sensibility and style it 's virtually its own hollywood remake . SST-2 1 +warm water under a red bridge is a quirky and poignant japanese film that explores the fascinating connections between women , water , nature , and sexuality . SST-2 1 +it has the charm of the original american road movies , feasting on the gorgeous , ramshackle landscape of the filmmaker 's motherland . SST-2 1 +a movie that successfully crushes a best selling novel into a timeframe that mandates that you avoid the godzilla sized soda . SST-2 1 +so , too , is this comedy about mild culture clashing in today 's new delhi . SST-2 1 +a compelling spanish film about the withering effects of jealousy in the life of a young monarch whose sexual passion for her husband becomes an obsession . SST-2 1 +what the film lacks in general focus it makes up for in compassion , as corcuera manages to find the seeds of hope in the form of collective action . SST-2 1 +uses high comedy to evoke surprising poignance . SST-2 1 +... a fun little timewaster , helped especially by the cool presence of jean reno . SST-2 1 +the mesmerizing performances of the leads keep the film grounded and keep the audience riveted . SST-2 1 +an infectious cultural fable with a tasty balance of family drama and frenetic comedy . SST-2 1 +this is wild surreal stuff , but brilliant and the camera just kind of sits there and lets you look at this and its like you 're going from one room to the next and none of them have any relation to the other . SST-2 1 +gives you the steady pulse of life in a beautiful city viewed through the eyes of a character who , in spite of tragic loss and increasing decrepitude , knows in his bones that he is one of the luckiest men alive . SST-2 1 +an intriguing cinematic omnibus and round-robin that occasionally is more interesting in concept than in execution . SST-2 1 +the movie does a good job of laying out some of the major issues that we encounter as we journey through life . SST-2 1 +(serry) wants to blend politics and drama , an admirable ambition . SST-2 1 +the minor figures surrounding (bobby) ... form a gritty urban mosaic . SST-2 1 +(næs) directed the stage version of elling , and gets fine performances from his two leads who originated the characters on stage . SST-2 1 +the film is beautifully mounted , but , more to the point , the issues are subtly presented , managing to walk a fine line with regard to the question of joan 's madness . SST-2 1 +it moves quickly , adroitly , and without fuss ; it does n't give you time to reflect on the inanity -- and the cold war datedness -- of its premise . SST-2 1 +it 's a remarkably solid and subtly satirical tour de force . SST-2 1 +a delightful coming-of-age story . SST-2 1 +uses sharp humor and insight into human nature to examine class conflict , adolescent yearning , the roots of friendship and sexual identity . SST-2 1 +rare birds has more than enough charm to make it memorable . SST-2 1 +the best revenge may just be living well because this film , unlike other dumas adaptations , is far more likened to a treasure than a lengthy jail sentence . SST-2 1 +a poignant , artfully crafted meditation on mortality . SST-2 1 +(grant 's) bumbling magic takes over the film , and it turns out to be another winning star vehicle . SST-2 1 +... an otherwise intense , twist-and-turn thriller that certainly should n't hurt talented young gaghan 's resume . SST-2 1 +trademark american triteness and simplicity are tossed out the window with the intelligent french drama that deftly explores the difficult relationship between a father and son . SST-2 1 +dazzles with its fully-written characters , its determined stylishness (which always relates to characters and story) and johnny dankworth 's best soundtrack in years . SST-2 1 +instead of a hyperbolic beat-charged urban western , it 's an unpretentious , sociologically pointed slice of life . SST-2 1 +will amuse and provoke adventurous adults in specialty venues . SST-2 1 +so much facile technique , such cute ideas , so little movie . SST-2 1 +an effectively creepy , fear-inducing (not fear-reducing) film from japanese director hideo nakata , who takes the superstitious curse on chain letters and actually applies it . SST-2 1 +add yet another hat to a talented head , clooney 's a good director . SST-2 1 +a gripping movie , played with performances that are all understated and touching . SST-2 1 +there 's something auspicious , and daring , too , about the artistic instinct that pushes a majority-oriented director like steven spielberg to follow a.i. with this challenging report so liable to unnerve the majority . SST-2 1 +not since japanese filmmaker akira kurosawa 's ran have the savagery of combat and the specter of death been visualized with such operatic grandeur . SST-2 1 +it 's fascinating to see how bettany and mcdowell play off each other . SST-2 1 +about a manga-like heroine who fights back at her abusers , it 's energetic and satisfying if not deep and psychological . SST-2 1 +enormously entertaining for moviegoers of any age . SST-2 1 +not only is undercover brother as funny , if not more so , than both austin powers films , but it 's also one of the smarter , savvier spoofs to come along in some time . SST-2 1 +one of the most significant moviegoing pleasures of the year . SST-2 1 +huston nails both the glad-handing and the choking sense of hollow despair . SST-2 1 +if you are an actor who can relate to the search for inner peace by dramatically depicting the lives of others onstage , then esther 's story is a compelling quest for truth . SST-2 1 +even if you do n't think (kissinger 's) any more guilty of criminal activity than most contemporary statesmen , he 'd sure make a courtroom trial great fun to watch . SST-2 1 +far more imaginative and ambitious than the trivial , cash-in features nickelodeon has made from its other animated tv series . SST-2 1 +without ever becoming didactic , director carlos carrera expertly weaves this novelistic story of entangled interrelationships and complex morality . SST-2 1 +looking aristocratic , luminous yet careworn in jane hamilton 's exemplary costumes , rampling gives a performance that could not be improved upon . ' SST-2 1 +so refreshingly incisive is grant that for the first time he 'll probably appeal more to guys than to their girlfriends who drag them to this movie for the hugh factor . SST-2 1 +affleck and jackson are good sparring partners . SST-2 1 +it haunts you , you ca n't forget it , you admire its conception and are able to resolve some of the confusions you had while watching it . SST-2 1 +this is an egotistical endeavor from the daughter of horror director dario argento (a producer here) , but her raw performance and utter fearlessness make it strangely magnetic . SST-2 1 +it 's an offbeat treat that pokes fun at the democratic exercise while also examining its significance for those who take part . SST-2 1 +another one of those estrogen overdose movies like `` divine secrets of the ya ya sisterhood , '' except that the writing , acting and character development are a lot better . SST-2 1 +a painfully funny ode to bad behavior . SST-2 1 +aside from minor tinkering , this is the same movie you probably loved in 1994 , except that it looks even better . SST-2 1 +the very definition of the ` small ' movie , but it is a good stepping stone for director sprecher . SST-2 1 +the fly-on-the-wall method used to document rural french school life is a refreshing departure from the now more prevalent technique of the docu-makers being a visible part of their work . SST-2 1 +instead , he shows them the respect they are due . SST-2 1 +brilliantly explores the conflict between following one 's heart and following the demands of tradition . SST-2 1 +a densely constructed , highly referential film , and an audacious return to form that can comfortably sit among jean-luc godard 's finest work . SST-2 1 +a moody , multi-dimensional love story and sci-fi mystery , solaris is a thought-provoking , haunting film that allows the seeds of the imagination to germinate . SST-2 1 +the magic of the film lies not in the mysterious spring but in the richness of its performances . SST-2 1 +funny but perilously slight . SST-2 1 +it provides an honest look at a community striving to anchor itself in new grounds . SST-2 1 +it 's made with deftly unsettling genre flair . SST-2 1 +jose campanella delivers a loosely autobiographical story brushed with sentimentality but brimming with gentle humor , bittersweet pathos , and lyric moments that linger like snapshots of memory . SST-2 1 +no screen fantasy-adventure in recent memory has the showmanship of clones ' last 45 minutes . SST-2 1 +this is a good script , good dialogue , funny even for adults . SST-2 1 +for this reason and this reason only -- the power of its own steadfast , hoity-toity convictions -- chelsea walls deserves a medal . SST-2 1 +it all adds up to good fun . SST-2 1 +an operatic , sprawling picture that 's entertainingly acted , magnificently shot and gripping enough to sustain most of its 170-minute length . SST-2 1 +but taken as a stylish and energetic one-shot , the queen of the damned can not be said to suck . SST-2 1 +we have n't seen such hilarity since say it is n't so ! SST-2 1 +maud and roland 's search for an unknowable past makes for a haunting literary detective story , but labute pulls off a neater trick in possession : he makes language sexy . SST-2 1 +a study in shades of gray , offering itself up in subtle plot maneuvers ... SST-2 1 +the most compelling wiseman epic of recent years . SST-2 1 +director andrew niccol ... demonstrates a wry understanding of the quirks of fame . SST-2 1 +not far beneath the surface , this reconfigured tale asks disturbing questions about those things we expect from military epics . SST-2 1 +... a story we have n't seen on the big screen before , and it 's a story that we as americans , and human beings , should know . SST-2 1 +holm ... embodies the character with an effortlessly regal charisma . SST-2 1 +as surreal as a dream and as detailed as a photograph , as visually dexterous as it is at times imaginatively overwhelming . SST-2 1 +this is human comedy at its most amusing , interesting and confirming . SST-2 1 +this tenth feature is a big deal , indeed -- at least the third-best , and maybe even a notch above the previous runner-up , nicholas meyer 's star trek vi : the undiscovered country . SST-2 1 +while its careful pace and seemingly opaque story may not satisfy every moviegoer 's appetite , the film 's final scene is soaringly , transparently moving . SST-2 1 +in a way , the film feels like a breath of fresh air , but only to those that allow it in . SST-2 1 +if you can stomach the rough content , it 's worth checking out for the performances alone . SST-2 1 +first-time writer-director serry shows a remarkable gift for storytelling with this moving , effective little film . SST-2 1 +a solid film ... but more conscientious than it is truly stirring . SST-2 1 +fresnadillo 's dark and jolting images have a way of plying into your subconscious like the nightmare you had a week ago that wo n't go away . SST-2 1 +the wild thornberrys movie is a jolly surprise . SST-2 1 +(a) n utterly charming and hilarious film that reminded me of the best of the disney comedies from the 60s . SST-2 1 +broomfield turns his distinctive ` blundering ' style into something that could really help clear up the case . SST-2 1 +not only are the special effects and narrative flow much improved , and daniel radcliffe more emotionally assertive this time around as harry , but the film conjures the magic of author j.k. rowling 's books . SST-2 1 +mr. tsai is a very original artist in his medium , and what time is it there ? SST-2 1 +by candidly detailing the politics involved in the creation of an extraordinary piece of music , (jones) calls our attention to the inherent conflict between commerce and creativity . SST-2 1 +something akin to a japanese alice through the looking glass , except that it seems to take itself far more seriously . SST-2 1 +binoche makes it interesting trying to find out . SST-2 1 +puts a human face on a land most westerners are unfamiliar with . SST-2 1 +this is a story of two misfits who do n't stand a chance alone , but together they are magnificent . SST-2 1 +like leon , it 's frustrating and still oddly likable . SST-2 1 +a quiet treasure -- a film to be savored . SST-2 1 +an exquisitely crafted and acted tale . SST-2 1 +even in its most tedious scenes , russian ark is mesmerizing . SST-2 1 +still , as a visual treat , the film is almost unsurpassed . SST-2 1 +the vitality of the actors keeps the intensity of the film high , even as the strafings blend together . SST-2 1 +mcconaughey 's fun to watch , the dragons are okay , not much fire in the script . SST-2 1 +a giggle-inducing comedy with snappy dialogue and winning performances by an unlikely team of oscar-winners : susan sarandon and goldie hawn . SST-2 1 +director of photography benoit delhomme shot the movie in delicious colors , and the costumes and sets are grand . SST-2 1 +and if you 're not nearly moved to tears by a couple of scenes , you 've got ice water in your veins . SST-2 1 +smart , provocative and blisteringly funny . SST-2 1 +the film 's performances are thrilling . SST-2 1 +while there 's something intrinsically funny about sir anthony hopkins saying ` get in the car , bitch , ' this jerry bruckheimer production has little else to offer SST-2 1 +there 's ... tremendous energy from the cast , a sense of playfulness and excitement that seems appropriate . SST-2 1 +it 's a lovely film with lovely performances by buy and accorsi . SST-2 1 +morton uses her face and her body language to bring us morvern 's soul , even though the character is almost completely deadpan . SST-2 1 +what really makes it special is that it pulls us into its world , gives us a hero whose suffering and triumphs we can share , surrounds him with interesting characters and sends us out of the theater feeling we 've shared a great adventure . SST-2 1 +the primitive force of this film seems to bubble up from the vast collective memory of the combatants . SST-2 1 +what 's surprising about full frontal is that despite its overt self-awareness , parts of the movie still manage to break past the artifice and thoroughly engage you . SST-2 1 +a rigorously structured and exquisitely filmed drama about a father and son connection that is a brief shooting star of love . SST-2 1 +as the two leads , lathan and diggs are charming and have chemistry both as friends and lovers . SST-2 1 +and that 's a big part of why we go to the movies . SST-2 1 +if you dig on david mamet 's mind tricks ... rent this movie and enjoy ! SST-2 1 +harrison 's flowers puts its heart in the right place , but its brains are in no particular place at all . SST-2 1 +... a magnificent drama well worth tracking down . SST-2 1 +a breezy romantic comedy that has the punch of a good sitcom , while offering exceptionally well-detailed characters . SST-2 1 +two hours fly by -- opera 's a pleasure when you do n't have to endure intermissions -- and even a novice to the form comes away exhilarated . SST-2 1 +reggio 's continual visual barrage is absorbing as well as thought-provoking . SST-2 1 +though only 60 minutes long , the film is packed with information and impressions . SST-2 1 +a lean , deftly shot , well-acted , weirdly retro thriller that recalls a raft of '60s and '70s european-set spy pictures . SST-2 1 +thanks to scott 's charismatic roger and eisenberg 's sweet nephew , roger dodger is one of the most compelling variations on in the company of men . SST-2 1 +blanchett 's performance confirms her power once again . SST-2 1 +that dogged good will of the parents and ` vain ' jia 's defoliation of ego , make the film touching despite some doldrums . SST-2 1 +altogether , this is successful as a film , while at the same time being a most touching reconsideration of the familiar masterpiece . SST-2 1 +as unseemly as its title suggests . SST-2 1 +writer-director 's mehta 's effort has tons of charm and the whimsy is in the mixture , the intoxicating masala , of cultures and film genres . SST-2 1 +a fitfully amusing romp that , if nothing else , will appeal to fans of malcolm in the middle and its pubescent star , frankie muniz . SST-2 1 +anchored by friel and williams 's exceptional performances , the film 's power lies in its complexity . SST-2 1 +jaglom ... put (s) the audience in the privileged position of eavesdropping on his characters SST-2 1 +(d) oes n't bother being as cloying or preachy as equivalent evangelical christian movies -- maybe the filmmakers know that the likely audience will already be among the faithful . SST-2 1 +the structure the film takes may find matt damon and ben affleck once again looking for residuals as this officially completes a good will hunting trilogy that was never planned . SST-2 1 +a taut psychological thriller that does n't waste a moment of its two-hour running time . SST-2 1 +yakusho and shimizu ... create engaging characterizations in imamura 's lively and enjoyable cultural mix . SST-2 1 +the lion king was a roaring success when it was released eight years ago , but on imax it seems better , not just bigger . SST-2 1 +a marvel like none you 've seen . SST-2 1 +the acting , costumes , music , cinematography and sound are all astounding given the production 's austere locales . SST-2 1 +it inspires a continuing and deeply satisfying awareness of the best movies as monumental ` picture shows . ' SST-2 1 +(chaiken 's) talent lies in an evocative , accurate observation of a distinctive milieu and in the lively , convincing dialogue she creates for her characters . SST-2 1 +a good piece of work more often than not . SST-2 1 +byler reveals his characters in a way that intrigues and even fascinates us , and he never reduces the situation to simple melodrama . SST-2 1 +despite its title , punch-drunk love is never heavy-handed . SST-2 1 +it 's one heck of a character study -- not of hearst or davies but of the unique relationship between them . SST-2 1 +if you 're hard up for raunchy college humor , this is your ticket right here . SST-2 1 +kinnear does n't aim for our sympathy , but rather delivers a performance of striking skill and depth . SST-2 1 +a warm , funny , engaging film . SST-2 1 +seldom has a movie so closely matched the spirit of a man and his work . SST-2 1 +awesome creatures , breathtaking scenery , and epic battle scenes add up to another ` spectacular spectacle . ' SST-2 1 +it deserves to be seen by anyone with even a passing interest in the events shaping the world beyond their own horizons . SST-2 1 +what better message than ` love thyself ' could young women of any size receive ? SST-2 1 +minority report is exactly what the title indicates , a report . SST-2 1 +neither parker nor donovan is a typical romantic lead , but they bring a fresh , quirky charm to the formula . SST-2 1 +the format gets used best ... to capture the dizzying heights achieved by motocross and bmx riders , whose balletic hotdogging occasionally ends in bone-crushing screwups . SST-2 1 +it 's so good that its relentless , polished wit can withstand not only inept school productions , but even oliver parker 's movie adaptation . SST-2 1 +a woman 's pic directed with resonance by ilya chaiken . SST-2 1 +travels a fascinating arc from hope and euphoria to reality and disillusionment . SST-2 1 +harris commands the screen , using his frailty to suggest the ravages of a life of corruption and ruthlessness . SST-2 1 +while undisputed is n't exactly a high , it is a gripping , tidy little movie that takes mr. hill higher than he 's been in a while . SST-2 1 +lapaglia 's ability to convey grief and hope works with weaver 's sensitive reactions to make this a two-actor master class . SST-2 1 +the old-world - meets-new mesh is incarnated in the movie 's soundtrack , a joyful effusion of disco bollywood that , by the end of monsoon wedding , sent my spirit soaring out of the theater . SST-2 1 +richard gere and diane lane put in fine performances as does french actor oliver martinez . SST-2 1 +the far future may be awesome to consider , but from period detail to matters of the heart , this film is most transporting when it stays put in the past . SST-2 1 +this flick is about as cool and crowd-pleasing as a documentary can get . SST-2 1 +the son 's room is a triumph of gentility that earns its moments of pathos . SST-2 1 +the film 's welcome breeziness and some unbelievably hilarious moments -- most portraying the idiocy of the film industry -- make it mostly worth the trip . SST-2 1 +you will emerge with a clearer view of how the gears of justice grind on and the death report comes to share airtime alongside the farm report . SST-2 1 +there has always been something likable about the marquis de sade . SST-2 1 +a difficult , absorbing film that manages to convey more substance despite its repetitions and inconsistencies than do most films than are far more pointed and clear . SST-2 1 +very psychoanalytical -- provocatively so -- and also refreshingly literary . SST-2 1 +the sort of film that makes me miss hitchcock , but also feel optimistic that there 's hope for popular cinema yet . SST-2 1 +highbrow self-appointed guardians of culture need not apply , but those who loved cool as ice have at last found a worthy follow-up . SST-2 1 +a superbly acted and funny\/gritty fable of the humanizing of one woman at the hands of the unseen forces of fate . SST-2 1 +we root for (clara and paul) , even like them , though perhaps it 's an emotion closer to pity . SST-2 1 +a poignant and compelling story about relationships , food of love takes us on a bumpy but satisfying journey of the heart . SST-2 1 +bogdanovich tantalizes by offering a peep show into the lives of the era 's creme de la celluloid . SST-2 1 +true tale of courage -- and complicity -- at auschwitz is a harrowing drama that tries to tell of the unspeakable . SST-2 1 +atom egoyan has conjured up a multilayered work that tackles any number of fascinating issues SST-2 1 +there 's a wickedly subversive bent to the best parts of birthday girl . SST-2 1 +inside the film 's conflict-powered plot there is a decent moral trying to get out , but it 's not that , it 's the tension that keeps you in your seat . SST-2 1 +challenging , intermittently engrossing and unflaggingly creative . SST-2 1 +i just loved every minute of this film . SST-2 1 +the film is powerful , accessible and funny . SST-2 1 +based on a devilishly witty script by heather mcgowan and niels mueller , the film gets great laughs , but never at the expense of its characters SST-2 1 +there 's really only one good idea in this movie , but the director runs with it and presents it with an unforgettable visual panache . SST-2 1 +does paint some memorable images ... , but makhmalbaf keeps her distance from the characters SST-2 1 +majidi is an unconventional storyteller , capable of finding beauty in the most depressing places . SST-2 1 +among the year 's most intriguing explorations of alientation . SST-2 1 +the continued good chemistry between carmen and juni is what keeps this slightly disappointing sequel going , with enough amusing banter -- blessedly curse-free -- to keep both kids and parents entertained . SST-2 1 +against all odds in heaven and hell , it creeped me out just fine . SST-2 1 +cool ? SST-2 1 +jones ... does offer a brutal form of charisma . SST-2 1 +an interesting story with a pertinent (cinematically unique) message , told fairly well and scored to perfection , i found myself struggling to put my finger on that elusive `` missing thing . '' SST-2 1 +it proves quite compelling as an intense , brooding character study . SST-2 1 +oscar wilde 's masterpiece , the importance of being earnest , may be the best play of the 19th century . SST-2 1 +... takes the beauty of baseball and melds it with a story that could touch anyone regardless of their familiarity with the sport SST-2 1 +for the first time in years , de niro digs deep emotionally , perhaps because he 's been stirred by the powerful work of his co-stars . SST-2 1 +leigh 's film is full of memorable performances from top to bottom . SST-2 1 +for movie lovers as well as opera lovers , tosca is a real treat . SST-2 1 +there are some wonderfully fresh moments that smooth the moral stiffness with human kindness and hopefulness . SST-2 1 +hilariously inept and ridiculous . SST-2 1 +it 's one of those baseball pictures where the hero is stoic , the wife is patient , the kids are as cute as all get-out and the odds against success are long enough to intimidate , but short enough to make a dream seem possible . SST-2 1 +a cartoon that 's truly cinematic in scope , and a story that 's compelling and heartfelt -- even if the heart belongs to a big , four-legged herbivore . SST-2 1 +the characters are interesting and often very creatively constructed from figure to backstory . SST-2 1 +an exhilarating futuristic thriller-noir , minority report twists the best of technology around a gripping story , delivering a riveting , pulse intensifying escapist adventure of the first order SST-2 1 +if you enjoy more thoughtful comedies with interesting conflicted characters ; this one is for you . SST-2 1 +candid and comfortable ; a film that deftly balances action and reflection as it lets you grasp and feel the passion others have for their work . SST-2 1 +manages to be sweet and wickedly satisfying at the same time . SST-2 1 +a solid examination of the male midlife crisis . SST-2 1 +fun , flip and terribly hip bit of cinematic entertainment . SST-2 1 +while locals will get a kick out of spotting cleveland sites , the rest of the world will enjoy a fast-paced comedy with quirks that might make the award-winning coen brothers envious . SST-2 1 +it 's a demented kitsch mess (although the smeary digital video does match the muddled narrative) , but it 's savvy about celebrity and has more guts and energy than much of what will open this year . SST-2 1 +it wants to tweak them with a taste of tangy new humor . SST-2 1 +a tender , witty , captivating film about friendship , love , memory , trust and loyalty . SST-2 1 +dense with characters and contains some thrilling moments . SST-2 1 +one of the more intelligent children 's movies to hit theaters this year . SST-2 1 +building slowly and subtly , the film , sporting a breezy spontaneity and realistically drawn characterizations , develops into a significant character study that is both moving and wise . SST-2 1 +birthday girl is an amusing joy ride , with some surprisingly violent moments . SST-2 1 +the piece plays as well as it does thanks in large measure to anspaugh 's three lead actresses . SST-2 1 +the band 's courage in the face of official repression is inspiring , especially for aging hippies (this one included) . SST-2 1 +the movie understands like few others how the depth and breadth of emotional intimacy give the physical act all of its meaning and most of its pleasure . SST-2 1 +a spellbinding african film about the modern condition of rootlessness , a state experienced by millions around the globe . SST-2 1 +it 's a scattershot affair , but when it hits its mark it 's brilliant . SST-2 1 +a romantic comedy enriched by a sharp eye for manners and mores . SST-2 1 +though it 's become almost redundant to say so , major kudos go to leigh for actually casting people who look working-class . SST-2 1 +filmmakers who can deftly change moods are treasures and even marvels . SST-2 1 +... mafia , rap stars and hood rats butt their ugly heads in a regurgitation of cinematic violence that gives brutal birth to an unlikely , but likable , hero . ' SST-2 1 +may be far from the best of the series , but it 's assured , wonderfully respectful of its past and thrilling enough to make it abundantly clear that this movie phenomenon has once again reinvented itself for a new generation . SST-2 1 +while the ideas about techno-saturation are far from novel , they 're presented with a wry dark humor . SST-2 1 +the chateau cleverly probes the cross-cultural differences between gauls and yanks . SST-2 1 +this surreal gilliam-esque film is also a troubling interpretation of ecclesiastes . SST-2 1 +dazzling in its complexity , disturbing for its extraordinary themes , the piano teacher is a film that defies categorisation . SST-2 1 +griffiths proves she 's that rare luminary who continually raises the standard of her profession . SST-2 1 +(t) his beguiling belgian fable , very much its own droll and delicate little film , has some touching things to say about what is important in life and why . SST-2 1 +crackerjack entertainment -- nonstop romance , music , suspense and action . SST-2 1 +entertains by providing good , lively company . SST-2 1 +whether writer-director anne fontaine 's film is a ghost story , an account of a nervous breakdown , a trip down memory lane , all three or none of the above , it is as seductive as it is haunting . SST-2 1 +turns potentially forgettable formula into something strangely diverting . SST-2 1 +no sophomore slump for director sam mendes , who segues from oscar winner to oscar-winning potential with a smooth sleight of hand . SST-2 1 +it 's a much more emotional journey than what shyamalan has given us in his past two movies , and gibson , stepping in for bruce willis , is the perfect actor to take us on the trip . SST-2 1 +good film , but very glum . SST-2 1 +` de niro ... is a veritable source of sincere passion that this hollywood contrivance orbits around . ' SST-2 1 +sade is an engaging look at the controversial eponymous and fiercely atheistic hero . SST-2 1 +although german cooking does not come readily to mind when considering the world 's best cuisine , mostly martha could make deutchland a popular destination for hungry tourists . SST-2 1 +this is a winning ensemble comedy that shows canadians can put gentle laughs and equally gentle sentiments on the button , just as easily as their counterparts anywhere else in the world . SST-2 1 +the vivid lead performances sustain interest and empathy , but the journey is far more interesting than the final destination . SST-2 1 +it 's about following your dreams , no matter what your parents think . SST-2 1 +a fast , funny , highly enjoyable movie . SST-2 1 +it 's hard to imagine alan arkin being better than he is in this performance . SST-2 1 +it seems like i have been waiting my whole life for this movie and now i ca n't wait for the sequel . SST-2 1 +(lawrence bounces) all over the stage , dancing , running , sweating , mopping his face and generally displaying the wacky talent that brought him fame in the first place . SST-2 1 +light years \/ several warp speeds \/ levels and levels of dilithium crystals better than the pitiful insurrection . SST-2 1 +corny , schmaltzy and predictable , but still manages to be kind of heartwarming , nonetheless . SST-2 1 +the socio-histo-political treatise is told in earnest strides ... (and) personal illusion is deconstructed with poignancy . SST-2 1 +an important movie , a reminder of the power of film to move us and to make us examine our values . SST-2 1 +the inspirational screenplay by mike rich covers a lot of ground , perhaps too much , but ties things together , neatly , by the end . SST-2 1 +a gorgeous , high-spirited musical from india that exquisitely blends music , dance , song , and high drama . SST-2 1 +a psychological thriller with a genuinely spooky premise and an above-average cast , actor bill paxton 's directing debut is a creepy slice of gothic rural americana . SST-2 1 +for the most part , director anne-sophie birot 's first feature is a sensitive , extraordinarily well-acted drama . SST-2 1 +good old-fashioned slash-and-hack is back ! SST-2 1 +the draw (for `` big bad love '') is a solid performance by arliss howard . SST-2 1 +scooby dooby doo \/ and shaggy too \/ you both look and sound great . SST-2 1 +thekids will probably stay amused at the kaleidoscope of big , colorful characters . SST-2 1 +there is a fabric of complex ideas here , and feelings that profoundly deepen them . SST-2 1 +one of creepiest , scariest movies to come along in a long , long time , easily rivaling blair witch or the others . SST-2 1 +liotta put on 30 pounds for the role , and has completely transformed himself from his smooth , goodfellas image . SST-2 1 +a deep and meaningful film . SST-2 1 +a coda in every sense , the pinochet case splits time between a minute-by-minute account of the british court 's extradition chess game and the regime 's talking-head survivors . SST-2 1 +a subtle and well-crafted (for the most part) chiller . SST-2 1 +the film may appear naked in its narrative form ... but it goes deeper than that , to fundamental choices that include the complexity of the catholic doctrine SST-2 1 +the quality of the art combined with the humor and intelligence of the script allow the filmmakers to present the biblical message of forgiveness without it ever becoming preachy or syrupy . SST-2 1 +my big fat greek wedding uses stereotypes in a delightful blend of sweet romance and lovingly dished out humor . SST-2 1 +at its best , queen is campy fun like the vincent price horror classics of the '60s . SST-2 1 +immersing us in the endlessly inventive , fiercely competitive world of hip-hop djs , the project is sensational and revelatory , even if scratching makes you itch . SST-2 1 +pacino is brilliant as the sleep-deprived dormer , his increasing weariness as much existential as it is physical . SST-2 1 +despite the 2-d animation , the wild thornberrys movie makes for a surprisingly cinematic experience . SST-2 1 +a literate presentation that wonderfully weaves a murderous event in 1873 with murderous rage in 2002 . SST-2 1 +the film will play equally well on both the standard and giant screens . SST-2 1 +preaches to two completely different choirs at the same time , which is a pretty amazing accomplishment . SST-2 1 +manages to transcend the sex , drugs and show-tunes plot into something far richer . SST-2 1 +this illuminating documentary transcends our preconceived vision of the holy land and its inhabitants , revealing the human complexities beneath . SST-2 1 +it haunts , horrifies , startles and fascinates ; it is impossible to look away . SST-2 1 +his healthy sense of satire is light and fun ... SST-2 1 +zhang ... has done an amazing job of getting realistic performances from his mainly nonprofessional cast . SST-2 1 +the terrific and bewilderingly underrated campbell scott gives a star performance that is nothing short of mesmerizing . SST-2 1 +` easily my choice for one of the year 's best films . ' SST-2 1 +a simple , but gritty and well-acted ensemble drama that encompasses a potent metaphor for a country still dealing with its fascist past . SST-2 1 +a movie that reminds us of just how exciting and satisfying the fantasy cinema can be when it 's approached with imagination and flair . SST-2 1 +people cinema at its finest . SST-2 1 +a rewarding work of art for only the most patient and challenge-hungry moviegoers . SST-2 1 +allows us to hope that nolan is poised to embark a major career as a commercial yet inventive filmmaker . SST-2 1 +it 's a beautiful madness . SST-2 1 +offers much to enjoy ... and a lot to mull over in terms of love , loyalty and the nature of staying friends . SST-2 1 +... routine , harmless diversion and little else . SST-2 1 +a movie with a real anarchic flair . SST-2 1 +old-form moviemaking at its best . SST-2 1 +on this tricky topic , tadpole is very much a step in the right direction , with its blend of frankness , civility and compassion . SST-2 1 +visually imaginative , thematically instructive and thoroughly delightful , it takes us on a roller-coaster ride from innocence to experience without even a hint of that typical kiddie-flick sentimentality . SST-2 1 +a tender , heartfelt family drama . SST-2 1 +ramsay , as in ratcatcher , remains a filmmaker with an acid viewpoint and a real gift for teasing chilly poetry out of lives and settings that might otherwise seem drab and sordid . SST-2 1 +we know the plot 's a little crazy , but it held my interest from start to finish . SST-2 1 +a quiet , pure , elliptical film SST-2 1 +with rabbit-proof fence , noyce has tailored an epic tale into a lean , economical movie . SST-2 1 +the performances take the movie to a higher level . SST-2 1 +it 's a charming and often affecting journey . SST-2 1 +further proof that the epicenter of cool , beautiful , thought-provoking foreign cinema is smack-dab in the middle of dubya 's axis of evil . SST-2 1 +for the most part stevens glides through on some solid performances and witty dialogue . SST-2 1 +the special effects and many scenes of weightlessness look as good or better than in the original , while the oscar-winning sound and james horner 's rousing score make good use of the hefty audio system . SST-2 1 +nine queens is not only than a frighteningly capable debut and genre piece , but also a snapshot of a dangerous political situation on the verge of coming to a head . SST-2 1 +featuring a dangerously seductive performance from the great daniel auteuil , `` sade '' covers the same period as kaufmann 's `` quills '' with more unsettlingly realistic results . SST-2 1 +escaping the studio , piccoli is warmly affecting and so is this adroitly minimalist movie . SST-2 1 +the jabs it employs are short , carefully placed and dead-center . SST-2 1 +chilling , well-acted , and finely directed : david jacobson 's dahmer . SST-2 1 +nothing 's at stake , just a twisty double-cross you can smell a mile away -- still , the derivative nine queens is lots of fun . SST-2 1 +it just may inspire a few younger moviegoers to read stevenson 's book , which is a treasure in and of itself . SST-2 1 +ahhhh ... revenge is sweet ! SST-2 1 +whether you like rap music or loathe it , you ca n't deny either the tragic loss of two young men in the prime of their talent or the power of this movie . SST-2 1 +beautifully observed , miraculously unsentimental comedy-drama . SST-2 1 +charles ' entertaining film chronicles seinfeld 's return to stand-up comedy after the wrap of his legendary sitcom , alongside wannabe comic adams ' attempts to get his shot at the big time . SST-2 1 +not since tom cruise in risky business has an actor made such a strong impression in his underwear . SST-2 1 +what distinguishes time of favor from countless other thrillers is its underlying concern with the consequences of words and with the complicated emotions fueling terrorist acts . SST-2 1 +it has charm to spare , and unlike many romantic comedies , it does not alienate either gender in the audience . SST-2 1 +collateral damage finally delivers the goods for schwarzenegger fans . SST-2 1 +it 's a stunning lyrical work of considerable force and truth . SST-2 1 +overall very good for what it 's trying to do . SST-2 1 +`` mostly martha '' is a bright , light modern day family parable that wears its heart on its sleeve for all to see . SST-2 1 +it 's the chemistry between the women and the droll scene-stealing wit and wolfish pessimism of anna chancellor that makes this `` two weddings and a funeral '' fun . SST-2 1 +a delectable and intriguing thriller filled with surprises , read my lips is an original . SST-2 1 +there is nothing outstanding about this film , but it is good enough and will likely be appreciated most by sailors and folks who know their way around a submarine . SST-2 1 +drops you into a dizzying , volatile , pressure-cooker of a situation that quickly snowballs out of control , while focusing on the what much more than the why . SST-2 1 +it 's refreshing to see a girl-power movie that does n't feel it has to prove anything . SST-2 1 +it 's a work by an artist so in control of both his medium and his message that he can improvise like a jazzman . SST-2 1 +a beguiling splash of pastel colors and prankish comedy from disney . SST-2 1 +the film tunes into a grief that could lead a man across centuries . SST-2 1 +a very well-made , funny and entertaining picture . SST-2 1 +macdowell , whose wifty southern charm has anchored lighter affairs ... brings an absolutely riveting conviction to her role . SST-2 1 +if your taste runs to ` difficult ' films you absolutely ca n't miss it . SST-2 1 +a full world has been presented onscreen , not some series of carefully structured plot points building to a pat resolution . SST-2 1 +the second coming of harry potter is a film far superior to its predecessor . SST-2 1 +michael gerbosi 's script is economically packed with telling scenes . SST-2 1 +it 's worth seeing just on the basis of the wisdom , and at times , the startling optimism , of the children . SST-2 1 +moody , heartbreaking , and filmed in a natural , unforced style that makes its characters seem entirely convincing even when its script is not . SST-2 1 +one from the heart . SST-2 1 +bennett 's naturalistic performance speaks volumes more truth than any ` reality ' show , and anybody contemplating their own drastic life changes should watch some body first . SST-2 1 +an artful , intelligent film that stays within the confines of a well-established genre . SST-2 1 +it 's also , clearly , great fun . SST-2 1 +a film about a young man finding god that is accessible and touching to the marrow . SST-2 1 +woody allen 's latest is an ambling , broad comedy about all there is to love -- and hate -- about the movie biz . SST-2 1 +montias ... pumps a lot of energy into his nicely nuanced narrative and surrounds himself with a cast of quirky -- but not stereotyped -- street characters . SST-2 1 +it 's great escapist fun that recreates a place and time that will never happen again . SST-2 1 +manages to show life in all of its banality when the intention is quite the opposite . SST-2 0 +though perry and hurley make inspiring efforts to breathe life into the disjointed , haphazard script by jay scherick and david ronn , neither the actors nor director reginald hudlin can make it more than fitfully entertaining . SST-2 0 +so devoid of any kind of intelligible story that it makes films like xxx and collateral damage seem like thoughtful treatises SST-2 0 +it 's a bad thing when a movie has about as much substance as its end credits blooper reel . SST-2 0 +coughs and sputters on its own postmodern conceit . SST-2 0 +... the film suffers from a lack of humor (something needed to balance out the violence) ... SST-2 0 +holden caulfield did it better . SST-2 0 +this is a shameless sham , calculated to cash in on the popularity of its stars . SST-2 0 +an absurdist comedy about alienation , separation and loss . SST-2 0 +this is n't even madonna 's swept away . SST-2 0 +if the movie succeeds in instilling a wary sense of ` there but for the grace of god , ' it is far too self-conscious to draw you deeply into its world . SST-2 0 +every time you look , sweet home alabama is taking another bummer of a wrong turn . SST-2 0 +a sequel that 's much too big for its britches . SST-2 0 +too restrained to be a freak show , too mercenary and obvious to be cerebral , too dull and pretentious to be engaging ... the isle defies an easy categorization . SST-2 0 +professionally speaking , it 's tempting to jump ship in january to avoid ridiculous schlock like this shoddy suspense thriller . SST-2 0 +why make a documentary about these marginal historical figures ? SST-2 0 +there is n't nearly enough fun here , despite the presence of some appealing ingredients . SST-2 0 +for each chuckle there are at least 10 complete misses , many coming from the amazingly lifelike tara reid , whose acting skills are comparable to a cardboard cutout . SST-2 0 +in execution , this clever idea is far less funny than the original , killers from space . SST-2 0 +it 's slow -- very , very slow . SST-2 0 +impostor has a handful of thrilling moments and a couple of good performances , but the movie does n't quite fly . SST-2 0 +green might want to hang onto that ski mask , as robbery may be the only way to pay for his next project . SST-2 0 +it 's a bit disappointing that it only manages to be decent instead of dead brilliant . SST-2 0 +this is so bad . SST-2 0 +the last 20 minutes are somewhat redeeming , but most of the movie is the same teenage american road-trip drek we 've seen before - only this time you have to read the fart jokes SST-2 0 +a lackluster , unessential sequel to the classic disney adaptation of j.m. barrie 's peter pan . SST-2 0 +a wildly inconsistent emotional experience . SST-2 0 +the film flat lines when it should peak and is more missed opportunity and trifle than dark , decadent truffle . SST-2 0 +excessive , profane , packed with cartoonish violence and comic-strip characters . SST-2 0 +given how heavy-handed and portent-heavy it is , this could be the worst thing soderbergh has ever done . SST-2 0 +it ca n't decide if it wants to be a mystery\/thriller , a romance or a comedy . SST-2 0 +this movie is something of an impostor itself , stretching and padding its material in a blur of dead ends and distracting camera work . SST-2 0 +unflinchingly bleak and desperate SST-2 0 +one of the more irritating cartoons you will see this , or any , year . SST-2 0 +you really have to wonder how on earth anyone , anywhere could have thought they 'd make audiences guffaw with a script as utterly diabolical as this . SST-2 0 +sticky sweet sentimentality , clumsy plotting and a rosily myopic view of life in the wwii-era mississippi delta undermine this adaptation . SST-2 0 +corpus collosum -- while undeniably interesting -- wore out its welcome well before the end credits rolled about 45 minutes in . SST-2 0 +in its best moments , resembles a bad high school production of grease , without benefit of song . SST-2 0 +the most hopelessly monotonous film of the year , noteworthy only for the gimmick of being filmed as a single unbroken 87-minute take . SST-2 0 +for all its technical virtuosity , the film is so mired in juvenile and near-xenophobic pedagogy that it 's enough to make one pine for the day when godard can no longer handle the rigors of filmmaking . SST-2 0 +there seems to be no clear path as to where the story 's going , or how long it 's going to take to get there . SST-2 0 +it has its moments of swaggering camaraderie , but more often just feels generic , derivative and done to death . SST-2 0 +an unclassifiably awful study in self - and audience-abuse . SST-2 0 +comes off like a rejected abc afterschool special , freshened up by the dunce of a screenwriting 101 class . SST-2 0 +however it may please those who love movies that blare with pop songs , young science fiction fans will stomp away in disgust . SST-2 0 +it 's dumb , but more importantly , it 's just not scary . SST-2 0 +an overemphatic , would-be wacky , ultimately tedious sex farce . SST-2 0 +not exactly the bees knees SST-2 0 +some of their jokes work , but most fail miserably and in the end , pumpkin is far more offensive than it is funny . SST-2 0 +it 's one pussy-ass world when even killer-thrillers revolve around group therapy sessions . SST-2 0 +i 'm just too bored to care . SST-2 0 +indifferently implausible popcorn programmer of a movie . SST-2 0 +tries to add some spice to its quirky sentiments but the taste is all too familiar . SST-2 0 +it 's too self-important and plodding to be funny , and too clipped and abbreviated to be an epic . SST-2 0 +this is the sort of burly action flick where one coincidence pummels another , narrative necessity is a drunken roundhouse , and whatever passes for logic is a factor of the last plot device left standing . SST-2 0 +it takes a certain kind of horror movie to qualify as ` worse than expected , ' but ghost ship somehow manages to do exactly that . SST-2 0 +the film contains no good jokes , no good scenes , barely a moment when carvey 's saturday night live-honed mimicry rises above the level of embarrassment . SST-2 0 +it all drags on so interminably it 's like watching a miserable relationship unfold in real time . SST-2 0 +in all , this is a watchable movie that 's not quite the memorable experience it might have been . SST-2 0 +it 's like watching a nightmare made flesh . SST-2 0 +every nanosecond of the the new guy reminds you that you could be doing something else far more pleasurable . SST-2 0 +... plot holes so large and obvious a marching band might as well be stomping through them in clown clothes , playing a college football fight song on untuned instruments . SST-2 0 +whaley 's determination to immerse you in sheer , unrelenting wretchedness is exhausting . SST-2 0 +the longer the movie goes , the worse it gets , but it 's actually pretty good in the first few minutes . SST-2 0 +has all the depth of a wading pool . SST-2 0 +expect the same-old , lame-old slasher nonsense , just with different scenery . SST-2 0 +i do n't think i laughed out loud once . SST-2 0 +a hamfisted romantic comedy that makes our girl the hapless facilitator of an extended cheap shot across the mason-dixon line . SST-2 0 +it 's of the quality of a lesser harrison ford movie - six days , seven nights , maybe , or that dreadful sabrina remake . SST-2 0 +the result is a gaudy bag of stale candy , something from a halloween that died . SST-2 0 +a by-the-numbers patient\/doctor pic that covers all the usual ground SST-2 0 +may reawaken discussion of the kennedy assassination but this fictional film looks made for cable rather than for the big screen . SST-2 0 +and that leaves a hole in the center of the salton sea . SST-2 0 +irwin is a man with enough charisma and audacity to carry a dozen films , but this particular result is ultimately held back from being something greater . SST-2 0 +this is not the undisputed worst boxing movie ever , but it 's certainly not a champion - the big loser is the audience . SST-2 0 +while (hill) has learned new tricks , the tricks alone are not enough to salvage this lifeless boxing film . SST-2 0 +it takes talent to make a lifeless movie about the most heinous man who ever lived . SST-2 0 +vera 's technical prowess ends up selling his film short ; he smoothes over hard truths even as he uncovers them . SST-2 0 +for close to two hours the audience is forced to endure three terminally depressed , mostly inarticulate , hyper dysfunctional families for the price of one . SST-2 0 +pumpkin means to be an outrageous dark satire on fraternity life , but its ambitions far exceed the abilities of writer adam larson broder and his co-director , tony r. abrams , in their feature debut . SST-2 0 +with virtually no interesting elements for an audience to focus on , chelsea walls is a triple-espresso endurance challenge . SST-2 0 +it 's so mediocre , despite the dynamic duo on the marquee , that we just ca n't get no satisfaction . SST-2 0 +attempts by this ensemble film to impart a message are so heavy-handed that they instead pummel the audience . SST-2 0 +davis ... is so enamored of her own creation that she ca n't see how insufferable the character is . SST-2 0 +no aspirations to social import inform the movie version . SST-2 0 +the words , ` frankly , my dear , i do n't give a damn , ' have never been more appropriate . SST-2 0 +a coarse and stupid gross-out . SST-2 0 +a bloated gasbag thesis grotesquely impressed by its own gargantuan aura of self-importance ... SST-2 0 +... a hollow joke told by a cinematic gymnast having too much fun embellishing the misanthropic tale to actually engage it . SST-2 0 +as vulgar as it is banal . SST-2 0 +\/ but daphne , you 're too buff \/ fred thinks he 's tough \/ and velma - wow , you 've lost weight ! SST-2 0 +but it 's too long and too convoluted and it ends in a muddle . SST-2 0 +not only unfunny , but downright repellent . SST-2 0 +this re-do is so dumb and so exploitative in its violence that , ironically , it becomes everything that the rather clumsy original was railing against . SST-2 0 +or emptying rat traps . SST-2 0 +basically a static series of semi-improvised (and semi-coherent) raps between the stars . SST-2 0 +without non-stop techno or the existential overtones of a kieslowski morality tale , maelström is just another winter sleepers . SST-2 0 +... nothing scary here except for some awful acting and lame special effects . SST-2 0 +nelson 's brutally unsentimental approach ... sucks the humanity from the film , leaving behind an horrific but weirdly unemotional spectacle . SST-2 0 +in the end , we are left with something like two ships passing in the night rather than any insights into gay love , chinese society or the price one pays for being dishonest . SST-2 0 +if the first men in black was money , the second is small change . SST-2 0 +it 's as if you 're watching a movie that was made in 1978 but not released then because it was so weak , and it has been unearthed and released now , when it has become even weaker . SST-2 0 +its well of thorn and vinegar (and simple humanity) has long been plundered by similar works featuring the insight and punch this picture so conspicuously lacks . SST-2 0 +like you could n't smell this turkey rotting from miles away . SST-2 0 +to call the other side of heaven `` appalling '' would be to underestimate just how dangerous entertainments like it can be . SST-2 0 +feels haphazard , as if the writers mistakenly thought they could achieve an air of frantic spontaneity by simply tossing in lots of characters doing silly stuff and stirring the pot . SST-2 0 +there is very little dread or apprehension , and though i like the creepy ideas , they are not executed with anything more than perfunctory skill . SST-2 0 +with its dogged hollywood naturalism and the inexorable passage of its characters toward sainthood , windtalkers is nothing but a sticky-sweet soap . SST-2 0 +no telegraphing is too obvious or simplistic for this movie . SST-2 0 +what was once original has been co-opted so frequently that it now seems pedestrian . SST-2 0 +teen movies have really hit the skids . SST-2 0 +hit and miss as far as the comedy goes and a big ole ' miss in the way of story . SST-2 0 +rarely has leukemia looked so shimmering and benign . SST-2 0 +the movie is dawn of the dead crossed with john carpenter 's ghosts of mars , with zombies not as ghoulish as the first and trains not as big as the second . SST-2 0 +it made me want to wrench my eyes out of my head and toss them at the screen . SST-2 0 +director uwe boll and the actors provide scant reason to care in this crude '70s throwback . SST-2 0 +trite , banal , cliched , mostly inoffensive . SST-2 0 +i had to look away - this was god awful . SST-2 0 +it 's difficult to imagine the process that produced such a script , but here 's guessing that spray cheese and underarm noises played a crucial role . SST-2 0 +a nightmare date with a half-formed wit done a great disservice by a lack of critical distance and a sad trust in liberal arts college bumper sticker platitudes . SST-2 0 +at once half-baked and overheated . SST-2 0 +just not campy enough SST-2 0 +the director knows how to apply textural gloss , but his portrait of sex-as-war is strictly sitcom . SST-2 0 +ultimately feels empty and unsatisfying , like swallowing a communion wafer without the wine . SST-2 0 +forced , familiar and thoroughly condescending . SST-2 0 +characters still need to function according to some set of believable and comprehensible impulses , no matter how many drugs they do or how much artistic license avary employs . SST-2 0 +even on those rare occasions when the narrator stops yammering , miller 's hand often feels unsure . SST-2 0 +... is an arthritic attempt at directing by callie khouri . SST-2 0 +i am sorry that i was unable to get the full brunt of the comedy . SST-2 0 +almost gags on its own gore . SST-2 0 +when the film ended , i felt tired and drained and wanted to lie on my own deathbed for a while . SST-2 0 +there 's not enough here to justify the almost two hours . SST-2 0 +jason x is positively anti-darwinian : nine sequels and 400 years later , the teens are none the wiser and jason still kills on auto-pilot . SST-2 0 +looks and feels like a project better suited for the small screen . SST-2 0 +the humor is n't as sharp , the effects not as innovative , nor the story as imaginative as in the original . SST-2 0 +the title not only describes its main characters , but the lazy people behind the camera as well . SST-2 0 +one of those pictures whose promising , if rather precious , premise is undercut by amateurish execution . SST-2 0 +comes ... uncomfortably close to coasting in the treads of the bicycle thief . SST-2 0 +slapstick buffoonery can tickle many a preschooler 's fancy , but when it costs a family of four about $ 40 to see a film in theaters , why spend money on a dog like this when you can rent a pedigree instead ? SST-2 0 +do n't be fooled by the impressive cast list - eye see you is pure junk . SST-2 0 +(a) shapeless blob of desperate entertainment . SST-2 0 +the film 's few ideas are stretched to the point of evaporation ; the whole central section is one big chase that seems to have no goal and no urgency . SST-2 0 +nervous breakdowns are not entertaining . SST-2 0 +k-19 exploits our substantial collective fear of nuclear holocaust to generate cheap hollywood tension . SST-2 0 +this piece of channel 5 grade trash is , quite frankly , an insult to the intelligence of the true genre enthusiast . SST-2 0 +while it 's genuinely cool to hear characters talk about early rap records (sugar hill gang , etc.) , the constant referencing of hip-hop arcana can alienate even the savviest audiences . SST-2 0 +of course , by more objective measurements it 's still quite bad . SST-2 0 +it treats women like idiots . SST-2 0 +the affectionate loopiness that once seemed congenital to demme 's perspective has a tough time emerging from between the badly dated cutesy-pie mystery scenario and the newfangled hollywood post-production effects . SST-2 0 +in exactly 89 minutes , most of which passed as slowly as if i 'd been sitting naked on an igloo , formula 51 sank from quirky to jerky to utter turkey . SST-2 0 +the x potion gives the quickly named blossom , bubbles and buttercup supernatural powers that include extraordinary strength and laser-beam eyes , which unfortunately do n't enable them to discern flimsy screenplays . SST-2 0 +no way i can believe this load of junk . SST-2 0 +fancy a real downer ? SST-2 0 +involves two mysteries -- one it gives away and the other featuring such badly drawn characters that its outcome hardly matters . SST-2 0 +there 's too much falseness to the second half , and what began as an intriguing look at youth fizzles into a dull , ridiculous attempt at heart-tugging . SST-2 0 +one long string of cliches . SST-2 0 +scores no points for originality , wit , or intelligence . SST-2 0 +it 's another stale , kill-by-numbers flick , complete with blade-thin characters and terrible , pun-laden dialogue . SST-2 0 +should have been someone else - SST-2 0 +the movie 's accumulated force still feels like an ugly knot tightening in your stomach . SST-2 0 +the film tries too hard to be funny and tries too hard to be hip . SST-2 0 +late marriage 's stiffness is unlikely to demonstrate the emotional clout to sweep u.s. viewers off their feet . SST-2 0 +villeneuve spends too much time wallowing in bibi 's generic angst (there are a lot of shots of her gazing out windows) . SST-2 0 +rarely has so much money delivered so little entertainment . SST-2 0 +the notion that bombing buildings is the funniest thing in the world goes entirely unexamined in this startlingly unfunny comedy . SST-2 0 +a subject like this should inspire reaction in its audience ; the pianist does not . SST-2 0 +outer-space buffs might love this film , but others will find its pleasures intermittent . SST-2 0 +at times , the suspense is palpable , but by the end there 's a sense that the crux of the mystery hinges on a technicality that strains credulity and leaves the viewer haunted by the waste of potential . SST-2 0 +dull , lifeless , and amateurishly assembled . SST-2 0 +big fat waste of time . SST-2 0 +american chai encourages rueful laughter at stereotypes only an indian-american would recognize . SST-2 0 +if director michael dowse only superficially understands his characters , he does n't hold them in contempt . SST-2 0 +(director) o'fallon manages to put some lovely pictures up on the big screen , but his skill at telling a story -- he also contributed to the screenplay -- falls short . SST-2 0 +it has all the excitement of eating oatmeal . SST-2 0 +a string of rehashed sight gags based in insipid vulgarity . SST-2 0 +just embarrassment and a vague sense of shame . SST-2 0 +if you believe any of this , i can make you a real deal on leftover enron stock that will double in value a week from friday . SST-2 0 +this movie seems to have been written using mad-libs . SST-2 0 +all that 's missing is the spontaneity , originality and delight . SST-2 0 +to say this was done better in wilder 's some like it hot is like saying the sun rises in the east . SST-2 0 +unfortunately , it 's not silly fun unless you enjoy really bad movies . SST-2 0 +(t) here 's only so much anyone can do with a florid , overplotted , anne rice rock 'n' roll vampire novel before the built-in silliness of the whole affair defeats them . SST-2 0 +is the time really ripe for a warmed-over james bond adventure , with a village idiot as the 007 clone ? SST-2 0 +plays like a volatile and overlong w magazine fashion spread . SST-2 0 +by getting myself wrapped up in the visuals and eccentricities of many of the characters , i found myself confused when it came time to get to the heart of the movie . SST-2 0 +on the whole , the movie lacks wit , feeling and believability to compensate for its incessant coarseness and banality . SST-2 0 +the script kicks in , and mr. hartley 's distended pace and foot-dragging rhythms follow . SST-2 0 +pumpkin takes an admirable look at the hypocrisy of political correctness , but it does so with such an uneven tone that you never know when humor ends and tragedy begins . SST-2 0 +the reality of the new live-action pinocchio he directed , cowrote and starred in borders on the grotesque . SST-2 0 +perceptive in its vision of nascent industrialized world politics as a new art form , but far too clunky , didactic and saddled with scenes that seem simply an ill fit for this movie . SST-2 0 +the plot convolutions ultimately add up to nothing more than jerking the audience 's chain . SST-2 0 +there is no pleasure in watching a child suffer . SST-2 0 +care deftly captures the wonder and menace of growing up , but he never really embraces the joy of fuhrman 's destructive escapism or the grace-in-rebellion found by his characters . SST-2 0 +... plays like somebody spliced random moments of a chris rock routine into what is otherwise a cliche-riddled but self-serious spy thriller . SST-2 0 +it 's a grab bag of genres that do n't add up to a whole lot of sense . SST-2 0 +i sympathize with the plight of these families , but the movie does n't do a very good job conveying the issue at hand . SST-2 0 +portentous and pretentious , the weight of water is appropriately titled , given the heavy-handedness of it drama . SST-2 0 +what the director ca n't do is make either of val kilmer 's two personas interesting or worth caring about . SST-2 0 +this is a train wreck of an action film -- a stupefying attempt by the filmmakers to force-feed james bond into the mindless xxx mold and throw 40 years of cinematic history down the toilet in favor of bright flashes and loud bangs . SST-2 0 +... a boring parade of talking heads and technical gibberish that will do little to advance the linux cause . SST-2 0 +the film is based on truth and yet there is something about it that feels incomplete , as if the real story starts just around the corner . SST-2 0 +but what are adults doing in the theater at all ? SST-2 0 +a dumb movie with dumb characters doing dumb things and you have to be really dumb not to see where this is going . SST-2 0 +this nickleby thing might have more homosexual undertones than an eddie murphy film . SST-2 0 +too much of the humor falls flat . SST-2 0 +though moonlight mile is replete with acclaimed actors and actresses and tackles a subject that 's potentially moving , the movie is too predictable and too self-conscious to reach a level of high drama . SST-2 0 +the man from elysian fields is a cold , bliss-less work that groans along thinking itself some important comment on how life throws us some beguiling curves . SST-2 0 +utterly lacking in charm , wit and invention , roberto benigni 's pinocchio is an astonishingly bad film . SST-2 0 +a science-fiction pastiche so lacking in originality that if you stripped away its inspirations there would be precious little left . SST-2 0 +yes , dull . SST-2 0 +i thought my own watch had stopped keeping time as i slogged my way through clockstoppers . SST-2 0 +prurient playthings aside , there 's little to love about this english trifle . SST-2 0 +like watching a dress rehearsal the week before the show goes up : everything 's in place but something 's just a little off-kilter . SST-2 0 +there are simply too many ideas floating around -- part farce , part sliding doors , part pop video -- and yet failing to exploit them . SST-2 0 +you wonder why enough was n't just a music video rather than a full-length movie . SST-2 0 +as ` chick flicks ' go , this one is pretty miserable , resorting to string-pulling rather than legitimate character development and intelligent plotting . SST-2 0 +there 's enough melodrama in this magnolia primavera to make pta proud yet director muccino 's characters are less worthy of puccini than they are of daytime television . SST-2 0 +mattei is tiresomely grave and long-winded , as if circularity itself indicated profundity . SST-2 0 +a disappointment for those who love alternate versions of the bard , particularly ones that involve deep fryers and hamburgers . SST-2 0 +because of an unnecessary and clumsy last scene , ` swimfan ' left me with a very bad feeling . SST-2 0 +no one but a convict guilty of some truly heinous crime should have to sit through the master of disguise . SST-2 0 +there are plot holes big enough for shamu the killer whale to swim through . SST-2 0 +wince-inducing dialogue , thrift-shop costumes , prosthetic makeup by silly putty and kmart blue-light-special effects all conspire to test trekkie loyalty . SST-2 0 +the humor is forced and heavy-handed , and occasionally simply unpleasant . SST-2 0 +feels too formulaic and too familiar to produce the transgressive thrills of early underground work . SST-2 0 +i 'd have to say the star and director are the big problems here . SST-2 0 +the film 's tone and pacing are off almost from the get-go . SST-2 0 +something like scrubbing the toilet . SST-2 0 +that 's pure pr hype . SST-2 0 +nicks , seemingly uncertain what 's going to make people laugh , runs the gamut from stale parody to raunchy sex gags to formula romantic comedy . SST-2 0 +visually rather stunning , but ultimately a handsome-looking bore , the true creativity would have been to hide treasure planet entirely and completely reimagine it . SST-2 0 +the character of zigzag is not sufficiently developed to support a film constructed around him . SST-2 0 +passable entertainment , but it 's the kind of motion picture that wo n't make much of a splash when it 's released , and will not be remembered long afterwards . SST-2 0 +does little more than play an innocuous game of fill-in - the-blanks with a tragic past . SST-2 0 +the piquant story needs more dramatic meat on its bones . SST-2 0 +like being trapped at a perpetual frat party ... how can something so gross be so boring ? SST-2 0 +my reaction in a word : disappointment . SST-2 0 +the talented and clever robert rodriguez perhaps put a little too much heart into his first film and did n't reserve enough for his second . SST-2 0 +part low rent godfather . SST-2 0 +i 'll bet the video game is a lot more fun than the film . SST-2 0 +despite all evidence to the contrary , this clunker has somehow managed to pose as an actual feature movie , the kind that charges full admission and gets hyped on tv and purports to amuse small children and ostensible adults . SST-2 0 +a very long movie , dull in stretches , with entirely too much focus on meal preparation and igloo construction . SST-2 0 +sit through this one , and you wo n't need a magic watch to stop time ; your dvd player will do it for you . SST-2 0 +a sometimes tedious film . SST-2 0 +it 's inoffensive , cheerful , built to inspire the young people , set to an unending soundtrack of beach party pop numbers and aside from its remarkable camerawork and awesome scenery , it 's about as exciting as a sunburn . SST-2 0 +suffers from the lack of a compelling or comprehensible narrative . SST-2 0 +a tv style murder mystery with a few big screen moments (including one that seems to be made for a different film altogether) . SST-2 0 +the experience of going to a film festival is a rewarding one ; the experiencing of sampling one through this movie is not . SST-2 0 +it 's just disappointingly superficial -- a movie that has all the elements necessary to be a fascinating , involving character study , but never does more than scratch the surface . SST-2 0 +what is 100 % missing here is a script of even the most elemental literacy , an inkling of genuine wit , and anything resembling acting . SST-2 0 +they should have called it gutterball . SST-2 0 +it offers little beyond the momentary joys of pretty and weightless intellectual entertainment . SST-2 0 +the only excitement comes when the credits finally roll and you get to leave the theater . SST-2 0 +the moviegoing equivalent of going to a dinner party and being forced to watch the host and hostess 's home video of their baby 's birth . SST-2 0 +the iditarod lasts for days - this just felt like it did . SST-2 0 +despite the evocative aesthetics evincing the hollow state of modern love life , the film never percolates beyond a monotonous whine . SST-2 0 +it 's not the ultimate depression-era gangster movie . SST-2 0 +schaeffer has to find some hook on which to hang his persistently useless movies , and it might as well be the resuscitation of the middle-aged character . SST-2 0 +taylor appears to have blown his entire budget on soundtrack rights and had nothing left over for jokes . SST-2 0 +too slow , too long and too little happens . SST-2 0 +it all feels like a monty python sketch gone horribly wrong . SST-2 0 +very special effects , brilliantly bold colors and heightened reality ca n't hide the giant achilles ' heel in `` stuart little 2 `` : there 's just no story , folks . SST-2 0 +stultifyingly , dumbfoundingly , mind-numbingly bad . SST-2 0 +suffocated by its fussy script and uptight characters , this musty adaptation is all the more annoying since it 's been packaged and sold back to us by hollywood . SST-2 0 +paid in full is so stale , in fact , that its most vibrant scene is one that uses clips from brian de palma 's scarface . SST-2 0 +well-nigh unendurable ... though the picture strains to become cinematic poetry , it remains depressingly prosaic and dull . SST-2 0 +moretti 's compelling anatomy of grief and the difficult process of adapting to loss . SST-2 0 +his comedy premises are often hackneyed or just plain crude , calculated to provoke shocked laughter , without following up on a deeper level . SST-2 0 +it appears that something has been lost in the translation to the screen . SST-2 0 +chokes on its own depiction of upper-crust decorum . SST-2 0 +so unremittingly awful that labeling it a dog probably constitutes cruelty to canines . SST-2 0 +the so-inept - it 's - surreal dubbing (featuring the voices of glenn close , regis philbin and breckin meyer) brings back memories of cheesy old godzilla flicks . SST-2 0 +it seems to me the film is about the art of ripping people off without ever letting them consciously know you have done so SST-2 0 +this one is definitely one to skip , even for horror movie fanatics . SST-2 0 +but it could have been worse . SST-2 0 +nothing in waking up in reno ever inspired me to think of its inhabitants as anything more than markers in a screenplay . SST-2 0 +made with no discernible craft and monstrously sanctimonious in dealing with childhood loss . SST-2 0 +oh come on . SST-2 0 +complete lack of originality , cleverness or even visible effort SST-2 0 +... designed to provide a mix of smiles and tears , `` crossroads '' instead provokes a handful of unintentional howlers and numerous yawns . SST-2 0 +sometimes seems less like storytelling than something the otherwise compelling director needed to get off his chest . SST-2 0 +it 's not original , and , robbed of the element of surprise , it does n't have any huge laughs in its story of irresponsible cops who love to play pranks . SST-2 0 +as the latest bid in the tv-to-movie franchise game , i spy makes its big-screen entry with little of the nervy originality of its groundbreaking small-screen progenitor . SST-2 0 +scorsese does n't give us a character worth giving a damn about . SST-2 0 +manages to be both repulsively sadistic and mundane . SST-2 0 +it 's another video movie photographed like a film , with the bad lighting that 's often written off as indie film naturalism . SST-2 0 +even with a green mohawk and a sheet of fire-red flame tattoos covering his shoulder , however , kilmer seems to be posing , rather than acting . SST-2 0 +the cold turkey would 've been a far better title . SST-2 0 +here 's yet another studio horror franchise mucking up its storyline with glitches casual fans could correct in their sleep . SST-2 0 +the script is n't very good ; not even someone as gifted as hoffman (the actor) can make it work . SST-2 0 +nothing is sacred in this gut-buster . SST-2 0 +makes for a pretty unpleasant viewing experience . SST-2 0 +the problem with this film is that it lacks focus . SST-2 0 +the end result is a film that 's neither . SST-2 0 +once the 50 year old benigni appears as the title character , we find ourselves longing for the block of wood to come back . SST-2 0 +its story may be a thousand years old , but why did it have to seem like it took another thousand to tell it to us ? SST-2 0 +a better title , for all concerned , might be swept under the rug . SST-2 0 +and when you 're talking about a slapstick comedy , that 's a pretty big problem . SST-2 0 +... turns so unforgivably trite in its last 10 minutes that anyone without a fortified sweet tooth will likely go into sugar shock . SST-2 0 +as a rumor of angels reveals itself to be a sudsy tub of supernatural hokum , not even ms. redgrave 's noblest efforts can redeem it from hopeless sentimentality . SST-2 0 +it 's a buggy drag . SST-2 0 +burns never really harnesses to full effect the energetic cast . SST-2 0 +it showcases carvey 's talent for voices , but not nearly enough and not without taxing every drop of one 's patience to get to the good stuff . SST-2 0 +it 's played in the most straight-faced fashion , with little humor to lighten things up . SST-2 0 +it 's everything you do n't go to the movies for . SST-2 0 +but the power of these (subjects) is obscured by the majority of the film that shows a stationary camera on a subject that could be mistaken for giving a public oration , rather than contributing to a film 's narrative . SST-2 0 +there 's no emotional pulse to solaris . SST-2 0 +less dizzying than just dizzy , the jaunt is practically over before it begins . SST-2 0 +it does nothing new with the old story , except to show fisticuffs in this sort of stop-go slow motion that makes the gang rumbles look like they 're being streamed over a 28k modem . SST-2 0 +this movie is maddening . SST-2 0 +instead of hiding pinocchio from critics , miramax should have hidden it from everyone . SST-2 0 +from the opening scenes , it 's clear that all about the benjamins is a totally formulaic movie . SST-2 0 +verbinski implements every hack-artist trick to give us the ooky-spookies . SST-2 0 +due to some script weaknesses and the casting of the director 's brother , the film trails off into inconsequentiality . SST-2 0 +deadeningly dull , mired in convoluted melodrama , nonsensical jargon and stiff-upper-lip laboriousness . SST-2 0 +for all the writhing and wailing , tears , rage and opium overdoses , there 's no sense of actual passion being washed away in love 's dissolution . SST-2 0 +`` the time machine '' is a movie that has no interest in itself . SST-2 0 +this riveting world war ii moral suspense story deals with the shadow side of american culture : racial prejudice in its ugly and diverse forms . SST-2 0 +offers very little genuine romance and even fewer laughs ... a sad sitcom of a movie , largely devoid of charm . SST-2 0 +nonsensical , dull `` cyber-horror '' flick is a grim , hollow exercise in flat scares and bad acting . SST-2 0 +a misogynistic piece of filth that attempts to pass itself off as hip , young adult entertainment . SST-2 0 +at its worst , it implodes in a series of very bad special effects . SST-2 0 +while the resident evil games may have set new standards for thrills , suspense , and gore for video games , the movie really only succeeds in the third of these . SST-2 0 +how do you spell cliché ? SST-2 0 +the story and the friendship proceeds in such a way that you 're watching a soap opera rather than a chronicle of the ups and downs that accompany lifelong friendships . SST-2 0 +stealing harvard is evidence that the farrelly bros. -- peter and bobby -- and their brand of screen comedy are wheezing to an end , along with green 's half-hearted movie career . SST-2 0 +partway through watching this saccharine , easter-egg-colored concoction , you realize that it is made up of three episodes of a rejected tv show . SST-2 0 +not really bad so much as distasteful : we need kidnapping suspense dramas right now like we need doomsday thrillers . SST-2 0 +very bad . SST-2 0 +adults will wish the movie were less simplistic , obvious , clumsily plotted and shallowly characterized . SST-2 0 +although huppert 's intensity and focus has a raw exhilaration about it , the piano teacher is anything but fun . SST-2 0 +the overall effect is less like a children 's movie than a recruitment film for future hollywood sellouts . SST-2 0 +samira makhmalbaf 's new film blackboards is much like the ethos of a stream of consciousness , although , it 's unfortunate for the viewer that the thoughts and reflections coming through are torpid and banal SST-2 0 +even the finest chef ca n't make a hotdog into anything more than a hotdog , and robert de niro ca n't make this movie anything more than a trashy cop buddy comedy . SST-2 0 +the entire movie is about a boring , sad man being boring and sad . SST-2 0 +sustains its dreamlike glide through a succession of cheesy coincidences and voluptuous cheap effects , not the least of which is rebecca romijn-stamos . SST-2 0 +the action switches between past and present , but the material link is too tenuous to anchor the emotional connections that purport to span a 125-year divide . SST-2 0 +it 's like every bad idea that 's ever gone into an after-school special compiled in one place , minus those daytime programs ' slickness and sophistication (and who knew they even had any ?) . SST-2 0 +directed in a paint-by-numbers manner . SST-2 0 +shaky close-ups of turkey-on-rolls , stubbly chins , liver spots , red noses and the filmmakers new bobbed do draw easy chuckles but lead nowhere . SST-2 0 +it can not be enjoyed , even on the level that one enjoys a bad slasher flick , primarily because it is dull . SST-2 0 +i 've always dreamed of attending cannes , but after seeing this film , it 's not that big a deal . SST-2 0 +but this films lacks the passion required to sell the material . SST-2 0 +or doing last year 's taxes with your ex-wife . SST-2 0 +there ought to be a directing license , so that ed burns can have his revoked . SST-2 0 +it 's a cookie-cutter movie , a cut-and-paste job . SST-2 0 +it feels like an after-school special gussied up with some fancy special effects , and watching its rote plot points connect is about as exciting as gazing at an egg timer for 93 minutes . SST-2 0 +(e) ventually , every idea in this film is flushed down the latrine of heroism . SST-2 0 +combining quick-cut editing and a blaring heavy metal much of the time , beck seems to be under the illusion that he 's shooting the latest system of a down video . SST-2 0 +another in-your-face wallow in the lower depths made by people who have never sung those blues . SST-2 0 +confirms the nagging suspicion that ethan hawke would be even worse behind the camera than he is in front of it . SST-2 0 +try as i may , i ca n't think of a single good reason to see this movie , even though everyone in my group extemporaneously shouted , ` thank you ! ' SST-2 0 +and the lesson , in the end , is nothing new . SST-2 0 +the movie is what happens when you blow up small potatoes to 10 times their natural size , and it ai n't pretty . SST-2 0 +movie fans , get ready to take off ... the other direction . SST-2 0 +chabrol has taken promising material for a black comedy and turned it instead into a somber chamber drama . SST-2 0 +a valueless kiddie paean to pro basketball underwritten by the nba . SST-2 0 +a by-the-numbers effort that wo n't do much to enhance the franchise . SST-2 0 +with the exception of some fleetingly amusing improvisations by cedric the entertainer as perry 's boss , there is n't a redeeming moment here . SST-2 0 +velocity represents everything wrong with '' independent film '' as a commodified , sold-out concept on the american filmmaking scene . SST-2 0 +it 's hampered by a lifetime-channel kind of plot and a lead actress who is out of her depth . SST-2 0 +zaidan 's script has barely enough plot to string the stunts together and not quite enough characterization to keep the faces straight . SST-2 0 +it does n't believe in itself , it has no sense of humor ... it 's just plain bored . SST-2 0 +i do n't mind having my heartstrings pulled , but do n't treat me like a fool . SST-2 0 +the lower your expectations , the more you 'll enjoy it . SST-2 0 +detox is ultimately a pointless endeavor . SST-2 0 +i got a headache watching this meaningless downer . SST-2 0 +sam mendes has become valedictorian at the school for soft landings and easy ways out . SST-2 0 +it takes a strange kind of laziness to waste the talents of robert forster , anne meara , eugene levy , and reginald veljohnson all in the same movie . SST-2 0 +it 's just filler . SST-2 0 +do not see this film . SST-2 0 +determined to be fun , and bouncy , with energetic musicals , the humor did n't quite engage this adult . SST-2 0 +bad . SST-2 0 +at least one scene is so disgusting that viewers may be hard pressed to retain their lunch . SST-2 0 +the heavy-handed film is almost laughable as a consequence . SST-2 0 +a broad , melodramatic estrogen opera that 's pretty toxic in its own right . SST-2 0 +more whiny downer than corruscating commentary . SST-2 0 +the best that can be said about the work here of scottish director ritchie ... is that he obviously does n't have his heart in it . SST-2 0 +it 's not that kung pow is n't funny some of the time -- it just is n't any funnier than bad martial arts movies are all by themselves , without all oedekerk 's impish augmentation . SST-2 0 +does n't offer much besides glib soullessness , raunchy language and a series of brutal set pieces ... that raise the bar on stylized screen violence . SST-2 0 +... the movie is just a plain old monster . SST-2 0 +every dance becomes about seduction , where backstabbing and betrayals are celebrated , and sex is currency . SST-2 0 +for all its impressive craftsmanship , and despite an overbearing series of third-act crescendos , lily chou-chou never really builds up a head of emotional steam . SST-2 0 +dragonfly has no atmosphere , no tension -- nothing but costner , flailing away . SST-2 0 +the film makes a fatal mistake : it asks us to care about a young man whose only apparent virtue is that he is not quite as unpleasant as some of the people in his life . SST-2 0 +works hard to establish rounded characters , but then has nothing fresh or particularly interesting to say about them . SST-2 0 +the movie , directed by mick jackson , leaves no cliche unturned , from the predictable plot to the characters straight out of central casting . SST-2 0 +the film 's hackneyed message is not helped by the thin characterizations , nonexistent plot and pretentious visual style . SST-2 0 +not an objectionable or dull film ; it merely lacks everything except good intentions . SST-2 0 +the movie fails to live up to the sum of its parts . SST-2 0 +this film seems thirsty for reflection , itself taking on adolescent qualities . SST-2 0 +it 's somewhat clumsy and too lethargically paced -- but its story about a mysterious creature with psychic abilities offers a solid build-up , a terrific climax , and some nice chills along the way . SST-2 0 +delivers the same old same old , tarted up with latin flava and turned out by hollywood playas . SST-2 0 +if looking for a thrilling sci-fi cinematic ride , do n't settle for this imposter . SST-2 0 +it 's hard to like a film about a guy who is utterly unlikeable , and shiner , starring michael caine as an aging british boxing promoter desperate for a taste of fame and fortune , is certainly that . SST-2 0 +that 's a cheat . SST-2 0 +in an effort , i suspect , not to offend by appearing either too serious or too lighthearted , it offends by just being wishy-washy . SST-2 0 +in the end , the movie collapses on its shaky foundation despite the best efforts of director joe carnahan . SST-2 0 +too much of it feels unfocused and underdeveloped . SST-2 0 +when leguizamo finally plugged an irritating character late in the movie . SST-2 0 +i can take infantile humor ... but this is the sort of infantile that makes you wonder about changing the director and writer 's diapers . SST-2 0 +an unwise amalgam of broadcast news and vibes . SST-2 0 +a great ensemble cast ca n't lift this heartfelt enterprise out of the familiar . SST-2 0 +stealing harvard aspires to comedic grand larceny but stands convicted of nothing more than petty theft of your time . SST-2 0 +an occasionally funny , but overall limp , fish-out-of-water story . SST-2 0 +a sequence of ridiculous shoot - 'em - up scenes . SST-2 0 +too often , the viewer is n't reacting to humor so much as they are wincing back in repugnance . SST-2 0 +it 's too bad that the helping hand he uses to stir his ingredients is also a heavy one . SST-2 0 +at the very least , if you do n't know anything about derrida when you walk into the theater , you wo n't know much more when you leave . SST-2 0 +if you 've ever entertained the notion of doing what the title of this film implies , what sex with strangers actually shows may put you off the idea forever . SST-2 0 +the tale of tok (andy lau) , a sleek sociopath on the trail of o (takashi sorimachi) , the most legendary of asian hitmen , is too scattershot to take hold . SST-2 0 +serving sara does n't serve up a whole lot of laughs . SST-2 0 +even horror fans will most likely not find what they 're seeking with trouble every day ; the movie lacks both thrills and humor . SST-2 0 +if there 's one thing this world needs less of , it 's movies about college that are written and directed by people who could n't pass an entrance exam . SST-2 0 +(w) hile long on amiable monkeys and worthy environmentalism , jane goodall 's wild chimpanzees is short on the thrills the oversize medium demands . SST-2 0 +just one bad idea after another . SST-2 0 +for starters , the story is just too slim . SST-2 0 +sacrifices the value of its wealth of archival foot-age with its less-than-objective stance . SST-2 0 +not since freddy got fingered has a major release been so painful to sit through . SST-2 0 +overall the film feels like a low-budget tv pilot that could not find a buyer to play it on the tube . SST-2 0 +this time mr. burns is trying something in the martin scorsese street-realist mode , but his self-regarding sentimentality trips him up again . SST-2 0 +once (kim) begins to overplay the shock tactics and bait-and-tackle metaphors , you may decide it 's too high a price to pay for a shimmering picture postcard . SST-2 0 +uncommonly stylish but equally silly ... the picture fails to generate much suspense , nor does it ask searching enough questions to justify its pretensions . SST-2 0 +you wo n't like roger , but you will quickly recognize him . SST-2 0 +his last movie was poetically romantic and full of indelible images , but his latest has nothing going for it . SST-2 0 +a synthesis of cliches and absurdities that seems positively decadent in its cinematic flash and emptiness . SST-2 0 +it 's clear the filmmakers were n't sure where they wanted their story to go , and even more clear that they lack the skills to get us to this undetermined destination . SST-2 0 +all the amped-up tony hawk-style stunts and thrashing rap-metal ca n't disguise the fact that , really , we 've been here , done that . SST-2 0 diff --git a/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/train.csv b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/train.csv new file mode 100644 index 0000000..322f03f --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data/k-shot-single/SST-2/16-42/train.csv @@ -0,0 +1,32 @@ +of all the halloween 's , this is the most visually unappealing . SST-2 0 +on the evidence before us , the answer is clear : not easily and , in the end , not well enough . SST-2 0 +even accepting this in the right frame of mind can only provide it with so much leniency . SST-2 0 +the latest vapid actor 's exercise to appropriate the structure of arthur schnitzler 's reigen . SST-2 0 +too many scenarios in which the hero might have an opportunity to triumphantly sermonize , and too few that allow us to wonder for ourselves if things will turn out okay . SST-2 0 +spider-man is about growing strange hairs , getting a more mature body , and finding it necessary to hide new secretions from the parental units . SST-2 0 +but its storytelling prowess and special effects are both listless . SST-2 0 +the biggest problem with roger avary 's uproar against the mpaa is that , even in all its director 's cut glory , he 's made a film that 's barely shocking , barely interesting and most of all , barely anything . SST-2 0 +the film starts promisingly , but the ending is all too predictable and far too cliched to really work . SST-2 0 +i 'm not sure which half of dragonfly is worse : the part where nothing 's happening , or the part where something 's happening , but it 's stupid . SST-2 0 +director george hickenlooper has had some success with documentaries , but here his sense of story and his juvenile camera movements smack of a film school undergrad , and his maudlin ending might not have gotten him into film school in the first place . SST-2 0 +the most horrific movie experience i 've had since `` ca n't stop the music . '' SST-2 0 +... the story simply putters along looking for astute observations and coming up blank . SST-2 0 +the story passes time until it 's time for an absurd finale of twisted metal , fireballs and revenge . SST-2 0 +collapses under its own meager weight . SST-2 0 +(carvey 's) characters are both overplayed and exaggerated , but then again , subtlety has never been his trademark . SST-2 0 +williams creates a stunning , taxi driver-esque portrayal of a man teetering on the edge of sanity . SST-2 1 +a completely spooky piece of business that gets under your skin and , some plot blips aside , stays there for the duration . SST-2 1 +the year 's greatest adventure , and jackson 's limited but enthusiastic adaptation has made literature literal without killing its soul -- a feat any thinking person is bound to appreciate . SST-2 1 +an engrossing portrait of uncompromising artists trying to create something original against the backdrop of a corporate music industry that only seems to care about the bottom line . SST-2 1 +the performers are so spot on , it is hard to conceive anyone else in their roles . SST-2 1 +however , it 's pleasant enough and its ecological , pro-wildlife sentiments are certainly welcome . SST-2 1 +short-story quaint , touchingly mending a child 's pain for his dead mother via communication with an old woman straight out of eudora welty . SST-2 1 +an uncluttered , resonant gem that relays its universal points without lectures or confrontations . ' SST-2 1 +a well made indieflick in need of some trims and a more chemistry between its stars . SST-2 1 +adams , with four scriptwriters , takes care with the characters , who are so believable that you feel what they feel . SST-2 1 +it 's far from a frothy piece , and the characters are complex , laden with plenty of baggage and tinged with tragic undertones . SST-2 1 +elegant , mannered and teasing . SST-2 1 +a thoroughly engaging , surprisingly touching british comedy . SST-2 1 +a stylish cast and some clever scripting solutions help chicago make the transition from stage to screen with considerable appeal intact . SST-2 1 +simone is not a bad film . SST-2 1 +criminal conspiracies and true romances move so easily across racial and cultural lines in the film that it makes my big fat greek wedding look like an apartheid drama . SST-2 1 diff --git a/NLPAlgo/MetaFineTuning/data_utils/data_process.py b/NLPAlgo/MetaFineTuning/data_utils/data_process.py new file mode 100644 index 0000000..6d4f9e5 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data_utils/data_process.py @@ -0,0 +1,268 @@ +import numpy as np +import random +import oneflow as flow +import string +from typing import Tuple, List, Union +from data_utils.utils import InputFeatures, InputExample + +domain_list = { + 'g1': ['sst-2', 'mr', 'cr'], + 'g2': ['mnli', 'snli'], + 'g3': ['mrpc', 'qqp'] +} + +class_list = { + 'g1': [0, 1], + 'sst-2': [0, 1], + 'mr': [0, 1], + 'cr': [0, 1], + 'g2': ['contradiction', 'entailment', 'neutral'], + 'mnli': ['contradiction', 'entailment', 'neutral'], + 'snli': ['contradiction', 'entailment', 'neutral'], + 'g3': [0, 1], + 'mrpc': [0, 1], + 'qqp': [0, 1], +} + +# 在 cross-task中,每个group的task需要使用对应的prompt-encoder,因此需要为每个task给予一个group内部的下标编号,用于指定对应的prompt-encoder +task_to_id = { + 'SST-2': 0, + 'mr': 1, + 'cr': 2, + 'mnli': 0, + 'snli': 1, + 'mrpc': 0, + 'qqp': 1, +} + + +# 名称对齐 +# 终端传入的参数会被默认小写,而磁盘目录会有大写,因此要做一个转换 +data_to_name = { + 'SST-2': 'SST-2', + 'sst-5': 'sst-5', + 'mr': 'mr', + 'cr': 'cr', + 'mpqa': 'mpqa', + 'subj': 'subj', + 'trec': 'trec', + 'CoLA': 'CoLA', + 'MRPC': 'MRPC', + 'QQP': 'QQP', + 'STS-B': 'STS-B', + 'MNLI': 'MNLI', + 'SNLI': 'SNLI', + 'QNLI': 'QNLI', + 'RTE': 'RTE', + 'sst-2': 'SST-2', + 'cola': 'CoLA', + 'mrpc': 'MRPC', + 'qqp': 'QQP', + 'sts-b': 'STS-B', + 'mnli': 'MNLI', + 'snli': 'SNLI', + 'qnli': 'QNLI', + 'rte': 'RTE', + 'g1': 'g1', + 'g2': 'g2', + 'g3': 'g3', + 'g4': 'g4' +} + +class Preprocessor(object): + """ + + """ + + def __init__(self, config, tokenizer, seed: int = 42): + """ + Create a new PVP. + + :param wrapper: the wrapper for the underlying language model + :param verbalizer_file: an optional file that contains the verbalizer to be used + :param seed: a seed to be used for generating random numbers if necessary + """ + self.config = config + self.tokenizer = tokenizer + self.rng = random.Random(seed) + self.label_map = {label: i for i, label in enumerate(self.config.label_list)} + + @property + def mask(self) -> str: + """Return the underlying LM's mask token""" + return '[MASK]' + + @property + def mask_id(self) -> int: + """Return the underlying LM's mask id""" + return self.tokenizer.mask_token_id + + @property + def max_num_verbalizers(self) -> int: + """Return the maximum number of verbalizers across all labels""" + # 正常情况下结果为1 + return max(len(self.verbalize(label)) for label in self.config.label_list) + + @staticmethod + def shortenable(s): + """Return an instance of this string that is marked as shortenable""" + return s, True + + # @staticmethod + # def remove_final_punc(s: Union[str, Tuple[str, bool]]): + # """Remove the final punctuation mark""" + # if isinstance(s, tuple): + # return remove_final_punc(s[0]), s[1] + # return s.rstrip(string.punctuation) + # + # @staticmethod + # def lowercase_first(s: Union[str, Tuple[str, bool]]): + # """Lowercase the first character""" + # if isinstance(s, tuple): + # return self.lowercase_first(s[0]), s[1] + # return s[0].lower() + s[1:] + + def encode(self, example: InputExample, priming: bool = False, labeled: bool = False) \ + -> Tuple[List[int], List[int]]: + """ + Encode an input example using this pattern-verbalizer pair. + 将输入的句子样本转化为feature + + :param example: the input example to encode + :param priming: whether to use this example for priming + :param labeled: if ``priming=True``, whether the label should be appended to this example + :return: A tuple, consisting of a list of input ids and a list of token type ids + """ + # 获得预训练分词工具 + tokenizer = self.tokenizer + # 不同的Task有不同的PVP get_parts方法,获得相应的成分。 + # 例如parts_a = [texta, 'x', 'x', MASK, '.] + # block_flag_a = [0. 1, 0, 0] + # parts_a, parts_b, block_flag_a, block_flag_b = self.get_parts(example) + + text_a, text_b = example.text_a, example.text_b + text_a = self.shortenable(text_a) + parts_a = [text_a] + parts_b = [] + if text_b is not None: + text_b = self.shortenable(text_b) + parts_b = [text_b] + # kwargs = {'add_prefix_space': True} if isinstance(tokenizer, GPT2Tokenizer) else {} + + parts_a = [x if isinstance(x, tuple) else (x, False) for x in parts_a] + # parts_a = [(tokenizer.encode(x, add_special_tokens=False, **kwargs), s) for x, s in parts_a if x] + parts_a = [(tokenizer.convert_tokens_to_ids(tokenizer.tokenize(x)), s) for x, s in parts_a if x] + + if parts_b: + parts_b = [x if isinstance(x, tuple) else (x, False) for x in parts_b] + parts_b = [(tokenizer.convert_tokens_to_ids(tokenizer.tokenize(x)), s) for x, s in parts_b if x] + + # self.truncate(parts_a, parts_b, max_length=self.config.seq_length) + num_special = self.tokenizer.num_special_tokens_to_add(bool(parts_b)) + + # 根据最大长度对text进行截断 + # print('parts_b=', parts_b) + self.truncate(parts_a, parts_b, max_length=self.config.seq_length - num_special) + + tokens_a = [token_id for part, _ in parts_a for token_id in part] + # tokens_b = [token_id for part, _ in parts_b for token_id in part] if parts_b else None + tokens_b = [token_id for part, _ in parts_b for token_id in part] if parts_b else [] + + if tokens_b: + input_ids = tokenizer.build_inputs_with_special_tokens(tokens_a, tokens_b) + token_type_ids = tokenizer.create_token_type_ids_from_sequences(tokens_a, tokens_b) + else: + input_ids = tokenizer.build_inputs_with_special_tokens(tokens_a) + token_type_ids = tokenizer.create_token_type_ids_from_sequences(tokens_a) + + ### return input_ids, token_type_ids + return input_ids, token_type_ids + + + @staticmethod + def _seq_length(parts: List[Tuple[str, bool]], only_shortenable: bool = False): + return sum([len(x) for x, shortenable in parts if not only_shortenable or shortenable]) if parts else 0 + + @staticmethod + def _remove_last(parts: List[Tuple[str, bool]]): + last_idx = max(idx for idx, (seq, shortenable) in enumerate(parts) if shortenable and seq) + parts[last_idx] = (parts[last_idx][0][:-1], parts[last_idx][1]) + + def truncate(self, parts_a: List[Tuple[str, bool]], parts_b: List[Tuple[str, bool]], max_length: int): + """Truncate two sequences of text to a predefined total maximum length""" + total_len = self._seq_length(parts_a) + self._seq_length(parts_b) + total_len += self.tokenizer.num_special_tokens_to_add(bool(parts_b)) + num_tokens_to_remove = total_len - max_length # 总长度如果超过设定的最大程度,则删除 + + if num_tokens_to_remove <= 0: + return parts_a, parts_b + + for _ in range(num_tokens_to_remove): + if self._seq_length(parts_a, only_shortenable=True) > self._seq_length(parts_b, only_shortenable=True): + self._remove_last(parts_a) + else: + self._remove_last(parts_b) + + def get_mask_positions(self, input_ids: List[int]) -> List[int]: + # print('input_ids=', input_ids) + label_idx = input_ids.index(self.mask_id) + labels = [-1] * len(input_ids) + labels[label_idx] = 1 + return labels + + + + def get_input_features(self, example: InputExample, labelled: bool, priming: bool = False, + **kwargs) -> InputFeatures: + # 获得PVP(模板句子+label mapping) + input_ids, token_type_ids = self.encode(example) + + attention_mask = [1] * len(input_ids) + padding_length = self.config.seq_length - len(input_ids) + + if padding_length < 0: + raise ValueError(f"Maximum sequence length is too small, got {len(input_ids)} input ids") + + input_ids = input_ids + ([self.tokenizer.pad_token_id] * padding_length) # wordid序列+padding + attention_mask = attention_mask + ([0] * padding_length) + token_type_ids = token_type_ids + ([0] * padding_length) + + assert len(input_ids) == self.config.seq_length + assert len(attention_mask) == self.config.seq_length + assert len(token_type_ids) == self.config.seq_length + + example_label = example.label + example_task = example.task # add by wjn 表示当前样本所属的task + # add by wjn 只有当数字型的label(0,1),可能真实标签是字符串('0', '1'),因此需要进行转换判断 + if example_label not in self.label_map.keys(): + if type(example_label) == int: + example_label = str(example_label) + elif type(example_label) == str: + example_label = int(example_label) + + label = self.label_map[example_label] if example.label is not None else -100 # 当前样本的类标 + task = task_to_id[example_task] # add by wjn 表示当前task对应group内的编号 + # task = example_task + logits = example.logits if example.logits else [-1] + + # if labelled: + # # 获得一个序列中[MASK]所在的索引 + # # eg 长度为5的序列[-1 -1 1 -1 -1],可知第3个token为[MASK] + # mlm_labels = self.pvp.get_mask_positions(input_ids) + # else: + # mlm_labels = [-1] * self.config.seq_length + + # masked_lm_positions = mlm_labels.index(1) + # # print('masked_lm_positions=', masked_lm_positions) + # label_word = self.pvp.verbalize(example_label)[0] + # masked_lm_ids = self.tokenizer.convert_tokens_to_ids(self.tokenizer.tokenize(label_word))[ + # 0] # list [label word token id] + # masked_lm_weights = 1.0 + + return InputFeatures(input_ids=input_ids, + attention_mask=attention_mask, + token_type_ids=token_type_ids, + task=task, + label=label, + logits=logits, + idx=example.guid,) \ No newline at end of file diff --git a/NLPAlgo/MetaFineTuning/data_utils/generate_feature.py b/NLPAlgo/MetaFineTuning/data_utils/generate_feature.py new file mode 100644 index 0000000..680abc3 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data_utils/generate_feature.py @@ -0,0 +1,164 @@ +''' +将InputExample转换为oneflow的ofrecord feature +''' + +import oneflow.core.record.record_pb2 as ofrecord +import six +import os +import numpy as np +import random +import struct +from typing import Tuple, List, Dict, Optional +import log +from data_utils.utils import InputFeatures, InputExample + +logger = log.get_logger('root') + + +def int32_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int32_list=ofrecord.Int32List(value=value)) + +def int64_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int64_list=ofrecord.Int64List(value=value)) + +def float_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(float_list=ofrecord.FloatList(value=value)) + +def double_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(double_list=ofrecord.DoubleList(value=value)) + +def bytes_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + if not six.PY2: + if isinstance(value[0], str): + value = [x.encode() for x in value] + return ofrecord.Feature(bytes_list=ofrecord.BytesList(value=value)) + +def generate_dataset( + config, + data: List[InputExample], + preprocessor, + labelled: bool = True, + ofrecord_dir: str = None, + stage: str = 'train', + load_weight: bool = False # 是否为每个样本添加权重 +): + features = convert_examples_to_features(config, data, preprocessor, labelled=labelled) # 将输入的样本(inputExample对象)进行转化为feature + # print('len(features)=', len(features)) + # print('===============train features================') + # print('len=', len(features)) + # print('feature 0:', features[0]) + # print('feature 1:', features[1]) + # print('===============================') + feature_dicts = [] # list(dict) + if not os.path.exists(os.path.join(ofrecord_dir, stage)): + os.makedirs(os.path.join(ofrecord_dir, stage)) + fw = open(os.path.join(ofrecord_dir, stage, stage + '.of_record-0'), 'wb') + + if load_weight: + ''' + sample_weight_dict = {idx: weight} + array([{0: 0.9915}, {1: 0.98956}, {2: 0.9723}, {3: 0.98445}, {4: 0.99237}, + ...... + {94: 0.96635}, {95: 0.98768}], dtype=object) + ''' + try: + sample_weight_dict = np.load(os.path.join(ofrecord_dir, 'train/weight.npy'), allow_pickle=True)[()] + weight_dict = dict() + for i in sample_weight_dict: + weight_dict.update(i) + except: + raise FileNotFoundError("Please run the preprocess.py to generate weight.npy at first") + + for f in features: + weight = [1.] + if load_weight: + assert f.idx in weight_dict.keys() + weight = [weight_dict[f.idx]] + print('weight=', weight) + feature_dict = { + 'input_ids': int32_feature(f.input_ids), + 'attention_masks': int32_feature(f.attention_mask), + 'token_type_ids': int32_feature(f.token_type_ids), + 'tasks': int32_feature(f.task), # add by wjn + 'labels': int32_feature(f.label), + 'logits': int32_feature(f.logits), + 'idxs': int32_feature(f.idx), + 'weights': float_feature(weight) + } + ofrecord_features = ofrecord.OFRecord(feature=feature_dict) # 调用 ofrecord.OFRecord 创建 OFRecord 对象 + serilizedBytes = ofrecord_features.SerializeToString() # 调用 OFRecord 对象的 SerializeToString 方法得到序列化结果 + length = ofrecord_features.ByteSize() + fw.write(struct.pack("q", length)) + fw.write(serilizedBytes) + feature_dicts.append(feature_dict) + fw.close() + return feature_dicts + + + +def load_glue_data(path: str, stage: str): + + int32_feature_list = ['input_ids', 'attention_masks', 'token_type_ids', 'tasks', + 'labels', 'logits', 'idxs'] + float_feature_list = [] + + feature_data = dict() + + with open(os.path.join(path, stage, stage + ".of_record-0"), "rb") as f: + while True: + try: + length = struct.unpack("q", f.read(8)) + except: + break + serilizedBytes = f.read(length[0]) + ofrecord_features = ofrecord.OFRecord.FromString(serilizedBytes) + + for feature_name in int32_feature_list: + data = ofrecord_features.feature[feature_name].int32_list.value + if feature_name not in feature_data.keys(): + feature_data[feature_name] = [] + feature_data[feature_name].append(data) + + for feature_name in float_feature_list: + data = ofrecord_features.feature[feature_name].float_list.value + if feature_name not in feature_data.keys(): + feature_data[feature_name] = [] + feature_data[feature_name].append(data) + + for feature_name, data in feature_data.items(): + feature_data[feature_name] = np.array(data) + + return feature_data + + +# InputExample -> InputFeatures +def convert_examples_to_features( + config, + examples: List[InputExample], + preprocessor, + labelled: bool = True +) -> List[InputFeatures]: + features = [] + # preprocessor = PREPROCESSORS[MLM_WRAPPER](config, config.task_name, config.pattern_id) + for (ex_index, example) in enumerate(examples): + if ex_index % 10000 == 0: + logger.info("Writing example {}".format(ex_index)) + # 获得input_feature。self.preprocessor根据当前的任务类型(比如MLM)获得相应的preprocessor + input_features = preprocessor.get_input_features(example, labelled=labelled) + features.append(input_features) + """ + if ex_index < 5: + logger.info(f'--- Example {ex_index} ---') + logger.info(input_features.pretty_print(self.tokenizer)) + """ + return features \ No newline at end of file diff --git a/NLPAlgo/MetaFineTuning/data_utils/task_processors.py b/NLPAlgo/MetaFineTuning/data_utils/task_processors.py new file mode 100644 index 0000000..0a9dc02 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data_utils/task_processors.py @@ -0,0 +1,990 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 运行命令 +# 本文件目标:生成训练集各个domain class对应的prototype embedding,并计算每个样本的prototypical score +# python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + +""" +This file contains the logic for loading data for all tasks. +""" + +import csv +import json +import os +import random +from abc import ABC, abstractmethod +from collections import defaultdict, Counter +from typing import List, Dict, Callable + +import log +# from pet import task_helpers +from data_utils.utils import InputExample +# from transformers import DataProcessor as TransDataProcessor + + +logger = log.get_logger('root') + +def _shuffle_and_restrict(examples: List[InputExample], num_examples: int, seed: int = 42) -> List[InputExample]: + """ + Shuffle a list of examples and restrict it to a given maximum size. + + :param examples: the examples to shuffle and restrict + :param num_examples: the maximum number of examples + :param seed: the random seed for shuffling + :return: the first ``num_examples`` elements of the shuffled list + """ + if 0 < num_examples < len(examples): + random.Random(seed).shuffle(examples) + examples = examples[:num_examples] + return examples + + +class LimitedExampleList: + def __init__(self, labels: List[str], max_examples=-1): + """ + Implementation of a list that stores only a limited amount of examples per label. + + :param labels: the set of all possible labels + :param max_examples: the maximum number of examples per label. This can either be a fixed number, + in which case `max_examples` examples are loaded for every label, or a list with the same size as + `labels`, in which case at most `max_examples[i]` examples are loaded for label `labels[i]`. + """ + self._labels = labels + self._examples = [] + self._examples_per_label = defaultdict(int) + + if isinstance(max_examples, list): + self._max_examples = dict(zip(self._labels, max_examples)) + else: + self._max_examples = {label: max_examples for label in self._labels} + + def is_full(self): + """Return `true` iff no more examples can be added to this list""" + for label in self._labels: + if self._examples_per_label[label] < self._max_examples[label] or self._max_examples[label] < 0: + return False + return True + + def add(self, example: InputExample) -> bool: + """ + Add a new input example to this list. + + :param example: the example to add + :returns: `true` iff the example was actually added to the list + """ + label = example.label + if self._examples_per_label[label] < self._max_examples[label] or self._max_examples[label] < 0: + self._examples_per_label[label] += 1 + self._examples.append(example) + return True + return False + + def to_list(self): + return self._examples + + +class DataProcessor(ABC): + """ + Abstract class that provides methods for loading train/dev32/dev/test/unlabeled examples for a given task. + """ + + def __init__(self, task_name): + self.task_name = task_name + + @abstractmethod + def get_train_examples(self, data_dir) -> List[InputExample]: + """Get a collection of `InputExample`s for the train set.""" + pass + + @abstractmethod + def get_dev_examples(self, data_dir) -> List[InputExample]: + """Get a collection of `InputExample`s for the dev set.""" + pass + + @abstractmethod + def get_dev32_examples(self, data_dir) -> List[InputExample]: + pass + + @abstractmethod + def get_test_examples(self, data_dir) -> List[InputExample]: + """Get a collection of `InputExample`s for the test set.""" + pass + + @abstractmethod + def get_unlabeled_examples(self, data_dir) -> List[InputExample]: + """Get a collection of `InputExample`s for the unlabeled set.""" + pass + + # @abstractmethod + # def get_labels(self) -> List[str]: + # """Get the list of labels for this data set.""" + # pass + + +class RteProcessor(DataProcessor): + """Processor for the RTE data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_labels(self): + return ["entailment", "not_entailment"] + + def _create_examples(self, path: str, set_type: str, hypothesis_name: str = "hypothesis", + premise_name: str = "premise") -> List[InputExample]: + examples = [] + + with open(path, encoding='utf8') as f: + for line_idx, line in enumerate(f): + example_json = json.loads(line) + idx = example_json['idx'] + if isinstance(idx, str): + try: + idx = int(idx) + except ValueError: + idx = line_idx + label = example_json.get('label') + guid = "%s-%s" % (set_type, idx) + text_a = example_json[premise_name] + text_b = example_json[hypothesis_name] + + example = InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, idx=idx) + examples.append(example) + + return examples + + +class CbProcessor(RteProcessor): + """Processor for the CB data set.""" + + def get_labels(self): + return ["entailment", "contradiction", "neutral"] + + +class WicProcessor(DataProcessor): + """Processor for the WiC data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_labels(self): + return ["F", "T"] + + @staticmethod + def _create_examples(path: str, set_type: str) -> List[InputExample]: + examples = [] + with open(path, encoding='utf8') as f: + for line in f: + example_json = json.loads(line) + idx = example_json['idx'] + if isinstance(idx, str): + idx = int(idx) + label = "T" if example_json.get('label') else "F" + guid = "%s-%s" % (set_type, idx) + text_a = example_json['sentence1'] + text_b = example_json['sentence2'] + meta = {'word': example_json['word']} + example = InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, idx=idx, meta=meta) + examples.append(example) + return examples + + +class WscProcessor(DataProcessor): + """Processor for the WSC data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_labels(self): + return ["False", "True"] + + @staticmethod + def _create_examples(path: str, set_type: str) -> List[InputExample]: + examples = [] + + with open(path, encoding='utf8') as f: + for line in f: + example_json = json.loads(line) + idx = example_json['idx'] + label = str(example_json['label']) if 'label' in example_json else None + guid = "%s-%s" % (set_type, idx) + text_a = example_json['text'] + meta = { + 'span1_text': example_json['target']['span1_text'], + 'span2_text': example_json['target']['span2_text'], + 'span1_index': example_json['target']['span1_index'], + 'span2_index': example_json['target']['span2_index'] + } + + # the indices in the dataset are wrong for some examples, so we manually fix them + span1_index, span1_text = meta['span1_index'], meta['span1_text'] + span2_index, span2_text = meta['span2_index'], meta['span2_text'] + words_a = text_a.split() + words_a_lower = text_a.lower().split() + words_span1_text = span1_text.lower().split() + span1_len = len(words_span1_text) + + if words_a_lower[span1_index:span1_index + span1_len] != words_span1_text: + for offset in [-1, +1]: + if words_a_lower[span1_index + offset:span1_index + span1_len + offset] == words_span1_text: + span1_index += offset + + if words_a_lower[span1_index:span1_index + span1_len] != words_span1_text: + logger.warning(f"Got '{words_a_lower[span1_index:span1_index + span1_len]}' but expected " + f"'{words_span1_text}' at index {span1_index} for '{words_a}'") + + if words_a[span2_index] != span2_text: + for offset in [-1, +1]: + if words_a[span2_index + offset] == span2_text: + span2_index += offset + + if words_a[span2_index] != span2_text and words_a[span2_index].startswith(span2_text): + words_a = words_a[:span2_index] \ + + [words_a[span2_index][:len(span2_text)], words_a[span2_index][len(span2_text):]] \ + + words_a[span2_index + 1:] + + assert words_a[span2_index] == span2_text, \ + f"Got '{words_a[span2_index]}' but expected '{span2_text}' at index {span2_index} for '{words_a}'" + + text_a = ' '.join(words_a) + meta['span1_index'], meta['span2_index'] = span1_index, span2_index + + example = InputExample(guid=guid, text_a=text_a, label=label, meta=meta, idx=idx) + if set_type == 'train' and label != 'True': + continue + examples.append(example) + + return examples + + +class BoolQProcessor(DataProcessor): + """Processor for the BoolQ data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_labels(self): + return ["False", "True"] + + @staticmethod + def _create_examples(path: str, set_type: str) -> List[InputExample]: + examples = [] + + with open(path, encoding='utf8') as f: + for line in f: + example_json = json.loads(line) + idx = example_json['idx'] + label = str(example_json['label']) if 'label' in example_json else None + guid = "%s-%s" % (set_type, idx) + text_a = example_json['passage'] + text_b = example_json['question'] + example = InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, idx=idx) + examples.append(example) + + return examples + + +class CopaProcessor(DataProcessor): + """Processor for the COPA data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_labels(self): + return ["0", "1"] + + @staticmethod + def _create_examples(path: str, set_type: str) -> List[InputExample]: + examples = [] + + with open(path, encoding='utf8') as f: + for line in f: + example_json = json.loads(line) + label = str(example_json['label']) if 'label' in example_json else None + idx = example_json['idx'] + guid = "%s-%s" % (set_type, idx) + text_a = example_json['premise'] + meta = { + 'choice1': example_json['choice1'], + 'choice2': example_json['choice2'], + 'question': example_json['question'] + } + example = InputExample(guid=guid, text_a=text_a, label=label, meta=meta, idx=idx) + examples.append(example) + + if set_type == 'train' or set_type == 'unlabeled': + mirror_examples = [] + for ex in examples: + label = "1" if ex.label == "0" else "0" + meta = { + 'choice1': ex.meta['choice2'], + 'choice2': ex.meta['choice1'], + 'question': ex.meta['question'] + } + mirror_example = InputExample(guid=ex.guid + 'm', text_a=ex.text_a, label=label, meta=meta) + mirror_examples.append(mirror_example) + examples += mirror_examples + logger.info(f"Added {len(mirror_examples)} mirror examples, total size is {len(examples)}...") + return examples + + +class MultiRcProcessor(DataProcessor): + """Processor for the MultiRC data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_labels(self): + return ["0", "1"] + + @staticmethod + def _create_examples(path: str, set_type: str) -> List[InputExample]: + examples = [] + + with open(path, encoding='utf8') as f: + for line in f: + example_json = json.loads(line) + + passage_idx = example_json['idx'] + text = example_json['passage']['text'] + questions = example_json['passage']['questions'] + for question_json in questions: + question = question_json["question"] + question_idx = question_json['idx'] + answers = question_json["answers"] + for answer_json in answers: + label = str(answer_json["label"]) if 'label' in answer_json else None + answer_idx = answer_json["idx"] + guid = f'{set_type}-p{passage_idx}-q{question_idx}-a{answer_idx}' + meta = { + 'passage_idx': passage_idx, + 'question_idx': question_idx, + 'answer_idx': answer_idx, + 'answer': answer_json["text"] + } + idx = [passage_idx, question_idx, answer_idx] + example = InputExample(guid=guid, text_a=text, text_b=question, label=label, meta=meta, idx=idx) + examples.append(example) + + question_indices = list(set(example.meta['question_idx'] for example in examples)) + label_distribution = Counter(example.label for example in examples) + logger.info(f"Returning {len(examples)} examples corresponding to {len(question_indices)} questions with label " + f"distribution {list(label_distribution.items())}") + return examples + + +class RecordProcessor(DataProcessor): + """Processor for the ReCoRD data set.""" + + def get_train_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "train.jsonl"), "train") + + def get_dev_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "val.jsonl"), "dev") + + def get_test_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "test.jsonl"), "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "dev32.jsonl"), "dev32") + + def get_labels(self): + return ["0", "1"] + + @staticmethod + def _create_examples(path, set_type, seed=42, max_train_candidates_per_question: int = 10) -> List[InputExample]: + examples = [] + + entity_shuffler = random.Random(seed) + + with open(path, encoding='utf8') as f: + for idx, line in enumerate(f): + example_json = json.loads(line) + + idx = example_json['idx'] + text = example_json['passage']['text'] + entities = set() + + for entity_json in example_json['passage']['entities']: + start = entity_json['start'] + end = entity_json['end'] + entity = text[start:end + 1] + entities.add(entity) + + entities = list(entities) + + text = text.replace("@highlight\n", "- ") # we follow the GPT-3 paper wrt @highlight annotations + questions = example_json['qas'] + + for question_json in questions: + question = question_json['query'] + question_idx = question_json['idx'] + answers = set() + + for answer_json in question_json.get('answers', []): + answer = answer_json['text'] + answers.add(answer) + + answers = list(answers) + + if set_type == 'train': + # create a single example per *correct* answer + for answer_idx, answer in enumerate(answers): + candidates = [ent for ent in entities if ent not in answers] + if len(candidates) > max_train_candidates_per_question - 1: + entity_shuffler.shuffle(candidates) + candidates = candidates[:max_train_candidates_per_question - 1] + + guid = f'{set_type}-p{idx}-q{question_idx}-a{answer_idx}' + meta = { + 'passage_idx': idx, + 'question_idx': question_idx, + 'candidates': [answer] + candidates, + 'answers': [answer] + } + ex_idx = [idx, question_idx, answer_idx] + example = InputExample(guid=guid, text_a=text, text_b=question, label="1", meta=meta, + idx=ex_idx) + examples.append(example) + + else: + # create just one example with *all* correct answers and *all* answer candidates + guid = f'{set_type}-p{idx}-q{question_idx}' + meta = { + 'passage_idx': idx, + 'question_idx': question_idx, + 'candidates': entities, + 'answers': answers + } + example = InputExample(guid=guid, text_a=text, text_b=question, label="1", meta=meta) + examples.append(example) + + question_indices = list(set(example.meta['question_idx'] for example in examples)) + label_distribution = Counter(example.label for example in examples) + logger.info(f"Returning {len(examples)} examples corresponding to {len(question_indices)} questions with label " + f"distribution {list(label_distribution.items())}") + return examples + + + + +### add by wjn 文本分类任务的处理(取自LM-BFF) +import pandas as pd + +class TextClassificationProcessor(DataProcessor): + """ + Data processor for text classification datasets (mr, sst-5, subj, trec, cr, mpqa). + """ + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + # self.task_name = task_name + """See base class.""" + print('task_name = ', self.task_name) + if self.task_name == "mr": + return list(range(2)) + elif self.task_name == "sst-5": + return list(range(5)) + elif self.task_name == "subj": + return list(range(2)) + elif self.task_name == "trec": + return list(range(6)) + elif self.task_name == "cr": + return list(range(2)) + elif self.task_name == "mpqa": + return list(range(2)) + elif self.task_name == 'g1': + return list(range(2)) + else: + raise Exception("task_name not supported.") + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + guid = i + # if self.task_name == "ag_news": + # examples.append( + # InputExample(guid=guid, text_a=line[1] + '. ' + line[2], short_text=line[1] + ".", label=line[0])) + # elif self.task_name == "yelp_review_full": + # examples.append(InputExample(guid=guid, text_a=line[1], short_text=line[1], label=line[0])) + # elif self.task_name == "yahoo_answers": + # text = line[1] + # if not pd.isna(line[2]): + # text += ' ' + line[2] + # if not pd.isna(line[3]): + # text += ' ' + line[3] + # examples.append(InputExample(guid=guid, text_a=text, short_text=line[1], label=line[0])) + # elif self.task_name in ['mr', 'sst-5', 'subj', 'trec', 'cr', 'mpqa', 'g1']: + # examples.append(InputExample(guid=guid, text_a=line[1], label=line[0])) + # else: + # raise Exception("Task_name not supported.") + + if self.task_name in ['mr', 'sst-5', 'subj', 'trec', 'cr', 'mpqa', 'g1']: + examples.append(InputExample(guid=guid, text_a=line[0], text_b=None, task=line[1], label=line[2])) + else: + raise Exception("Task_name not supported.") + + return examples + +### add by wjn SST-2任务的处理 +class Sst2Processor(DataProcessor): + """Processor for the SST-2 data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + text_index = 0 + for (i, line) in enumerate(lines): + guid = i + examples.append(InputExample(guid=guid, text_a=line[0], text_b=None, task=line[1], label=line[2])) + return examples + +### add by wjn +class G2Processor(DataProcessor): + """Processor for the Group2 data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + guid = i + examples.append(InputExample(guid=guid, text_a=line[0], text_b=line[1], task=line[2], label=line[3])) + return examples + +### add by wjn +class MnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + guid = i + examples.append(InputExample(guid=guid, text_a=line[0], text_b=line[1], task=line[2], label=line[3])) + return examples + +### add by wjn +class SnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + guid = i + examples.append(InputExample(guid=guid, text_a=line[0], text_b=line[1], task=line[2], label=line[3])) + return examples + +## add by wjn +class G3Processor(DataProcessor): + """Processor for the Group3 data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + test_mode = set_type == "test" + q1_index = 0 + q2_index = 1 + examples = [] + for (i, line) in enumerate(lines): + guid = i + try: + text_a = line[q1_index] + text_b = line[q2_index] + label = line[3] + except IndexError: + continue + examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, task=line[2], label=label)) + return examples + +### add by wjn +class MrpcProcessor(DataProcessor): + """Processor for the MRPC data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + guid = i + examples.append(InputExample(guid=guid, text_a=line[0], text_b=line[1], task=line[2], label=line[3])) + return examples + + +### add by wjn +class QqpProcessor(DataProcessor): + """Processor for the QQP data set (GLUE version).""" + + def get_train_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "train.csv"), sep='\t', header=None).values.tolist(), + "train") + + def get_dev_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_test_examples(self, data_dir): + """See base class.""" + return self._create_examples(pd.read_csv(os.path.join(data_dir, "test.csv"), sep='\t', header=None).values.tolist(), + "test") + + def get_unlabeled_examples(self, data_dir): + return self._create_examples(os.path.join(data_dir, "unlabeled.jsonl"), "unlabeled") + + def get_dev32_examples(self, data_dir): + return self._create_examples(pd.read_csv(os.path.join(data_dir, "dev.csv"), sep='\t', header=None).values.tolist(), "dev") + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training, dev and test sets.""" + test_mode = set_type == "test" + q1_index = 0 + q2_index = 1 + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "%s-%s" % (set_type, i) + try: + text_a = line[q1_index] + text_b = line[q2_index] + label = line[3] + except IndexError: + continue + examples.append(InputExample(guid=guid, text_a=text_a, text_b=line[2], label=label)) + return examples + + +PROCESSORS = { + "g1": TextClassificationProcessor, # cross task group 1 + "sst-2": Sst2Processor, # SST-2 + "mr": TextClassificationProcessor, # MR + "cr": TextClassificationProcessor, # CR + "g2": G2Processor, + "mnli": MnliProcessor, + "snli": SnliProcessor, + "g3": G3Processor, + "mrpc": MrpcProcessor, + "qqp": QqpProcessor, +} # type: Dict[str,Callable[[],DataProcessor]] + + +# TASK_HELPERS = { +# "wsc": task_helpers.WscTaskHelper, +# "multirc": task_helpers.MultiRcTaskHelper, +# "copa": task_helpers.CopaTaskHelper, +# # "record": task_helpers.RecordTaskHelper, +# } + +METRICS = { + "cb": ["acc", "f1-macro"], + "multirc": ["acc", "f1", "em"], + "record": ["acc", "f1"] +} + +DEFAULT_METRICS = ["acc"] + + +TRAIN_SET = "train" +DEV_SET = "dev" +TEST_SET = "test" +UNLABELED_SET = "unlabeled" +DEV32_SET = "dev32" + + +SET_TYPES = [TRAIN_SET, DEV_SET, TEST_SET, UNLABELED_SET, DEV32_SET] + + +def load_examples(task, data_dir: str, set_type: str, *_, num_examples: int = None, + num_examples_per_label: int = None, seed: int = 42) -> List[InputExample]: + """Load examples for a given task.""" + + assert (num_examples is not None) ^ (num_examples_per_label is not None), \ + "Exactly one of 'num_examples' and 'num_examples_per_label' must be set." + assert (not set_type == UNLABELED_SET) or (num_examples is not None), \ + "For unlabeled data, 'num_examples_per_label' is not allowed" + + processor = PROCESSORS[task](task) + + ex_str = f"num_examples={num_examples}" if num_examples is not None \ + else f"num_examples_per_label={num_examples_per_label}" + logger.info( + f"Creating features from dataset file at {data_dir} ({ex_str}, set_type={set_type})" + ) + + if set_type == DEV_SET: + examples = processor.get_dev_examples(data_dir) + elif set_type == DEV32_SET: ### TODO + examples = processor.get_dev32_examples(data_dir) + elif set_type == TEST_SET: + examples = processor.get_test_examples(data_dir) + elif set_type == TRAIN_SET: + examples = processor.get_train_examples(data_dir) + elif set_type == UNLABELED_SET: + examples = processor.get_unlabeled_examples(data_dir) + for example in examples: + example.label = processor.get_labels()[0] + else: + raise ValueError(f"'set_type' must be one of {SET_TYPES}, got '{set_type}' instead") + + if num_examples is not None: + examples = _shuffle_and_restrict(examples, num_examples, seed) + + elif num_examples_per_label is not None: + limited_examples = LimitedExampleList(processor.get_labels(), num_examples_per_label) + for example in examples: + limited_examples.add(example) + examples = limited_examples.to_list() + + label_distribution = Counter(example.label for example in examples) + logger.info(f"Returning {len(examples)} {set_type} examples with label dist.: {list(label_distribution.items())}") + + return examples diff --git a/NLPAlgo/MetaFineTuning/data_utils/utils.py b/NLPAlgo/MetaFineTuning/data_utils/utils.py new file mode 100644 index 0000000..39c8fc3 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/data_utils/utils.py @@ -0,0 +1,319 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import json +import pickle +import random +import string +from collections import defaultdict +from typing import Dict, List, Optional, Union +import numpy as np + + +class LogitsList: + """A list of logits obtained from a finetuned PET model""" + + def __init__(self, score: float, logits: List[List[float]]): + """ + Create a new LogitsList. + :param score: the corresponding PET model's score on the training set + :param logits: the list of logits, where ``logits[i][j]`` is the score for label ``j`` at example ``i`` + """ + self.score = score + self.logits = logits + + def __repr__(self): + return 'LogitsList(score={}, logits[:2]={})'.format(self.score, self.logits[:2]) + + def save(self, path: str) -> None: + """Save this list to a file.""" + with open(path, 'w') as fh: + fh.write(str(self.score) + '\n') + for example_logits in self.logits: + fh.write(' '.join(str(logit) for logit in example_logits) + '\n') + + @staticmethod + def load(path: str, with_score: bool = True) -> 'LogitsList': + """Load a list from a file""" + score = -1 + logits = [] + with open(path, 'r') as fh: + for line_idx, line in enumerate(fh.readlines()): + line = line.rstrip('\n') + if line_idx == 0 and with_score: + score = float(line) + else: + logits.append([float(x) for x in line.split()]) + return LogitsList(score=score, logits=logits) + + +class InputExample(object): + """A raw input example consisting of one or two segments of text and a label""" + + def __init__(self, guid, text_a, text_b=None, task=None, label=None, logits=None, meta: Optional[Dict] = None, idx=-1): + """ + Create a new InputExample. + :param guid: a unique textual identifier + :param text_a: the sequence of text + :param text_b: an optional, second sequence of text + :param task: the corresponding task name of the current example # add by wjn + :param label: an optional label + :param logits: an optional list of per-class logits + :param meta: an optional dictionary to store arbitrary meta information + :param idx: an optional numeric index + """ + self.guid = guid + self.text_a = text_a + self.text_b = text_b + self.task = task # add by wjn + self.label = label + self.logits = logits + self.idx = idx + self.meta = meta if meta else {} + + def __repr__(self): + return str(self.to_json_string()) + + def to_dict(self): + """Serialize this instance to a Python dictionary.""" + output = copy.deepcopy(self.__dict__) + return output + + def to_json_string(self): + """Serialize this instance to a JSON string.""" + return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" + + @staticmethod + def load_examples(path: str) -> List['InputExample']: + """Load a set of input examples from a file""" + with open(path, 'rb') as fh: + return pickle.load(fh) + + @staticmethod + def save_examples(examples: List['InputExample'], path: str) -> None: + """Save a set of input examples to a file""" + with open(path, 'wb') as fh: + pickle.dump(examples, fh) + + +class InputFeatures(object): + """A set of numeric features obtained from an :class:`InputExample`""" + + def __init__(self, input_ids, attention_mask, token_type_ids, task: int, label, + logits=None, meta: Optional[Dict] = None, idx=-1): + """ + Create new InputFeatures. + :param input_ids: the input ids corresponding to the original text or text sequence + :param attention_mask: an attention mask, with 0 = no attention, 1 = attention + :param token_type_ids: segment ids as used by BERT + :param task: The corresponding task id of the current example # add by wjn + :param label: the label + :param logits: an optional sequence of per-class logits + :param meta: an optional dictionary to store arbitrary meta information + :param idx: an optional numeric index + """ + self.input_ids = input_ids + self.attention_mask = attention_mask + self.token_type_ids = token_type_ids + self.task = task # add by wjn + self.label: int = label + self.logits = logits + self.idx = idx + self.meta = meta if meta else {} + + def __repr__(self): + return str(self.to_json_string()) + + def pretty_print(self, tokenizer): + return f'input_ids = {tokenizer.convert_ids_to_tokens(self.input_ids)}\n' + \ + f'attention_mask = {self.attention_mask}\n' + \ + f'token_type_ids = {self.token_type_ids}\n' + \ + f'logits = {self.logits}\n' + \ + f'label = {self.label}\n' + \ + f'block_flag = {self.block_flag}' + + def to_dict(self): + """Serialize this instance to a Python dictionary.""" + output = copy.deepcopy(self.__dict__) + return output + + def to_json_string(self): + """Serialize this instance to a JSON string.""" + return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" + + +class PLMInputFeatures(InputFeatures): + """A set of numeric input features for a model pretrained with a permuted language modeling objective.""" + + def __init__(self, *_, perm_mask, target_mapping, **kwargs): + super().__init__(**kwargs) + self.perm_mask = perm_mask + self.target_mapping = target_mapping + + def pretty_print(self, tokenizer): + return super().pretty_print(tokenizer) + '\n' + \ + f'perm_mask = {self.perm_mask}\n' + \ + f'target_mapping = {self.target_mapping}' + + + +# def set_seed(seed: int): +# """ Set RNG seeds for python's `random` module, numpy and torch""" +# random.seed(seed) +# np.random.seed(seed) +# torch.manual_seed(seed) +# if torch.cuda.is_available(): +# torch.cuda.manual_seed_all(seed) + + +def eq_div(N, i): + """ Equally divide N examples among i buckets. For example, `eq_div(12,3) = [4,4,4]`. """ + return [] if i <= 0 else [N // i + 1] * (N % i) + [N // i] * (i - N % i) + + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i:i + n] + + +def remove_final_punc(s: str): + """Remove the last character from a string if it is some form of punctuation""" + return s.rstrip(string.punctuation) + + +def lowercase_first(s: str): + """Lowercase the first letter of a string""" + return s[0].lower() + s[1:] + + +def save_logits(path: str, logits: np.ndarray): + """Save an array of logits to a file""" + with open(path, 'w') as fh: + for example_logits in logits: + fh.write(' '.join(str(logit) for logit in example_logits) + '\n') + pass + + +def save_predictions(path: str, wrapper, results: Dict): + """Save a sequence of predictions to a file""" + predictions_with_idx = [] + + if wrapper.task_helper and wrapper.task_helper.output: + predictions_with_idx = wrapper.task_helper.output + else: + inv_label_map = {idx: label for label, idx in wrapper.preprocessor.label_map.items()} + for idx, prediction_idx in zip(results['indices'], results['predictions']): + prediction = inv_label_map[prediction_idx] + idx = idx.tolist() if isinstance(idx, np.ndarray) else int(idx) + predictions_with_idx.append({'idx': idx, 'label': prediction}) + + with open(path, 'w', encoding='utf8') as fh: + for line in predictions_with_idx: + fh.write(json.dumps(line) + '\n') + + +def softmax(x, temperature=1.0, axis=None): + """Custom softmax implementation""" + y = np.atleast_2d(x) + + if axis is None: + axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1) + + y = y * float(temperature) + y = y - np.expand_dims(np.max(y, axis=axis), axis) + y = np.exp(y) + + ax_sum = np.expand_dims(np.sum(y, axis=axis), axis) + p = y / ax_sum + + if len(x.shape) == 1: + p = p.flatten() + return p + + +def get_verbalization_ids(word: str, tokenizer, force_single_token: bool) -> Union[int, List[int]]: + """ + Get the token ids corresponding to a verbalization + :param word: the verbalization + :param tokenizer: the tokenizer to use + :param force_single_token: whether it should be enforced that the verbalization corresponds to a single token. + If set to true, this method returns a single int instead of a list and throws an error if the word + corresponds to multiple tokens. + :return: either the list of token ids or the single token id corresponding to this word + """ + # kwargs = {'add_prefix_space': True} if isinstance(tokenizer, GPT2Tokenizer) else {} + ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(word)) + if not force_single_token: + return ids + assert len(ids) == 1, \ + f'Verbalization "{word}" does not correspond to a single token, got {tokenizer.convert_ids_to_tokens(ids)}' + verbalization_id = ids[0] + # assert verbalization_id not in tokenizer.all_special_ids, \ + # f'Verbalization {word} is mapped to a special token {tokenizer.convert_ids_to_tokens(verbalization_id)}' + return verbalization_id + + +# def trim_input_ids(input_ids, pad_token_id, mask_token_id, num_masks: int): +# """ +# Trim a sequence of input ids by removing all padding tokens and keeping at most a specific number of mask tokens. +# :param input_ids: the sequence of input token ids +# :param pad_token_id: the id of the pad token +# :param mask_token_id: the id of the mask tokens +# :param num_masks: the number of masks to keeps +# :return: the trimmed sequence of input ids +# """ +# assert input_ids.shape[0] == 1 +# input_ids_without_pad = [x for x in input_ids[0] if x != pad_token_id] +# +# trimmed_input_ids = [] +# mask_count = 0 +# for input_id in input_ids_without_pad: +# if input_id == mask_token_id: +# if mask_count >= num_masks: +# continue +# mask_count += 1 +# trimmed_input_ids.append(input_id) +# +# return torch.tensor([trimmed_input_ids], dtype=torch.long, device=input_ids.device) + + +def exact_match(predictions: np.ndarray, actuals: np.ndarray, question_ids: np.ndarray): + """Compute the exact match (EM) for a sequence of predictions and actual labels""" + unique_questions = set(question_ids) + + q_actuals = list(zip(question_ids, actuals)) + q_predictions = list(zip(question_ids, predictions)) + + actuals_per_question = defaultdict(list) + predictions_per_question = defaultdict(list) + + for qid, val in q_actuals: + actuals_per_question[qid].append(val) + for qid, val in q_predictions: + predictions_per_question[qid].append(val) + + em = 0 + for qid in unique_questions: + if actuals_per_question[qid] == predictions_per_question[qid]: + em += 1 + em /= len(unique_questions) + + return em + + +# def distillation_loss(predictions, targets, temperature): +# """Compute the distillation loss (KL divergence between predictions and targets) as described in the PET paper""" +# p = F.log_softmax(predictions / temperature, dim=1) +# q = F.softmax(targets / temperature, dim=1) +# return F.kl_div(p, q, reduction='sum') * (temperature ** 2) / predictions.shape[0] \ No newline at end of file diff --git a/NLPAlgo/MetaFineTuning/finetuning.py b/NLPAlgo/MetaFineTuning/finetuning.py new file mode 100644 index 0000000..d7c0c4a --- /dev/null +++ b/NLPAlgo/MetaFineTuning/finetuning.py @@ -0,0 +1,293 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令: + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MFTBERT, BERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from data_utils.data_process import Preprocessor +from data_utils.task_processors import PROCESSORS, load_examples, DEV32_SET, TRAIN_SET, DEV_SET, TEST_SET, METRICS, DEFAULT_METRICS +from data_utils.data_process import domain_list, class_list, task_to_id +from data_utils.generate_feature import generate_dataset +import scipy.spatial.distance as distance +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='sst-2', choices=['sst-2', 'mr', 'cr']) +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--train_example_num", type=int, default=88614, + help="example number in dataset") +parser.add_argument("--batch_size_per_device", type=int, default=2) +parser.add_argument("--train_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument("--dev_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--dev_batch_size_per_device", type=int, default=2) +parser.add_argument("--dev_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--dev_every_step_num", type=int, default=10, + help="") +parser.add_argument("--eval_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--eval_batch_size_per_device", type=int, default=2) +parser.add_argument("--eval_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument('--seed', type=int, default=42, + help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +dev_batch_size = args.num_nodes * args.gpu_num_per_node * args.dev_batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +num_dev_steps = math.ceil(args.dev_example_num / dev_batch_size) +num_eval_steps = math.ceil(args.dev_example_num / eval_batch_size) +args.iter_num = epoch_size * args.num_epochs +configs.print_args(args) + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("attention_masks", [seq_length]) + _blob_conf("token_type_ids", [seq_length]) + # _blob_conf("tasks", [1]) + _blob_conf("labels", [1]) + _blob_conf("logits", [1]) + _blob_conf("idxs", [1]) + # _blob_conf("weights", [1], dtype=flow.float32) + # print('blob_confs=', blob_confs['input_ids'].shape) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = BERT( + decoders['input_ids'], + decoders['attention_masks'], + decoders['token_type_ids'], + decoders['labels'], + args.vocab_size, + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + ) + return loss, logits, decoders['labels'] + + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + # return loss + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalDevJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.dev_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.eval_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'eval'), + args.eval_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + +def main(): + processor = PROCESSORS[args.task_name](args.task_name) + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + preprocessor = Preprocessor(args, tokenizer, args.seed) + label_map = preprocessor.label_map # class2id + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_data = load_examples( + args.task_name, args.data_dir, TRAIN_SET, num_examples=-1, num_examples_per_label=None) + print('===============train examples================') + print('len=', len(train_data)) + print('example 0:', train_data[0]) + print('example 1:', train_data[1]) + print('===============================') + eval_data = load_examples( + args.task_name, args.data_dir, TEST_SET, num_examples=-1, num_examples_per_label=None) + dev_data = load_examples( + args.task_name, args.data_dir, DEV_SET, num_examples=-1, num_examples_per_label=None) + train_feature_dict = generate_dataset(args, train_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='train') + eval_feature_dict = generate_dataset(args, eval_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='eval') + dev_feature_dict = generate_dataset(args, dev_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='dev') + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + print("starting fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.dev_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalDevJob, + num_steps=num_dev_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + print('saving model ...') + snapshot.save("best_ft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + + print("best dev acc: {}".format(best_dev_acc)) + + print("starting testing ...") + + # 将finetuning最优模型重新加载 + snapshot.load(os.path.join(snapshot._model_save_dir, "snapshot_best_ft_model_{}_dev_{}".format(args.task_name, best_dev_acc))) + + run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + desc='eval') + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaFineTuning/images/fine_tuning.png b/NLPAlgo/MetaFineTuning/images/fine_tuning.png new file mode 100644 index 0000000000000000000000000000000000000000..fbcabce25bca6f540dd54a0aa10987af9598a51a GIT binary patch literal 59560 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
    Eb(Cs^^ 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/images/meta_fine_tuning.png b/NLPAlgo/MetaFineTuning/images/meta_fine_tuning.png new file mode 100644 index 0000000000000000000000000000000000000000..fbcabce25bca6f540dd54a0aa10987af9598a51a GIT binary patch literal 59560 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
    Eb(Cs^^ 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaFineTuning/log.py b/NLPAlgo/MetaFineTuning/log.py new file mode 100644 index 0000000..d702616 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/log.py @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This file contains basic logging logic. +""" +import logging + +names = set() + + +def __setup_custom_logger(name: str) -> logging.Logger: + root_logger = logging.getLogger() + root_logger.handlers.clear() + + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') + + names.add(name) + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + ## TODO + file_handler = logging.FileHandler(name + ".txt") + file_handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + logger.addHandler(handler) + + ## TODO + logger.addHandler(file_handler) + + return logger + + +def get_logger(name: str) -> logging.Logger: + if name in names: + return logging.getLogger(name) + else: + return __setup_custom_logger(name) diff --git a/NLPAlgo/MetaFineTuning/meta_finetuning.py b/NLPAlgo/MetaFineTuning/meta_finetuning.py new file mode 100644 index 0000000..e1441d7 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/meta_finetuning.py @@ -0,0 +1,281 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令: + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MFTBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from data_utils.data_process import Preprocessor +from data_utils.task_processors import PROCESSORS, load_examples, DEV32_SET, TRAIN_SET, DEV_SET, TEST_SET, METRICS, DEFAULT_METRICS +from data_utils.data_process import domain_list, class_list, task_to_id +from data_utils.generate_feature import generate_dataset +import scipy.spatial.distance as distance +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='g1', choices=['g1', 'g2', 'g3']) +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--train_example_num", type=int, default=88614, + help="example number in dataset") +parser.add_argument("--batch_size_per_device", type=int, default=2) +parser.add_argument("--train_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument("--dev_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--dev_batch_size_per_device", type=int, default=2) +parser.add_argument("--dev_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--dev_every_step_num", type=int, default=10, + help="") +parser.add_argument('--seed', type=int, default=42, + help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +dev_batch_size = args.num_nodes * args.gpu_num_per_node * args.dev_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +num_dev_steps = math.ceil(args.dev_example_num / dev_batch_size) +args.iter_num = epoch_size * args.num_epochs +configs.print_args(args) + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("attention_masks", [seq_length]) + _blob_conf("token_type_ids", [seq_length]) + _blob_conf("tasks", [1]) + _blob_conf("labels", [1]) + _blob_conf("logits", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + # print('blob_confs=', blob_confs['input_ids'].shape) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = MFTBERT( + decoders['input_ids'], + decoders['attention_masks'], + decoders['token_type_ids'], + decoders['labels'], + decoders['tasks'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + layer_indexes=[3, 7, 11], + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=False + ) + return loss, logits, decoders['labels'] + + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + # return loss + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.dev_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + +def main(): + # 加载domain以及对应的class + if args.task_name not in domain_list: + raise AttributeError('The task name can only be selected from [g1, g2, g3]') + domains = domain_list[args.task_name] + global num_domains + num_domains = len(domains) + processor = PROCESSORS[args.task_name](args.task_name) + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + preprocessor = Preprocessor(args, tokenizer, args.seed) + label_map = preprocessor.label_map # class2id + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_data = load_examples( + args.task_name, args.data_dir, TRAIN_SET, num_examples=-1, num_examples_per_label=None) + print('===============train examples================') + print('len=', len(train_data)) + print('example 0:', train_data[0]) + print('example 1:', train_data[1]) + print('===============================') + dev_data = load_examples( + args.task_name, args.data_dir, DEV_SET, num_examples=-1, num_examples_per_label=None) + train_feature_dict = generate_dataset(args, train_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='train') + dev_feature_dict = generate_dataset(args, dev_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='dev') + # 初始化每个 domain class 的prototypical embedding + domain_class_embeddings = dict() + temp_output_data = list() + + # 遍历每一个数据集domains + for domain_name in domains: + # 遍历每个类标 + for class_name, class_id in label_map.items(): + key_name = domain_name + "\t" + str(class_id) + # 初始化每个domain对应class的样本列表 + domain_class_embeddings[key_name] = list() + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + print("starting meta fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='meta-finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.dev_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_dev_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + snapshot.save("best_mft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + print("best dev acc: {}".format(best_dev_acc)) + + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaFineTuning/preprocess.py b/NLPAlgo/MetaFineTuning/preprocess.py new file mode 100644 index 0000000..df36eb6 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/preprocess.py @@ -0,0 +1,277 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令:python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MFTBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from data_utils.data_process import Preprocessor +from data_utils.task_processors import PROCESSORS, load_examples, DEV32_SET, TRAIN_SET, DEV_SET, TEST_SET, METRICS, DEFAULT_METRICS +from data_utils.data_process import domain_list, class_list, task_to_id +from data_utils.generate_feature import generate_dataset +import scipy.spatial.distance as distance +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='g1', choices=['g1', 'g2', 'g3']) +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--train_example_num", type=int, default=88614, + help="example number in dataset") +parser.add_argument("--batch_size_per_device", type=int, default=2) +parser.add_argument("--train_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--eval_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--eval_batch_size_per_device", type=int, default=2) +parser.add_argument("--eval_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument('--seed', type=int, default=42, + help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +num_eval_steps = math.ceil(args.eval_example_num / eval_batch_size) + + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("attention_masks", [seq_length]) + _blob_conf("token_type_ids", [seq_length]) + _blob_conf("tasks", [1]) + _blob_conf("labels", [1]) + _blob_conf("logits", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + output = MFTBERT( + decoders['input_ids'], + decoders['attention_masks'], + decoders['token_type_ids'], + decoders['labels'], + decoders['tasks'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=True # 只获得隐向量 + ) + return output, decoders['tasks'], decoders['labels'], decoders['idxs'], + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + output, tasks, labels, idxs = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return output, tasks, labels, idxs + + +def get_hidden_embedding(eval_job_func, num_steps, domain_class_embeddings, temp_output_data, desc='train'): + labels = [] + predictions = [] + for index in tqdm(range(num_steps)): + output, tasks, labels, idxs = eval_job_func().get() + current_size = output.shape[0] + output = list(output.numpy().tolist()) + tasks = list(tasks.numpy().tolist()) + labels = list(labels.numpy().tolist()) + idxs = list(idxs.numpy().tolist()) + # print('tasks=', tasks, ',output=', output) + + for i in range(current_size): + pool_output = output[i] + task_name = domain_list[args.task_name][tasks[i][0]] + label = str(labels[i][0]) + domain_class_embeddings[task_name + '\t' + label].append(pool_output) + temp_output_data.append((idxs[i][0], task_name, label, pool_output)) + + return domain_class_embeddings, temp_output_data + + + + +# 计算 prototypical score +def compute_weight(domain, label, current_embedding, centroid_embeddings): + key_name = domain + "\t" + str(label) + current_centroid = centroid_embeddings[key_name] + other_centroids = list() + for current_key in centroid_embeddings.keys(): + items = current_key.split("\t") + current_domain = items[0] + current_label = items[1] + if not (current_domain == domain) and (current_label==label): + other_centroids.append(centroid_embeddings[current_key]) + other_centroids = np.array(other_centroids) + other_centroid_mean = np.mean(other_centroids, axis=0) + first_cos_sim = 1 - distance.cosine(current_embedding, current_centroid) + second_cos_sim = 1 - distance.cosine(current_embedding, other_centroid_mean) + return (first_cos_sim + second_cos_sim) / 2 + + + +def main(): + + # 加载domain以及对应的class + if args.task_name not in domain_list: + raise AttributeError('The task name can only be selected from [g1, g2, g3]') + domains = domain_list[args.task_name] + global num_domains + num_domains = len(domains) + processor = PROCESSORS[args.task_name](args.task_name) + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + preprocessor = Preprocessor(args, tokenizer, args.seed) + label_map = preprocessor.label_map # class2id + + + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_data = load_examples( + args.task_name, args.data_dir, TRAIN_SET, num_examples=-1, num_examples_per_label=None) + print('===============================') + print('len=', len(train_data)) + print('===============================') + # eval_data = load_examples( + # args.task_name, args.data_dir, TEST_SET, num_examples=-1, num_examples_per_label=None) + dev_data = load_examples( + args.task_name, args.data_dir, DEV_SET, num_examples=-1, num_examples_per_label=None) + train_feature_dict = generate_dataset(args, train_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='train') + # eval_feature_dict = generate_dataset(args, eval_data, preprocessor, ofrecord_dir=ofrecord_dir, + # stage='eval') + dev_feature_dict = generate_dataset(args, dev_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='dev') + # 初始化每个 domain class 的prototypical embedding + domain_class_embeddings = dict() + temp_output_data = list() + + # 遍历每一个数据集domains + for domain_name in domains: + # 遍历每个类标 + for class_name, class_id in label_map.items(): + key_name = domain_name + "\t" + str(class_id) + # 初始化每个domain对应class的样本列表 + domain_class_embeddings[key_name] = list() + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + # 执行一次prediction,获得BERT的embedding + domain_class_embeddings, temp_output_data = get_hidden_embedding( + eval_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + domain_class_embeddings=domain_class_embeddings, + temp_output_data=temp_output_data, + desc='eval') + + # do inference for training data + + + # compute centroids + # 对于每个domain class,取所有样本embedding的均值,作为prototype embedding + centroid_embeddings = dict() + for key_name in domain_class_embeddings: + domain_class_data_embeddings = np.array(domain_class_embeddings[key_name]) + centroid_embeddings[key_name] = np.mean(domain_class_data_embeddings, axis=0) + + # output files for meta fine-tune + # 计算prototypical score,并保存在本地文件中 + #write odps tables + records = [] + for idx, domain, label, embeddings in temp_output_data: + weight = compute_weight(domain, label, embeddings, centroid_embeddings) + tup = {idx: np.around(weight, decimals=5)} + records.append(tup) + + np.save(os.path.join(ofrecord_dir, 'train/weight.npy'), records, allow_pickle=True) + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaFineTuning/tokenization.py b/NLPAlgo/MetaFineTuning/tokenization.py new file mode 100644 index 0000000..64d3a27 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/tokenization.py @@ -0,0 +1,467 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +# coding=utf-8 +# Copyright 2018 The Google AI Language Team Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tokenization classes.""" + +import collections +import re +import unicodedata +import six +from typing import List, Optional +#import tensorflow as tf + + +def validate_case_matches_checkpoint(do_lower_case, init_checkpoint): + """Checks whether the casing config is consistent with the checkpoint name.""" + + # The casing has to be passed in by the user and there is no explicit check + # as to whether it matches the checkpoint. The casing information probably + # should have been stored in the bert_config.json file, but it's not, so + # we have to heuristically detect it to validate. + + if not init_checkpoint: + return + + m = re.match("^.*?([A-Za-z0-9_-]+)/bert_model.ckpt", init_checkpoint) + if m is None: + return + + model_name = m.group(1) + + lower_models = [ + "uncased_L-24_H-1024_A-16", "uncased_L-12_H-768_A-12", + "multilingual_L-12_H-768_A-12", "chinese_L-12_H-768_A-12" + ] + + cased_models = [ + "cased_L-12_H-768_A-12", "cased_L-24_H-1024_A-16", + "multi_cased_L-12_H-768_A-12" + ] + + is_bad_config = False + if model_name in lower_models and not do_lower_case: + is_bad_config = True + actual_flag = "False" + case_name = "lowercased" + opposite_flag = "True" + + if model_name in cased_models and do_lower_case: + is_bad_config = True + actual_flag = "True" + case_name = "cased" + opposite_flag = "False" + + if is_bad_config: + raise ValueError( + "You passed in `--do_lower_case=%s` with `--init_checkpoint=%s`. " + "However, `%s` seems to be a %s model, so you " + "should pass in `--do_lower_case=%s` so that the fine-tuning matches " + "how the model was pre-training. If this error is wrong, please " + "just comment out this check." % (actual_flag, init_checkpoint, + model_name, case_name, opposite_flag)) + + +def convert_to_unicode(text): + """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text.decode("utf-8", "ignore") + elif isinstance(text, unicode): + return text + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def printable_text(text): + """Returns text encoded in a way suitable for print or `tf.logging`.""" + + # These functions want `str` for both Python2 and Python3, but in one case + # it's a Unicode string and in the other it's a byte string. + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text + elif isinstance(text, unicode): + return text.encode("utf-8") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def load_vocab(vocab_file): + """Loads a vocabulary file into a dictionary.""" + vocab = collections.OrderedDict() + index = 0 + #with tf.gfile.GFile(vocab_file, "r") as reader: + with open(vocab_file, "r") as reader: + while True: + token = convert_to_unicode(reader.readline()) + if not token: + break + token = token.strip() + vocab[token] = index + index += 1 + return vocab + + +def convert_by_vocab(vocab, items): + """Converts a sequence of [tokens|ids] using the vocab.""" + output = [] + for item in items: + output.append(vocab[item]) + return output + + +def convert_tokens_to_ids(vocab, tokens): + return convert_by_vocab(vocab, tokens) + + +def convert_ids_to_tokens(inv_vocab, ids): + return convert_by_vocab(inv_vocab, ids) + + +def whitespace_tokenize(text): + """Runs basic whitespace cleaning and splitting on a piece of text.""" + text = text.strip() + if not text: + return [] + tokens = text.split() + return tokens + + +class FullTokenizer(object): + """Runs end-to-end tokenziation.""" + + def __init__(self, vocab_file, do_lower_case=True): + self.vocab = load_vocab(vocab_file) + self.inv_vocab = {v: k for k, v in self.vocab.items()} + self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) + self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) + ## add by wjn + self.unk_token_id = self.vocab['[UNK]'] + self.mask_token_id = self.vocab['[MASK]'] + self.cls_token_id = self.vocab['[CLS]'] + self.pad_token_id = self.vocab['[PAD]'] + + def tokenize(self, text): + # print('text=', text) + split_tokens = [] + for token in self.basic_tokenizer.tokenize(text): + for sub_token in self.wordpiece_tokenizer.tokenize(token): + split_tokens.append(sub_token) + # print('split_tokens=', split_tokens) + return split_tokens + + def convert_tokens_to_ids(self, tokens): + return convert_by_vocab(self.vocab, tokens) + + def convert_ids_to_tokens(self, ids): + return convert_by_vocab(self.inv_vocab, ids) + + def num_special_tokens_to_add(self, pair: bool): + num_special = 2 + if pair: + num_special += 1 + return num_special + + def build_inputs_with_special_tokens( + self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None + ) -> List[int]: + """ + Build model inputs from a sequence or a pair of sequence for sequence classification tasks + by concatenating and adding special tokens. + + This implementation does not add special tokens and this method should be overriden in a subclass. + + Args: + token_ids_0 (:obj:`List[int]`): The first tokenized sequence. + token_ids_1 (:obj:`List[int]`, `optional`): The second tokenized sequence. + + Returns: + :obj:`List[int]`: The model input with special tokens. + """ + if token_ids_1 is None: + return token_ids_0 + return token_ids_0 + token_ids_1 + + def create_token_type_ids_from_sequences( + self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None + ) -> List[int]: + """ + Create the token type IDs corresponding to the sequences passed. + `What are token type IDs? <../glossary.html#token-type-ids>`__ + + Should be overriden in a subclass if the model has a special way of building those. + + Args: + token_ids_0 (:obj:`List[int]`): The first tokenized sequence. + token_ids_1 (:obj:`List[int]`, `optional`): The second tokenized sequence. + + Returns: + :obj:`List[int]`: The token type ids. + """ + if token_ids_1 is None: + return len(token_ids_0) * [0] + return [0] * len(token_ids_0) + [1] * len(token_ids_1) + +class BasicTokenizer(object): + """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" + + def __init__(self, do_lower_case=True): + """Constructs a BasicTokenizer. + + Args: + do_lower_case: Whether to lower case the input. + """ + self.do_lower_case = do_lower_case + + def tokenize(self, text): + """Tokenizes a piece of text.""" + # print('wordpiece_text=', text) + # special token不参与word piece 分词 + if text in ['[UNK]', '[MASK]', '[PAD]', '[SEP]']: + return [text] + text = convert_to_unicode(text) + text = self._clean_text(text) + + # This was added on November 1st, 2018 for the multilingual and Chinese + # models. This is also applied to the English models now, but it doesn't + # matter since the English models were not trained on any Chinese data + # and generally don't have any Chinese data in them (there are Chinese + # characters in the vocabulary because Wikipedia does have some Chinese + # words in the English Wikipedia.). + text = self._tokenize_chinese_chars(text) + + orig_tokens = whitespace_tokenize(text) + split_tokens = [] + for token in orig_tokens: + if self.do_lower_case: + token = token.lower() + token = self._run_strip_accents(token) + split_tokens.extend(self._run_split_on_punc(token)) + + output_tokens = whitespace_tokenize(" ".join(split_tokens)) + return output_tokens + + def _run_strip_accents(self, text): + """Strips accents from a piece of text.""" + text = unicodedata.normalize("NFD", text) + output = [] + for char in text: + cat = unicodedata.category(char) + if cat == "Mn": + continue + output.append(char) + return "".join(output) + + def _run_split_on_punc(self, text): + """Splits punctuation on a piece of text.""" + chars = list(text) + i = 0 + start_new_word = True + output = [] + while i < len(chars): + char = chars[i] + if _is_punctuation(char): + output.append([char]) + start_new_word = True + else: + if start_new_word: + output.append([]) + start_new_word = False + output[-1].append(char) + i += 1 + + return ["".join(x) for x in output] + + def _tokenize_chinese_chars(self, text): + """Adds whitespace around any CJK character.""" + output = [] + for char in text: + cp = ord(char) + if self._is_chinese_char(cp): + output.append(" ") + output.append(char) + output.append(" ") + else: + output.append(char) + return "".join(output) + + def _is_chinese_char(self, cp): + """Checks whether CP is the codepoint of a CJK character.""" + # This defines a "chinese character" as anything in the CJK Unicode block: + # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) + # + # Note that the CJK Unicode block is NOT all Japanese and Korean characters, + # despite its name. The modern Korean Hangul alphabet is a different block, + # as is Japanese Hiragana and Katakana. Those alphabets are used to write + # space-separated words, so they are not treated specially and handled + # like the all of the other languages. + if ((cp >= 0x4E00 and cp <= 0x9FFF) or # + (cp >= 0x3400 and cp <= 0x4DBF) or # + (cp >= 0x20000 and cp <= 0x2A6DF) or # + (cp >= 0x2A700 and cp <= 0x2B73F) or # + (cp >= 0x2B740 and cp <= 0x2B81F) or # + (cp >= 0x2B820 and cp <= 0x2CEAF) or + (cp >= 0xF900 and cp <= 0xFAFF) or # + (cp >= 0x2F800 and cp <= 0x2FA1F)): # + return True + + return False + + def _clean_text(self, text): + """Performs invalid character removal and whitespace cleanup on text.""" + output = [] + for char in text: + cp = ord(char) + if cp == 0 or cp == 0xfffd or _is_control(char): + continue + if _is_whitespace(char): + output.append(" ") + else: + output.append(char) + return "".join(output) + + +class WordpieceTokenizer(object): + """Runs WordPiece tokenziation.""" + + def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200): + self.vocab = vocab + self.unk_token = unk_token + self.max_input_chars_per_word = max_input_chars_per_word + + def tokenize(self, text): + """Tokenizes a piece of text into its word pieces. + + This uses a greedy longest-match-first algorithm to perform tokenization + using the given vocabulary. + + For example: + input = "unaffable" + output = ["un", "##aff", "##able"] + + Args: + text: A single token or whitespace separated tokens. This should have + already been passed through `BasicTokenizer. + + Returns: + A list of wordpiece tokens. + """ + text = convert_to_unicode(text) + + output_tokens = [] + for token in whitespace_tokenize(text): + chars = list(token) + if len(chars) > self.max_input_chars_per_word: + output_tokens.append(self.unk_token) + continue + + is_bad = False + start = 0 + sub_tokens = [] + while start < len(chars): + end = len(chars) + cur_substr = None + while start < end: + substr = "".join(chars[start:end]) + if start > 0: + substr = "##" + substr + if substr in self.vocab: + cur_substr = substr + break + end -= 1 + if cur_substr is None: + is_bad = True + break + sub_tokens.append(cur_substr) + start = end + + if is_bad: + output_tokens.append(self.unk_token) + else: + output_tokens.extend(sub_tokens) + return output_tokens + + +def _is_whitespace(char): + """Checks whether `chars` is a whitespace character.""" + # \t, \n, and \r are technically contorl characters but we treat them + # as whitespace since they are generally considered as such. + if char == " " or char == "\t" or char == "\n" or char == "\r": + return True + cat = unicodedata.category(char) + if cat == "Zs": + return True + return False + + +def _is_control(char): + """Checks whether `chars` is a control character.""" + # These are technically control characters but we count them as whitespace + # characters. + if char == "\t" or char == "\n" or char == "\r": + return False + cat = unicodedata.category(char) + if cat.startswith("C"): + return True + return False + + +def _is_punctuation(char): + """Checks whether `chars` is a punctuation character.""" + cp = ord(char) + # We treat all non-letter/number ASCII as punctuation. + # Characters such as "^", "$", and "`" are not in the Unicode + # Punctuation class but we treat them as punctuation anyways, for + # consistency. + if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or + (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)): + return True + cat = unicodedata.category(char) + if cat.startswith("P"): + return True + return False diff --git a/NLPAlgo/MetaFineTuning/util.py b/NLPAlgo/MetaFineTuning/util.py new file mode 100644 index 0000000..3c22a25 --- /dev/null +++ b/NLPAlgo/MetaFineTuning/util.py @@ -0,0 +1,190 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os +import time +import numpy as np +from collections import OrderedDict +import pandas as pd +from datetime import datetime +import oneflow as flow + + +def InitNodes(args): + if args.num_nodes > 1: + assert args.num_nodes <= len(args.node_ips) + flow.env.ctrl_port(args.ctrl_port) + nodes = [] + for ip in args.node_ips[:args.num_nodes]: + addr_dict = {} + addr_dict["addr"] = ip + nodes.append(addr_dict) + + flow.env.machine(nodes) + + +class Snapshot(object): + def __init__(self, model_save_dir, model_load_dir): + self._model_save_dir = model_save_dir + self._check_point = flow.train.CheckPoint() + if model_load_dir: + assert os.path.isdir(model_load_dir) + print("Restoring model from {}.".format(model_load_dir)) + self._check_point.load(model_load_dir) + else: + self._check_point.init() + self.save('initial_model') + print("Init model on demand.") + + def load(self, model_load_dir): + assert os.path.isdir(model_load_dir) + print("Restoring model from {}.".format(model_load_dir)) + self._check_point.load(model_load_dir) + + def save(self, name): + snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) + if not os.path.exists(snapshot_save_path): + os.makedirs(snapshot_save_path) + print("Saving model to {}.".format(snapshot_save_path)) + if not os.path.exists(snapshot_save_path): + self._check_point.save(snapshot_save_path) + + +# class Snapshot(object): +# def __init__(self, model_save_dir, model_load_dir): +# self._model_save_dir = model_save_dir +# # self._check_point = flow.train.CheckPoint() +# if model_load_dir: +# assert os.path.isdir(model_load_dir) +# print("Restoring model from {}.".format(model_load_dir)) +# flow.checkpoint.get(model_load_dir) +# else: +# flow.checkpoint.init() +# self.save('initial_model') +# print("Init model on demand.") +# +# def save(self, name): +# snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) +# if not os.path.exists(snapshot_save_path): +# os.makedirs(snapshot_save_path) +# print("Saving model to {}.".format(snapshot_save_path)) +# flow.checkpoint.save(snapshot_save_path) + + +class StopWatch(object): + def __init__(self): + pass + + def start(self): + self.start_time = time.time() + self.last_split = self.start_time + + def split(self): + now = time.time() + duration = now - self.last_split + self.last_split = now + return duration + + def stop(self): + self.stop_time = time.time() + + def duration(self): + return self.stop_time - self.start_time + + +class Metric(object): + def __init__(self, desc='train', print_steps=-1, batch_size=256, keys=[]): + r"""accumulate and calculate metric + + Args: + desc: `str` general description of the metric to show + print_steps: `Int` print metrics every nth steps + batch_size: `Int` batch size per step + keys: keys in callback outputs + Returns: + """ + self.desc = desc + self.print_steps = print_steps + assert batch_size > 0 + self.batch_size = batch_size + + assert isinstance(keys, (list, tuple)) + self.keys = keys + self.metric_dict = OrderedDict() + self.metric_dict['step'] = 0 + + self.timer = StopWatch() + self.timer.start() + self._clear() + + def _clear(self): + for key in self.keys: + self.metric_dict[key] = 0.0 + self.metric_dict['n_' + key] = 0.0 + self.metric_dict['throughput'] = 0.0 + self.num_samples = 0.0 + + def update_and_save(self, key, value, step, **kwargs): + self.metric_dict[key] = value + self.metric_dict.pop('n_' + key, None) + + def metric_cb(self, step=0, **kwargs): + def callback(outputs): + if step == 0: self._clear() + + for key in self.keys: + self.metric_dict[key] += outputs[key].sum() + self.metric_dict['n_' + key] += outputs[key].size + + self.num_samples += self.batch_size + + if (step + 1) % self.print_steps == 0: + self.metric_dict['step'] = step + for k, v in kwargs.items(): + self.metric_dict[k] = v + throughput = self.num_samples / self.timer.split() + self.update_and_save('throughput', throughput, step) + for key in self.keys: + value = self.metric_dict[key] / self.metric_dict['n_' + key] + self.update_and_save(key, value, step, **kwargs) + print(', '.join(('{}: {}' if type(v) is int else '{}: {:.3f}').format(k, v) \ + for k, v in self.metric_dict.items()), time.time()) + self._clear() + + return callback + +def CreateOptimizer(args): + warmup_batches = int(args.iter_num * args.warmup_proportion) + lr_warmup = flow.optimizer.warmup.linear(warmup_batches, 0) + lr_scheduler = flow.optimizer.PolynomialSchduler(args.learning_rate, args.iter_num, 0.0, + warmup=lr_warmup) + loss_scale_policy = None + if args.use_fp16: + loss_scale_policy = flow.optimizer.loss_scale.dynamic_loss_scale(increment_period=2000); + return flow.optimizer.AdamW(lr_scheduler, epsilon=1e-6, weight_decay=args.weight_decay_rate, + weight_decay_excludes=["bias", "LayerNorm", "layer_norm"], + grad_clipping=flow.optimizer.grad_clipping.by_global_norm(1.0), + loss_scale_policy=loss_scale_policy) + +def GetFunctionConfig(args): + config = flow.function_config() + config.enable_auto_mixed_precision(args.use_fp16) + if args.use_xla: + config.use_xla_jit(True) + config.enable_fuse_add_to_output(True) + config.enable_fuse_model_update_ops(True) + return config + diff --git "a/NLPAlgo/MetaFineTuning/\345\221\275\344\273\244.txt" "b/NLPAlgo/MetaFineTuning/\345\221\275\344\273\244.txt" new file mode 100644 index 0000000..a641fe3 --- /dev/null +++ "b/NLPAlgo/MetaFineTuning/\345\221\275\344\273\244.txt" @@ -0,0 +1,12 @@ +生成prototype embedding并计算prototypical score + +python3 preprocess.py --task_name g1 --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --dev_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +meta fine-tuning部分 + +python3 meta_finetuning.py --task_name g1 --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/k-shot-cross/g1/16-42 --num_epochs 63 --seed 42 --seq_length=128 --train_example_num 96 --dev_example_num 96 --batch_size_per_device 4 --dev_batch_size_per_device 2 --dev_every_step_num 100 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 5e-5 --resave_ofrecord + +fine-tuning部分 + +python3 finetuning.py --task_name sst-2 --model_load_dir output/model_save-2021-06-15-08:54:42/snapshot_best_mft_model_g1_dev_0.7083333333333334 --data_dir data/k-shot-single/SST-2/16-42 --num_epochs 64 --seed 42 --seq_length=128 --train_example_num 32 --dev_example_num 32 --eval_example_num 872 --batch_size_per_device 2 --dev_batch_size_per_device 2 --eval_batch_size_per_device 2 --dev_every_step_num 50 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 1e-5 --resave_ofrecord \ No newline at end of file diff --git a/NLPAlgo/VanillaKowledgeDistillation/README.md b/NLPAlgo/VanillaKowledgeDistillation/README.md new file mode 100644 index 0000000..62093f3 --- /dev/null +++ b/NLPAlgo/VanillaKowledgeDistillation/README.md @@ -0,0 +1,45 @@ +## Knowledge Distillation +Oneflow实现最基础的知识蒸馏(Knowledge Distillation, KD)算法 + +--- +## KD概述: +为了避免超大模型不利于上线的问题,知识蒸馏旨在解决如何使用学习一个参数量较小的模型,使得其与大模型具有相当的效果。 + +KD主要包含两个部分,分别是Teacher和Student: +- Teacher:表示原始的大模型,通常直接在有监督数据上进行学习。在推理阶段,获得每个样本的概率分布; +- Student:表示要获得的小模型,其在Teacher模型获得每个样本的概率分布基础上进行学习,即学习Teacher模型的先验 + +因此简单的KD主要分为两个步骤,首先训练Teacher模型,然后训练Student模型。 + +## 数据获取 +选择MNIST进行实验(训练集60000,测试集10000) +Oneflow已经实现了数据获取代码(ofrecord格式),因此执行后续代码将自动下载,无需手动下载。如若查看具体数据,详情:http://yann.lecun.com/exdb/mnist/ + + +## 实验设置 + +#### Step1:训练Teacher模型: + +```shell +python3 main.py \ + --model_type teacher \ + --epoch 10 \ + --temperature 5 +``` +运行效果:在测试集上最佳准确率为98.98%,模型保存至output中 +![Teacher实验截图](images/kd_teacher.png) + +#### Step2:训练Student模型: +挑选在测试集上最好的Teacher模型,然后以Teacher模型获得的soft label作为监督,训练Student模型 +获取准确率最高的模型文件(例如`output/model_save-2021-06-20-09:18:50`),然后执行 +```shell +python3 main.py \ + --model_type student \ + --load_teacher_from_checkpoint \ + --load_teacher_checkpoint_dir ./output/model_save-2021-06-20-09:18:50 \ + --epoch 50 \ + --temperature 5 +``` +运行效果:在测试集上达到89.19%。 +![Student实验截图](images/kd_student.png) + diff --git a/NLPAlgo/VanillaKowledgeDistillation/images/kd_student.png b/NLPAlgo/VanillaKowledgeDistillation/images/kd_student.png new file mode 100644 index 0000000000000000000000000000000000000000..6589e2f884034528ebfbe9c48672cc8748526917 GIT binary patch literal 90939 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 O-wl0}YvpXA!TxYoSZ{K+2y=O6=}Cta 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 BeM|rV literal 0 HcmV?d00001 diff --git a/NLPAlgo/VanillaKowledgeDistillation/main.py b/NLPAlgo/VanillaKowledgeDistillation/main.py new file mode 100644 index 0000000..b7cb050 --- /dev/null +++ b/NLPAlgo/VanillaKowledgeDistillation/main.py @@ -0,0 +1,231 @@ +import argparse +import numpy as np +import os +import oneflow as flow +import oneflow.typing as tp +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score +from model import TeacherModel, StudentModel +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from datetime import datetime +from tqdm import tqdm + +def str2bool(v): + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Unsupported value encountered.') + +parser = argparse.ArgumentParser(description="flags for knowledge distillation") +parser.add_argument('--display_step', type=int, default=200) +parser.add_argument('--loss_print_every_n_iter', type=int, default=100) +# parser.add_argument('--checkpoint_dir', type=str, default="checkpoint") +parser.add_argument("--model_save_dir", type=str, + default="./output/model_save-{}".format(str(datetime.now().strftime("%Y-%m-%d-%H:%M:%S"))), + required=False, help="model save directory") +parser.add_argument('--log_dir', type=str, default="logs") +parser.add_argument('--gpu', type=int, default=None, choices=[None, 0, 1]) +parser.add_argument('--use_fp16', type=str2bool, nargs='?', default='False', const=True, + help='use use fp16 or not') +parser.add_argument('--use_xla', type=str2bool, nargs='?', const=True, + help='Whether to use use xla') + +# Training Parameters +parser.add_argument("--load_teacher_from_checkpoint", action='store_true', help="Whether to training with teacher model") +parser.add_argument('--load_teacher_checkpoint_dir', type=str, default=None) +parser.add_argument('--model_type', type=str, default="teacher", choices=["teacher", "student"]) +parser.add_argument('--num_steps', type=int, default=500) +parser.add_argument('--epoch', type=int, default=10) +parser.add_argument('--batch_size', type=int, default=100) +parser.add_argument('--num_classes', type=int, default=10) + +# Model Parameters +parser.add_argument('--temperature', type=float, default=1.0) +parser.add_argument('--learning_rate', type=float, default=0.1) +parser.add_argument('--dropoutprob', type=float, default=0.25) +parser.add_argument("--weight_decay_rate", type=float, default=0.01, help="weight decay rate") +parser.add_argument("--warmup_proportion", type=float, default=0.1) +args = parser.parse_args() + + + + +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def train_teacher_job( + images: tp.Numpy.Placeholder((args.batch_size, 1, 28, 28), dtype=flow.float), + labels: tp.Numpy.Placeholder((args.batch_size, ), dtype=flow.int32), +): + model = TeacherModel( + args=args, + model_type='teacher', + X=images, + Y=labels + ) + loss, _, __ = model.get_res() + flow.losses.add_loss(loss) + # opt = CreateOptimizer(args) + # opt.minimize(loss) + lr_scheduler = flow.optimizer.PiecewiseConstantScheduler([], [0.001]) + flow.optimizer.Adam(lr_scheduler).minimize(loss) + return {'loss': loss} + + +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def train_student_job( + images: tp.Numpy.Placeholder((args.batch_size, 1, 28, 28), dtype=flow.float), + labels: tp.Numpy.Placeholder((args.batch_size, ), dtype=flow.int32), + soft_labels: tp.Numpy.Placeholder((args.batch_size, args.num_classes), dtype=flow.float), + +): + model = StudentModel( + args=args, + model_type='student', + X=images, + Y=labels, + soft_Y=soft_labels, + flag=True + ) + loss, _, __ = model.get_res() + flow.losses.add_loss(loss) + # opt = CreateOptimizer(args) + # opt.minimize(loss) + lr_scheduler = flow.optimizer.PiecewiseConstantScheduler([], [0.001]) + flow.optimizer.Adam(lr_scheduler).minimize(loss) + return {'loss': loss} + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def eval_teacher_job( + images: tp.Numpy.Placeholder((args.batch_size, 1, 28, 28), dtype=flow.float), + labels: tp.Numpy.Placeholder((args.batch_size, ), dtype=flow.int32), +): + model = TeacherModel( + args=args, + model_type='teacher', + X=images, + Y=labels + ) + _, logits, soft_labels = model.get_res() + return logits, labels, soft_labels + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def eval_student_job( + images: tp.Numpy.Placeholder((args.batch_size, 1, 28, 28), dtype=flow.float), + labels: tp.Numpy.Placeholder((args.batch_size, ), dtype=flow.int32), +): + model = StudentModel( + args=args, + model_type='student', + X=images, + Y=labels, + flag=False + ) + _, logits, _ = model.get_res() + return logits, labels, _ + + +def run_eval_job(dev_job_func, X, Y, model_type='teacher'): + labels = [] + predictions = [] + for ei, (input_images, input_labels) in enumerate(zip(X, Y)): + logits, label, _ = dev_job_func(input_images, input_labels).get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + # "precision": precision_score(labels, predictions), + # "recall": recall_score(labels, predictions), + # "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(model_type, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + +def main(): + # 加载数据集 + print('===== loading dataset ... =====') + (train_images, train_labels), (test_images, test_labels) = flow.data.load_mnist(args.batch_size, args.batch_size) + + snapshot = Snapshot(args.model_save_dir) + if args.model_type == 'teacher': + print('===== start training teacher model ... =====') + # 训练teacher模型 + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.epoch)): + # print('train_images.shape=', train_images.shape) + # print('train_labels.shape=', train_labels.shape) + r = np.random.permutation(len(train_labels)) + train_images_ = train_images[r, :] + train_labels_ = train_labels[r] + metric = Metric(desc='teacher-training', print_steps=args.loss_print_every_n_iter, + batch_size=args.batch_size, keys=['loss']) + + for (images, labels) in tqdm(zip(train_images_, train_labels_)): + global_step += 1 + train_teacher_job(images, labels).async_get(metric.metric_cb(global_step, epoch=epoch)) + + if (global_step % args.display_step) == 0: + # 因为只提供了训练集和测试集的迭代器,因此直接在测试集上进行验证 + dev_acc = run_eval_job( + dev_job_func=eval_teacher_job, + X=test_images, + Y=test_labels, + model_type='teacher') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving teacher model ... =====') + snapshot.save("best_teacher_model_dev_{}".format(best_dev_acc), best_dev_acc) + # 保存最好的验证准确率 + + else: + print('===== start training student model ... =====') + # 训练student模型 + # 加载teacher模型 + if args.load_teacher_from_checkpoint: + snapshot.load(args.load_teacher_checkpoint_dir, args.model_type) + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.epoch)): + r = np.random.permutation(len(train_labels)) + train_images_ = train_images[r, :] + train_labels_ = train_labels[r] + metric = Metric(desc='student-training', print_steps=args.loss_print_every_n_iter, + batch_size=args.batch_size, keys=['loss']) + + for (images, labels) in tqdm(zip(train_images_, train_labels_)): + global_step += 1 + # 获得相应样本的soft_label + soft_labels = labels + if args.load_teacher_from_checkpoint: + logits, _, soft_labels = eval_teacher_job(images, labels).get() + # print('soft_labels.shape=', soft_labels.shape) + train_student_job(images, labels, soft_labels.numpy()).async_get(metric.metric_cb(global_step, epoch=epoch)) + + if (global_step % args.display_step) == 0: + # 因为只提供了训练集和测试集的迭代器,因此直接在测试集上进行验证 + dev_acc = run_eval_job( + dev_job_func=eval_student_job, + X=test_images, + Y=test_labels, + model_type='student') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving student model ... =====') + snapshot.save("best_student_model_dev_{}".format(best_dev_acc), best_dev_acc) + # 保存最好的验证准确率 + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/NLPAlgo/VanillaKowledgeDistillation/model.py b/NLPAlgo/VanillaKowledgeDistillation/model.py new file mode 100644 index 0000000..e370cba --- /dev/null +++ b/NLPAlgo/VanillaKowledgeDistillation/model.py @@ -0,0 +1,309 @@ +import tensorflow as tf +import oneflow as flow +import os + + +class TeacherModel: + def __init__(self, args, model_type, X, Y): + self.X = X + self.Y = Y + self.learning_rate = 0.001 + self.num_steps = args.num_steps + self.batch_size = args.batch_size + self.display_step = args.display_step + self.num_input = 784 # MNIST data input (img shape: 28*28) + self.num_classes = 10 + self.dropoutprob = args.dropoutprob + # self.checkpoint_dir = args.checkpoint_dir + # self.checkpoint_file = "bigmodel" + self.softmax_temperature = args.temperature + # self.log_dir = os.path.join(args.log_dir, self.checkpoint_file) + self.model_type = model_type + self.initializer = flow.random_normal_initializer(stddev=0.1) + + # Store layers weight & bias + self.weights = { + # 'wc1': flow.get_variable( + # shape=[5, 5, 1, 32], # 32个5*5的卷积核,图像通道为1 + # dtype=flow.float, + # initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + # name="%s_%s" % (self.model_type, "wc1") + # ), + # 'wc2': flow.get_variable( + # shape=[5, 5, 32, 64], + # dtype=flow.float, + # initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + # name="%s_%s" % (self.model_type, "wc2") + # ), + 'wd1': flow.get_variable( + shape=[2 * 7 * 64, 1024], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "wd1") + ), + 'wout': flow.get_variable( + shape=[1024, self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "wout") + ) + } + + self.biases = { + 'bc1': flow.get_variable( + shape=[32], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "bc1") + ), + 'bc2': flow.get_variable( + shape=[64], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "bc2") + ), + 'bd1': flow.get_variable( + shape=[1024], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "bd1") + ), + 'bout': flow.get_variable( + shape=[self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "bout") + ) + } + + return self.build_model() + # self.saver = tf.train.Saver() + + # def conv2d(self, x, W, b, strides=1, name=''): + # # Conv2D wrapper, with bias and relu activation + # with flow.scope.namespace("%sconv2d" % (self.model_type)): + # x = flow.layers.conv2d(x, filters=) + # x = flow.layers.conv2d(x, W, strides=[1, strides, strides, 1], data_format="NCHW", + # kernel_initializer=self.initializer, padding='SAME', name=name) + # x = flow.nn.bias_add(x, b) + # return flow.nn.relu(x) + + def conv2d(self, x, filters: int, b, kernel_size: int, strides=1, name=''): + # Conv2D wrapper, with bias and relu activation + with flow.scope.namespace("%sconv2d" % (self.model_type)): + x = flow.layers.conv2d( + x, + filters=filters, + kernel_size=kernel_size, + strides=[strides, strides], + data_format="NCHW", + kernel_initializer=self.initializer, + padding='SAME', + name="{}-{}".format(self.model_type, name) + ) + x = flow.nn.bias_add(x, b) + return flow.nn.relu(x) + + def maxpool2d(self, x, k=2): + # MaxPool2D wrapper + with flow.scope.namespace("%smaxpool2d" % (self.model_type)): + return flow.nn.max_pool2d(x, ksize=[k, k], strides=[k, k], + padding='SAME') + + # Create model + def build_model(self): + # self.X = tf.placeholder(tf.float32, [None, self.num_input], name="%s_%s" % (self.model_type, "xinput")) + # self.Y = tf.placeholder(tf.float32, [None, self.num_classes], name="%s_%s" % (self.model_type, "yinput")) + # self.keep_prob = tf.placeholder(tf.float32, + # name="%s_%s" % (self.model_type, "dropoutprob")) # dropout (keep probability) + + # self.softmax_temperature = tf.placeholder(tf.float32, name="%s_%s" % (self.model_type, "softmaxtemp")) + # MNIST data input is a 1-D vector of 784 features (28*28 pixels) + # Reshape to match picture format [Height x Width x Channel] + # Tensor input become 4-D: [Batch Size, Height, Width, Channel] + with flow.scope.namespace("%sinputreshape" % (self.model_type)): + x = flow.reshape(self.X, shape=[-1, 28, 28, 1]) + + # Convolution Layer + with flow.scope.namespace("%sconvmaxpool" % (self.model_type)): + # print('x.shape=', x.shape) + conv1 = self.conv2d(x, 32, self.biases['bc1'], 5, name='conv1') # 第一层卷积,32个卷积核,维度为5*5 + # Max Pooling (down-sampling) + conv1 = self.maxpool2d(conv1, k=2) + + # Convolution Layer + conv2 = self.conv2d(x, 64, self.biases['bc2'], 5, name='conv2') # 第一层卷积,64个卷积核,维度为5*5 + # Max Pooling (down-sampling) + conv2 = self.maxpool2d(conv2, k=2) + + # Fully connected layer + # Reshape conv2 output to fit fully connected layer input + with flow.scope.namespace("%sfclayer" % (self.model_type)): + # print("conv2.shape=", conv2.shape) # [100, 64, 14, 1] + fc1 = flow.reshape(conv2, [-1, self.weights['wd1'].shape[0]]) # 2 * 7 * 64 表示隐状态维度,与 + + # print("fc1.shape=", fc1.shape) # [100, 896] + # print("self.weights.shape=", self.weights['wd1'].shape) # [896, 1024] + fc1 = flow.nn.bias_add(flow.matmul(fc1, self.weights['wd1']), self.biases['bd1']) + fc1 = flow.nn.relu(fc1) + # Apply Dropout + fc1 = flow.nn.dropout(fc1, self.dropoutprob) + + # Output, class prediction + # print("fc1.shape=", fc1.shape) + # print("self.weights['out'].shape=", self.weights['wout'].shape) + # print("self.biases['out'].shape=", self.biases['bout'].shape) + self.logits = flow.nn.bias_add(flow.matmul(fc1, self.weights['wout']), self.biases['bout']) / self.softmax_temperature + + with flow.scope.namespace("%sprediction" % (self.model_type)): + self.prediction = flow.nn.softmax(self.logits) + # # Evaluate model + # correct_pred = tf.equal(tf.argmax(self.prediction, 1), tf.argmax(self.Y, 1)) + # self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) + + with flow.scope.namespace("%soptimization" % (self.model_type)): + # Define loss and optimizer + # print('logits.shape=', self.logits.shape) + # print('Y.shape=', self.Y.shape) + self.loss = flow.math.reduce_mean(flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=self.logits, labels=self.Y)) + + def get_res(self): + return self.loss, self.logits, self.prediction + + + +class StudentModel: + def __init__(self, args, model_type, X, Y, soft_Y = None, flag: bool = False): + self.X = X + self.Y = Y # 真实标签 + self.soft_Y = soft_Y # teacher模型获得的概率分布 + self.flag = flag # 用于判断是否添加soft_Y的loss + self.learning_rate = 0.001 + self.num_steps = args.num_steps + self.batch_size = args.batch_size + self.display_step = args.display_step + self.n_hidden_1 = 256 # 1st layer number of neurons + self.n_hidden_2 = 256 # 2nd layer number of neurons + self.num_input = 784 # MNIST data input (img shape: 28*28) + self.num_classes = 10 + self.softmax_temperature = args.temperature + # self.checkpoint_dir = args.checkpoint_dir + # self.checkpoint_file = "smallmodel" + # self.checkpoint_path = os.path.join(self.checkpoint_dir, self.checkpoint_file) + # self.max_checkpoint_path = os.path.join(self.checkpoint_dir, self.checkpoint_file + "max") + # self.log_dir = os.path.join(args.log_dir, self.checkpoint_file) + self.model_type = model_type + self.initializer = flow.random_normal_initializer(stddev=0.1) + + # Store layers weight & bias + self.weights = { + 'h1': flow.get_variable( + shape=[self.num_input, self.n_hidden_1], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "h1") + ), + 'h2': flow.get_variable( + shape=[self.n_hidden_1, self.n_hidden_2], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "h2") + ), + 'wout': flow.get_variable( + shape=[self.n_hidden_2, self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "wout") + ), + 'wlinear': flow.get_variable( + shape=[self.num_input, self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "wlinear") + ) + } + + self.biases = { + 'b1': flow.get_variable( + shape=[self.n_hidden_1], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "b1") + ), + 'b2': flow.get_variable( + shape=[self.n_hidden_2], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "b2") + ), + 'bout': flow.get_variable( + shape=[self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "bout") + ), + 'blinear': flow.get_variable( + shape=[self.num_classes], + dtype=flow.float, + initializer=flow.random_normal_initializer(mean=0.0, stddev=0.02, seed=None, dtype=None), + name="%s_%s" % (self.model_type, "blinear") + ) + } + + self.build_model() + + # self.saver = tf.train.Saver() + + # Create model + def build_model(self): + # self.X = tf.placeholder(tf.float32, [None, self.num_input], name="%s_%s" % (self.model_type, "xinput")) + # self.Y = tf.placeholder(tf.float32, [None, self.num_classes], name="%s_%s" % (self.model_type, "yinput")) + + # self.flag = tf.placeholder(tf.bool, None, name="%s_%s" % (self.model_type, "flag")) + # self.soft_Y = tf.placeholder(tf.float32, [None, self.num_classes], name="%s_%s" % (self.model_type, "softy")) + # self.softmax_temperature = tf.placeholder(tf.float32, name="%s_%s" % (self.model_type, "softmaxtemperature")) + + with flow.scope.namespace("%sfclayer" % (self.model_type)): + # Hidden fully connected layer with 256 neurons + # layer_1 = tf.add(tf.matmul(self.X, self.weights['h1']), self.biases['b1']) + # print('self.X.shape=', self.X.shape) + # print('self.weights["h1"].shape=', self.weights['h1'].shape) + # print('self.biases["b1"].shape=', self.biases['b1'].shape) + x = flow.reshape(self.X, shape=[-1, self.X.shape[-1] * self.X.shape[-2]]) + layer_1 = flow.nn.bias_add(flow.matmul(x, self.weights['h1']), self.biases['b1']) + # # Hidden fully connected layer with 256 neurons + # layer_2 = tf.add(tf.matmul(layer_1, self.weights['h2']), self.biases['b2']) + layer_2 = flow.nn.bias_add(flow.matmul(layer_1, self.weights['h2']), self.biases['b2']) + # # Output fully connected layer with a neuron for each class + # logits = (tf.matmul(layer_2, self.weights['out']) + self.biases['out']) + self.logits = flow.nn.bias_add(flow.matmul(layer_2, self.weights['wout']), self.biases['bout']) + # logits = flow.nn.bias_add(flow.matmul(self.X, self.weights['linear']), self.biases['linear']) + # logits = flow.nn.bias_add(flow.matmul(layer_2, self.weights['wlinear']), self.biases['blinear']) + + with flow.scope.namespace("%sprediction" % (self.model_type)): + self.prediction = flow.nn.softmax(self.logits) + + # self.correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(self.Y, 1)) + # self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32)) + + with flow.scope.namespace("%soptimization" % (self.model_type)): + # Define loss and optimizer + self.loss_standard = flow.math.reduce_mean(flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=self.logits, labels=self.Y)) + + self.total_loss = self.loss_standard + + self.loss_soft = 0.0 + + if self.flag: + # print("self.logits.shape=", self.logits.shape) # [100, 10] + # print("self.soft_Y.shape=", self.soft_Y.shape) # [100, 10] + self.loss_soft = flow.math.reduce_mean(flow.nn.softmax_cross_entropy_with_logits( + logits=self.logits / self.softmax_temperature, labels=self.soft_Y)) + + self.total_loss += self.softmax_temperature * self.softmax_temperature * self.loss_soft + + + def get_res(self): + return self.total_loss, self.logits, self.prediction \ No newline at end of file diff --git a/NLPAlgo/VanillaKowledgeDistillation/util.py b/NLPAlgo/VanillaKowledgeDistillation/util.py new file mode 100644 index 0000000..55f0697 --- /dev/null +++ b/NLPAlgo/VanillaKowledgeDistillation/util.py @@ -0,0 +1,187 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os +import time +import numpy as np +from collections import OrderedDict +import pandas as pd +from datetime import datetime +import oneflow as flow + + +def InitNodes(args): + if args.num_nodes > 1: + assert args.num_nodes <= len(args.node_ips) + flow.env.ctrl_port(args.ctrl_port) + nodes = [] + for ip in args.node_ips[:args.num_nodes]: + addr_dict = {} + addr_dict["addr"] = ip + nodes.append(addr_dict) + + flow.env.machine(nodes) + + +class Snapshot(object): + def __init__(self, model_save_dir): + self._model_save_dir = model_save_dir + self._check_point = flow.train.CheckPoint() + self._check_point.init() + self.save('initial_model', 0.0) + print("Init model on demand.") + + def load(self, model_load_dir, model_type): + assert os.path.isdir(model_load_dir) + best_dev_acc = np.load(os.path.join(model_load_dir, "best_dev_acc.npy"), allow_pickle=True)[()] + print("Restoring model from {}.".format(os.path.join(model_load_dir, "snapshot_best_{}_model_dev_{}".format(model_type, best_dev_acc)))) + self._check_point.load(model_load_dir) + + def save(self, name, best_dev_acc): + snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) + if not os.path.exists(snapshot_save_path): + os.makedirs(snapshot_save_path) + print("Saving model to {}.".format(snapshot_save_path)) + np.save(os.path.join(self._model_save_dir, "best_dev_acc.npy"), best_dev_acc, allow_pickle=True) + if not os.path.exists(snapshot_save_path): + self._check_point.save(snapshot_save_path) + + +# class Snapshot(object): +# def __init__(self, model_save_dir, model_load_dir): +# self._model_save_dir = model_save_dir +# # self._check_point = flow.train.CheckPoint() +# if model_load_dir: +# assert os.path.isdir(model_load_dir) +# print("Restoring model from {}.".format(model_load_dir)) +# flow.checkpoint.get(model_load_dir) +# else: +# flow.checkpoint.init() +# self.save('initial_model') +# print("Init model on demand.") +# +# def save(self, name): +# snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) +# if not os.path.exists(snapshot_save_path): +# os.makedirs(snapshot_save_path) +# print("Saving model to {}.".format(snapshot_save_path)) +# flow.checkpoint.save(snapshot_save_path) + + +class StopWatch(object): + def __init__(self): + pass + + def start(self): + self.start_time = time.time() + self.last_split = self.start_time + + def split(self): + now = time.time() + duration = now - self.last_split + self.last_split = now + return duration + + def stop(self): + self.stop_time = time.time() + + def duration(self): + return self.stop_time - self.start_time + + +class Metric(object): + def __init__(self, desc='train', print_steps=-1, batch_size=256, keys=[]): + r"""accumulate and calculate metric + + Args: + desc: `str` general description of the metric to show + print_steps: `Int` print metrics every nth steps + batch_size: `Int` batch size per step + keys: keys in callback outputs + Returns: + """ + self.desc = desc + self.print_steps = print_steps + assert batch_size > 0 + self.batch_size = batch_size + + assert isinstance(keys, (list, tuple)) + self.keys = keys + self.metric_dict = OrderedDict() + self.metric_dict['step'] = 0 + + self.timer = StopWatch() + self.timer.start() + self._clear() + + def _clear(self): + for key in self.keys: + self.metric_dict[key] = 0.0 + self.metric_dict['n_' + key] = 0.0 + self.metric_dict['throughput'] = 0.0 + self.num_samples = 0.0 + + def update_and_save(self, key, value, step, **kwargs): + self.metric_dict[key] = value + self.metric_dict.pop('n_' + key, None) + + def metric_cb(self, step=0, **kwargs): + def callback(outputs): + if step == 0: self._clear() + + for key in self.keys: + self.metric_dict[key] += outputs[key].sum() + self.metric_dict['n_' + key] += outputs[key].size + + self.num_samples += self.batch_size + + if (step + 1) % self.print_steps == 0: + self.metric_dict['step'] = step + for k, v in kwargs.items(): + self.metric_dict[k] = v + throughput = self.num_samples / self.timer.split() + self.update_and_save('throughput', throughput, step) + for key in self.keys: + value = self.metric_dict[key] / self.metric_dict['n_' + key] + self.update_and_save(key, value, step, **kwargs) + print(', '.join(('{}: {}' if type(v) is int else '{}: {:.3f}').format(k, v) \ + for k, v in self.metric_dict.items()), time.time()) + self._clear() + + return callback + +def CreateOptimizer(args): + warmup_batches = int(args.epoch * args.warmup_proportion) + lr_warmup = flow.optimizer.warmup.linear(warmup_batches, 0) + lr_scheduler = flow.optimizer.PolynomialSchduler(args.learning_rate, args.epoch, 0.0, + warmup=lr_warmup) + loss_scale_policy = None + if args.use_fp16: + loss_scale_policy = flow.optimizer.loss_scale.dynamic_loss_scale(increment_period=2000); + return flow.optimizer.AdamW(lr_scheduler, epsilon=1e-6, weight_decay=args.weight_decay_rate, + weight_decay_excludes=["bias", "LayerNorm", "layer_norm"], + grad_clipping=flow.optimizer.grad_clipping.by_global_norm(1.0), + loss_scale_policy=loss_scale_policy) + +def GetFunctionConfig(args): + config = flow.function_config() + config.enable_auto_mixed_precision(args.use_fp16) + if args.use_xla: + config.use_xla_jit(True) + config.enable_fuse_add_to_output(True) + config.enable_fuse_model_update_ops(True) + return config + diff --git a/README.md b/README.md index 3494045..3674bc3 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ This repository provides OneFlow deep learning benchmark examples for CV, CTR an ## [GPT](./LanguageModeling/GPT) for Generative Pre-trained Transformer - [Generative Pre-trained Transformer](./LanguageModeling/GPT) +## [NLPAlgo](./NLPAlgo) for series of Natural Language Processing Model +- [Meta Fine-tuning](./NLPAlgo/MetaFineTuning) +- [VanillaKD](./NLPAlgo/VanillaKowledgeDistillation) + + ## OneFlow Benchmark Test Reports | Model | DType | XLA | Throughput | Speedup on 32 devices | From 1d728b69f5b9fe6e22c6e4501d884b7d36ec9a48 Mon Sep 17 00:00:00 2001 From: WangJianing <851019059@qq.com> Date: Tue, 28 Sep 2021 23:35:01 +0800 Subject: [PATCH 2/3] wjn-pr --- NLPAlgo/MetaKD/README.md | 18 + NLPAlgo/MetaKD/bert.py | 367 ++++++++++++++ NLPAlgo/MetaKD/classifier.py | 447 +++++++++++++++++ NLPAlgo/MetaKD/config.py | 106 ++++ NLPAlgo/MetaKD/convert_tf_ckpt_to_of.py | 90 ++++ NLPAlgo/MetaKD/data_utils/task_processors1.py | 342 +++++++++++++ NLPAlgo/MetaKD/data_utils/task_processors2.py | 429 ++++++++++++++++ NLPAlgo/MetaKD/data_utils/task_processors3.py | 446 +++++++++++++++++ NLPAlgo/MetaKD/data_utils/utils.py | 337 +++++++++++++ NLPAlgo/MetaKD/finetuning.py | 293 +++++++++++ NLPAlgo/MetaKD/images/fine_tuning.png | Bin 0 -> 59560 bytes NLPAlgo/MetaKD/images/meta_fine_tuning.png | Bin 0 -> 59560 bytes NLPAlgo/MetaKD/log.py | 50 ++ NLPAlgo/MetaKD/meta_distill.py | 288 +++++++++++ NLPAlgo/MetaKD/meta_finetuning.py | 280 +++++++++++ NLPAlgo/MetaKD/meta_teacher.py | 277 +++++++++++ NLPAlgo/MetaKD/meta_teacher_eval.py | 231 +++++++++ NLPAlgo/MetaKD/preprocess.py | 264 ++++++++++ NLPAlgo/MetaKD/tokenization.py | 467 ++++++++++++++++++ NLPAlgo/MetaKD/util.py | 210 ++++++++ 20 files changed, 4942 insertions(+) create mode 100644 NLPAlgo/MetaKD/README.md create mode 100644 NLPAlgo/MetaKD/bert.py create mode 100644 NLPAlgo/MetaKD/classifier.py create mode 100644 NLPAlgo/MetaKD/config.py create mode 100644 NLPAlgo/MetaKD/convert_tf_ckpt_to_of.py create mode 100644 NLPAlgo/MetaKD/data_utils/task_processors1.py create mode 100644 NLPAlgo/MetaKD/data_utils/task_processors2.py create mode 100644 NLPAlgo/MetaKD/data_utils/task_processors3.py create mode 100644 NLPAlgo/MetaKD/data_utils/utils.py create mode 100644 NLPAlgo/MetaKD/finetuning.py create mode 100644 NLPAlgo/MetaKD/images/fine_tuning.png create mode 100644 NLPAlgo/MetaKD/images/meta_fine_tuning.png create mode 100644 NLPAlgo/MetaKD/log.py create mode 100644 NLPAlgo/MetaKD/meta_distill.py create mode 100644 NLPAlgo/MetaKD/meta_finetuning.py create mode 100644 NLPAlgo/MetaKD/meta_teacher.py create mode 100644 NLPAlgo/MetaKD/meta_teacher_eval.py create mode 100644 NLPAlgo/MetaKD/preprocess.py create mode 100644 NLPAlgo/MetaKD/tokenization.py create mode 100644 NLPAlgo/MetaKD/util.py diff --git a/NLPAlgo/MetaKD/README.md b/NLPAlgo/MetaKD/README.md new file mode 100644 index 0000000..a212a53 --- /dev/null +++ b/NLPAlgo/MetaKD/README.md @@ -0,0 +1,18 @@ +The Amazon Review dataset can be found in this [link](https://www.cs.jhu.edu/~mdredze/datasets/sentiment/index2.html + +生成prototype embedding并计算prototypical score + +python3 preprocess.py --task_name senti --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/SENTI/ --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 6480 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + +meta-teacher learning部分 + +python3 meta_teacher.py --task_name senti --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/SENTI/ --num_epochs 63 --seed 42 --seq_length=128 --train_example_num 6480 --eval_example_num 720 --batch_size_per_device 24 --eval_batch_size_per_device 48 --eval_every_step_num 100 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 5e-5 --resave_ofrecord --do_train --do_eval + +meta distillation部分 + +首先在训练集上,获得meta-teacher的soft-label、attention、embedding等参数,并保存至本地 +python3 meta_teacher_eval.py --task_name senti --model_load_dir output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 --data_dir data/SENTI/ --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 6480 --eval_batch_size_per_device 1 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +执行distillation +python3 meta_distill.py --task_name senti --student_model uncased_L-12_H-768_A-12_oneflow --teacher_model output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 --data_dir data/SENTI/ --num_epochs 63 --seed 42 --seq_length=128 --train_example_num 6480 --eval_example_num 720 --batch_size_per_device 24 --eval_batch_size_per_device 48 --eval_every_step_num 100 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 5e-5 --resave_ofrecord --do_train --do_eval diff --git a/NLPAlgo/MetaKD/bert.py b/NLPAlgo/MetaKD/bert.py new file mode 100644 index 0000000..5c0d0aa --- /dev/null +++ b/NLPAlgo/MetaKD/bert.py @@ -0,0 +1,367 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import oneflow as flow +import oneflow.core.common.data_type_pb2 as data_type_util +import oneflow.core.operator.op_conf_pb2 as op_conf_util +import math + +# BERT的基础模型 +class BertBackbone(object): + + def __init__(self, + input_ids_blob, # 句子id序列 [batch_size, sequence_length] + input_mask_blob, + token_type_ids_blob, + vocab_size, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02): + + with flow.scope.namespace("bert"): + with flow.scope.namespace("embeddings"): + # 输出句子token对应的embedding,以及embedding table + (self.embedding_output_, self.embedding_table_) = _EmbeddingLookup( + input_ids_blob=input_ids_blob, + vocab_size=vocab_size, + embedding_size=hidden_size, # 词向量维度 + initializer_range=initializer_range, + word_embedding_name="word_embeddings") + # 累加type embedding或position embedding + self.embedding_output_ = _EmbeddingPostprocessor( + input_blob=self.embedding_output_, + seq_length=seq_length, + embedding_size=hidden_size, + use_token_type=True, + token_type_ids_blob=token_type_ids_blob, + token_type_vocab_size=type_vocab_size, + token_type_embedding_name="token_type_embeddings", + use_position_embeddings=True, + position_embedding_name="position_embeddings", + initializer_range=initializer_range, + max_position_embeddings=max_position_embeddings, + dropout_prob=hidden_dropout_prob, + ) + with flow.scope.namespace("encoder"): + attention_mask_blob = _CreateAttentionMaskFromInputMask( # 生成mask矩阵 + input_mask_blob, from_seq_length=seq_length, to_seq_length=seq_length) + addr_blob = _CreateAddrFromAttentionMask( # 将mask矩阵进行转换 + attention_mask_blob, from_seq_length=seq_length, to_seq_length=seq_length) + # self.all_encoder_layers_ = _TransformerModel( # Transformer模型(默认12层) + self.all_encoder_layers_, self.all_attention_scores_blob = _TransformerModel( # Transformer模型(默认12层) + input_blob=self.embedding_output_, + addr_blob=addr_blob, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + intermediate_act_fn=GetActivation(hidden_act), + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + initializer_range=initializer_range, + do_return_all_layers=True) + self.sequence_output_ = self.all_encoder_layers_[-1] # Transformer最后一层输出向量 + + def embedding_output(self): return self.embedding_output_ + def all_encoder_layers(self): return self.all_encoder_layers_ + def sequence_output(self): return self.sequence_output_ + def embedding_table(self): return self.embedding_table_ + def get_layer_embedding(self, index): # 获得指定层的embedding + assert index >= 0 and index < len(self.all_encoder_layers_) + return self.all_encoder_layers_[index] + def get_layer_attention_scores(self, index=None): # add by wjn 获得某一层(所有层的attention score) + if index is None: + return self.all_attention_scores_blob + else: + assert index >= 0 and index < len(self.all_attention_scores_blob) + return self.all_attention_scores_blob[index] + +# oneflow初始化函数加载器 +def CreateInitializer(std): + return flow.truncated_normal(std) # 截断的产生正态分布的随机数,即随机数与均值的差值若大于两倍的标准差,则重新生成 + +def _Gelu(in_blob): + return flow.math.gelu(in_blob) + +# Transformer模型 +def _TransformerModel(input_blob, + addr_blob, + seq_length, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + intermediate_act_fn=_Gelu, + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + initializer_range=0.02, + do_return_all_layers=False): + + assert hidden_size % num_attention_heads == 0 + attention_head_size = int(hidden_size / num_attention_heads) + input_width = hidden_size + prev_output_blob = flow.reshape(input_blob, (-1, input_width)) + all_layer_output_blobs = [] + all_attention_scores_blob = [] # add by wjn 保存每一层的attention vector + for layer_idx in range(num_hidden_layers): # Transformer包含若干层 + with flow.scope.namespace("layer_%d"%layer_idx): + layer_input_blob = prev_output_blob + with flow.scope.namespace("attention"): + with flow.scope.namespace("self"): + # attention_output_blob = _AttentionLayer( + attention_output_blob, attention_scores_blob = _AttentionLayer( + from_blob=layer_input_blob, + to_blob=layer_input_blob, + addr_blob=addr_blob, + num_attention_heads=num_attention_heads, + size_per_head=attention_head_size, + attention_probs_dropout_prob=attention_probs_dropout_prob, + initializer_range=initializer_range, + do_return_2d_tensor=True, + from_seq_length=seq_length, + to_seq_length=seq_length) + all_attention_scores_blob.append(attention_scores_blob) # add by wjn + with flow.scope.namespace("output"): + attention_output_blob = _FullyConnected( + attention_output_blob, + input_size=num_attention_heads * attention_head_size, + units=hidden_size, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + attention_output_blob = _Dropout(attention_output_blob, hidden_dropout_prob) + attention_output_blob = attention_output_blob + layer_input_blob # 残差 + attention_output_blob = _LayerNorm(attention_output_blob, hidden_size) + with flow.scope.namespace("intermediate"): + if callable(intermediate_act_fn): + act_fn = op_conf_util.kNone + else: + act_fn = intermediate_act_fn + intermediate_output_blob = _FullyConnected( + attention_output_blob, + input_size=num_attention_heads * attention_head_size, + units=intermediate_size, + activation=act_fn, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + if callable(intermediate_act_fn): + intermediate_output_blob = intermediate_act_fn(intermediate_output_blob) + with flow.scope.namespace("output"): + layer_output_blob = _FullyConnected( + intermediate_output_blob, + input_size=intermediate_size, + units=hidden_size, + weight_initializer=CreateInitializer(initializer_range), + name='dense') + layer_output_blob = _Dropout(layer_output_blob, hidden_dropout_prob) + layer_output_blob = layer_output_blob + attention_output_blob + layer_output_blob = _LayerNorm(layer_output_blob, hidden_size) + prev_output_blob = layer_output_blob + all_layer_output_blobs.append(layer_output_blob) + + input_shape = (-1, seq_length, hidden_size) + if do_return_all_layers: + final_output_blobs = [] + for layer_output_blob in all_layer_output_blobs: + final_output_blob = flow.reshape(layer_output_blob, input_shape) + final_output_blobs.append(final_output_blob) + return final_output_blobs, all_attention_scores_blob + else: + final_output_blob = flow.reshape(prev_output_blob, input_shape) + # return [final_output_blob] + return [final_output_blob], all_attention_scores_blob + +def _AttentionLayer(from_blob, + to_blob, + addr_blob, + num_attention_heads=1, + size_per_head=512, + query_act=op_conf_util.kNone, + key_act=op_conf_util.kNone, + value_act=op_conf_util.kNone, + attention_probs_dropout_prob=0.0, + initializer_range=0.02, + do_return_2d_tensor=False, + batch_size=None, + from_seq_length=None, + to_seq_length=None): + + def TransposeForScores(input_blob, num_attention_heads, seq_length, width): + output_blob = flow.reshape(input_blob, [-1, seq_length, num_attention_heads, width]) + output_blob = flow.transpose(output_blob, perm=[0, 2, 1, 3]) + return output_blob + + from_blob_2d = flow.reshape(from_blob, [-1, num_attention_heads * size_per_head]) + to_blob_2d = flow.reshape(to_blob, [-1, num_attention_heads * size_per_head]) + + query_blob = _FullyConnected( + from_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=query_act, + name="query", + weight_initializer=CreateInitializer(initializer_range)) + + key_blob = _FullyConnected( + to_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=key_act, + name="key", + weight_initializer=CreateInitializer(initializer_range)) + + value_blob = _FullyConnected( + to_blob_2d, + input_size=num_attention_heads * size_per_head, + units=num_attention_heads * size_per_head, + activation=value_act, + name="value", + weight_initializer=CreateInitializer(initializer_range)) + + query_blob = TransposeForScores(query_blob, num_attention_heads, from_seq_length, size_per_head) + key_blob = TransposeForScores(key_blob, num_attention_heads, to_seq_length, size_per_head) + + attention_scores_blob = flow.matmul(query_blob, key_blob, transpose_b=True) + attention_scores_blob = attention_scores_blob * (1.0 / math.sqrt(float(size_per_head))) + + attention_scores_blob = attention_scores_blob + addr_blob + attention_probs_blob = flow.nn.softmax(attention_scores_blob) + attention_probs_blob = _Dropout(attention_probs_blob, attention_probs_dropout_prob) + + value_blob = flow.reshape(value_blob, [-1, to_seq_length, num_attention_heads, size_per_head]) + value_blob = flow.transpose(value_blob, perm=[0, 2, 1, 3]) + context_blob = flow.matmul(attention_probs_blob, value_blob) + context_blob = flow.transpose(context_blob, perm=[0, 2, 1, 3]) + + if do_return_2d_tensor: + context_blob = flow.reshape(context_blob, [-1, num_attention_heads * size_per_head]) + else: + context_blob = flow.reshape(context_blob, [-1, from_seq_length, num_attention_heads * size_per_head]) + # return context_blob + return context_blob, attention_scores_blob + +def _FullyConnected(input_blob, input_size, units, activation=None, name=None, + weight_initializer=None): + weight_blob = flow.get_variable( + name=name + '-weight', + shape=[input_size, units], + dtype=input_blob.dtype, + initializer=weight_initializer) + bias_blob = flow.get_variable( + name=name + '-bias', + shape=[units], + dtype=input_blob.dtype, + initializer=flow.constant_initializer(0.0)) + output_blob = flow.matmul(input_blob, weight_blob) + output_blob = flow.nn.bias_add(output_blob, bias_blob) + return output_blob + +def _Dropout(input_blob, dropout_prob): + if dropout_prob == 0.0: + return input_blob + return flow.nn.dropout(input_blob, rate=dropout_prob) + + +def _LayerNorm(input_blob, hidden_size): + return flow.layers.layer_norm(input_blob, name='LayerNorm', begin_norm_axis=-1, begin_params_axis=-1) + + +def _CreateAttentionMaskFromInputMask(to_mask_blob, from_seq_length, to_seq_length): + output = flow.cast(to_mask_blob, dtype=flow.float) # 将指定的张量转换为对应的类型 + output = flow.reshape(output, [-1, 1, to_seq_length]) # [batch_size, 1, seq_len] + zeros = flow.constant(0.0, dtype=flow.float, shape=[from_seq_length, to_seq_length]) + output = zeros + output # 广播机制相加 + return output + + +def _CreateAddrFromAttentionMask(attention_mask_blob, from_seq_length, to_seq_length): + attention_mask_blob = flow.reshape(attention_mask_blob, [-1, 1, from_seq_length, to_seq_length]) + attention_mask_blob = flow.cast(attention_mask_blob, dtype=flow.float) + addr_blob = (attention_mask_blob - 1.0) * 10000.0 # mask为1的值变为0,mask为0的值变为-10000(非常小的数) + return addr_blob + +# 添加额外的embedding信息 +def _EmbeddingPostprocessor(input_blob, + seq_length, + embedding_size, + use_token_type=False, + token_type_ids_blob=None, + token_type_vocab_size=16, + token_type_embedding_name="token_type_embeddings", + use_position_embeddings=True, + position_embedding_name="position_embeddings", + initializer_range=0.02, + max_position_embeddings=512, + dropout_prob=0.1): + output = input_blob + + if use_token_type: # 如果使用token type embedding,使用预先加载或初始化的type embedding table + assert token_type_ids_blob is not None + token_type_table = flow.get_variable(name=token_type_embedding_name, + shape=[token_type_vocab_size, embedding_size], + dtype=input_blob.dtype, + initializer=CreateInitializer(initializer_range)) + token_type_embeddings = flow.gather(params=token_type_table, indices=token_type_ids_blob, axis=0) + output = output + token_type_embeddings # 按照BERT原文,直接加和即可 + + if use_position_embeddings: + position_table = flow.get_variable(name=position_embedding_name, + shape=[1, max_position_embeddings, embedding_size], + dtype=input_blob.dtype, + initializer=CreateInitializer(initializer_range)) + assert seq_length <= max_position_embeddings + if seq_length != max_position_embeddings: + position_table = flow.slice(position_table, begin=[None, 0, 0], size=[None, seq_length, -1]) + output = output + position_table + + output = _LayerNorm(output, embedding_size) + output = _Dropout(output, dropout_prob) + + return output + +# 创建EmbeddingLookup功能 +def _EmbeddingLookup(input_ids_blob, + vocab_size, + embedding_size=128, + initializer_range=0.02, + word_embedding_name="word_embeddings"): + # 创建初始化(或获取实现已经定义加载的)的embedding table + embedding_table = flow.get_variable(name=word_embedding_name, shape=[vocab_size, embedding_size], + dtype=flow.float, + initializer=CreateInitializer(initializer_range)) + output = flow.gather(params=embedding_table, indices=input_ids_blob, axis=0) + return output, embedding_table # 输出句子对应的embedding,以及整个embedding table + +# 获取激活函数(oneflow对象) +def GetActivation(name): + if name == 'linear': + return None + elif name == 'relu': + return flow.math.relu + elif name == 'tanh': + return flow.math.tanh + elif name == 'gelu': + return flow.math.gelu + else: + raise Exception("unsupported activation") + diff --git a/NLPAlgo/MetaKD/classifier.py b/NLPAlgo/MetaKD/classifier.py new file mode 100644 index 0000000..90db3a7 --- /dev/null +++ b/NLPAlgo/MetaKD/classifier.py @@ -0,0 +1,447 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import oneflow as flow +import bert as bert_util +import oneflow.core.operator.op_conf_pb2 as op_conf_util + +### Meta Fine-tuning +def MFTBERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + input_domain, + vocab_size, + input_weight, + layer_indexes=[-1], + num_domains=3, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + lambda_=0.1, # 两个loss的权重 + replace_prob=None, + get_output=False, # add by wjn,当为True时,表示只获取BERT的embedding +): + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + if get_output: + last_layer_output = backbone.sequence_output() # [bz, len, dim] + # indices = flow.tensor([0]) + # cls_output = flow.gather(last_layer_output, indices=indices, axis=1) # [bz, dim] 取CLS对应的embedding + cls_output = flow.math.reduce_mean(last_layer_output, axis=1) + # print('cls_output.shape=', cls_output.shape) + return cls_output + + # Meta Fine-tuning: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + ## Meta Fine-tuing: Corrupted Domain Classification loss + corrupted_loss = _AddCorruptedDomainCLSLoss(backbone.all_encoder_layers_, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range) + + # 对corrupted_loss进行加权求和 + corrupted_loss = flow.math.multiply(corrupted_loss, input_weight) + + return cls_loss + lambda_ * corrupted_loss, logit_blob + # return cls_loss, logit_blob + + + + +### Meta Teacher Model +def MetaTeacherBERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + input_domain, + vocab_size, + input_weight, + layer_indexes=[-1], + num_domains=3, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + lambda_=0.1, # 两个loss的权重 + replace_prob=None, + get_output=False, # add by wjn,当为True时,表示只获取BERT的embedding + get_att_reps=False, # add by wjn,当为True时,获得BERT的attention、 +): + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + if get_output: + last_layer_output = backbone.sequence_output() # [bz, len, dim] + # indices = flow.tensor([0]) + # cls_output = flow.gather(last_layer_output, indices=indices, axis=1) # [bz, dim] 取CLS对应的embedding + cls_output = flow.math.reduce_mean(last_layer_output, axis=1) + # print('cls_output.shape=', cls_output.shape) + return cls_output + + # Meta-teacher: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + ## Meta-teacher: Corrupted Domain Classification loss + corrupted_loss = _AddCorruptedDomainCLSLoss(backbone.all_encoder_layers_, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range) + + # 对corrupted_loss进行加权求和 + corrupted_loss = flow.math.multiply(corrupted_loss, input_weight) + + if get_att_reps: + all_attention_scores_blob = backbone.get_layer_attention_scores(num_hidden_layers - 1) # 最末层 + return cls_loss + lambda_ * corrupted_loss, logit_blob, all_attention_scores_blob, pooled_output + + + return cls_loss + lambda_ * corrupted_loss, logit_blob + + + + # return cls_loss, logit_blob + + + + + + +### Meta Student Model +def MetaStudentBERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + input_domain, + vocab_size, + input_weight, + input_logit_blob, + input_attention_blob, + input_pooled_blob, + layer_indexes=[-1], + num_domains=3, + seq_length=512, + hidden_size=768, + num_hidden_layers=6, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + lambda_=0.1, # 两个loss的权重 + replace_prob=None, + get_output=False, # add by wjn,当为True时,表示只获取BERT的embedding + get_att_reps=False, # add by wjn,当为True时,获得BERT的attention、 +): + # batch_size = input_ids_blob.shape[0] + # hidden_size_of_each_head = int(hidden_size / num_attention_heads) + # input_attention_blob = flow.reshape(input_attention_blob, (batch_size, 12, hidden_size_of_each_head, -1)) + # input_pooled_blob = flow.reshape(input_pooled_blob, (batch_size, 12, hidden_size_of_each_head, -1)) + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + if get_output: + last_layer_output = backbone.sequence_output() # [bz, len, dim] + # indices = flow.tensor([0]) + # cls_output = flow.gather(last_layer_output, indices=indices, axis=1) # [bz, dim] 取CLS对应的embedding + cls_output = flow.math.reduce_mean(last_layer_output, axis=1) + # print('cls_output.shape=', cls_output.shape) + return cls_output + + # Meta-teacher: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + ## Meta-teacher: Corrupted Domain Classification loss + corrupted_loss = _AddCorruptedDomainCLSLoss(backbone.all_encoder_layers_, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range) + + # 对corrupted_loss进行加权求和 + corrupted_loss = flow.math.multiply(corrupted_loss, input_weight) + + # 使用MSE对包括attention_score、pool_output以及soft-label计算loss + all_attention_scores_blob = backbone.get_layer_attention_scores(num_hidden_layers - 1) + # print('all_attention_scores_blob.shape=', all_attention_scores_blob.shape) + # print('input_attention_blob.shape=', input_attention_blob.shape) + # print('logit_blob.shape=', logit_blob.shape) + # print('input_logit_blob.shape=', input_logit_blob.shape) + # print('pooled_output.shape=', pooled_output.shape) + # print('input_pooled_blob.shape=', input_pooled_blob.shape) + # attention_loss = flow.math.reduce_mean(flow.nn.MSELoss(input=all_attention_scores_blob, target=input_attention_blob)) + # logit_loss = flow.math.reduce_mean(flow.nn.MSELoss(input=logit_blob, target=input_logit_blob)) + # pooled_loss = flow.math.reduce_mean(flow.nn.MSELoss(input=pooled_output, target=input_pooled_blob)) + # return cls_loss + lambda_ * corrupted_loss + attention_loss + logit_loss + pooled_loss, logit_blob + return cls_loss + lambda_ * corrupted_loss, logit_blob + + + + + + +### standard Fine-tuning +def BERT( + input_ids_blob, + input_mask_blob, + token_type_ids_blob, + label_blob, + vocab_size, + seq_length=512, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=16, + initializer_range=0.02, + label_num=2, + replace_prob=None, +): + backbone = bert_util.BertBackbone( # 创建BERT基础模型 + input_ids_blob=input_ids_blob, + input_mask_blob=input_mask_blob, + token_type_ids_blob=token_type_ids_blob, + vocab_size=vocab_size, + seq_length=seq_length, + hidden_size=hidden_size, + num_hidden_layers=num_hidden_layers, + num_attention_heads=num_attention_heads, + intermediate_size=intermediate_size, + hidden_act=hidden_act, + hidden_dropout_prob=hidden_dropout_prob, + attention_probs_dropout_prob=attention_probs_dropout_prob, + max_position_embeddings=max_position_embeddings, + type_vocab_size=type_vocab_size, + initializer_range=initializer_range, + ) + + # Meta Fine-tuning: CLS classification loss + pooled_output = PooledOutput( + sequence_output=backbone.sequence_output(), + hidden_size=hidden_size, + initializer_range=initializer_range + ) + # 添加分类损失函数 + cls_loss, _, logit_blob = _AddClassficationLoss( + input_blob=pooled_output, + label_blob=label_blob, + hidden_size=hidden_size, + label_num=label_num, + initializer_range=initializer_range, + scope_name='classification' + ) + + return cls_loss, logit_blob + + + + +# BERT的CLS token进行分类 +def PooledOutput(sequence_output, hidden_size, initializer_range): + with flow.scope.namespace("bert-pooler"): + first_token_tensor = flow.slice( # 切片操作,提取每个样本的第一个token([CLS])对应的表征向量 + sequence_output, [None, 0, 0], [None, 1, -1]) + first_token_tensor = flow.reshape( + first_token_tensor, [-1, hidden_size]) + pooled_output = bert_util._FullyConnected( # 添加一个全连接层 + first_token_tensor, + input_size=hidden_size, + units=hidden_size, + weight_initializer=bert_util.CreateInitializer(initializer_range), + name="dense", + ) + pooled_output = flow.math.tanh(pooled_output) + return pooled_output + + +def _AddClassficationLoss(input_blob, label_blob, hidden_size, label_num, initializer_range, + scope_name='classification'): + with flow.scope.namespace(scope_name): + output_weight_blob = flow.get_variable( + name="output_weights", + shape=[label_num, hidden_size], + dtype=input_blob.dtype, + # initializer=bert_util.CreateInitializer(initializer_range), + initializer=flow.random_normal_initializer( + mean=0.0, stddev=initializer_range, seed=None, dtype=None) + ) + output_bias_blob = flow.get_variable( + name="output_bias", + shape=[label_num], + dtype=input_blob.dtype, + initializer=flow.constant_initializer(0.0), + ) + logit_blob = flow.matmul( # output_weight_blob先转置,再与input_bob相乘 + input_blob, output_weight_blob, transpose_b=True) # [batch_size, label_num] + logit_blob = flow.nn.bias_add(logit_blob, output_bias_blob) + # 获得每个样本的loss [batch_size] + pre_example_loss = flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=logit_blob, labels=label_blob + ) + loss = pre_example_loss + # print('loss.shape=', loss.shape) # [bz, ] + loss = flow.math.reduce_mean(loss) + # print('loss.shape=', loss.shape) # [1, ] + return loss, pre_example_loss, logit_blob + + + + + +def _AddCorruptedDomainCLSLoss(input_blob, label_blob, input_domain, hidden_size, num_domains, + layer_indexes, initializer_range, + scope_name='corrupted_domain_classification'): + ''' + input_blob: [layer_num, batch_size, seq_length, hidden_size] + ''' + # print('len(input_blob)=', len(input_blob)) + domain_logits = dict() + total_domain_loss = 0. + with flow.scope.namespace(scope_name): + domain_embedded_matrix = flow.get_variable("domain_projection", [num_domains, hidden_size], + initializer=flow.truncated_normal_initializer(stddev=0.02)) + domain_embedded = flow.gather(params=domain_embedded_matrix, indices=input_domain, axis=0) + domain_embedded = flow.reshape(domain_embedded, shape=[-1, hidden_size]) + # print('domain_embedded.shape=', domain_embedded.shape) # [bz, dim] + for layer_index in layer_indexes: + content_tensor = flow.math.reduce_mean(input_blob[layer_index], axis=1) + content_tensor_with_domains = domain_embedded + content_tensor + # print('content_tensor.shape=', content_tensor.shape) # [bz, dim] + domain_weights = flow.get_variable("domain_weights", [num_domains, hidden_size], initializer=flow.truncated_normal_initializer(stddev=0.02)) + domain_bias = flow.get_variable("domain_bias", [num_domains], initializer=flow.zeros_initializer()) + # print('content_tensor_with_domains.shape=', content_tensor_with_domains.shape) # [bz, dim] + current_domain_logits = flow.matmul(content_tensor_with_domains, domain_weights, transpose_b=True) + current_domain_logits = flow.nn.bias_add(current_domain_logits, domain_bias) + + # domain_logits["domain_logits_"+str(layer_index)] = current_domain_logits + + # 计算当前layer对应的loss + shuffle_domain_labels = flow.random.shuffle(input_domain) # 随机生成错误的domain标签 + shuffle_domain_labels = flow.reshape(shuffle_domain_labels, shape=[-1]) + # print('shuffle_domain_labels.shape=', shuffle_domain_labels.shape) # [bz] + shuffle_domain_labels = flow.squeeze(shuffle_domain_labels) + # one_hot_labels = flow.one_hot(shuffle_domain_labels, depth=num_domains, dtype=flow.float32) + # print('current_domain_logits.shape=', current_domain_logits.shape) # [bz, domain_num] + # print('one_hot_labels.shape=', one_hot_labels.shape) + domain_loss = flow.nn.sparse_softmax_cross_entropy_with_logits( + logits=current_domain_logits, labels=shuffle_domain_labels + ) + total_domain_loss += domain_loss + total_domain_loss = total_domain_loss / len(layer_indexes) + return total_domain_loss diff --git a/NLPAlgo/MetaKD/config.py b/NLPAlgo/MetaKD/config.py new file mode 100644 index 0000000..a0b43fb --- /dev/null +++ b/NLPAlgo/MetaKD/config.py @@ -0,0 +1,106 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +from datetime import datetime + + +def str_list(x): + return x.split(',') + +def int_list(x): + return list(map(int, x.split(','))) + +def float_list(x): + return list(map(float, x.split(','))) + +def str2bool(v): + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Unsupported value encountered.') + +def get_parser(parser=None): + + parser = argparse.ArgumentParser(description="flags for bert") + + parser.add_argument('--do_train', type=str2bool, nargs='?', const=True, help='train or not') + parser.add_argument('--do_eval', type=str2bool, nargs='?', const=True, help='eval or not') + # resouce + parser.add_argument("--model", type=str, default='BERT Pretrain') + parser.add_argument("--gpu_num_per_node", type=int, default=1) + parser.add_argument('--num_nodes', type=int, default=1, + help='node/machine number for training') + parser.add_argument('--node_ips', type=str_list, default=['192.168.1.13', '192.168.1.14'], + help='nodes ip list for training, devided by ",", length >= num_nodes') + parser.add_argument("--ctrl_port", type=int, default=50051, help='ctrl_port for multinode job') + + # train + parser.add_argument("--learning_rate", type=float, default=1e-5, help="Learning rate") + parser.add_argument("--weight_decay_rate", type=float, default=0.01, help="weight decay rate") + parser.add_argument("--warmup_proportion", type=float, default=0.1) + parser.add_argument('--use_fp16', type=str2bool, nargs='?', default='False', const=True, + help='use use fp16 or not') + parser.add_argument('--use_xla', type=str2bool, nargs='?', const=True, + help='Whether to use use xla') + + # log and resore/save + parser.add_argument("--loss_print_every_n_iter", type=int, default=10, required=False, + help="print loss every n iteration") + parser.add_argument("--model_save_every_n_iter", type=int, default=10000, required=False, + help="save model every n iteration",) + parser.add_argument("--model_save_dir", type=str, + default="./output/model_save-{}".format(str(datetime.now().strftime("%Y-%m-%d-%H:%M:%S"))), + required=False, help="model save directory") + parser.add_argument("--save_last_snapshot", type=str2bool, default=False, required=False, + help="save model snapshot for last iteration") + parser.add_argument("--model_load_dir", type=str, default=None, help="model load directory") + parser.add_argument("--log_dir", type=str, default="./output", help="log info save directory") + + # bert backbone + parser.add_argument('--do_lower_case', type=str2bool, nargs='?', const=True, default='True') + parser.add_argument("--seq_length", type=int, default=512) + parser.add_argument("--max_predictions_per_seq", type=int, default=80) + parser.add_argument("--num_hidden_layers", type=int, default=12) + parser.add_argument("--num_attention_heads", type=int, default=12) + parser.add_argument("--max_position_embeddings", type=int, default=512) + parser.add_argument("--type_vocab_size", type=int, default=2) + parser.add_argument("--vocab_size", type=int, default=30522) + parser.add_argument("--attention_probs_dropout_prob", type=float, default=0.1) + parser.add_argument("--hidden_dropout_prob", type=float, default=0.1) + parser.add_argument("--hidden_size_per_head", type=int, default=64) + + return parser + + +def print_args(args): + print("=".ljust(66, "=")) + print("Running {}: num_gpu_per_node = {}, num_nodes = {}.".format( + args.model, args.gpu_num_per_node, args.num_nodes)) + print("=".ljust(66, "=")) + for arg in vars(args): + print("{} = {}".format(arg, getattr(args, arg))) + print("-".ljust(66, "-")) + print("Time stamp: {}".format( + str(datetime.now().strftime("%Y-%m-%d-%H:%M:%S")))) + + +if __name__ == '__main__': + parser = get_parser() + args = parser.parse_args() + print_args(args) diff --git a/NLPAlgo/MetaKD/convert_tf_ckpt_to_of.py b/NLPAlgo/MetaKD/convert_tf_ckpt_to_of.py new file mode 100644 index 0000000..edae13a --- /dev/null +++ b/NLPAlgo/MetaKD/convert_tf_ckpt_to_of.py @@ -0,0 +1,90 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +"""Convert tensorflow checkpoint to oneflow snapshot""" + +import re +import argparse +import tensorflow as tf +import numpy as np +import os + +parser = argparse.ArgumentParser() + +## Required parameters +parser.add_argument("--tf_checkpoint_path", + default = None, + type = str, + required = True, + help = "Path the TensorFlow checkpoint path.") +parser.add_argument("--of_dump_path", + default = None, + type = str, + required = True, + help = "Path to the output OneFlow model.") + +#args = parser.parse_args() +args, unknown = parser.parse_known_args() +print(args) + +# parse unknown arguments for extra weights +extra_weights = {} +for u in unknown: + w = u.split("=") + assert len(w) == 2 + if len(w) == 2: + extra_weights[w[0]] = float(w[1]) + + +def _write_blob(folder, blob): + os.makedirs(folder, exist_ok=True) + filename = os.path.join(folder, "out") + f = open(filename, 'wb') + f.write(blob.tobytes()) + f.close() + print(filename, blob.shape) + +def _SaveWeightBlob2File(blob, folder): + _write_blob(folder, blob) + + for weight, default_value in extra_weights.items(): + d = np.full_like(blob, default_value) + _write_blob(folder + weight, d) + +def convert(): + path = args.tf_checkpoint_path + init_vars = tf.train.list_variables(path) + for name, shape in init_vars: + array = tf.train.load_variable(path, name) + + sep = name.rfind('/') + blob_name = name[sep + 1:] + op_name = name[:sep].replace('/', '-') + + if blob_name == "kernel": + blob_name = "weight" + elif blob_name in ['adam_m', 'adam_v']: + print("find m, v weights") + + folder_name = op_name+"-"+blob_name + folder = os.path.join(args.of_dump_path, folder_name) + #print("saved to:", folder) + + _SaveWeightBlob2File(array, folder) + + +if __name__ == "__main__": + convert() + diff --git a/NLPAlgo/MetaKD/data_utils/task_processors1.py b/NLPAlgo/MetaKD/data_utils/task_processors1.py new file mode 100644 index 0000000..2d12244 --- /dev/null +++ b/NLPAlgo/MetaKD/data_utils/task_processors1.py @@ -0,0 +1,342 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 运行命令 +# 本文件目标:生成训练集各个domain class对应的prototype embedding,并计算每个样本的prototypical score +# python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + +""" +This file contains the logic for loading data for all tasks. +""" + +import csv +import sys +import uuid +import json +import os +import random +from abc import ABC, abstractmethod +from collections import defaultdict, Counter +from typing import List, Dict, Callable +from tqdm import tqdm +import struct +import six +import oneflow as flow +import oneflow.core.record.record_pb2 as ofrecord +import numpy as np +import log +# from pet import task_helpers +from data_utils.utils import domain_to_id, label_map +# from transformers import DataProcessor as TransDataProcessor + + +logger = log.get_logger('root') + + +def int32_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int32_list=ofrecord.Int32List(value=value)) + +def int64_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int64_list=ofrecord.Int64List(value=value)) + +def float_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(float_list=ofrecord.FloatList(value=value)) + +def double_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(double_list=ofrecord.DoubleList(value=value)) + +def bytes_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + if not six.PY2: + if isinstance(value[0], str): + value = [x.encode() for x in value] + return ofrecord.Feature(bytes_list=ofrecord.BytesList(value=value)) + + + +class InputExample(object): + """A single training/test example for simple sequence classification.""" + + def __init__(self, guid, text_a, text_b=None, label=None, domain=None): + """Constructs a InputExample. + + Args: + guid: Unique id for the example. + text_a: string. The untokenized text of the first sequence. For single + sequence tasks, only this sequence must be specified. + text_b: (Optional) string. The untokenized text of the second sequence. + Only must be specified for sequence pair tasks. + label: (Optional) string. The label of the example. This should be + specified for train and dev examples, but not for test examples. + """ + self.guid = guid + self.text_a = text_a + self.text_b = text_b + self.label = label + self.domain = domain + + +class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, idxs, input_ids, input_mask, segment_ids, weights, example, task_name='senti'): + self.idxs = idxs + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.domain = domain_to_id[task_name][example.domain] + self.label = label_map[task_name][example.label] + self.weights = weights + self.example = example + + +class DataProcessor(object): + """Base class for data converters for sequence classification data sets.""" + + def get_examples(self, data_dir): + """Gets a collection of `InputExample`s for the train set.""" + raise NotImplementedError() + + def get_labels(self): + """Gets the list of labels for this data set.""" + raise NotImplementedError() + + @classmethod + def _read_tsv(cls, input_file, quotechar=None): + """Reads a tab separated value file.""" + with open(input_file, "r", encoding="utf-8") as f: + reader = csv.reader(f, delimiter="\t", quotechar=quotechar) + lines = [] + for line in reader: + if sys.version_info[0] == 2: + line = list(unicode(cell, 'utf-8') for cell in line) + lines.append(line) + return lines + + +class MnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_examples(self, data_path, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_path, 'train.tsv')), "train", domain) + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type, domain=None): + """Creates examples for the training and dev sets.""" + examples = [] + cnt = 0 + domain_list = domain.split(",") if domain else None + for (i, line) in enumerate(lines): + if i == 0: + continue + if domain and line[3] not in domain_list: + continue + if cnt == 0: + print(line[0], line[1], line[2], line[3], line[8], line[9], line[-1]) + cnt += 1 + guid = line[2] + text_a = line[8] + text_b = line[9] + label = line[-1] + examples.append( + InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, domain=line[3])) + return examples + + +class SentiProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_examples(self, data_path, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_path, 'train.tsv')), "train", domain) + + def get_labels(self): + """See base class.""" + return ["positive", "negative"] + + def _create_examples(self, lines, set_type, genre=None): + """Creates examples for the training, dev and test sets.""" + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + if len(line) != 3: + import pdb + pdb.set_trace() + review, domain, sentiment = line + if genre and genre != "mix" and domain != genre: + continue + guid = uuid.uuid4() + text_a = review + examples.append(InputExample(guid=guid, text_a=text_a, text_b=None, label=sentiment, domain=domain)) + return examples + + + +def _truncate_seq_pair(tokens_a, tokens_b, max_length): + """Truncates a sequence pair in place to the maximum length.""" + while True: + total_length = len(tokens_a) + len(tokens_b) + if total_length <= max_length: + break + if len(tokens_a) > len(tokens_b): + tokens_a.pop() + else: + tokens_b.pop() + + +def convert_examples_to_features(args, examples, max_seq_length, tokenizer): + """Loads a data file into a list of `InputBatch`s.""" + + features = [] + for (ex_index, example) in enumerate(tqdm(examples)): + + tokens_a = tokenizer.tokenize(example.text_a) + + tokens_b = None + if example.text_b: + tokens_b = tokenizer.tokenize(example.text_b) + _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) + else: + if len(tokens_a) > max_seq_length - 2: + tokens_a = tokens_a[:(max_seq_length - 2)] + + tokens = ["[CLS]"] + tokens_a + ["[SEP]"] + segment_ids = [0] * len(tokens) + + if tokens_b: + tokens += tokens_b + ["[SEP]"] + segment_ids += [1] * (len(tokens_b) + 1) + + input_ids = tokenizer.convert_tokens_to_ids(tokens) + input_mask = [1] * len(input_ids) + seq_length = len(input_ids) + + padding = [0] * (max_seq_length - len(input_ids)) + input_ids += padding + input_mask += padding + segment_ids += padding + + assert len(input_ids) == max_seq_length + assert len(input_mask) == max_seq_length + assert len(segment_ids) == max_seq_length + + if ex_index < 1: + print("*** Example ***") + print("guid: %s" % (example.guid)) + print("tokens: %s" % " ".join( + [str(x) for x in tokens])) + print("input_ids: %s" % " ".join([str(x) for x in input_ids])) + print("input_mask: %s" % " ".join([str(x) for x in input_mask])) + print( + "segment_ids: %s" % " ".join([str(x) for x in segment_ids])) + print("label: {}".format(example.label)) + + features.append( + InputFeatures(idxs=ex_index, + input_ids=input_ids, + input_mask=input_mask, + segment_ids=segment_ids, + weights=1.0, + example=example, + task_name=args.task_name) + ) + return features + + + +def generate_dataset( + config, + data: List[InputExample], + tokenizer, + labelled: bool = True, + ofrecord_dir: str = None, + stage: str = 'train', + load_weight: bool = False # 是否为每个样本添加权重 +): + features = convert_examples_to_features(config, data, config.max_seq_length, tokenizer) # 将输入的样本(inputExample对象)进行转化为feature + # print('len(features)=', len(features)) + # print('===============train features================') + # print('len=', len(features)) + # print('feature 0:', features[0]) + # print('feature 1:', features[1]) + # print('===============================') + feature_dicts = [] # list(dict) + if not os.path.exists(os.path.join(ofrecord_dir, stage)): + os.makedirs(os.path.join(ofrecord_dir, stage)) + fw = open(os.path.join(ofrecord_dir, stage, stage + '.of_record-0'), 'wb') + + if load_weight: + ''' + sample_weight_dict = {idx: weight} + array([{0: 0.9915}, {1: 0.98956}, {2: 0.9723}, {3: 0.98445}, {4: 0.99237}, + ...... + {94: 0.96635}, {95: 0.98768}], dtype=object) + ''' + try: + sample_weight_dict = np.load(os.path.join(ofrecord_dir, 'train/weight.npy'), allow_pickle=True)[()] + weight_dict = dict() + for i in sample_weight_dict: + weight_dict.update(i) + except: + raise FileNotFoundError("Please run the preprocess.py to generate weight.npy at first") + + for f in features: + weight = [1.] + if load_weight: + assert f.idxs in weight_dict.keys() + weight = [weight_dict[f.idxs]] + # print('weight=', weight) + feature_dict = { + 'input_ids': int32_feature(f.input_ids), + 'input_mask': int32_feature(f.input_mask), + 'segment_ids': int32_feature(f.segment_ids), + 'domain': int32_feature(f.domain), # add by wjn + 'label': int32_feature(f.label), + 'idxs': int32_feature(f.idxs), + 'weights': float_feature(weight) + } + # feature_dict = { + # 'input_ids': int32_feature(f.input_ids), + # 'input_mask': int32_feature(f.input_mask), + # 'segment_ids': int32_feature(f.segment_ids), + # } + ofrecord_features = ofrecord.OFRecord(feature=feature_dict) # 调用 ofrecord.OFRecord 创建 OFRecord 对象 + serilizedBytes = ofrecord_features.SerializeToString() # 调用 OFRecord 对象的 SerializeToString 方法得到序列化结果 + length = ofrecord_features.ByteSize() + fw.write(struct.pack("q", length)) + fw.write(serilizedBytes) + feature_dicts.append(feature_dict) + fw.close() + return feature_dicts + + + +processors = { + "mnli": MnliProcessor, + "senti": SentiProcessor +} \ No newline at end of file diff --git a/NLPAlgo/MetaKD/data_utils/task_processors2.py b/NLPAlgo/MetaKD/data_utils/task_processors2.py new file mode 100644 index 0000000..6f5450a --- /dev/null +++ b/NLPAlgo/MetaKD/data_utils/task_processors2.py @@ -0,0 +1,429 @@ +import csv +import sys +import uuid +import json +import os +import random +from abc import ABC, abstractmethod +from collections import defaultdict, Counter +from typing import List, Dict, Callable +from tqdm import tqdm +import struct +import six +import oneflow as flow +import oneflow.core.record.record_pb2 as ofrecord +import numpy as np +import log +# from pet import task_helpers +from data_utils.utils import domain_to_id, label_map +# from transformers import DataProcessor as TransDataProcessor + + +logger = log.get_logger('root') + + +def int32_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int32_list=ofrecord.Int32List(value=value)) + +def int64_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int64_list=ofrecord.Int64List(value=value)) + +def float_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(float_list=ofrecord.FloatList(value=value)) + +def double_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(double_list=ofrecord.DoubleList(value=value)) + +def bytes_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + if not six.PY2: + if isinstance(value[0], str): + value = [x.encode() for x in value] + return ofrecord.Feature(bytes_list=ofrecord.BytesList(value=value)) + + + +class InputExample(object): + """A single training/test example for simple sequence classification.""" + + def __init__(self, guid, text_a, text_b=None, label=None, domain=None): + """Constructs a InputExample. + + Args: + guid: Unique id for the example. + text_a: string. The untokenized text of the first sequence. For single + sequence tasks, only this sequence must be specified. + text_b: (Optional) string. The untokenized text of the second sequence. + Only must be specified for sequence pair tasks. + label: (Optional) string. The label of the example. This should be + specified for train and dev examples, but not for test examples. + """ + self.guid = guid + self.text_a = text_a + self.text_b = text_b + self.label = label + self.domain = domain + + +class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, idxs, input_ids, input_mask, segment_ids, weights, example, task_name='senti'): + self.idxs = idxs + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.domain = domain_to_id[task_name][example.domain] + self.label = label_map[task_name][example.label] + self.weights = weights + self.example = example + + + +class DataProcessor(object): + """Base class for data converters for sequence classification data sets.""" + def __init__(self, portion=1.0): + self.data_portion = portion + + def get_train_examples(self, data_dir): + """Gets a collection of `InputExample`s for the train set.""" + raise NotImplementedError() + + def get_dev_examples(self, data_dir): + """Gets a collection of `InputExample`s for the dev set.""" + raise NotImplementedError() + + def get_test_examples(self, data_dir): + """Gets a collection of `InputExample`s for the test set.""" + raise NotImplementedError() + + def get_labels(self): + """Gets the list of labels for this data set.""" + raise NotImplementedError() + + @classmethod + def _read_tsv(cls, input_file, quotechar=None): + """Reads a tab separated value file.""" + with open(input_file, "r", encoding="utf-8") as f: + reader = csv.reader(f, delimiter="\t", quotechar=quotechar) + lines = [] + for line in reader: + if sys.version_info[0] == 2: + line = list(unicode(cell, 'utf-8') for cell in line) + lines.append(line) + return lines + + +class MnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_train_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "train_with_weights.tsv")), "train", domain) + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev_matched.tsv")), + "dev_matched", domain) + + def get_test_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "test_matched.tsv")), + "test_matched", domain) + + def get_aug_examples(self, data_dir, domain=None): + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "train_aug.tsv")), "aug", domain) + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type, select_domains=None): + """Creates examples for the training and dev sets.""" + examples = [] + cnt = 0 + domain_list = select_domains.split(",") if select_domains else None + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "%s-%s" % (set_type, line[0]) + if set_type == "train": + text_a = line[1] + text_b = line[2] + label = line[3] + domain = line[4] + weight = line[5] + if cnt == 0: + print(line) + cnt += 1 + else: + text_a = line[8] + text_b = line[9] + label = line[-1] + domain = line[3] + weight = 1.0 + if cnt == 0: + print(line[0], line[1], line[2], line[3], line[8], line[9], line[-1]) + cnt += 1 + if select_domains and domain not in domain_list: + continue + examples.append( + InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, domain=domain)) + + random.shuffle(examples) + if set_type == "train": + return examples[:int(len(examples) * self.data_portion)] + else: + return examples + + +class MnliMismatchedProcessor(MnliProcessor): + """Processor for the MultiNLI Mismatched data set (GLUE version).""" + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev_mismatched.tsv")), + "dev_matched", domain) + + + +class SentiProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_example_from_tensor_dict(self, tensor_dict): + """See base class.""" + return InputExample( + tensor_dict["idx"].numpy(), + tensor_dict["premise"].numpy().decode("utf-8"), + tensor_dict["hypothesis"].numpy().decode("utf-8"), + str(tensor_dict["label"].numpy()), + ) + + def get_train_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train", domain) + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev", domain) + + def get_test_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test", domain) + + def get_labels(self): + """See base class.""" + return ["positive", "negative"] + + def _create_examples(self, lines, set_type, select_domains=None): + """Creates examples for the training, dev and test sets.""" + examples = [] + cnt = 0 + domain_list = select_domains.split(",") if select_domains else None + for (i, line) in enumerate(lines): + if i == 0: + continue + # if set_type == "train": + # guid, text_a, _, sentiment, domain, weight = line + # if select_domains and domain not in domain_list: + # continue + # if cnt == 0: + # print(line) + # cnt += 1 + # else: + text_a, domain, sentiment = line + if select_domains and domain not in domain_list: + continue + guid = "%s-%s" % (set_type, line[0]) + weight = 1.0 + examples.append( + InputExample(guid=guid, text_a=text_a, text_b=None, label=sentiment, domain=domain)) + return examples + + +def convert_examples_to_features(args, examples, max_seq_length, tokenizer, output_mode: str='classification'): + """Loads a data file into a list of `InputBatch`s.""" + + features = [] + for (ex_index, example) in enumerate(tqdm(examples)): + if ex_index % 10000 == 0: + logger.info("Writing example %d of %d" % (ex_index, len(examples))) + + tokens_a = tokenizer.tokenize(example.text_a) + + tokens_b = None + if example.text_b: + tokens_b = tokenizer.tokenize(example.text_b) + _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) + else: + if len(tokens_a) > max_seq_length - 2: + tokens_a = tokens_a[:(max_seq_length - 2)] + + tokens = ["[CLS]"] + tokens_a + ["[SEP]"] + segment_ids = [0] * len(tokens) + + if tokens_b: + tokens += tokens_b + ["[SEP]"] + segment_ids += [1] * (len(tokens_b) + 1) + + input_ids = tokenizer.convert_tokens_to_ids(tokens) + input_mask = [1] * len(input_ids) + seq_length = len(input_ids) + + padding = [0] * (max_seq_length - len(input_ids)) + input_ids += padding + input_mask += padding + segment_ids += padding + + assert len(input_ids) == max_seq_length + assert len(input_mask) == max_seq_length + assert len(segment_ids) == max_seq_length + + if output_mode == "classification": + label_id = label_map[args.task_name][example.label] + elif output_mode == "regression": + label_id = float(example.label) + else: + raise KeyError(output_mode) + + domain_id = domain_to_id[args.task_name][example.domain] + + if ex_index < 1: + logger.info("*** Example ***") + logger.info("guid: %s" % (example.guid)) + logger.info("tokens: %s" % " ".join( + [str(x) for x in tokens])) + logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids])) + logger.info("input_mask: %s" % " ".join([str(x) for x in input_mask])) + logger.info( + "segment_ids: %s" % " ".join([str(x) for x in segment_ids])) + logger.info("label: {}".format(example.label)) + logger.info("label_id: {}".format(label_id)) + logger.info("domain_id: {}".format(domain_id)) + + features.append( + InputFeatures(idxs=ex_index, + input_ids=input_ids, + input_mask=input_mask, + segment_ids=segment_ids, + weights=1.0, + example=example, + task_name=args.task_name) + ) + ''' + class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, idxs, input_ids, input_mask, segment_ids, weights, example, task_name='senti'): + self.idxs = idxs + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.domain = domain_to_id[task_name][example.domain] + self.label = label_map[task_name][example.label] + self.weights = weights + self.example = example + ''' + return features + + + +def _truncate_seq_pair(tokens_a, tokens_b, max_length): + """Truncates a sequence pair in place to the maximum length.""" + while True: + total_length = len(tokens_a) + len(tokens_b) + if total_length <= max_length: + break + if len(tokens_a) > len(tokens_b): + tokens_a.pop() + else: + tokens_b.pop() + + + + +def generate_dataset( + config, + data: List[InputExample], + tokenizer, + labelled: bool = True, + ofrecord_dir: str = None, + stage: str = 'train', + load_weight: bool = False # 是否为每个样本添加权重 +): + features = convert_examples_to_features(config, data, config.max_seq_length, tokenizer) # 将输入的样本(inputExample对象)进行转化为feature + # print('len(features)=', len(features)) + # print('===============train features================') + # print('len=', len(features)) + # print('feature 0:', features[0]) + # print('feature 1:', features[1]) + # print('===============================') + feature_dicts = [] # list(dict) + if not os.path.exists(os.path.join(ofrecord_dir, stage)): + os.makedirs(os.path.join(ofrecord_dir, stage)) + fw = open(os.path.join(ofrecord_dir, stage, stage + '.of_record-0'), 'wb') + + if load_weight: + ''' + sample_weight_dict = {idx: weight} + array([{0: 0.9915}, {1: 0.98956}, {2: 0.9723}, {3: 0.98445}, {4: 0.99237}, + ...... + {94: 0.96635}, {95: 0.98768}], dtype=object) + ''' + try: + sample_weight_dict = np.load(os.path.join(ofrecord_dir, 'train/weight.npy'), allow_pickle=True)[()] + weight_dict = dict() + for i in sample_weight_dict: + weight_dict.update(i) + except: + raise FileNotFoundError("Please run the preprocess.py to generate weight.npy at first") + + for f in features: + weight = [1.] + if load_weight: + assert f.idxs in weight_dict.keys() + weight = [weight_dict[f.idxs]] + # print('weight=', weight) + feature_dict = { + 'input_ids': int32_feature(f.input_ids), + 'input_mask': int32_feature(f.input_mask), + 'segment_ids': int32_feature(f.segment_ids), + 'domain': int32_feature(f.domain), # add by wjn + 'label': int32_feature(f.label), + 'idxs': int32_feature(f.idxs), + 'weights': float_feature(weight) + } + # feature_dict = { + # 'input_ids': int32_feature(f.input_ids), + # 'input_mask': int32_feature(f.input_mask), + # 'segment_ids': int32_feature(f.segment_ids), + # } + ofrecord_features = ofrecord.OFRecord(feature=feature_dict) # 调用 ofrecord.OFRecord 创建 OFRecord 对象 + serilizedBytes = ofrecord_features.SerializeToString() # 调用 OFRecord 对象的 SerializeToString 方法得到序列化结果 + length = ofrecord_features.ByteSize() + fw.write(struct.pack("q", length)) + fw.write(serilizedBytes) + feature_dicts.append(feature_dict) + fw.close() + return feature_dicts + + + +processors = { + "mnli": MnliProcessor, + "senti": SentiProcessor +} \ No newline at end of file diff --git a/NLPAlgo/MetaKD/data_utils/task_processors3.py b/NLPAlgo/MetaKD/data_utils/task_processors3.py new file mode 100644 index 0000000..5a6ef8c --- /dev/null +++ b/NLPAlgo/MetaKD/data_utils/task_processors3.py @@ -0,0 +1,446 @@ +import csv +import sys +import uuid +import json +import os +import random +from abc import ABC, abstractmethod +from collections import defaultdict, Counter +from typing import List, Dict, Callable +from tqdm import tqdm +import struct +import six +import oneflow as flow +import oneflow.core.record.record_pb2 as ofrecord +import numpy as np +import log +# from pet import task_helpers +from data_utils.utils import domain_to_id, label_map +# from transformers import DataProcessor as TransDataProcessor + + +logger = log.get_logger('root') + + +def int32_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int32_list=ofrecord.Int32List(value=value)) + +def int64_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(int64_list=ofrecord.Int64List(value=value)) + +def float_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(float_list=ofrecord.FloatList(value=value)) + +def double_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + return ofrecord.Feature(double_list=ofrecord.DoubleList(value=value)) + +def bytes_feature(value): + if not isinstance(value, (list, tuple)): + value = [value] + if not six.PY2: + if isinstance(value[0], str): + value = [x.encode() for x in value] + return ofrecord.Feature(bytes_list=ofrecord.BytesList(value=value)) + + + +class InputExample(object): + """A single training/test example for simple sequence classification.""" + + def __init__(self, guid, text_a, text_b=None, label=None, domain=None): + """Constructs a InputExample. + + Args: + guid: Unique id for the example. + text_a: string. The untokenized text of the first sequence. For single + sequence tasks, only this sequence must be specified. + text_b: (Optional) string. The untokenized text of the second sequence. + Only must be specified for sequence pair tasks. + label: (Optional) string. The label of the example. This should be + specified for train and dev examples, but not for test examples. + """ + self.guid = guid + self.text_a = text_a + self.text_b = text_b + self.label = label + self.domain = domain + + +class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, idxs, input_ids, input_mask, segment_ids, weights, example, task_name='senti'): + self.idxs = idxs + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.domain = domain_to_id[task_name][example.domain] + self.label = label_map[task_name][example.label] + self.weights = weights + self.example = example + + + +class DataProcessor(object): + """Base class for data converters for sequence classification data sets.""" + def __init__(self, portion=1.0): + self.data_portion = portion + + def get_train_examples(self, data_dir): + """Gets a collection of `InputExample`s for the train set.""" + raise NotImplementedError() + + def get_dev_examples(self, data_dir): + """Gets a collection of `InputExample`s for the dev set.""" + raise NotImplementedError() + + def get_test_examples(self, data_dir): + """Gets a collection of `InputExample`s for the test set.""" + raise NotImplementedError() + + def get_labels(self): + """Gets the list of labels for this data set.""" + raise NotImplementedError() + + @classmethod + def _read_tsv(cls, input_file, quotechar=None): + """Reads a tab separated value file.""" + with open(input_file, "r", encoding="utf-8") as f: + reader = csv.reader(f, delimiter="\t", quotechar=quotechar) + lines = [] + for line in reader: + if sys.version_info[0] == 2: + line = list(unicode(cell, 'utf-8') for cell in line) + lines.append(line) + return lines + + +class MnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_train_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "train_with_weights.tsv")), "train", domain) + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev_matched.tsv")), + "dev_matched", domain) + + def get_test_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "test_matched.tsv")), + "test_matched", domain) + + def get_aug_examples(self, data_dir, domain=None): + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "train_aug.tsv")), "aug", domain) + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type, select_domains=None): + """Creates examples for the training and dev sets.""" + examples = [] + cnt = 0 + domain_list = select_domains.split(",") if select_domains else None + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "%s-%s" % (set_type, line[0]) + if set_type == "train": + text_a = line[1] + text_b = line[2] + label = line[3] + domain = line[4] + weight = line[5] + if cnt == 0: + print(line) + cnt += 1 + else: + text_a = line[8] + text_b = line[9] + label = line[-1] + domain = line[3] + weight = 1.0 + if cnt == 0: + print(line[0], line[1], line[2], line[3], line[8], line[9], line[-1]) + cnt += 1 + if select_domains and domain not in domain_list: + continue + examples.append( + InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label, domain=domain)) + + random.shuffle(examples) + if set_type == "train": + return examples[:int(len(examples) * self.data_portion)] + else: + return examples + + +class MnliMismatchedProcessor(MnliProcessor): + """Processor for the MultiNLI Mismatched data set (GLUE version).""" + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev_mismatched.tsv")), + "dev_matched", domain) + + + +class SentiProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_example_from_tensor_dict(self, tensor_dict): + """See base class.""" + return InputExample( + tensor_dict["idx"].numpy(), + tensor_dict["premise"].numpy().decode("utf-8"), + tensor_dict["hypothesis"].numpy().decode("utf-8"), + str(tensor_dict["label"].numpy()), + ) + + def get_train_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train", domain) + + def get_dev_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev", domain) + + def get_test_examples(self, data_dir, domain=None): + """See base class.""" + return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test", domain) + + def get_labels(self): + """See base class.""" + return ["positive", "negative"] + + def _create_examples(self, lines, set_type, select_domains=None): + """Creates examples for the training, dev and test sets.""" + examples = [] + cnt = 0 + domain_list = select_domains.split(",") if select_domains else None + for (i, line) in enumerate(lines): + if i == 0: + continue + # if set_type == "train": + # guid, text_a, _, sentiment, domain, weight = line + # if select_domains and domain not in domain_list: + # continue + # if cnt == 0: + # print(line) + # cnt += 1 + # else: + text_a, domain, sentiment = line + if select_domains and domain not in domain_list: + continue + guid = "%s-%s" % (set_type, line[0]) + weight = 1.0 + examples.append( + InputExample(guid=guid, text_a=text_a, text_b=None, label=sentiment, domain=domain)) + return examples + + +def convert_examples_to_features(args, examples, max_seq_length, tokenizer, output_mode: str='classification'): + """Loads a data file into a list of `InputBatch`s.""" + + features = [] + for (ex_index, example) in enumerate(tqdm(examples)): + if ex_index % 10000 == 0: + logger.info("Writing example %d of %d" % (ex_index, len(examples))) + + tokens_a = tokenizer.tokenize(example.text_a) + + tokens_b = None + if example.text_b: + tokens_b = tokenizer.tokenize(example.text_b) + _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) + else: + if len(tokens_a) > max_seq_length - 2: + tokens_a = tokens_a[:(max_seq_length - 2)] + + tokens = ["[CLS]"] + tokens_a + ["[SEP]"] + segment_ids = [0] * len(tokens) + + if tokens_b: + tokens += tokens_b + ["[SEP]"] + segment_ids += [1] * (len(tokens_b) + 1) + + input_ids = tokenizer.convert_tokens_to_ids(tokens) + input_mask = [1] * len(input_ids) + seq_length = len(input_ids) + + padding = [0] * (max_seq_length - len(input_ids)) + input_ids += padding + input_mask += padding + segment_ids += padding + + assert len(input_ids) == max_seq_length + assert len(input_mask) == max_seq_length + assert len(segment_ids) == max_seq_length + + if output_mode == "classification": + label_id = label_map[args.task_name][example.label] + elif output_mode == "regression": + label_id = float(example.label) + else: + raise KeyError(output_mode) + + domain_id = domain_to_id[args.task_name][example.domain] + + if ex_index < 1: + logger.info("*** Example ***") + logger.info("guid: %s" % (example.guid)) + logger.info("tokens: %s" % " ".join( + [str(x) for x in tokens])) + logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids])) + logger.info("input_mask: %s" % " ".join([str(x) for x in input_mask])) + logger.info( + "segment_ids: %s" % " ".join([str(x) for x in segment_ids])) + logger.info("label: {}".format(example.label)) + logger.info("label_id: {}".format(label_id)) + logger.info("domain_id: {}".format(domain_id)) + + features.append( + InputFeatures(idxs=ex_index, + input_ids=input_ids, + input_mask=input_mask, + segment_ids=segment_ids, + weights=1.0, + example=example, + task_name=args.task_name) + ) + ''' + class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, idxs, input_ids, input_mask, segment_ids, weights, example, task_name='senti'): + self.idxs = idxs + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.domain = domain_to_id[task_name][example.domain] + self.label = label_map[task_name][example.label] + self.weights = weights + self.example = example + ''' + return features + + + +def _truncate_seq_pair(tokens_a, tokens_b, max_length): + """Truncates a sequence pair in place to the maximum length.""" + while True: + total_length = len(tokens_a) + len(tokens_b) + if total_length <= max_length: + break + if len(tokens_a) > len(tokens_b): + tokens_a.pop() + else: + tokens_b.pop() + + + + +def generate_dataset( + config, + data: List[InputExample], + tokenizer, + labelled: bool = True, + ofrecord_dir: str = None, + stage: str = 'train', + load_weight: bool = False # 是否为每个样本添加权重 +): + features = convert_examples_to_features(config, data, config.max_seq_length, tokenizer) # 将输入的样本(inputExample对象)进行转化为feature + # print('len(features)=', len(features)) + # print('===============train features================') + # print('len=', len(features)) + # print('feature 0:', features[0]) + # print('feature 1:', features[1]) + # print('===============================') + feature_dicts = [] # list(dict) + if not os.path.exists(os.path.join(ofrecord_dir, stage)): + os.makedirs(os.path.join(ofrecord_dir, stage)) + fw = open(os.path.join(ofrecord_dir, stage, stage + '.of_record-0'), 'wb') + + if load_weight: + ''' + sample_weight_dict = {idx: weight} + array([{0: 0.9915}, {1: 0.98956}, {2: 0.9723}, {3: 0.98445}, {4: 0.99237}, + ...... + {94: 0.96635}, {95: 0.98768}], dtype=object) + ''' + try: + sample_to_blob = np.load(os.path.join(ofrecord_dir, 'train/sample_to_blob.npy'), allow_pickle=True)[()] + # print('len(sample_to_blob)=', sample_to_blob) + sample_weight_dict = np.load(os.path.join(ofrecord_dir, 'train/weight.npy'), allow_pickle=True)[()] + # print('len(sample_weight_dict)=', sample_weight_dict) + weight_dict = dict() + for i in sample_weight_dict: + weight_dict.update(i) + except: + raise FileNotFoundError("Please run the preprocess.py to generate weight.npy at first") + + for f in features: + weight = [1.] + logit_blob = [0.] + all_attention_scores_blob = [0.] + pooled_output = [0.] + if load_weight: + # 加载每个样本对应meta-teacher的各个参数 + assert f.idxs in sample_to_blob.keys() + # [logit_blob[ei], all_attention_scores_blob[ei], pooled_output[ei]] + logit_blob = sample_to_blob[f.idxs][0] + all_attention_scores_blob = sample_to_blob[f.idxs][1].tolist() + all_attention_scores_blob = np.array(all_attention_scores_blob).reshape(-1).tolist() + pooled_output = sample_to_blob[f.idxs][2] + pooled_output = np.array(pooled_output).reshape(-1).tolist() + weight = [weight_dict[f.idxs]] + # print('weight=', weight) + # print('isinstance(all_attention_scores_blob, (list, tuple))=', isinstance(all_attention_scores_blob, (list, tuple))) + feature_dict = { + 'input_ids': int32_feature(f.input_ids), + 'input_mask': int32_feature(f.input_mask), + 'segment_ids': int32_feature(f.segment_ids), + 'domain': int32_feature(f.domain), # add by wjn + 'label': int32_feature(f.label), + 'idxs': int32_feature(f.idxs), + 'weights': float_feature(weight), + 'logit_blob': float_feature(logit_blob), + 'all_attention_scores_blob': float_feature(all_attention_scores_blob), + 'pooled_output': float_feature(pooled_output), + } + # feature_dict = { + # 'input_ids': int32_feature(f.input_ids), + # 'input_mask': int32_feature(f.input_mask), + # 'segment_ids': int32_feature(f.segment_ids), + # } + ofrecord_features = ofrecord.OFRecord(feature=feature_dict) # 调用 ofrecord.OFRecord 创建 OFRecord 对象 + serilizedBytes = ofrecord_features.SerializeToString() # 调用 OFRecord 对象的 SerializeToString 方法得到序列化结果 + length = ofrecord_features.ByteSize() + fw.write(struct.pack("q", length)) + fw.write(serilizedBytes) + feature_dicts.append(feature_dict) + fw.close() + return feature_dicts + + + +processors = { + "mnli": MnliProcessor, + "senti": SentiProcessor +} \ No newline at end of file diff --git a/NLPAlgo/MetaKD/data_utils/utils.py b/NLPAlgo/MetaKD/data_utils/utils.py new file mode 100644 index 0000000..1d3825c --- /dev/null +++ b/NLPAlgo/MetaKD/data_utils/utils.py @@ -0,0 +1,337 @@ +# # Licensed under the Apache License, Version 2.0 (the "License"); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an "AS IS" BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. + +domain_list = { + "mnli": ['fiction', 'government', 'slate', 'telephone', 'travel'], + "senti": ['books', 'dvd', 'electronics', 'kitchen'], +} + +domain_to_id = { + "mnli": { + 'fiction': 0, + 'government': 1, + 'slate': 2, + 'telephone': 3, + 'travel': 4, + }, + "senti": { + 'books': 0, + 'dvd': 1, + 'electronics': 2, + 'kitchen': 3, + } +} + +label_map = { + "mnli": {}, + "senti": {'positive': 0, 'negative': 1}, +} + + +# +# class LogitsList: +# """A list of logits obtained from a finetuned PET model""" +# +# def __init__(self, score: float, logits: List[List[float]]): +# """ +# Create a new LogitsList. +# :param score: the corresponding PET model's score on the training set +# :param logits: the list of logits, where ``logits[i][j]`` is the score for label ``j`` at example ``i`` +# """ +# self.score = score +# self.logits = logits +# +# def __repr__(self): +# return 'LogitsList(score={}, logits[:2]={})'.format(self.score, self.logits[:2]) +# +# def save(self, path: str) -> None: +# """Save this list to a file.""" +# with open(path, 'w') as fh: +# fh.write(str(self.score) + '\n') +# for example_logits in self.logits: +# fh.write(' '.join(str(logit) for logit in example_logits) + '\n') +# +# @staticmethod +# def load(path: str, with_score: bool = True) -> 'LogitsList': +# """Load a list from a file""" +# score = -1 +# logits = [] +# with open(path, 'r') as fh: +# for line_idx, line in enumerate(fh.readlines()): +# line = line.rstrip('\n') +# if line_idx == 0 and with_score: +# score = float(line) +# else: +# logits.append([float(x) for x in line.split()]) +# return LogitsList(score=score, logits=logits) +# +# +# class InputExample(object): +# """A raw input example consisting of one or two segments of text and a label""" +# +# def __init__(self, guid, text_a, text_b=None, task=None, label=None, logits=None, meta: Optional[Dict] = None, idx=-1): +# """ +# Create a new InputExample. +# :param guid: a unique textual identifier +# :param text_a: the sequence of text +# :param text_b: an optional, second sequence of text +# :param task: the corresponding task name of the current example # add by wjn +# :param label: an optional label +# :param logits: an optional list of per-class logits +# :param meta: an optional dictionary to store arbitrary meta information +# :param idx: an optional numeric index +# """ +# self.guid = guid +# self.text_a = text_a +# self.text_b = text_b +# self.task = task # add by wjn +# self.label = label +# self.logits = logits +# self.idx = idx +# self.meta = meta if meta else {} +# +# def __repr__(self): +# return str(self.to_json_string()) +# +# def to_dict(self): +# """Serialize this instance to a Python dictionary.""" +# output = copy.deepcopy(self.__dict__) +# return output +# +# def to_json_string(self): +# """Serialize this instance to a JSON string.""" +# return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" +# +# @staticmethod +# def load_examples(path: str) -> List['InputExample']: +# """Load a set of input examples from a file""" +# with open(path, 'rb') as fh: +# return pickle.load(fh) +# +# @staticmethod +# def save_examples(examples: List['InputExample'], path: str) -> None: +# """Save a set of input examples to a file""" +# with open(path, 'wb') as fh: +# pickle.dump(examples, fh) +# +# +# class InputFeatures(object): +# """A set of numeric features obtained from an :class:`InputExample`""" +# +# def __init__(self, input_ids, attention_mask, token_type_ids, task: int, label, +# logits=None, meta: Optional[Dict] = None, idx=-1): +# """ +# Create new InputFeatures. +# :param input_ids: the input ids corresponding to the original text or text sequence +# :param attention_mask: an attention mask, with 0 = no attention, 1 = attention +# :param token_type_ids: segment ids as used by BERT +# :param task: The corresponding task id of the current example # add by wjn +# :param label: the label +# :param logits: an optional sequence of per-class logits +# :param meta: an optional dictionary to store arbitrary meta information +# :param idx: an optional numeric index +# """ +# self.input_ids = input_ids +# self.attention_mask = attention_mask +# self.token_type_ids = token_type_ids +# self.task = task # add by wjn +# self.label: int = label +# self.logits = logits +# self.idx = idx +# self.meta = meta if meta else {} +# +# def __repr__(self): +# return str(self.to_json_string()) +# +# def pretty_print(self, tokenizer): +# return f'input_ids = {tokenizer.convert_ids_to_tokens(self.input_ids)}\n' + \ +# f'attention_mask = {self.attention_mask}\n' + \ +# f'token_type_ids = {self.token_type_ids}\n' + \ +# f'logits = {self.logits}\n' + \ +# f'label = {self.label}\n' + \ +# f'block_flag = {self.block_flag}' +# +# def to_dict(self): +# """Serialize this instance to a Python dictionary.""" +# output = copy.deepcopy(self.__dict__) +# return output +# +# def to_json_string(self): +# """Serialize this instance to a JSON string.""" +# return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" +# +# +# class PLMInputFeatures(InputFeatures): +# """A set of numeric input features for a model pretrained with a permuted language modeling objective.""" +# +# def __init__(self, *_, perm_mask, target_mapping, **kwargs): +# super().__init__(**kwargs) +# self.perm_mask = perm_mask +# self.target_mapping = target_mapping +# +# def pretty_print(self, tokenizer): +# return super().pretty_print(tokenizer) + '\n' + \ +# f'perm_mask = {self.perm_mask}\n' + \ +# f'target_mapping = {self.target_mapping}' +# +# +# +# # def set_seed(seed: int): +# # """ Set RNG seeds for python's `random` module, numpy and torch""" +# # random.seed(seed) +# # np.random.seed(seed) +# # torch.manual_seed(seed) +# # if torch.cuda.is_available(): +# # torch.cuda.manual_seed_all(seed) +# +# +# def eq_div(N, i): +# """ Equally divide N examples among i buckets. For example, `eq_div(12,3) = [4,4,4]`. """ +# return [] if i <= 0 else [N // i + 1] * (N % i) + [N // i] * (i - N % i) +# +# +# def chunks(lst, n): +# """Yield successive n-sized chunks from lst.""" +# for i in range(0, len(lst), n): +# yield lst[i:i + n] +# +# +# def remove_final_punc(s: str): +# """Remove the last character from a string if it is some form of punctuation""" +# return s.rstrip(string.punctuation) +# +# +# def lowercase_first(s: str): +# """Lowercase the first letter of a string""" +# return s[0].lower() + s[1:] +# +# +# def save_logits(path: str, logits: np.ndarray): +# """Save an array of logits to a file""" +# with open(path, 'w') as fh: +# for example_logits in logits: +# fh.write(' '.join(str(logit) for logit in example_logits) + '\n') +# pass +# +# +# def save_predictions(path: str, wrapper, results: Dict): +# """Save a sequence of predictions to a file""" +# predictions_with_idx = [] +# +# if wrapper.task_helper and wrapper.task_helper.output: +# predictions_with_idx = wrapper.task_helper.output +# else: +# inv_label_map = {idx: label for label, idx in wrapper.preprocessor.label_map.items()} +# for idx, prediction_idx in zip(results['indices'], results['predictions']): +# prediction = inv_label_map[prediction_idx] +# idx = idx.tolist() if isinstance(idx, np.ndarray) else int(idx) +# predictions_with_idx.append({'idx': idx, 'label': prediction}) +# +# with open(path, 'w', encoding='utf8') as fh: +# for line in predictions_with_idx: +# fh.write(json.dumps(line) + '\n') +# +# +# def softmax(x, temperature=1.0, axis=None): +# """Custom softmax implementation""" +# y = np.atleast_2d(x) +# +# if axis is None: +# axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1) +# +# y = y * float(temperature) +# y = y - np.expand_dims(np.max(y, axis=axis), axis) +# y = np.exp(y) +# +# ax_sum = np.expand_dims(np.sum(y, axis=axis), axis) +# p = y / ax_sum +# +# if len(x.shape) == 1: +# p = p.flatten() +# return p +# +# +# def get_verbalization_ids(word: str, tokenizer, force_single_token: bool) -> Union[int, List[int]]: +# """ +# Get the token ids corresponding to a verbalization +# :param word: the verbalization +# :param tokenizer: the tokenizer to use +# :param force_single_token: whether it should be enforced that the verbalization corresponds to a single token. +# If set to true, this method returns a single int instead of a list and throws an error if the word +# corresponds to multiple tokens. +# :return: either the list of token ids or the single token id corresponding to this word +# """ +# # kwargs = {'add_prefix_space': True} if isinstance(tokenizer, GPT2Tokenizer) else {} +# ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(word)) +# if not force_single_token: +# return ids +# assert len(ids) == 1, \ +# f'Verbalization "{word}" does not correspond to a single token, got {tokenizer.convert_ids_to_tokens(ids)}' +# verbalization_id = ids[0] +# # assert verbalization_id not in tokenizer.all_special_ids, \ +# # f'Verbalization {word} is mapped to a special token {tokenizer.convert_ids_to_tokens(verbalization_id)}' +# return verbalization_id +# +# +# # def trim_input_ids(input_ids, pad_token_id, mask_token_id, num_masks: int): +# # """ +# # Trim a sequence of input ids by removing all padding tokens and keeping at most a specific number of mask tokens. +# # :param input_ids: the sequence of input token ids +# # :param pad_token_id: the id of the pad token +# # :param mask_token_id: the id of the mask tokens +# # :param num_masks: the number of masks to keeps +# # :return: the trimmed sequence of input ids +# # """ +# # assert input_ids.shape[0] == 1 +# # input_ids_without_pad = [x for x in input_ids[0] if x != pad_token_id] +# # +# # trimmed_input_ids = [] +# # mask_count = 0 +# # for input_id in input_ids_without_pad: +# # if input_id == mask_token_id: +# # if mask_count >= num_masks: +# # continue +# # mask_count += 1 +# # trimmed_input_ids.append(input_id) +# # +# # return torch.tensor([trimmed_input_ids], dtype=torch.long, device=input_ids.device) +# +# +# def exact_match(predictions: np.ndarray, actuals: np.ndarray, question_ids: np.ndarray): +# """Compute the exact match (EM) for a sequence of predictions and actual labels""" +# unique_questions = set(question_ids) +# +# q_actuals = list(zip(question_ids, actuals)) +# q_predictions = list(zip(question_ids, predictions)) +# +# actuals_per_question = defaultdict(list) +# predictions_per_question = defaultdict(list) +# +# for qid, val in q_actuals: +# actuals_per_question[qid].append(val) +# for qid, val in q_predictions: +# predictions_per_question[qid].append(val) +# +# em = 0 +# for qid in unique_questions: +# if actuals_per_question[qid] == predictions_per_question[qid]: +# em += 1 +# em /= len(unique_questions) +# +# return em +# +# +# # def distillation_loss(predictions, targets, temperature): +# # """Compute the distillation loss (KL divergence between predictions and targets) as described in the PET paper""" +# # p = F.log_softmax(predictions / temperature, dim=1) +# # q = F.softmax(targets / temperature, dim=1) +# # return F.kl_div(p, q, reduction='sum') * (temperature ** 2) / predictions.shape[0] \ No newline at end of file diff --git a/NLPAlgo/MetaKD/finetuning.py b/NLPAlgo/MetaKD/finetuning.py new file mode 100644 index 0000000..0d70f98 --- /dev/null +++ b/NLPAlgo/MetaKD/finetuning.py @@ -0,0 +1,293 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令: + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MFTBERT, BERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from data_utils.data_process import Preprocessor +from data_utils.task_processors1 import PROCESSORS, load_examples, DEV32_SET, TRAIN_SET, DEV_SET, TEST_SET, METRICS, DEFAULT_METRICS +from data_utils.data_process import domain_list, class_list, task_to_id +from data_utils.generate_feature import generate_dataset +import scipy.spatial.distance as distance +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='sst-2', choices=['sst-2', 'mr', 'cr']) +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--train_example_num", type=int, default=88614, + help="example number in dataset") +parser.add_argument("--batch_size_per_device", type=int, default=2) +parser.add_argument("--train_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument("--dev_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--dev_batch_size_per_device", type=int, default=2) +parser.add_argument("--dev_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--dev_every_step_num", type=int, default=10, + help="") +parser.add_argument("--eval_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--eval_batch_size_per_device", type=int, default=2) +parser.add_argument("--eval_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument('--seed', type=int, default=42, + help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +dev_batch_size = args.num_nodes * args.gpu_num_per_node * args.dev_batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +num_dev_steps = math.ceil(args.dev_example_num / dev_batch_size) +num_eval_steps = math.ceil(args.dev_example_num / eval_batch_size) +args.iter_num = epoch_size * args.num_epochs +configs.print_args(args) + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("attention_masks", [seq_length]) + _blob_conf("token_type_ids", [seq_length]) + # _blob_conf("tasks", [1]) + _blob_conf("labels", [1]) + _blob_conf("logits", [1]) + _blob_conf("idxs", [1]) + # _blob_conf("weights", [1], dtype=flow.float32) + # print('blob_confs=', blob_confs['input_ids'].shape) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = BERT( + decoders['input_ids'], + decoders['attention_masks'], + decoders['token_type_ids'], + decoders['labels'], + args.vocab_size, + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + ) + return loss, logits, decoders['labels'] + + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + # return loss + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalDevJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.dev_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.eval_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'eval'), + args.eval_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + +def main(): + processor = PROCESSORS[args.task_name](args.task_name) + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + preprocessor = Preprocessor(args, tokenizer, args.seed) + label_map = preprocessor.label_map # class2id + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_data = load_examples( + args.task_name, args.data_dir, TRAIN_SET, num_examples=-1, num_examples_per_label=None) + print('===============train examples================') + print('len=', len(train_data)) + print('example 0:', train_data[0]) + print('example 1:', train_data[1]) + print('===============================') + eval_data = load_examples( + args.task_name, args.data_dir, TEST_SET, num_examples=-1, num_examples_per_label=None) + dev_data = load_examples( + args.task_name, args.data_dir, DEV_SET, num_examples=-1, num_examples_per_label=None) + train_feature_dict = generate_dataset(args, train_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='train') + eval_feature_dict = generate_dataset(args, eval_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='eval') + dev_feature_dict = generate_dataset(args, dev_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='dev') + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + print("starting fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.dev_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalDevJob, + num_steps=num_dev_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + print('saving model ...') + snapshot.save("best_ft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + + print("best dev acc: {}".format(best_dev_acc)) + + print("starting testing ...") + + # 将finetuning最优模型重新加载 + snapshot.load(os.path.join(snapshot._model_save_dir, "snapshot_best_ft_model_{}_dev_{}".format(args.task_name, best_dev_acc))) + + run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + desc='eval') + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/images/fine_tuning.png b/NLPAlgo/MetaKD/images/fine_tuning.png new file mode 100644 index 0000000000000000000000000000000000000000..fbcabce25bca6f540dd54a0aa10987af9598a51a GIT binary patch literal 59560 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
    Eb(Cs^^ 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaKD/images/meta_fine_tuning.png b/NLPAlgo/MetaKD/images/meta_fine_tuning.png new file mode 100644 index 0000000000000000000000000000000000000000..fbcabce25bca6f540dd54a0aa10987af9598a51a GIT binary patch literal 59560 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
    Eb(Cs^^ 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 literal 0 HcmV?d00001 diff --git a/NLPAlgo/MetaKD/log.py b/NLPAlgo/MetaKD/log.py new file mode 100644 index 0000000..d702616 --- /dev/null +++ b/NLPAlgo/MetaKD/log.py @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This file contains basic logging logic. +""" +import logging + +names = set() + + +def __setup_custom_logger(name: str) -> logging.Logger: + root_logger = logging.getLogger() + root_logger.handlers.clear() + + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') + + names.add(name) + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + ## TODO + file_handler = logging.FileHandler(name + ".txt") + file_handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + logger.addHandler(handler) + + ## TODO + logger.addHandler(file_handler) + + return logger + + +def get_logger(name: str) -> logging.Logger: + if name in names: + return logging.getLogger(name) + else: + return __setup_custom_logger(name) diff --git a/NLPAlgo/MetaKD/meta_distill.py b/NLPAlgo/MetaKD/meta_distill.py new file mode 100644 index 0000000..a9c08a5 --- /dev/null +++ b/NLPAlgo/MetaKD/meta_distill.py @@ -0,0 +1,288 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令:python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MetaStudentBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +# from data_utils.data_process import Preprocessor +from data_utils.task_processors3 import processors, convert_examples_to_features, generate_dataset +# from util import compute_metrics, simple_accuracy +from data_utils.utils import domain_list, domain_to_id, label_map +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score + +# from data_utils.data_process import domain_list, class_list, task_to_id +import scipy.spatial.distance as distance +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--teacher_model", type=str, default='', required=True, help="The path of meta-teacher.") +parser.add_argument("--student_model", type=str, default='', required=True, help="The path of student model.") +parser.add_argument("--task_name", type=str, default='senti', required=True, help="The name of the task to train.") +# parser.add_argument("--domain", type=str, default='all', required=True, help="The domain of given model.") +parser.add_argument("--use_domain_loss", action='store_true', help="Whether to use domain loss") +parser.add_argument('--data_portion', type=float, default=1.0, help='How many data selected.') +parser.add_argument("--domain_loss_weight", default=0.2, type=float, help="The loss weight of domain.") +parser.add_argument("--use_sample_weights",default=False, type=bool, help="The loss weight of domain.") +# parser.add_argument("--input", default="./inputs.tsv", type=str, required=True, help="bert embedding path") +# parser.add_argument("--output", default="./output.tsv", type=str, required=True, help="bert embedding path") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--label_list", type=list, default=None) +parser.add_argument("--max_seq_length", default=128, type=int, required=False, help="The max sequence length of input sentences.") +parser.add_argument("--batch_size_per_device", default=128, type=int, required=False, help="training batch size") +parser.add_argument("--train_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_batch_size_per_device", default=128, type=int, required=False, help="evalutation batch size") +parser.add_argument("--eval_every_step_num", default=100, type=int, required=False, help="evaluate at each step") +parser.add_argument("--train_example_num", default=6480, type=int, required=False, help="The number of training data that need to calculate weighted values") +parser.add_argument("--eval_example_num", default=720, type=int, required=False, help="The number of development data that need to calculate weighted values") +parser.add_argument('--seed', type=int, default=42, help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") + +# parser.add_argument('--aug_train', action='store_true') +# parser.add_argument('--eval_step', type=int, default=50) +parser.add_argument('--pred_distill', action='store_true') +parser.add_argument('--data_url', type=str, default="") +parser.add_argument('--temperature', type=float, default=1.) +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +args.iter_num = epoch_size * args.num_epochs +num_eval_steps = math.ceil(args.train_example_num / eval_batch_size) + +hidden_size = 768 +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("input_mask", [seq_length]) + _blob_conf("segment_ids", [seq_length]) + _blob_conf("domain", [1]) + _blob_conf("label", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + _blob_conf("logit_blob", [seq_length], dtype=flow.float32) + _blob_conf("all_attention_scores_blob", [seq_length * hidden_size], dtype=flow.float32) + _blob_conf("pooled_output", [seq_length * hidden_size], dtype=flow.float32) + # 'logit_blob': float_feature(logit_blob), + # 'all_attention_scores_blob': float_feature(all_attention_scores_blob), + # 'pooled_output': float_feature(pooled_output), + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + num_domains = len(domain_list[args.task_name]) + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = MetaStudentBERT( + decoders['input_ids'], + decoders['input_mask'], + decoders['segment_ids'], + decoders['label'], + decoders['domain'], + args.vocab_size, + input_weight=decoders['weights'], + input_logit_blob=decoders['logit_blob'], + input_attention_blob=decoders['all_attention_scores_blob'], + input_pooled_blob=decoders['pooled_output'], + num_domains=num_domains, + layer_indexes=[2, 3, 5], + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=6, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=False, + ) + return loss, logits, decoders['label'] + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.eval_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + + +def main(): + + processor = processors[args.task_name]() + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + if args.do_train: + train_examples = processor.get_train_examples(args.data_dir) + print('===============================') + print('len=', len(train_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + train_feature_dict = generate_dataset(args, train_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='train', load_weight=True) + if args.do_eval: + dev_examples = processor.get_dev_examples(args.data_dir) + print('===============================') + print('len=', len(dev_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + dev_feature_dict = generate_dataset(args, dev_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='dev') + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.teacher_model) + + print("starting meta fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='meta-finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.eval_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + snapshot.save("best_mft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + print("best dev acc: {}".format(best_dev_acc)) + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/meta_finetuning.py b/NLPAlgo/MetaKD/meta_finetuning.py new file mode 100644 index 0000000..a3702ff --- /dev/null +++ b/NLPAlgo/MetaKD/meta_finetuning.py @@ -0,0 +1,280 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令: + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MFTBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +from data_utils.task_processors1 import PROCESSORS, load_examples, DEV32_SET, TRAIN_SET, DEV_SET, TEST_SET, METRICS, DEFAULT_METRICS +from data_utils.data_process import domain_list, class_list, task_to_id +from data_utils.generate_feature import generate_dataset +import scipy.spatial.distance as distance +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='g1', choices=['g1', 'g2', 'g3']) +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--train_example_num", type=int, default=88614, + help="example number in dataset") +parser.add_argument("--batch_size_per_device", type=int, default=2) +parser.add_argument("--train_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument("--dev_example_num", type=int, default=10833, + help="example number in dataset") +parser.add_argument("--dev_batch_size_per_device", type=int, default=2) +parser.add_argument("--dev_data_part_num", type=int, default=1, + help="data part number in dataset") +parser.add_argument("--dev_every_step_num", type=int, default=10, + help="") +parser.add_argument('--seed', type=int, default=42, + help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +dev_batch_size = args.num_nodes * args.gpu_num_per_node * args.dev_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +num_dev_steps = math.ceil(args.dev_example_num / dev_batch_size) +args.iter_num = epoch_size * args.num_epochs +configs.print_args(args) + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("attention_masks", [seq_length]) + _blob_conf("token_type_ids", [seq_length]) + _blob_conf("tasks", [1]) + _blob_conf("labels", [1]) + _blob_conf("logits", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + # print('blob_confs=', blob_confs['input_ids'].shape) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = MFTBERT( + decoders['input_ids'], + decoders['attention_masks'], + decoders['token_type_ids'], + decoders['labels'], + decoders['tasks'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + layer_indexes=[3, 7, 11], + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=False + ) + return loss, logits, decoders['labels'] + + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + # return loss + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.dev_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + +def main(): + # 加载domain以及对应的class + if args.task_name not in domain_list: + raise AttributeError('The task name can only be selected from [g1, g2, g3]') + domains = domain_list[args.task_name] + global num_domains + num_domains = len(domains) + processor = PROCESSORS[args.task_name](args.task_name) + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + preprocessor = Preprocessor(args, tokenizer, args.seed) + label_map = preprocessor.label_map # class2id + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_data = load_examples( + args.task_name, args.data_dir, TRAIN_SET, num_examples=-1, num_examples_per_label=None) + print('===============train examples================') + print('len=', len(train_data)) + print('example 0:', train_data[0]) + print('example 1:', train_data[1]) + print('===============================') + dev_data = load_examples( + args.task_name, args.data_dir, DEV_SET, num_examples=-1, num_examples_per_label=None) + train_feature_dict = generate_dataset(args, train_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='train') + dev_feature_dict = generate_dataset(args, dev_data, preprocessor, ofrecord_dir=ofrecord_dir, + stage='dev') + # 初始化每个 domain class 的prototypical embedding + domain_class_embeddings = dict() + temp_output_data = list() + + # 遍历每一个数据集domains + for domain_name in domains: + # 遍历每个类标 + for class_name, class_id in label_map.items(): + key_name = domain_name + "\t" + str(class_id) + # 初始化每个domain对应class的样本列表 + domain_class_embeddings[key_name] = list() + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + print("starting meta fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='meta-finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.dev_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_dev_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + snapshot.save("best_mft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + print("best dev acc: {}".format(best_dev_acc)) + + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/meta_teacher.py b/NLPAlgo/MetaKD/meta_teacher.py new file mode 100644 index 0000000..4b0e3e5 --- /dev/null +++ b/NLPAlgo/MetaKD/meta_teacher.py @@ -0,0 +1,277 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令:python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MetaTeacherBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +# from data_utils.data_process import Preprocessor +from data_utils.task_processors2 import processors, convert_examples_to_features, generate_dataset +# from util import compute_metrics, simple_accuracy +from data_utils.utils import domain_list, domain_to_id, label_map +from sklearn.metrics import accuracy_score, matthews_corrcoef, precision_score, recall_score, f1_score + +# from data_utils.data_process import domain_list, class_list, task_to_id +import scipy.spatial.distance as distance +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='senti', required=True, help="The name of the task to train.") +# parser.add_argument("--domain", type=str, default='all', required=True, help="The domain of given model.") +parser.add_argument("--use_domain_loss", action='store_true', help="Whether to use domain loss") +parser.add_argument('--data_portion', type=float, default=1.0, help='How many data selected.') +parser.add_argument("--domain_loss_weight", default=0.2, type=float, help="The loss weight of domain.") +parser.add_argument("--use_sample_weights",default=False, type=bool, help="The loss weight of domain.") +# parser.add_argument("--input", default="./inputs.tsv", type=str, required=True, help="bert embedding path") +# parser.add_argument("--output", default="./output.tsv", type=str, required=True, help="bert embedding path") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--label_list", type=list, default=None) +parser.add_argument("--max_seq_length", default=128, type=int, required=False, help="The max sequence length of input sentences.") +parser.add_argument("--batch_size_per_device", default=128, type=int, required=False, help="training batch size") +parser.add_argument("--train_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_batch_size_per_device", default=128, type=int, required=False, help="evalutation batch size") +parser.add_argument("--eval_every_step_num", default=100, type=int, required=False, help="evaluate at each step") +parser.add_argument("--train_example_num", default=6480, type=int, required=False, help="The number of training data that need to calculate weighted values") +parser.add_argument("--eval_example_num", default=720, type=int, required=False, help="The number of development data that need to calculate weighted values") +parser.add_argument('--seed', type=int, default=42, help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") + +# parser.add_argument('--aug_train', action='store_true') +# parser.add_argument('--eval_step', type=int, default=50) +parser.add_argument('--pred_distill', action='store_true') +parser.add_argument('--data_url', type=str, default="") +parser.add_argument('--temperature', type=float, default=1.) +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +epoch_size = math.ceil(args.train_example_num / batch_size) +args.iter_num = epoch_size * args.num_epochs +num_eval_steps = math.ceil(args.train_example_num / eval_batch_size) + + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("input_mask", [seq_length]) + _blob_conf("segment_ids", [seq_length]) + _blob_conf("domain", [1]) + _blob_conf("label", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + num_domains = len(domain_list[args.task_name]) + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + loss, logits = MetaTeacherBERT( + decoders['input_ids'], + decoders['input_mask'], + decoders['segment_ids'], + decoders['label'], + decoders['domain'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + layer_indexes=[3, 7, 11], + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=False, + ) + return loss, logits, decoders['label'] + +# 作业函数 +@flow.global_function(type='train', function_config=GetFunctionConfig(args)) +def BertGlueFinetuneJob(): + # 跑一个batch + loss, logits, _ = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + ) + flow.losses.add_loss(loss) + opt = CreateOptimizer(args) + opt.minimize(loss) + return {'loss': loss} + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalTrainJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logits, label_ids + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + _, logits, label_ids = BuildBert( + batch_size, + args.eval_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'dev'), + args.dev_data_prefix, + shuffle=False + ) + return logits, label_ids + + +def run_eval_job(dev_job_func, num_steps, desc='dev'): + labels = [] + predictions = [] + for index in range(num_steps): + logits, label = dev_job_func().get() + predictions.extend(list(logits.numpy().argmax(axis=1))) + labels.extend(list(label)) + + def metric_fn(predictions, labels): + return { + "accuarcy": accuracy_score(labels, predictions), + "matthews_corrcoef": matthews_corrcoef(labels, predictions), + "precision": precision_score(labels, predictions), + "recall": recall_score(labels, predictions), + "f1": f1_score(labels, predictions), + } + + metric_dict = metric_fn(predictions, labels) + print(desc, ', '.join('{}: {:.3f}'.format(k, v) for k, v in metric_dict.items())) + return metric_dict['accuarcy'] + + +def main(): + + processor = processors[args.task_name]() + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + if args.do_train: + train_examples = processor.get_train_examples(args.data_dir) + print('===============================') + print('len=', len(train_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + train_feature_dict = generate_dataset(args, train_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='train', load_weight=True) + if args.do_eval: + dev_examples = processor.get_dev_examples(args.data_dir) + print('===============================') + print('len=', len(dev_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + dev_feature_dict = generate_dataset(args, dev_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='dev') + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + print("starting meta fine-tuning") + + global_step = 0 + best_dev_acc = 0.0 + for epoch in tqdm(range(args.num_epochs)): + metric = Metric(desc='meta-finetune', print_steps=args.loss_print_every_n_iter, + batch_size=batch_size, keys=['loss']) + + for step in range(epoch_size): + global_step += 1 + loss = BertGlueFinetuneJob().async_get(metric.metric_cb(global_step, epoch=epoch)) + + if global_step % args.eval_every_step_num == 0: + print("===== evaluating ... =====") + dev_acc = run_eval_job( + dev_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + desc='dev') + + if best_dev_acc < dev_acc: + best_dev_acc = dev_acc + # 保存最好模型参数 + print('===== saving model ... =====') + snapshot.save("best_mft_model_{}_dev_{}".format(args.task_name, best_dev_acc)) + + print("best dev acc: {}".format(best_dev_acc)) + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/meta_teacher_eval.py b/NLPAlgo/MetaKD/meta_teacher_eval.py new file mode 100644 index 0000000..7e14c36 --- /dev/null +++ b/NLPAlgo/MetaKD/meta_teacher_eval.py @@ -0,0 +1,231 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令:python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +import sys + +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MetaTeacherBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +# from data_utils.data_process import Preprocessor +from data_utils.task_processors1 import processors, convert_examples_to_features, generate_dataset +from data_utils.utils import domain_list, domain_to_id, label_map + +# from data_utils.data_process import domain_list, class_list, task_to_id +import scipy.spatial.distance as distance +import config as configs + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='senti', required=True, help="The name of the task to train.") +# parser.add_argument("--domain", type=str, default='all', required=True, help="The domain of given model.") +# parser.add_argument("--use_domain_loss", action='store_true', help="Whether to use domain loss") +# parser.add_argument('--data_portion', type=float, default=1.0, help='How many data selected.') +# parser.add_argument("--domain_loss_weight", default=0.2, type=float, help="The loss weight of domain.") +# parser.add_argument("--use_sample_weights",default=False, type=bool, help="The loss weight of domain.") +# parser.add_argument("--input", default="./inputs.tsv", type=str, required=True, help="bert embedding path") +# parser.add_argument("--output", default="./output.tsv", type=str, required=True, help="bert embedding path") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--label_list", type=list, default=None) +parser.add_argument("--max_seq_length", default=128, type=int, required=False, + help="The max sequence length of input sentences.") +parser.add_argument("--batch_size_per_device", default=128, type=int, required=False, help="training batch size") +parser.add_argument("--train_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_batch_size_per_device", default=1, type=int, required=False, + help="evalutation batch size") +parser.add_argument("--train_example_num", default=6480, type=int, required=False, + help="The number of training data that need to calculate weighted values") +parser.add_argument('--seed', type=int, default=42, help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +# epoch_size = math.ceil(args.train_example_num / batch_size) +num_eval_steps = math.ceil(args.train_example_num / eval_batch_size) + + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=1, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + + _blob_conf("input_ids", [seq_length]) + _blob_conf("input_mask", [seq_length]) + _blob_conf("segment_ids", [seq_length]) + _blob_conf("domain", [1]) + _blob_conf("label", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + return blob_confs + + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + num_domains = len(domain_list[args.task_name]) + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + # is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + _, logit_blob, all_attention_scores_blob, pooled_output = MetaTeacherBERT( + decoders['input_ids'], + decoders['input_mask'], + decoders['segment_ids'], + decoders['label'], + decoders['domain'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=False, # 只获得隐向量 + get_att_reps=True, # 获得包括attention、hidden layers以及soft-label等信息 + ) + return logit_blob, all_attention_scores_blob, pooled_output, decoders['idxs'] + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + logit_blob, all_attention_scores_blob, pooled_output, idxs = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return logit_blob, all_attention_scores_blob, pooled_output, idxs + + +def get_hidden_embedding(eval_job_func, num_steps, desc='train'): + sample_to_blob = dict() + for index in tqdm(range(num_steps)): + logit_blob, all_attention_scores_blob, pooled_output, idxs = eval_job_func().get() + current_size = logit_blob.shape[0] + logit_blob = list(logit_blob.numpy().tolist()) + pooled_output = list(pooled_output.numpy().tolist()) + idxs = list(idxs.numpy().tolist()) + # print('idxs=', len(idxs)) + # print('logit_blob=', len(logit_blob)) + # print('all_attention_scores_blob=', len(all_attention_scores_blob)) + # print('pooled_output=', len(pooled_output)) + for ei, idx in enumerate(idxs): + sample_to_blob[idx[0]] = [logit_blob[ei], all_attention_scores_blob[ei], pooled_output[ei]] + + return sample_to_blob + + +# 计算 prototypical score +def compute_weight(domain, label, current_embedding, centroid_embeddings): + key_name = domain + "\t" + str(label) + current_centroid = centroid_embeddings[key_name] + other_centroids = list() + for current_key in centroid_embeddings.keys(): + items = current_key.split("\t") + current_domain = items[0] + current_label = items[1] + if not (current_domain == domain) and (current_label == label): + other_centroids.append(centroid_embeddings[current_key]) + other_centroids = np.array(other_centroids) + other_centroid_mean = np.mean(other_centroids, axis=0) + first_cos_sim = 1 - distance.cosine(current_embedding, current_centroid) + second_cos_sim = 1 - distance.cosine(current_embedding, other_centroid_mean) + return (first_cos_sim + second_cos_sim) / 2 + + +def main(): + processor = processors[args.task_name]() + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_examples = processor.get_examples(args.data_dir) + print('===============================') + print('len=', len(train_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + train_feature_dict = generate_dataset(args, train_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='train') + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + # 在训练集上使用训练好的meta-teacher再次进行一次推理,得到每个样本对应的hidden embedding、soft label以及attention score等 + sample_to_blob = get_hidden_embedding( + eval_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + desc='eval') + + np.save(os.path.join(ofrecord_dir, 'train/sample_to_blob.npy'), sample_to_blob, allow_pickle=True) + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/preprocess.py b/NLPAlgo/MetaKD/preprocess.py new file mode 100644 index 0000000..f860372 --- /dev/null +++ b/NLPAlgo/MetaKD/preprocess.py @@ -0,0 +1,264 @@ +# coding=utf-8 +# Copyright (c) 2019 Alibaba PAI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# 本文件用于生成训练集的prototype embedding,并计算每个样本的prototypical score +# 执行命令:python3 preprocess.py --task_name g1 --data_dir data/k-shot-cross/g1/16-42 --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 96 --eval_example_num 96 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord + + +import sys +sys.path.append("../..") +import os +import numpy as np +import math +from tqdm import tqdm +import oneflow as flow +from classifier import MetaTeacherBERT +import tokenization +from util import Snapshot, InitNodes, Metric, CreateOptimizer, GetFunctionConfig +# from data_utils.data_process import Preprocessor +from data_utils.task_processors1 import processors, convert_examples_to_features, generate_dataset +from data_utils.utils import domain_list, domain_to_id, label_map + +# from data_utils.data_process import domain_list, class_list, task_to_id +import scipy.spatial.distance as distance +import config as configs + + +parser = configs.get_parser() +parser.add_argument("--task_name", type=str, default='senti', required=True, help="The name of the task to train.") +# parser.add_argument("--domain", type=str, default='all', required=True, help="The domain of given model.") +# parser.add_argument("--use_domain_loss", action='store_true', help="Whether to use domain loss") +# parser.add_argument('--data_portion', type=float, default=1.0, help='How many data selected.') +# parser.add_argument("--domain_loss_weight", default=0.2, type=float, help="The loss weight of domain.") +# parser.add_argument("--use_sample_weights",default=False, type=bool, help="The loss weight of domain.") +# parser.add_argument("--input", default="./inputs.tsv", type=str, required=True, help="bert embedding path") +# parser.add_argument("--output", default="./output.tsv", type=str, required=True, help="bert embedding path") +parser.add_argument("--vocab_file", type=str, default=None) +parser.add_argument("--train_data_prefix", type=str, default='train.of_record-') +parser.add_argument("--eval_data_prefix", type=str, default='eval.of_record-') +parser.add_argument("--dev_data_prefix", type=str, default='dev.of_record-') +parser.add_argument('--num_epochs', type=int, default=3, help='number of epochs') +parser.add_argument("--data_dir", type=str, default=None) +parser.add_argument("--label_list", type=list, default=None) +parser.add_argument("--max_seq_length", default=128, type=int, required=False, help="The max sequence length of input sentences.") +parser.add_argument("--batch_size_per_device", default=128, type=int, required=False, help="training batch size") +parser.add_argument("--train_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_data_part_num", type=int, default=1, help="data part number in dataset") +parser.add_argument("--eval_batch_size_per_device", default=128, type=int, required=False, help="evalutation batch size") +parser.add_argument("--train_example_num", default=6480, type=int, required=False, help="The number of training data that need to calculate weighted values") +parser.add_argument('--seed', type=int, default=42, help="random seed for initialization") +parser.add_argument("--resave_ofrecord", action='store_true', help="Whether to resave the data to ofrecord") +args = parser.parse_args() + +args.num_nodes = 1 # 默认只有一个设备 +args.gpu_num_per_node = 1 +batch_size = args.num_nodes * args.gpu_num_per_node * args.batch_size_per_device +eval_batch_size = args.num_nodes * args.gpu_num_per_node * args.eval_batch_size_per_device +# epoch_size = math.ceil(args.train_example_num / batch_size) +num_eval_steps = math.ceil(args.train_example_num / eval_batch_size) + + +def BertDecoder( + data_dir, batch_size, data_part_num, seq_length, part_name_prefix, shuffle=True +): + with flow.scope.placement("cpu", "0:0"): + # 使用ofrecord读取数据 + ofrecord = flow.data.ofrecord_reader(data_dir, + batch_size=batch_size, + data_part_num=data_part_num, + part_name_prefix=part_name_prefix, + random_shuffle=shuffle, + shuffle_after_epoch=shuffle) + blob_confs = {} + def _blob_conf(name, shape, dtype=flow.int32): + # 获得标签 + blob_confs[name] = flow.data.OFRecordRawDecoder(ofrecord, name, shape=shape, dtype=dtype) + _blob_conf("input_ids", [seq_length]) + _blob_conf("input_mask", [seq_length]) + _blob_conf("segment_ids", [seq_length]) + _blob_conf("domain", [1]) + _blob_conf("label", [1]) + _blob_conf("idxs", [1]) + _blob_conf("weights", [1], dtype=flow.float32) + return blob_confs + +# 跑一个batch +def BuildBert( + batch_size, + data_part_num, + data_dir, + part_name_prefix, + shuffle=True +): + hidden_size = 64 * args.num_attention_heads # , H = 64, size per head + intermediate_size = hidden_size * 4 + num_domains = len(domain_list[args.task_name]) + # 获得一批数据 + decoders = BertDecoder( + data_dir, batch_size, data_part_num, args.seq_length, part_name_prefix, shuffle=shuffle + ) + #is_real_example = decoders['is_real_example'] + # 使用带有分类器的BERT进行微调,并获得loss和logit + output = MetaTeacherBERT( + decoders['input_ids'], + decoders['input_mask'], + decoders['segment_ids'], + decoders['label'], + decoders['domain'], + args.vocab_size, + input_weight=decoders['weights'], + num_domains=num_domains, + seq_length=args.seq_length, + hidden_size=hidden_size, + num_hidden_layers=args.num_hidden_layers, + num_attention_heads=args.num_attention_heads, + intermediate_size=intermediate_size, + hidden_act="gelu", + hidden_dropout_prob=args.hidden_dropout_prob, + attention_probs_dropout_prob=args.attention_probs_dropout_prob, + max_position_embeddings=args.max_position_embeddings, + type_vocab_size=args.type_vocab_size, + initializer_range=0.02, + get_output=True # 只获得隐向量 + ) + return output, decoders['domain'], decoders['label'], decoders['idxs'], + + +@flow.global_function(type='predict', function_config=GetFunctionConfig(args)) +def BertGlueEvalValJob(): + output, domains, labels, idxs = BuildBert( + batch_size, + args.train_data_part_num, + os.path.join(args.data_dir, 'ofrecord', 'train'), + args.train_data_prefix, + shuffle=False + ) + return output, domains, labels, idxs + + +def get_hidden_embedding(eval_job_func, num_steps, domain_class_embeddings, temp_output_data, desc='train'): + labels = [] + predictions = [] + for index in tqdm(range(num_steps)): + output, domains, labels, idxs = eval_job_func().get() + current_size = output.shape[0] + output = list(output.numpy().tolist()) + domains = list(domains.numpy().tolist()) + labels = list(labels.numpy().tolist()) + idxs = list(idxs.numpy().tolist()) + # print('domains=', domains, ',output=', output) + + for i in range(current_size): + pool_output = output[i] + domain_name = domain_list[args.task_name][domains[i][0]] + label = str(labels[i][0]) + domain_class_embeddings[domain_name + '\t' + label].append(pool_output) + temp_output_data.append((idxs[i][0], domain_name, label, pool_output)) + + return domain_class_embeddings, temp_output_data + + + + +# 计算 prototypical score +def compute_weight(domain, label, current_embedding, centroid_embeddings): + key_name = domain + "\t" + str(label) + current_centroid = centroid_embeddings[key_name] + other_centroids = list() + for current_key in centroid_embeddings.keys(): + items = current_key.split("\t") + current_domain = items[0] + current_label = items[1] + if not (current_domain == domain) and (current_label==label): + other_centroids.append(centroid_embeddings[current_key]) + other_centroids = np.array(other_centroids) + other_centroid_mean = np.mean(other_centroids, axis=0) + first_cos_sim = 1 - distance.cosine(current_embedding, current_centroid) + second_cos_sim = 1 - distance.cosine(current_embedding, other_centroid_mean) + return (first_cos_sim + second_cos_sim) / 2 + + + +def main(): + + processor = processors[args.task_name]() + args.label_list = processor.get_labels() + + # 获得BERT分词工具 + tokenizer = tokenization.FullTokenizer(vocab_file=args.vocab_file) + + # 将原始数据集生成ofrecord数据集 + ofrecord_dir = os.path.join(args.data_dir, 'ofrecord') + + if not os.path.exists(ofrecord_dir) or args.resave_ofrecord: + # 获得训练集、验证集和测试集的example格式数据 List[InputExample] + train_examples = processor.get_examples(args.data_dir) + print('===============================') + print('len=', len(train_examples)) + print('===============================') + # 为input examples生成input feature,并生成ofrecord格式的数据 + train_feature_dict = generate_dataset(args, train_examples, tokenizer, ofrecord_dir=ofrecord_dir, + stage='train') + # 初始化每个 domain class 的prototypical embedding + domain_class_embeddings = dict() + temp_output_data = list() + + # 遍历每一个数据集domains + for domain_name in domain_list[args.task_name]: + # 遍历每个类标 + for class_name, class_id in label_map[args.task_name].items(): + key_name = domain_name + "\t" + str(class_id) + # 初始化每个domain对应class的样本列表 + domain_class_embeddings[key_name] = list() + + # 加载预训练模型参数 + flow.config.gpu_device_num(args.gpu_num_per_node) + flow.env.log_dir(args.log_dir) + InitNodes(args) + snapshot = Snapshot(args.model_save_dir, args.model_load_dir) + + # 执行一次prediction,获得BERT的embedding + domain_class_embeddings, temp_output_data = get_hidden_embedding( + eval_job_func=BertGlueEvalValJob, + num_steps=num_eval_steps, + domain_class_embeddings=domain_class_embeddings, + temp_output_data=temp_output_data, + desc='eval') + + # do inference for training data + + + # compute centroids + # 对于每个domain class,取所有样本embedding的均值,作为prototype embedding + centroid_embeddings = dict() + for key_name in domain_class_embeddings: + domain_class_data_embeddings = np.array(domain_class_embeddings[key_name]) + centroid_embeddings[key_name] = np.mean(domain_class_data_embeddings, axis=0) + + # output files for meta fine-tune + # 计算prototypical score,并保存在本地文件中 + #write odps tables + records = [] + for idx, domain, label, embeddings in temp_output_data: + weight = compute_weight(domain, label, embeddings, centroid_embeddings) + tup = {idx: np.around(weight, decimals=5)} + records.append(tup) + + np.save(os.path.join(ofrecord_dir, 'train/weight.npy'), records, allow_pickle=True) + + +if __name__ == "__main__": + main() diff --git a/NLPAlgo/MetaKD/tokenization.py b/NLPAlgo/MetaKD/tokenization.py new file mode 100644 index 0000000..64d3a27 --- /dev/null +++ b/NLPAlgo/MetaKD/tokenization.py @@ -0,0 +1,467 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +# coding=utf-8 +# Copyright 2018 The Google AI Language Team Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tokenization classes.""" + +import collections +import re +import unicodedata +import six +from typing import List, Optional +#import tensorflow as tf + + +def validate_case_matches_checkpoint(do_lower_case, init_checkpoint): + """Checks whether the casing config is consistent with the checkpoint name.""" + + # The casing has to be passed in by the user and there is no explicit check + # as to whether it matches the checkpoint. The casing information probably + # should have been stored in the bert_config.json file, but it's not, so + # we have to heuristically detect it to validate. + + if not init_checkpoint: + return + + m = re.match("^.*?([A-Za-z0-9_-]+)/bert_model.ckpt", init_checkpoint) + if m is None: + return + + model_name = m.group(1) + + lower_models = [ + "uncased_L-24_H-1024_A-16", "uncased_L-12_H-768_A-12", + "multilingual_L-12_H-768_A-12", "chinese_L-12_H-768_A-12" + ] + + cased_models = [ + "cased_L-12_H-768_A-12", "cased_L-24_H-1024_A-16", + "multi_cased_L-12_H-768_A-12" + ] + + is_bad_config = False + if model_name in lower_models and not do_lower_case: + is_bad_config = True + actual_flag = "False" + case_name = "lowercased" + opposite_flag = "True" + + if model_name in cased_models and do_lower_case: + is_bad_config = True + actual_flag = "True" + case_name = "cased" + opposite_flag = "False" + + if is_bad_config: + raise ValueError( + "You passed in `--do_lower_case=%s` with `--init_checkpoint=%s`. " + "However, `%s` seems to be a %s model, so you " + "should pass in `--do_lower_case=%s` so that the fine-tuning matches " + "how the model was pre-training. If this error is wrong, please " + "just comment out this check." % (actual_flag, init_checkpoint, + model_name, case_name, opposite_flag)) + + +def convert_to_unicode(text): + """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text.decode("utf-8", "ignore") + elif isinstance(text, unicode): + return text + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def printable_text(text): + """Returns text encoded in a way suitable for print or `tf.logging`.""" + + # These functions want `str` for both Python2 and Python3, but in one case + # it's a Unicode string and in the other it's a byte string. + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text + elif isinstance(text, unicode): + return text.encode("utf-8") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def load_vocab(vocab_file): + """Loads a vocabulary file into a dictionary.""" + vocab = collections.OrderedDict() + index = 0 + #with tf.gfile.GFile(vocab_file, "r") as reader: + with open(vocab_file, "r") as reader: + while True: + token = convert_to_unicode(reader.readline()) + if not token: + break + token = token.strip() + vocab[token] = index + index += 1 + return vocab + + +def convert_by_vocab(vocab, items): + """Converts a sequence of [tokens|ids] using the vocab.""" + output = [] + for item in items: + output.append(vocab[item]) + return output + + +def convert_tokens_to_ids(vocab, tokens): + return convert_by_vocab(vocab, tokens) + + +def convert_ids_to_tokens(inv_vocab, ids): + return convert_by_vocab(inv_vocab, ids) + + +def whitespace_tokenize(text): + """Runs basic whitespace cleaning and splitting on a piece of text.""" + text = text.strip() + if not text: + return [] + tokens = text.split() + return tokens + + +class FullTokenizer(object): + """Runs end-to-end tokenziation.""" + + def __init__(self, vocab_file, do_lower_case=True): + self.vocab = load_vocab(vocab_file) + self.inv_vocab = {v: k for k, v in self.vocab.items()} + self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) + self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) + ## add by wjn + self.unk_token_id = self.vocab['[UNK]'] + self.mask_token_id = self.vocab['[MASK]'] + self.cls_token_id = self.vocab['[CLS]'] + self.pad_token_id = self.vocab['[PAD]'] + + def tokenize(self, text): + # print('text=', text) + split_tokens = [] + for token in self.basic_tokenizer.tokenize(text): + for sub_token in self.wordpiece_tokenizer.tokenize(token): + split_tokens.append(sub_token) + # print('split_tokens=', split_tokens) + return split_tokens + + def convert_tokens_to_ids(self, tokens): + return convert_by_vocab(self.vocab, tokens) + + def convert_ids_to_tokens(self, ids): + return convert_by_vocab(self.inv_vocab, ids) + + def num_special_tokens_to_add(self, pair: bool): + num_special = 2 + if pair: + num_special += 1 + return num_special + + def build_inputs_with_special_tokens( + self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None + ) -> List[int]: + """ + Build model inputs from a sequence or a pair of sequence for sequence classification tasks + by concatenating and adding special tokens. + + This implementation does not add special tokens and this method should be overriden in a subclass. + + Args: + token_ids_0 (:obj:`List[int]`): The first tokenized sequence. + token_ids_1 (:obj:`List[int]`, `optional`): The second tokenized sequence. + + Returns: + :obj:`List[int]`: The model input with special tokens. + """ + if token_ids_1 is None: + return token_ids_0 + return token_ids_0 + token_ids_1 + + def create_token_type_ids_from_sequences( + self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None + ) -> List[int]: + """ + Create the token type IDs corresponding to the sequences passed. + `What are token type IDs? <../glossary.html#token-type-ids>`__ + + Should be overriden in a subclass if the model has a special way of building those. + + Args: + token_ids_0 (:obj:`List[int]`): The first tokenized sequence. + token_ids_1 (:obj:`List[int]`, `optional`): The second tokenized sequence. + + Returns: + :obj:`List[int]`: The token type ids. + """ + if token_ids_1 is None: + return len(token_ids_0) * [0] + return [0] * len(token_ids_0) + [1] * len(token_ids_1) + +class BasicTokenizer(object): + """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" + + def __init__(self, do_lower_case=True): + """Constructs a BasicTokenizer. + + Args: + do_lower_case: Whether to lower case the input. + """ + self.do_lower_case = do_lower_case + + def tokenize(self, text): + """Tokenizes a piece of text.""" + # print('wordpiece_text=', text) + # special token不参与word piece 分词 + if text in ['[UNK]', '[MASK]', '[PAD]', '[SEP]']: + return [text] + text = convert_to_unicode(text) + text = self._clean_text(text) + + # This was added on November 1st, 2018 for the multilingual and Chinese + # models. This is also applied to the English models now, but it doesn't + # matter since the English models were not trained on any Chinese data + # and generally don't have any Chinese data in them (there are Chinese + # characters in the vocabulary because Wikipedia does have some Chinese + # words in the English Wikipedia.). + text = self._tokenize_chinese_chars(text) + + orig_tokens = whitespace_tokenize(text) + split_tokens = [] + for token in orig_tokens: + if self.do_lower_case: + token = token.lower() + token = self._run_strip_accents(token) + split_tokens.extend(self._run_split_on_punc(token)) + + output_tokens = whitespace_tokenize(" ".join(split_tokens)) + return output_tokens + + def _run_strip_accents(self, text): + """Strips accents from a piece of text.""" + text = unicodedata.normalize("NFD", text) + output = [] + for char in text: + cat = unicodedata.category(char) + if cat == "Mn": + continue + output.append(char) + return "".join(output) + + def _run_split_on_punc(self, text): + """Splits punctuation on a piece of text.""" + chars = list(text) + i = 0 + start_new_word = True + output = [] + while i < len(chars): + char = chars[i] + if _is_punctuation(char): + output.append([char]) + start_new_word = True + else: + if start_new_word: + output.append([]) + start_new_word = False + output[-1].append(char) + i += 1 + + return ["".join(x) for x in output] + + def _tokenize_chinese_chars(self, text): + """Adds whitespace around any CJK character.""" + output = [] + for char in text: + cp = ord(char) + if self._is_chinese_char(cp): + output.append(" ") + output.append(char) + output.append(" ") + else: + output.append(char) + return "".join(output) + + def _is_chinese_char(self, cp): + """Checks whether CP is the codepoint of a CJK character.""" + # This defines a "chinese character" as anything in the CJK Unicode block: + # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) + # + # Note that the CJK Unicode block is NOT all Japanese and Korean characters, + # despite its name. The modern Korean Hangul alphabet is a different block, + # as is Japanese Hiragana and Katakana. Those alphabets are used to write + # space-separated words, so they are not treated specially and handled + # like the all of the other languages. + if ((cp >= 0x4E00 and cp <= 0x9FFF) or # + (cp >= 0x3400 and cp <= 0x4DBF) or # + (cp >= 0x20000 and cp <= 0x2A6DF) or # + (cp >= 0x2A700 and cp <= 0x2B73F) or # + (cp >= 0x2B740 and cp <= 0x2B81F) or # + (cp >= 0x2B820 and cp <= 0x2CEAF) or + (cp >= 0xF900 and cp <= 0xFAFF) or # + (cp >= 0x2F800 and cp <= 0x2FA1F)): # + return True + + return False + + def _clean_text(self, text): + """Performs invalid character removal and whitespace cleanup on text.""" + output = [] + for char in text: + cp = ord(char) + if cp == 0 or cp == 0xfffd or _is_control(char): + continue + if _is_whitespace(char): + output.append(" ") + else: + output.append(char) + return "".join(output) + + +class WordpieceTokenizer(object): + """Runs WordPiece tokenziation.""" + + def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200): + self.vocab = vocab + self.unk_token = unk_token + self.max_input_chars_per_word = max_input_chars_per_word + + def tokenize(self, text): + """Tokenizes a piece of text into its word pieces. + + This uses a greedy longest-match-first algorithm to perform tokenization + using the given vocabulary. + + For example: + input = "unaffable" + output = ["un", "##aff", "##able"] + + Args: + text: A single token or whitespace separated tokens. This should have + already been passed through `BasicTokenizer. + + Returns: + A list of wordpiece tokens. + """ + text = convert_to_unicode(text) + + output_tokens = [] + for token in whitespace_tokenize(text): + chars = list(token) + if len(chars) > self.max_input_chars_per_word: + output_tokens.append(self.unk_token) + continue + + is_bad = False + start = 0 + sub_tokens = [] + while start < len(chars): + end = len(chars) + cur_substr = None + while start < end: + substr = "".join(chars[start:end]) + if start > 0: + substr = "##" + substr + if substr in self.vocab: + cur_substr = substr + break + end -= 1 + if cur_substr is None: + is_bad = True + break + sub_tokens.append(cur_substr) + start = end + + if is_bad: + output_tokens.append(self.unk_token) + else: + output_tokens.extend(sub_tokens) + return output_tokens + + +def _is_whitespace(char): + """Checks whether `chars` is a whitespace character.""" + # \t, \n, and \r are technically contorl characters but we treat them + # as whitespace since they are generally considered as such. + if char == " " or char == "\t" or char == "\n" or char == "\r": + return True + cat = unicodedata.category(char) + if cat == "Zs": + return True + return False + + +def _is_control(char): + """Checks whether `chars` is a control character.""" + # These are technically control characters but we count them as whitespace + # characters. + if char == "\t" or char == "\n" or char == "\r": + return False + cat = unicodedata.category(char) + if cat.startswith("C"): + return True + return False + + +def _is_punctuation(char): + """Checks whether `chars` is a punctuation character.""" + cp = ord(char) + # We treat all non-letter/number ASCII as punctuation. + # Characters such as "^", "$", and "`" are not in the Unicode + # Punctuation class but we treat them as punctuation anyways, for + # consistency. + if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or + (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)): + return True + cat = unicodedata.category(char) + if cat.startswith("P"): + return True + return False diff --git a/NLPAlgo/MetaKD/util.py b/NLPAlgo/MetaKD/util.py new file mode 100644 index 0000000..dd24e67 --- /dev/null +++ b/NLPAlgo/MetaKD/util.py @@ -0,0 +1,210 @@ +""" +Copyright 2020 The OneFlow Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os +import time +import numpy as np +from collections import OrderedDict +import pandas as pd +from datetime import datetime +import oneflow as flow + + +def InitNodes(args): + if args.num_nodes > 1: + assert args.num_nodes <= len(args.node_ips) + flow.env.ctrl_port(args.ctrl_port) + nodes = [] + for ip in args.node_ips[:args.num_nodes]: + addr_dict = {} + addr_dict["addr"] = ip + nodes.append(addr_dict) + + flow.env.machine(nodes) + + +class Snapshot(object): + def __init__(self, model_save_dir, model_load_dir): + self._model_save_dir = model_save_dir + self._check_point = flow.train.CheckPoint() + if model_load_dir: + assert os.path.isdir(model_load_dir) + print("Restoring model from {}.".format(model_load_dir)) + self._check_point.load(model_load_dir) + else: + self._check_point.init() + self.save('initial_model') + print("Init model on demand.") + + def load(self, model_load_dir): + assert os.path.isdir(model_load_dir) + print("Restoring model from {}.".format(model_load_dir)) + self._check_point.load(model_load_dir) + + def save(self, name): + snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) + if not os.path.exists(snapshot_save_path): + os.makedirs(snapshot_save_path) + print("Saving model to {}.".format(snapshot_save_path)) + if os.path.exists(snapshot_save_path): + self._check_point.save(snapshot_save_path) + + +# class Snapshot(object): +# def __init__(self, model_save_dir, model_load_dir): +# self._model_save_dir = model_save_dir +# # self._check_point = flow.train.CheckPoint() +# if model_load_dir: +# assert os.path.isdir(model_load_dir) +# print("Restoring model from {}.".format(model_load_dir)) +# flow.checkpoint.get(model_load_dir) +# else: +# flow.checkpoint.init() +# self.save('initial_model') +# print("Init model on demand.") +# +# def save(self, name): +# snapshot_save_path = os.path.join(self._model_save_dir, "snapshot_{}".format(name)) +# if not os.path.exists(snapshot_save_path): +# os.makedirs(snapshot_save_path) +# print("Saving model to {}.".format(snapshot_save_path)) +# flow.checkpoint.save(snapshot_save_path) + + +class StopWatch(object): + def __init__(self): + pass + + def start(self): + self.start_time = time.time() + self.last_split = self.start_time + + def split(self): + now = time.time() + duration = now - self.last_split + self.last_split = now + return duration + + def stop(self): + self.stop_time = time.time() + + def duration(self): + return self.stop_time - self.start_time + + +class Metric(object): + def __init__(self, desc='train', print_steps=-1, batch_size=256, keys=[]): + r"""accumulate and calculate metric + + Args: + desc: `str` general description of the metric to show + print_steps: `Int` print metrics every nth steps + batch_size: `Int` batch size per step + keys: keys in callback outputs + Returns: + """ + self.desc = desc + self.print_steps = print_steps + assert batch_size > 0 + self.batch_size = batch_size + + assert isinstance(keys, (list, tuple)) + self.keys = keys + self.metric_dict = OrderedDict() + self.metric_dict['step'] = 0 + + self.timer = StopWatch() + self.timer.start() + self._clear() + + def _clear(self): + for key in self.keys: + self.metric_dict[key] = 0.0 + self.metric_dict['n_' + key] = 0.0 + self.metric_dict['throughput'] = 0.0 + self.num_samples = 0.0 + + def update_and_save(self, key, value, step, **kwargs): + self.metric_dict[key] = value + self.metric_dict.pop('n_' + key, None) + + def metric_cb(self, step=0, **kwargs): + def callback(outputs): + if step == 0: self._clear() + + for key in self.keys: + self.metric_dict[key] += outputs[key].sum() + self.metric_dict['n_' + key] += outputs[key].size + + self.num_samples += self.batch_size + + if (step + 1) % self.print_steps == 0: + self.metric_dict['step'] = step + for k, v in kwargs.items(): + self.metric_dict[k] = v + throughput = self.num_samples / self.timer.split() + self.update_and_save('throughput', throughput, step) + for key in self.keys: + value = self.metric_dict[key] / self.metric_dict['n_' + key] + self.update_and_save(key, value, step, **kwargs) + print(', '.join(('{}: {}' if type(v) is int else '{}: {:.3f}').format(k, v) \ + for k, v in self.metric_dict.items()), time.time()) + self._clear() + + return callback + +def CreateOptimizer(args): + warmup_batches = int(args.iter_num * args.warmup_proportion) + lr_warmup = flow.optimizer.warmup.linear(warmup_batches, 0) + lr_scheduler = flow.optimizer.PolynomialSchduler(args.learning_rate, args.iter_num, 0.0, + warmup=lr_warmup) + loss_scale_policy = None + if args.use_fp16: + loss_scale_policy = flow.optimizer.loss_scale.dynamic_loss_scale(increment_period=2000); + return flow.optimizer.AdamW(lr_scheduler, epsilon=1e-6, weight_decay=args.weight_decay_rate, + weight_decay_excludes=["bias", "LayerNorm", "layer_norm"], + grad_clipping=flow.optimizer.grad_clipping.by_global_norm(1.0), + loss_scale_policy=loss_scale_policy) + +def GetFunctionConfig(args): + config = flow.function_config() + config.enable_auto_mixed_precision(args.use_fp16) + if args.use_xla: + config.use_xla_jit(True) + config.enable_fuse_add_to_output(True) + config.enable_fuse_model_update_ops(True) + return config + + + + + +# +# +# def simple_accuracy(labels, preds): +# return (preds == labels).mean() + +# +# def compute_metrics(task_name, preds, labels): +# assert len(preds) == len(labels) +# if task_name == "mnli": +# return {"acc": simple_accuracy(labels, preds)} +# elif task_name == "mnli-mm": +# return {"acc": simple_accuracy(labels, preds)} +# elif task_name == "senti": +# return {"acc": simple_accuracy(labels, preds)} +# else: +# raise KeyError(task_name) From 700a8d1b08f4579c5c5087bba7abf812468f6981 Mon Sep 17 00:00:00 2001 From: WangJianing <851019059@qq.com> Date: Tue, 12 Oct 2021 11:58:10 +0800 Subject: [PATCH 3/3] Update README.md --- NLPAlgo/MetaKD/README.md | 101 +++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 8 deletions(-) diff --git a/NLPAlgo/MetaKD/README.md b/NLPAlgo/MetaKD/README.md index a212a53..56066a7 100644 --- a/NLPAlgo/MetaKD/README.md +++ b/NLPAlgo/MetaKD/README.md @@ -1,18 +1,103 @@ +## Meta Knowledge Distillation +Oneflow实现[Meta Knowledge Distillation(Meta-KD)](https://arxiv.org/pdf/2012.01266.pdf")算法 + +--- + +## Meta-KD概述: +预训练语言模型进行知识蒸馏可以在尽可能不降低模型效果的前提下大大提高模型的执行效率,先前的方法则是遵循teacher-student方法实现模型蒸馏,但他们忽略了teacher模型学习得到的领域知识与student模型之间的偏差问题。基于此,Meta-KD提出采用元学习的方法,让teacher模型先学习得到不同domain上的meta knowledge,得到meta-teacher,然后再让student模型学习meta-teacher的先验知识,试图让student模型也具备meta-knowledge。 + +以文本分类任务为例,算法的流程大致如下: +- 首先获得N-way K-shot分类数据,根据每个数据集,获得各个类的prototypical embedding,并计算各个样本的prototypical score; +- 训练meta-teacher,采用元学习的方法学习domain-knowledge,包括prototype以及domain corruption等; +- 根据训练好的meta-teacher,在训练集上进行推理,得到每个样本的先验知识,包括该样本对应的attention values、最末层的表示向量以及预测的logits values; +- 训练meta-student,对于logits values则采用交叉信息熵损失函数,对于attention values和表示向量则采用平均MSE; + +## 数据获取 +以亚马逊评论评测任务为例,可下载语料: The Amazon Review dataset can be found in this [link](https://www.cs.jhu.edu/~mdredze/datasets/sentiment/index2.html -生成prototype embedding并计算prototypical score +## 实验设置 + +#### Step1:生成prototype embedding并计算prototypical score -python3 preprocess.py --task_name senti --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/SENTI/ --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 6480 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord +```shell +python3 preprocess.py \ + --task_name senti \ + --model_load_dir uncased_L-12_H-768_A-12_oneflow \ + --data_dir data/SENTI/ \ + --num_epochs 4 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 6480 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --resave_ofrecord +``` -meta-teacher learning部分 +执行完将得到相应的ofrecord数据 -python3 meta_teacher.py --task_name senti --model_load_dir uncased_L-12_H-768_A-12_oneflow --data_dir data/SENTI/ --num_epochs 63 --seed 42 --seq_length=128 --train_example_num 6480 --eval_example_num 720 --batch_size_per_device 24 --eval_batch_size_per_device 48 --eval_every_step_num 100 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 5e-5 --resave_ofrecord --do_train --do_eval -meta distillation部分 +#### Step2:meta-teacher learning部分 + +本部分需要使用teacher模型在目标数据上进行元学习: + +```shell +python3 meta_teacher.py \ + --task_name senti \ + --model_load_dir uncased_L-12_H-768_A-12_oneflow \ + --data_dir data/SENTI/ \ + --num_epochs 63 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 6480 \ + --eval_example_num 720 \ + --batch_size_per_device 24 \ + --eval_batch_size_per_device 48 \ + --eval_every_step_num 100 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --learning_rate 5e-5 \ + --resave_ofrecord \ + --do_train \ + --do_eval +``` +训练完后将得到meta-teacher模型,模型保存在output目录下 + +#### Step3:meta distillation部分 首先在训练集上,获得meta-teacher的soft-label、attention、embedding等参数,并保存至本地 -python3 meta_teacher_eval.py --task_name senti --model_load_dir output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 --data_dir data/SENTI/ --num_epochs 4 --seed 42 --seq_length=128 --train_example_num 6480 --eval_batch_size_per_device 1 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --resave_ofrecord +```shell +python3 meta_teacher_eval.py \ + --task_name senti \ + --model_load_dir output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 \ # 需要换为实际的目录 + --data_dir data/SENTI/ \ + --num_epochs 4 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 6480 \ + --eval_batch_size_per_device 1 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --resave_ofrecord +``` + +然后再训练student模型,执行distillation: -执行distillation -python3 meta_distill.py --task_name senti --student_model uncased_L-12_H-768_A-12_oneflow --teacher_model output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 --data_dir data/SENTI/ --num_epochs 63 --seed 42 --seq_length=128 --train_example_num 6480 --eval_example_num 720 --batch_size_per_device 24 --eval_batch_size_per_device 48 --eval_every_step_num 100 --vocab_file uncased_L-12_H-768_A-12/vocab.txt --learning_rate 5e-5 --resave_ofrecord --do_train --do_eval +```shell +python3 meta_distill.py + --task_name senti \ + --student_model uncased_L-12_H-768_A-12_oneflow \ + --teacher_model output/model_save-2021-09-26-15:31:15/snapshot_best_mft_model_senti_dev_0.8691358024691358 \ # 需要换为实际的目录 + --data_dir data/SENTI/ \ + --num_epochs 63 \ + --seed 42 \ + --seq_length=128 \ + --train_example_num 6480 \ + --eval_example_num 720 \ + --batch_size_per_device 24 \ + --eval_batch_size_per_device 48 \ + --eval_every_step_num 100 \ + --vocab_file uncased_L-12_H-768_A-12/vocab.txt \ + --learning_rate 5e-5 \ + --resave_ofrecord \ + --do_train \ + --do_eval +```