├── README.md ├── __init__.py ├── albert_small_zh_google ├── albert_config.json └── vocab_chinese.txt ├── classifier_utils.py ├── data ├── test.csv ├── test_onehot.csv ├── train.csv ├── train_onehot.csv └── vocabulary_label.txt ├── hyperparameters.py ├── image └── graph.png ├── lamb_optimizer.py ├── logdir └── model_01 │ └── events.out.tfevents.1595940723.DESKTOP-QC1A83I ├── model ├── model_01 │ └── README.md └── saved_01 │ ├── README.md │ └── checkpoint ├── modeling.py ├── modules.py ├── networks.py ├── optimization.py ├── predict.py ├── tokenization.py ├── train.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 1、本项目是在tensorflow版本1.15.0的基础上做的训练和测试。 3 | 2、本项目为中文的多标签文本分类。 4 | 3、欢迎大家联系我 www.hellonlp.com 5 | 4、albert_small_zh_google对应的百度云下载地址: 6 | 链接:https://pan.baidu.com/s/1RKzGJTazlZ7y12YRbAWvyA 7 | 提取码:wuxw 8 | 9 | # 使用方法 10 | 1、准备数据 11 | 数据格式为:classifier_multi_label/data/test_onehot.csv 12 | 2、参数设置 13 | 参考脚本 hyperparameters.py,直接修改里面的数值即可。 14 | 3、训练 15 | python train.py 16 | 4、预测 17 | python predict.py 18 | 19 | # 知乎代码解读 20 | https://zhuanlan.zhihu.com/p/164873441 21 | 22 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Copyright 2018 The Google AI Team Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /albert_small_zh_google/albert_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "attention_probs_dropout_prob": 0.0, 3 | "hidden_act": "gelu", 4 | "hidden_dropout_prob": 0.0, 5 | "embedding_size": 128, 6 | "hidden_size": 384, 7 | "initializer_range": 0.02, 8 | "intermediate_size": 1536, 9 | "max_position_embeddings": 512, 10 | "num_attention_heads": 12, 11 | "num_hidden_layers": 6, 12 | "num_hidden_groups": 1, 13 | "net_structure_type": 0, 14 | "gap_size": 0, 15 | "num_memory_blocks": 0, 16 | "inner_group_num": 1, 17 | "down_scale_factor": 1, 18 | "type_vocab_size": 2, 19 | "vocab_size": 21128 20 | } -------------------------------------------------------------------------------- /classifier_utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Utility functions for GLUE classification tasks.""" 3 | 4 | 5 | import os 6 | import csv 7 | import collections 8 | import tensorflow_hub as hub 9 | import tensorflow.compat.v1 as tf 10 | from tensorflow.contrib import data as contrib_data 11 | from tensorflow.contrib import metrics as contrib_metrics 12 | from tensorflow.contrib import tpu as contrib_tpu 13 | from classifier_multi_label import modeling 14 | from classifier_multi_label import optimization 15 | from classifier_multi_label import tokenization 16 | from classifier_multi_label.hyperparameters import Hyperparamters as hp 17 | from classifier_multi_label.utils import load_csv,shuffle_one 18 | 19 | 20 | 21 | def label2id(label): 22 | return hp.dict_label2id[str(label)] 23 | 24 | 25 | def id2label(index): 26 | return hp.dict_id2label[str(index)] 27 | 28 | 29 | def read_csv(input_file): 30 | """Reads a tab separated value file.""" 31 | df = load_csv(input_file,header=0).fillna('|') 32 | jobcontent = df['content'].tolist() 33 | jlabel = df.loc[:,hp.label_vocabulary].values 34 | print('Read csv finished!(1)') 35 | return shuffle_one([[jlabel[i],jobcontent[i]] for i in range(len(jlabel)) if type(jobcontent[i])==str]) 36 | 37 | 38 | class InputExample(object): 39 | """A single training/test example for simple sequence classification.""" 40 | 41 | def __init__(self, guid, text_a, text_b=None, label=None): 42 | """Constructs a InputExample. 43 | 44 | Args: 45 | guid: Unique id for the example. 46 | text_a: string. The untokenized text of the first sequence. For single 47 | sequence tasks, only this sequence must be specified. 48 | text_b: (Optional) string. The untokenized text of the second sequence. 49 | Only must be specified for sequence pair tasks. 50 | label: (Optional) string. The label of the example. This should be 51 | specified for train and dev examples, but not for test examples. 52 | """ 53 | self.guid = guid 54 | self.text_a = text_a 55 | self.text_b = text_b 56 | self.label = label 57 | 58 | 59 | class PaddingInputExample(object): 60 | """Fake example so the num input examples is a multiple of the batch size. 61 | 62 | When running eval/predict on the TPU, we need to pad the number of examples 63 | to be a multiple of the batch size, because the TPU requires a fixed batch 64 | size. The alternative is to drop the last batch, which is bad because it means 65 | the entire output data won't be generated. 66 | 67 | We use this class instead of `None` because treating `None` as padding 68 | battches could cause silent errors. 69 | """ 70 | 71 | 72 | class InputFeatures(object): 73 | """A single set of features of data.""" 74 | 75 | def __init__(self, 76 | input_ids, 77 | input_mask, 78 | segment_ids, 79 | label_id, 80 | guid=None, 81 | example_id=None, 82 | is_real_example=True): 83 | self.input_ids = input_ids 84 | self.input_mask = input_mask 85 | self.segment_ids = segment_ids 86 | self.label_id = label_id 87 | self.example_id = example_id 88 | self.guid = guid 89 | self.is_real_example = is_real_example 90 | 91 | 92 | class DataProcessor(object): 93 | """Base class for data converters for sequence classification data sets.""" 94 | 95 | def __init__(self, use_spm, do_lower_case): 96 | super(DataProcessor, self).__init__() 97 | self.use_spm = use_spm 98 | self.do_lower_case = do_lower_case 99 | 100 | def get_train_examples(self, data_dir): 101 | """Gets a collection of `InputExample`s for the train set.""" 102 | raise NotImplementedError() 103 | 104 | def get_dev_examples(self, data_dir): 105 | """Gets a collection of `InputExample`s for the dev set.""" 106 | raise NotImplementedError() 107 | 108 | def get_test_examples(self, data_dir): 109 | """Gets a collection of `InputExample`s for prediction.""" 110 | raise NotImplementedError() 111 | 112 | def get_labels(self): 113 | """Gets the list of labels for this data set.""" 114 | raise NotImplementedError() 115 | 116 | @classmethod 117 | def _read_tsv(cls, input_file, quotechar=None): 118 | """Reads a tab separated value file.""" 119 | with tf.gfile.Open(input_file, "r") as f: 120 | reader = csv.reader(f, delimiter="\t", quotechar=quotechar) 121 | lines = [] 122 | for line in reader: 123 | lines.append(line) 124 | return lines 125 | 126 | @classmethod 127 | def _read_csv(cls,input_file): 128 | """Reads a tab separated value file.""" 129 | df = load_csv(input_file,header=0).fillna('|') 130 | jobcontent = df['content'].tolist() 131 | jlabel = df.loc[:,hp.label_vocabulary].values 132 | lines = [[jlabel[i],jobcontent[i]] for i in range(len(jlabel)) if type(jobcontent[i])==str] 133 | lines2 = shuffle_one(lines) 134 | print('Read csv finished!(1)') 135 | print('Head data:',lines2[0:5]) 136 | print('Length of data:',len(lines2)) 137 | return lines2 138 | 139 | 140 | class ClassifyProcessor(DataProcessor): 141 | """Processor for the MRPC data set (GLUE version).""" 142 | 143 | def __init__(self): 144 | self.labels = set() 145 | 146 | def get_train_examples(self, data_dir): 147 | """See base class.""" 148 | return self._create_examples( 149 | self._read_csv(os.path.join(data_dir, hp.train_data)), "train") 150 | 151 | def get_dev_examples(self, data_dir): 152 | """See base class.""" 153 | return self._create_examples( 154 | self._read_tsv(os.path.join(data_dir, hp.test_data)), "dev") 155 | 156 | def get_test_examples(self, data_dir): 157 | """See base class.""" 158 | return self._create_examples( 159 | self._read_tsv(os.path.join(data_dir, hp.test_data)), "test") 160 | 161 | def get_labels(self): 162 | """See base class.""" 163 | return list(hp.dict_id2label.keys()) 164 | 165 | def _create_examples(self, lines, set_type): 166 | """Creates examples for the training and dev sets.""" 167 | examples = [] 168 | for (i, line) in enumerate(lines): 169 | guid = "%s-%s" % (set_type, i) 170 | text_a = tokenization.convert_to_unicode(line[1]) 171 | label = tokenization.convert_to_unicode(line[0]) 172 | #self.labels.add(label) 173 | # by chenming 174 | for l in label: 175 | self.labels.add(l) 176 | examples.append( 177 | InputExample(guid=guid, text_a=text_a, text_b=None, label=label)) 178 | return examples 179 | 180 | 181 | 182 | def convert_single_example(ex_index, example, label_list, max_seq_length, 183 | tokenizer, task_name): 184 | """Converts a single `InputExample` into a single `InputFeatures`.""" 185 | 186 | # by chenming(Multi-Label) 187 | task_name = "sts-b" 188 | 189 | if isinstance(example, PaddingInputExample): 190 | return InputFeatures( 191 | input_ids=[0] * max_seq_length, 192 | input_mask=[0] * max_seq_length, 193 | segment_ids=[0] * max_seq_length, 194 | label_id=0, 195 | is_real_example=False) 196 | 197 | if task_name != "sts-b": 198 | label_map = {} 199 | for (i, label) in enumerate(label_list): 200 | label_map[label] = i 201 | 202 | tokens_a = tokenizer.tokenize(example.text_a) 203 | tokens_b = None 204 | if example.text_b: 205 | tokens_b = tokenizer.tokenize(example.text_b) 206 | 207 | if tokens_b: 208 | # Modifies `tokens_a` and `tokens_b` in place so that the total 209 | # length is less than the specified length. 210 | # Account for [CLS], [SEP], [SEP] with "- 3" 211 | _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) 212 | else: 213 | # Account for [CLS] and [SEP] with "- 2" 214 | if len(tokens_a) > max_seq_length - 2: 215 | tokens_a = tokens_a[0:(max_seq_length - 2)] 216 | 217 | # The convention in ALBERT is: 218 | # (a) For sequence pairs: 219 | # tokens: [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP] 220 | # type_ids: 0 0 0 0 0 0 0 0 1 1 1 1 1 1 221 | # (b) For single sequences: 222 | # tokens: [CLS] the dog is hairy . [SEP] 223 | # type_ids: 0 0 0 0 0 0 0 224 | # 225 | # Where "type_ids" are used to indicate whether this is the first 226 | # sequence or the second sequence. The embedding vectors for `type=0` and 227 | # `type=1` were learned during pre-training and are added to the 228 | # embedding vector (and position vector). This is not *strictly* necessary 229 | # since the [SEP] token unambiguously separates the sequences, but it makes 230 | # it easier for the model to learn the concept of sequences. 231 | # 232 | # For classification tasks, the first vector (corresponding to [CLS]) is 233 | # used as the "sentence vector". Note that this only makes sense because 234 | # the entire model is fine-tuned. 235 | tokens = [] 236 | segment_ids = [] 237 | tokens.append("[CLS]") 238 | segment_ids.append(0) 239 | for token in tokens_a: 240 | tokens.append(token) 241 | segment_ids.append(0) 242 | tokens.append("[SEP]") 243 | segment_ids.append(0) 244 | 245 | if tokens_b: 246 | for token in tokens_b: 247 | tokens.append(token) 248 | segment_ids.append(1) 249 | tokens.append("[SEP]") 250 | segment_ids.append(1) 251 | 252 | input_ids = tokenizer.convert_tokens_to_ids(tokens) 253 | 254 | # The mask has 1 for real tokens and 0 for padding tokens. Only real 255 | # tokens are attended to. 256 | input_mask = [1] * len(input_ids) 257 | 258 | # Zero-pad up to the sequence length. 259 | while len(input_ids) < max_seq_length: 260 | input_ids.append(0) 261 | input_mask.append(0) 262 | segment_ids.append(0) 263 | 264 | assert len(input_ids) == max_seq_length 265 | assert len(input_mask) == max_seq_length 266 | assert len(segment_ids) == max_seq_length 267 | 268 | if task_name != "sts-b": 269 | label_id = label_map[example.label] 270 | else: 271 | label_id = example.label 272 | 273 | # if ex_index < 5: 274 | # tf.logging.info("*** Example ***") 275 | # tf.logging.info("guid: %s" % (example.guid)) 276 | # tf.logging.info("tokens: %s" % " ".join( 277 | # [tokenization.printable_text(x) for x in tokens])) 278 | # tf.logging.info("input_ids: %s" % " ".join([str(x) for x in input_ids])) 279 | # tf.logging.info("input_mask: %s" % " ".join([str(x) for x in input_mask])) 280 | # tf.logging.info("segment_ids: %s" % " ".join([str(x) for x in segment_ids])) 281 | # tf.logging.info("label: %s (id = %d)" % (example.label, label_id)) 282 | 283 | feature = InputFeatures( 284 | input_ids=input_ids, 285 | input_mask=input_mask, 286 | segment_ids=segment_ids, 287 | label_id=label_id, 288 | is_real_example=True) 289 | return feature 290 | 291 | 292 | def file_based_convert_examples_to_features( 293 | examples, label_list, max_seq_length, tokenizer, output_file, task_name): 294 | """Convert a set of `InputExample`s to a TFRecord file.""" 295 | 296 | writer = tf.python_io.TFRecordWriter(output_file) 297 | 298 | for (ex_index, example) in enumerate(examples): 299 | if ex_index % 10000 == 0: 300 | tf.logging.info("Writing example %d of %d" % (ex_index, len(examples))) 301 | 302 | feature = convert_single_example(ex_index, example, label_list, 303 | max_seq_length, tokenizer, task_name) 304 | 305 | def create_int_feature(values): 306 | f = tf.train.Feature(int64_list=tf.train.Int64List(value=list(values))) 307 | return f 308 | 309 | def create_float_feature(values): 310 | f = tf.train.Feature(float_list=tf.train.FloatList(value=list(values))) 311 | return f 312 | 313 | features = collections.OrderedDict() 314 | features["input_ids"] = create_int_feature(feature.input_ids) 315 | features["input_mask"] = create_int_feature(feature.input_mask) 316 | features["segment_ids"] = create_int_feature(feature.segment_ids) 317 | features["label_ids"] = create_float_feature([feature.label_id])\ 318 | if task_name == "sts-b" else create_int_feature([feature.label_id]) 319 | features["is_real_example"] = create_int_feature( 320 | [int(feature.is_real_example)]) 321 | 322 | tf_example = tf.train.Example(features=tf.train.Features(feature=features)) 323 | writer.write(tf_example.SerializeToString()) 324 | writer.close() 325 | 326 | 327 | def file_based_input_fn_builder(input_file, seq_length, is_training, 328 | drop_remainder, task_name, use_tpu, bsz, 329 | multiple=1): 330 | """Creates an `input_fn` closure to be passed to TPUEstimator.""" 331 | labeltype = tf.float32 if task_name == "sts-b" else tf.int64 332 | 333 | name_to_features = { 334 | "input_ids": tf.FixedLenFeature([seq_length * multiple], tf.int64), 335 | "input_mask": tf.FixedLenFeature([seq_length * multiple], tf.int64), 336 | "segment_ids": tf.FixedLenFeature([seq_length * multiple], tf.int64), 337 | "label_ids": tf.FixedLenFeature([], labeltype), 338 | "is_real_example": tf.FixedLenFeature([], tf.int64), 339 | } 340 | 341 | def _decode_record(record, name_to_features): 342 | """Decodes a record to a TensorFlow example.""" 343 | example = tf.parse_single_example(record, name_to_features) 344 | 345 | # tf.Example only supports tf.int64, but the TPU only supports tf.int32. 346 | # So cast all int64 to int32. 347 | for name in list(example.keys()): 348 | t = example[name] 349 | if t.dtype == tf.int64: 350 | t = tf.to_int32(t) 351 | example[name] = t 352 | 353 | return example 354 | 355 | def input_fn(params): 356 | """The actual input function.""" 357 | if use_tpu: 358 | batch_size = params["batch_size"] 359 | else: 360 | batch_size = bsz 361 | 362 | # For training, we want a lot of parallel reading and shuffling. 363 | # For eval, we want no shuffling and parallel reading doesn't matter. 364 | d = tf.data.TFRecordDataset(input_file) 365 | if is_training: 366 | d = d.repeat() 367 | d = d.shuffle(buffer_size=100) 368 | 369 | d = d.apply( 370 | contrib_data.map_and_batch( 371 | lambda record: _decode_record(record, name_to_features), 372 | batch_size=batch_size, 373 | drop_remainder=drop_remainder)) 374 | 375 | return d 376 | 377 | return input_fn 378 | 379 | 380 | def _truncate_seq_pair(tokens_a, tokens_b, max_length): 381 | """Truncates a sequence pair in place to the maximum length.""" 382 | 383 | # This is a simple heuristic which will always truncate the longer sequence 384 | # one token at a time. This makes more sense than truncating an equal percent 385 | # of tokens from each, since if one sequence is very short then each token 386 | # that's truncated likely contains more information than a longer sequence. 387 | while True: 388 | total_length = len(tokens_a) + len(tokens_b) 389 | if total_length <= max_length: 390 | break 391 | if len(tokens_a) > len(tokens_b): 392 | tokens_a.pop() 393 | else: 394 | tokens_b.pop() 395 | 396 | 397 | def _create_model_from_hub(hub_module, is_training, input_ids, input_mask, 398 | segment_ids): 399 | """Creates an ALBERT model from TF-Hub.""" 400 | tags = set() 401 | if is_training: 402 | tags.add("train") 403 | albert_module = hub.Module(hub_module, tags=tags, trainable=True) 404 | albert_inputs = dict( 405 | input_ids=input_ids, 406 | input_mask=input_mask, 407 | segment_ids=segment_ids) 408 | albert_outputs = albert_module( 409 | inputs=albert_inputs, 410 | signature="tokens", 411 | as_dict=True) 412 | output_layer = albert_outputs["pooled_output"] 413 | return output_layer 414 | 415 | 416 | def _create_model_from_scratch(albert_config, is_training, input_ids, 417 | input_mask, segment_ids, use_one_hot_embeddings): 418 | """Creates an ALBERT model from scratch (as opposed to hub).""" 419 | model = modeling.AlbertModel( 420 | config=albert_config, 421 | is_training=is_training, 422 | input_ids=input_ids, 423 | input_mask=input_mask, 424 | token_type_ids=segment_ids, 425 | use_one_hot_embeddings=use_one_hot_embeddings) 426 | output_layer = model.get_pooled_output() 427 | return output_layer 428 | 429 | 430 | def create_model(albert_config, is_training, input_ids, input_mask, segment_ids, 431 | labels, num_labels, use_one_hot_embeddings, task_name, 432 | hub_module): 433 | """Creates a classification model.""" 434 | if hub_module: 435 | tf.logging.info("creating model from hub_module: %s", hub_module) 436 | output_layer = _create_model_from_hub(hub_module, is_training, input_ids, 437 | input_mask, segment_ids) 438 | else: 439 | tf.logging.info("creating model from albert_config") 440 | output_layer = _create_model_from_scratch(albert_config, is_training, 441 | input_ids, input_mask, 442 | segment_ids, 443 | use_one_hot_embeddings) 444 | 445 | hidden_size = output_layer.shape[-1].value 446 | 447 | output_weights = tf.get_variable( 448 | "output_weights", [num_labels, hidden_size], 449 | initializer=tf.truncated_normal_initializer(stddev=0.02)) 450 | 451 | output_bias = tf.get_variable( 452 | "output_bias", [num_labels], initializer=tf.zeros_initializer()) 453 | 454 | with tf.variable_scope("loss"): 455 | if is_training: 456 | # I.e., 0.1 dropout 457 | output_layer = tf.nn.dropout(output_layer, keep_prob=0.9) 458 | 459 | logits = tf.matmul(output_layer, output_weights, transpose_b=True) 460 | logits = tf.nn.bias_add(logits, output_bias) 461 | if task_name != "sts-b": # softmax(Multi-Class) 462 | probabilities = tf.nn.softmax(logits, axis=-1) 463 | predictions = tf.argmax(probabilities, axis=-1, output_type=tf.int32) 464 | log_probs = tf.nn.log_softmax(logits, axis=-1) 465 | one_hot_labels = tf.one_hot(labels, depth=num_labels, dtype=tf.float32) 466 | per_example_loss = -tf.reduce_sum(one_hot_labels * log_probs, axis=-1) 467 | else: 468 | probabilities = logits 469 | logits = tf.squeeze(logits, [-1]) 470 | predictions = logits 471 | per_example_loss = tf.square(logits - labels) 472 | loss = tf.reduce_mean(per_example_loss) 473 | 474 | return (loss, per_example_loss, probabilities, logits, predictions) 475 | 476 | 477 | def model_fn_builder(albert_config, num_labels, init_checkpoint, learning_rate, 478 | num_train_steps, num_warmup_steps, use_tpu, 479 | use_one_hot_embeddings, task_name, hub_module=None, 480 | optimizer="adamw"): 481 | """Returns `model_fn` closure for TPUEstimator.""" 482 | 483 | def model_fn(features, labels, mode, params): # pylint: disable=unused-argument 484 | """The `model_fn` for TPUEstimator.""" 485 | 486 | tf.logging.info("*** Features ***") 487 | for name in sorted(features.keys()): 488 | tf.logging.info(" name = %s, shape = %s" % (name, features[name].shape)) 489 | 490 | input_ids = features["input_ids"] 491 | input_mask = features["input_mask"] 492 | segment_ids = features["segment_ids"] 493 | label_ids = features["label_ids"] 494 | is_real_example = None 495 | if "is_real_example" in features: 496 | is_real_example = tf.cast(features["is_real_example"], dtype=tf.float32) 497 | else: 498 | is_real_example = tf.ones(tf.shape(label_ids), dtype=tf.float32) 499 | 500 | is_training = (mode == tf.estimator.ModeKeys.TRAIN) 501 | 502 | (total_loss, per_example_loss, probabilities, logits, predictions) = \ 503 | create_model(albert_config, is_training, input_ids, input_mask, 504 | segment_ids, label_ids, num_labels, 505 | use_one_hot_embeddings, task_name, hub_module) 506 | 507 | tvars = tf.trainable_variables() 508 | initialized_variable_names = {} 509 | scaffold_fn = None 510 | if init_checkpoint: 511 | (assignment_map, initialized_variable_names 512 | ) = modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint) 513 | if use_tpu: 514 | 515 | def tpu_scaffold(): 516 | tf.train.init_from_checkpoint(init_checkpoint, assignment_map) 517 | return tf.train.Scaffold() 518 | 519 | scaffold_fn = tpu_scaffold 520 | else: 521 | tf.train.init_from_checkpoint(init_checkpoint, assignment_map) 522 | 523 | tf.logging.info("**** Trainable Variables ****") 524 | for var in tvars: 525 | init_string = "" 526 | if var.name in initialized_variable_names: 527 | init_string = ", *INIT_FROM_CKPT*" 528 | tf.logging.info(" name = %s, shape = %s%s", var.name, var.shape, 529 | init_string) 530 | 531 | output_spec = None 532 | if mode == tf.estimator.ModeKeys.TRAIN: 533 | 534 | train_op = optimization.create_optimizer( 535 | total_loss, learning_rate, num_train_steps, num_warmup_steps, 536 | use_tpu, optimizer) 537 | 538 | output_spec = contrib_tpu.TPUEstimatorSpec( 539 | mode=mode, 540 | loss=total_loss, 541 | train_op=train_op, 542 | scaffold_fn=scaffold_fn) 543 | elif mode == tf.estimator.ModeKeys.EVAL: 544 | if task_name not in ["sts-b", "cola"]: 545 | def metric_fn(per_example_loss, label_ids, logits, is_real_example): 546 | predictions = tf.argmax(logits, axis=-1, output_type=tf.int32) 547 | accuracy = tf.metrics.accuracy( 548 | labels=label_ids, predictions=predictions, 549 | weights=is_real_example) 550 | loss = tf.metrics.mean( 551 | values=per_example_loss, weights=is_real_example) 552 | return { 553 | "eval_accuracy": accuracy, 554 | "eval_loss": loss, 555 | } 556 | elif task_name == "sts-b": 557 | def metric_fn(per_example_loss, label_ids, logits, is_real_example): 558 | """Compute Pearson correlations for STS-B.""" 559 | # Display labels and predictions 560 | concat1 = contrib_metrics.streaming_concat(logits) 561 | concat2 = contrib_metrics.streaming_concat(label_ids) 562 | 563 | # Compute Pearson correlation 564 | pearson = contrib_metrics.streaming_pearson_correlation( 565 | logits, label_ids, weights=is_real_example) 566 | 567 | # Compute MSE 568 | # mse = tf.metrics.mean(per_example_loss) 569 | mse = tf.metrics.mean_squared_error( 570 | label_ids, logits, weights=is_real_example) 571 | 572 | loss = tf.metrics.mean( 573 | values=per_example_loss, 574 | weights=is_real_example) 575 | 576 | return {"pred": concat1, "label_ids": concat2, "pearson": pearson, 577 | "MSE": mse, "eval_loss": loss,} 578 | elif task_name == "cola": 579 | def metric_fn(per_example_loss, label_ids, logits, is_real_example): 580 | """Compute Matthew's correlations for STS-B.""" 581 | predictions = tf.argmax(logits, axis=-1, output_type=tf.int32) 582 | # https://en.wikipedia.org/wiki/Matthews_correlation_coefficient 583 | tp, tp_op = tf.metrics.true_positives( 584 | predictions, label_ids, weights=is_real_example) 585 | tn, tn_op = tf.metrics.true_negatives( 586 | predictions, label_ids, weights=is_real_example) 587 | fp, fp_op = tf.metrics.false_positives( 588 | predictions, label_ids, weights=is_real_example) 589 | fn, fn_op = tf.metrics.false_negatives( 590 | predictions, label_ids, weights=is_real_example) 591 | 592 | # Compute Matthew's correlation 593 | mcc = tf.div_no_nan( 594 | tp * tn - fp * fn, 595 | tf.pow((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn), 0.5)) 596 | 597 | # Compute accuracy 598 | accuracy = tf.metrics.accuracy( 599 | labels=label_ids, predictions=predictions, 600 | weights=is_real_example) 601 | 602 | loss = tf.metrics.mean( 603 | values=per_example_loss, 604 | weights=is_real_example) 605 | 606 | return {"matthew_corr": (mcc, tf.group(tp_op, tn_op, fp_op, fn_op)), 607 | "eval_accuracy": accuracy, "eval_loss": loss,} 608 | 609 | eval_metrics = (metric_fn, 610 | [per_example_loss, label_ids, logits, is_real_example]) 611 | output_spec = contrib_tpu.TPUEstimatorSpec( 612 | mode=mode, 613 | loss=total_loss, 614 | eval_metrics=eval_metrics, 615 | scaffold_fn=scaffold_fn) 616 | else: 617 | output_spec = contrib_tpu.TPUEstimatorSpec( 618 | mode=mode, 619 | predictions={ 620 | "probabilities": probabilities, 621 | "predictions": predictions 622 | }, 623 | scaffold_fn=scaffold_fn) 624 | return output_spec 625 | 626 | return model_fn 627 | 628 | 629 | # This function is not used by this file but is still used by the Colab and 630 | # people who depend on it. 631 | def input_fn_builder(features, seq_length, is_training, drop_remainder): 632 | """Creates an `input_fn` closure to be passed to TPUEstimator.""" 633 | 634 | all_input_ids = [] 635 | all_input_mask = [] 636 | all_segment_ids = [] 637 | all_label_ids = [] 638 | 639 | for feature in features: 640 | all_input_ids.append(feature.input_ids) 641 | all_input_mask.append(feature.input_mask) 642 | all_segment_ids.append(feature.segment_ids) 643 | all_label_ids.append(feature.label_id) 644 | 645 | def input_fn(params): 646 | """The actual input function.""" 647 | batch_size = params["batch_size"] 648 | 649 | num_examples = len(features) 650 | 651 | # This is for demo purposes and does NOT scale to large data sets. We do 652 | # not use Dataset.from_generator() because that uses tf.py_func which is 653 | # not TPU compatible. The right way to load data is with TFRecordReader. 654 | d = tf.data.Dataset.from_tensor_slices({ 655 | "input_ids": 656 | tf.constant( 657 | all_input_ids, shape=[num_examples, seq_length], 658 | dtype=tf.int32), 659 | "input_mask": 660 | tf.constant( 661 | all_input_mask, 662 | shape=[num_examples, seq_length], 663 | dtype=tf.int32), 664 | "segment_ids": 665 | tf.constant( 666 | all_segment_ids, 667 | shape=[num_examples, seq_length], 668 | dtype=tf.int32), 669 | "label_ids": 670 | tf.constant(all_label_ids, shape=[num_examples], dtype=tf.int32), 671 | }) 672 | 673 | if is_training: 674 | d = d.repeat() 675 | d = d.shuffle(buffer_size=100) 676 | 677 | d = d.batch(batch_size=batch_size, drop_remainder=drop_remainder) 678 | return d 679 | 680 | return input_fn 681 | 682 | 683 | # This function is not used by this file but is still used by the Colab and 684 | # people who depend on it. 685 | def convert_examples_to_features(examples, label_list, max_seq_length, 686 | tokenizer, task_name): 687 | """Convert a set of `InputExample`s to a list of `InputFeatures`.""" 688 | 689 | features = [] 690 | for (ex_index, example) in enumerate(examples): 691 | if ex_index % 10000 == 0: 692 | print("Writing example %d of %d" % (ex_index, len(examples))) 693 | feature = convert_single_example(ex_index, example, label_list, 694 | max_seq_length, tokenizer, task_name) 695 | features.append(feature) 696 | return features 697 | 698 | 699 | 700 | # Load parameters 701 | max_seq_length = hp.sequence_length 702 | do_lower_case = hp.do_lower_case 703 | vocab_file = hp.vocab_file 704 | tokenizer = tokenization.FullTokenizer.from_scratch(vocab_file=vocab_file, 705 | do_lower_case=do_lower_case, 706 | spm_model_file=None) 707 | processor = ClassifyProcessor() 708 | label_list = processor.get_labels() 709 | data_dir = hp.data_dir 710 | 711 | 712 | def get_features(): 713 | # Load train data 714 | train_examples = processor.get_train_examples(data_dir) 715 | # Get onehot feature 716 | features = convert_examples_to_features( train_examples, label_list, max_seq_length, tokenizer,task_name='classify') 717 | input_ids = [f.input_ids for f in features] 718 | input_masks = [f.input_mask for f in features] 719 | segment_ids = [f.segment_ids for f in features] 720 | label_ids = [f.label_id for f in features] 721 | print('Get features finished!') 722 | return input_ids,input_masks,segment_ids,label_ids 723 | 724 | def get_features_test(): 725 | # Load test data 726 | train_examples = processor.get_test_examples(data_dir) 727 | # Get onehot feature 728 | features = convert_examples_to_features( train_examples, label_list, max_seq_length, tokenizer,task_name='classify_test') 729 | input_ids = [f.input_ids for f in features] 730 | input_masks = [f.input_mask for f in features] 731 | segment_ids = [f.segment_ids for f in features] 732 | label_ids = [f.label_id for f in features] 733 | print('Get features(test) finished!') 734 | return input_ids,input_masks,segment_ids,label_ids 735 | 736 | 737 | def create_example(line,set_type): 738 | """Creates examples for the training and dev sets.""" 739 | guid = "%s-%s" % (set_type, 1) 740 | text_a = tokenization.convert_to_unicode(line[1]) 741 | label = tokenization.convert_to_unicode(line[0]) 742 | example = InputExample(guid=guid, text_a=text_a, text_b=None, label=label) 743 | return example 744 | 745 | def get_feature_test(sentence): 746 | example = create_example(['0',sentence],'test') 747 | feature = convert_single_example(0, example, label_list,max_seq_length, tokenizer,task_name='classify') 748 | return feature.input_ids,feature.input_mask,feature.segment_ids,feature.label_id 749 | 750 | 751 | if __name__ == '__main__': 752 | # 753 | sentence = '天天向上' 754 | feature = get_feature_test(sentence) 755 | 756 | 757 | 758 | 759 | 760 | -------------------------------------------------------------------------------- /data/test.csv: -------------------------------------------------------------------------------- 1 | content,label 2 | 也没有烧伤,| 3 | 主要是之前在北方呆过几几年,| 4 | 温暖过冬,| 5 | 开箱试了一下看着很不错,| 6 | 还可以给宝宝凉衣服,| 7 | 快递师傅送货迅速,| 8 | 快递员差评,| 9 | 沒有什么噪音,| 10 | 就感觉有一部分是强行捏到一起去的,| 11 | 今天刚到货还没有使用,| 12 | 后面还有个设计,| 13 | 非常好用的宝贝,| 14 | 这个开关我是一直没搞懂,| 15 | 该有的功能都有了,| 16 | 我只说一次,| 17 | 收到l等了15天,| 18 | 所以系统默认好评,| 19 | 但是卖家很快就补发了,| 20 | 现在过了二十天了也没退,| 21 | 冬天有她很暖和,制热效果 22 | 收到第一时间体验,| 23 | 刮花了,| 24 | 也不知道耗不好点,| 25 | 表扬一下物流服务,| 26 | 外观设计:不站地方,| 27 | 换了小米,| 28 | 总之我自己用下来,| 29 | 价格实惠量又足,| 30 | 耗电情况:整体来说耗电不是特别严重,产品功耗 31 | 小兔兔外型还不错了,| 32 | 其他特色:利用小爱或者APP可以智能设定,| 33 | 京东的服务超好,| 34 | 这款取暖器已经买两个了,| 35 | 吹到的风才暖,| 36 | 来的及时,| 37 | 全网最便宜,| 38 | 错过了7天试用,| 39 | 连几十块钱的取暖器都不如,| 40 | 家里有小朋友的建议还是使用空调,| 41 | 定时升温控制都很简单,| 42 | 用了近一月,| 43 | 南方湿冷的冬天是很不错的选择,| 44 | 但是我刚开始使用确实有味道,味道 45 | 说是也转到人工客服,| 46 | 只有在旁边能暖一点,| 47 | 2、结构简单,| 48 | 是想象的模样,| 49 | 1档热风和2档热风,| 50 | 除了物流慢一点点,| 51 | 5分钟屋里就暖和了,制热效果 52 | 碰到长期下雨阴雨的天气,| 53 | 颜色比图片还好看,| 54 | 用起来还可以吧,| 55 | 就是感觉大了一点,| 56 | 安全设计:反正加热中我是一点不敢碰的,| 57 | 尤其自营商品,| 58 | 实在是感人,| 59 | 到现在按钮也开不起,| 60 | 开关,| 61 | 放在客房足够用,| 62 | 一会就暖和,制热效果 63 | 回来赶快拆了包装试下效果,| 64 | 性价比还好吧,| 65 | 使用过后一段时间感觉还不错,| 66 | 但是售后服务太差,| 67 | *元多一点,| 68 | 一如既往地又快又好呀,| 69 | 给东北老家买的,| 70 | 再过几分钟就感觉到开始散热了,| 71 | 整个屋子都很暖静音效果:完全没有声音,声音 72 | 比老款要美观,| 73 | 是划线价的一半多一丢丢,| 74 | 这次再买个送婆婆,| 75 | 小太阳完胜,| 76 | 最重要的是,| 77 | 功能强大发热快,制热效果 78 | 塑料味道太大,味道 79 | 格力原装产品,| 80 | 取暖效果:取暖好,制热效果 81 | 是我们买的第二个了,| 82 | 需要个人安装的也不多,| 83 | 没有任何破损的地方,| 84 | 自营售后服务品质有保障,| 85 | 南方冬天暖气是刚需啊,| 86 | 用了两次我和孩子都喉咙发炎了,| 87 | 十几平方的房间开一夜都没觉得有效果,| 88 | 概念&rdquo,| 89 | 外观设计:很不错取暖效果:很好耗电情况:蛮高的静音效果:很不错安全设计:安全其他特色:没有了,产品功耗/制热效果 90 | 我觉得效果比浴霸好,制热效果 91 | 可根据需要选择功率,| 92 | 身边的朋友都有介绍此款,| 93 | 安全设计:设计独特完美,| 94 | 总体来说物美价廉,| 95 | 放书桌旁干活用很合适,| 96 | 如果属于南方的没暖气喜欢的宝宝可以放心购买,| 97 | 还赠送了晾衣架,| 98 | 贴墙放置,| 99 | 非常的美观还耐用,| 100 | 买了你就后悔,| 101 | 说24小时回电话,| 102 | 家里离不开了,| 103 | 估计到冬天没太阳的时候烘衣服方便,| 104 | 十天用了一百六的电费,产品功耗 105 | 有遥控装置,| 106 | 体积虽小,| 107 | 不把手凑上去都感觉不到他的温度,| 108 | 用了两天的,| 109 | 小房间很快就能加热,| 110 | 当天下单第二天就到,| 111 | 加热很快??,| 112 | 静音效果:二档声音不小,| 113 | 也没有长螺丝刀,| 114 | 平均每天就开两小时,| 115 | 但声音还能接受,| 116 | 静音效果:试用了几天,| 117 | 取暖效果:开到二挡很暖和,制热效果 118 | 今年给老人又买一个,| 119 | 看着小,| 120 | 外形取暖效果:超级棒耗电情况:没在意静音效果:安静舒适安全设计:童锁设计省心,产品功耗 121 | 不知道能不能优化,| 122 | 货到手没有一点损坏,| 123 | 今天就到货了,| 124 | 希望要买好好选别家的,| 125 | 利用热空气上升的自然流动,| 126 | 长的差不多也是200左右,| 127 | 现在穿着睡衣,| 128 | 产品的质量不错,| 129 | 希望能耐用,| 130 | 再也不用担心受凉感冒了,| 131 | 一晚耗电30不是梦,产品功耗 132 | 还会有电火花闪现,| 133 | 有一定的防水,| 134 | 双十二特价很便宜,| 135 | 但是一个小时两度电也是真的,| 136 | 在广州用很合适,| 137 | 大人小朋友不容易烫伤,| 138 | 选择小米唯一吸引我的是支持小爱和手机,| 139 | 着实让我觉得有点生气,| 140 | 主要是家里没有安装暖气片,| 141 | 好多人都和我一样的情况,| 142 | 有风扇设计所致,| 143 | 外观设计:很好看取暖效果:取暖很快效果很好,制热效果 144 | 强卖,| 145 | 还危险了,| 146 | 必须要贴着机器才有点温度,| 147 | 耐用也就有安全保障其他特色:冬天衣服难干,| 148 | 时不时的会咔的一声响,| 149 | 就算开低档温度也不错,| 150 | 挺适合南方的冬天,| 151 | 这东西买的真好,| 152 | -------------------------------------------------------------------------------- /data/test_onehot.csv: -------------------------------------------------------------------------------- 1 | content,|,互联互通,产品功耗,滑轮提手,声音,APP操控性,呼吸灯,外观,底座,制热范围,遥控器电池,味道,制热效果,衣物烘干,体积大小 2 | 也没有烧伤,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 3 | 主要是之前在北方呆过几几年,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 4 | 温暖过冬,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 5 | 开箱试了一下看着很不错,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 6 | 还可以给宝宝凉衣服,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 7 | 快递师傅送货迅速,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8 | 快递员差评,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 9 | 沒有什么噪音,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 10 | 就感觉有一部分是强行捏到一起去的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 11 | 今天刚到货还没有使用,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 12 | 后面还有个设计,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 13 | 非常好用的宝贝,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14 | 这个开关我是一直没搞懂,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15 | 该有的功能都有了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 我只说一次,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 17 | 收到l等了15天,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 18 | 所以系统默认好评,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 19 | 但是卖家很快就补发了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 20 | 现在过了二十天了也没退,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 21 | 冬天有她很暖和,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 22 | 收到第一时间体验,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 23 | 刮花了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 24 | 也不知道耗不好点,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 25 | 表扬一下物流服务,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 26 | 外观设计:不站地方,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 27 | 换了小米,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 28 | 总之我自己用下来,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 29 | 价格实惠量又足,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 30 | 耗电情况:整体来说耗电不是特别严重,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 31 | 小兔兔外型还不错了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 32 | 其他特色:利用小爱或者APP可以智能设定,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 33 | 京东的服务超好,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 34 | 这款取暖器已经买两个了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 35 | 吹到的风才暖,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 36 | 来的及时,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 37 | 全网最便宜,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 38 | 错过了7天试用,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 39 | 连几十块钱的取暖器都不如,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 40 | 家里有小朋友的建议还是使用空调,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 41 | 定时升温控制都很简单,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 42 | 用了近一月,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 43 | 南方湿冷的冬天是很不错的选择,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 44 | 但是我刚开始使用确实有味道,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 45 | 说是也转到人工客服,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 46 | 只有在旁边能暖一点,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 47 | 2、结构简单,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 48 | 是想象的模样,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 49 | 1档热风和2档热风,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 50 | 除了物流慢一点点,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 51 | 5分钟屋里就暖和了,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 52 | 碰到长期下雨阴雨的天气,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 53 | 颜色比图片还好看,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 54 | 用起来还可以吧,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 55 | 就是感觉大了一点,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 56 | 安全设计:反正加热中我是一点不敢碰的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 57 | 尤其自营商品,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 58 | 实在是感人,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 59 | 到现在按钮也开不起,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 60 | 开关,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 61 | 放在客房足够用,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 62 | 一会就暖和,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 63 | 回来赶快拆了包装试下效果,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 64 | 性价比还好吧,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 65 | 使用过后一段时间感觉还不错,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 66 | 但是售后服务太差,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 67 | *元多一点,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 68 | 一如既往地又快又好呀,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 69 | 给东北老家买的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 70 | 再过几分钟就感觉到开始散热了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 71 | 整个屋子都很暖静音效果:完全没有声音,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0 72 | 比老款要美观,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 73 | 是划线价的一半多一丢丢,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 74 | 这次再买个送婆婆,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 75 | 小太阳完胜,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 76 | 最重要的是,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 77 | 功能强大发热快,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 78 | 塑料味道太大,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 79 | 格力原装产品,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 80 | 取暖效果:取暖好,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 81 | 是我们买的第二个了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 82 | 需要个人安装的也不多,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 83 | 没有任何破损的地方,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 84 | 自营售后服务品质有保障,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 85 | 南方冬天暖气是刚需啊,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 86 | 用了两次我和孩子都喉咙发炎了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 87 | 十几平方的房间开一夜都没觉得有效果,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 88 | 概念&rdquo,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 89 | 外观设计:很不错取暖效果:很好耗电情况:蛮高的静音效果:很不错安全设计:安全其他特色:没有了,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0 90 | 我觉得效果比浴霸好,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 91 | 可根据需要选择功率,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 92 | 身边的朋友都有介绍此款,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 93 | 安全设计:设计独特完美,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 94 | 总体来说物美价廉,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 95 | 放书桌旁干活用很合适,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 96 | 如果属于南方的没暖气喜欢的宝宝可以放心购买,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 97 | 还赠送了晾衣架,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 98 | 贴墙放置,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 99 | 非常的美观还耐用,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 100 | 买了你就后悔,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 101 | 说24小时回电话,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 102 | 家里离不开了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 103 | 估计到冬天没太阳的时候烘衣服方便,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 104 | 十天用了一百六的电费,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 105 | 有遥控装置,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 106 | 体积虽小,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 107 | 不把手凑上去都感觉不到他的温度,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 108 | 用了两天的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 109 | 小房间很快就能加热,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 110 | 当天下单第二天就到,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 111 | 加热很快??,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 112 | 静音效果:二档声音不小,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 113 | 也没有长螺丝刀,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 114 | 平均每天就开两小时,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 115 | 但声音还能接受,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 116 | 静音效果:试用了几天,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 117 | 取暖效果:开到二挡很暖和,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 118 | 今年给老人又买一个,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 119 | 看着小,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 120 | 外形取暖效果:超级棒耗电情况:没在意静音效果:安静舒适安全设计:童锁设计省心,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 121 | 不知道能不能优化,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 122 | 货到手没有一点损坏,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 123 | 今天就到货了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 124 | 希望要买好好选别家的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 125 | 利用热空气上升的自然流动,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 126 | 长的差不多也是200左右,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 127 | 现在穿着睡衣,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 128 | 产品的质量不错,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 129 | 希望能耐用,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 130 | 再也不用担心受凉感冒了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 131 | 一晚耗电30不是梦,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 132 | 还会有电火花闪现,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 133 | 有一定的防水,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 134 | 双十二特价很便宜,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 135 | 但是一个小时两度电也是真的,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 136 | 在广州用很合适,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 137 | 大人小朋友不容易烫伤,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 138 | 选择小米唯一吸引我的是支持小爱和手机,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 139 | 着实让我觉得有点生气,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 140 | 主要是家里没有安装暖气片,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 141 | 好多人都和我一样的情况,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 142 | 有风扇设计所致,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 143 | 外观设计:很好看取暖效果:取暖很快效果很好,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 144 | 强卖,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 145 | 还危险了,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 146 | 必须要贴着机器才有点温度,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 147 | 耐用也就有安全保障其他特色:冬天衣服难干,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 148 | 时不时的会咔的一声响,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 149 | 就算开低档温度也不错,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 150 | 挺适合南方的冬天,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 151 | 这东西买的真好,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 152 | -------------------------------------------------------------------------------- /data/train.csv: -------------------------------------------------------------------------------- 1 | content,label 2 | 我与京东已相识9年,| 3 | 真的无话可说,| 4 | 当然自营快递时效也是相当的高效,| 5 | 尾片温度高与宣传不符,| 6 | 物流很快性价比高,| 7 | 非常不错的小家电,| 8 | 温度太慢,| 9 | 具体怎么样不知道,| 10 | 车费都要花大几十,| 11 | 格力品牌质量就是好,| 12 | 想拒收就说要承担45元运费,| 13 | 太迷你了,| 14 | 取暖特别慢,| 15 | 不会再在这家店买第二次,| 16 | 可是使用了发现还挺好的,| 17 | 当天上午买下午就到了,| 18 | 这款电暖器在十多平的屋子里开的话,| 19 | 价格不是很贵,| 20 | 十万分感谢,| 21 | 一提起就自动停机,| 22 | 一个小时房间仍然没暖和,制热效果 23 | 把门窗闭实开到2200瓦30度的最高功率和最高温度,| 24 | 就是没有声音的,| 25 | 太恐怖??了,| 26 | 之前买了艾美特的踢脚线取暖器,| 27 | 耗电情况:这个没有注意,产品功耗 28 | 希望看见这条追评的朋友,| 29 | 冬天有的时候衣服没有干,| 30 | 最近变天我才开始用,| 31 | 取暖效果:热的很快静音效果:声音非常小,| 32 | 浴室洗澡必入,| 33 | 还不给退款,| 34 | 快递很棒,| 35 | 底座安装方便,| 36 | 就是有点小费电,产品功耗 37 | 静音效果优秀,| 38 | 开着坐旁边才能暖和,制热效果 39 | 烤手挺不错,| 40 | 使用时不同档位声音大小也不同总体来说声音控制的不错,| 41 | 尤其自营商品,| 42 | 关键是电暖器就得买好的品牌,| 43 | 转天就能送到,| 44 | 超级实用方便,| 45 | 水褥子,| 46 | 暖和安全设计:考虑周全,制热效果 47 | 在江苏老家使用,| 48 | 很满意的一次网购还没有用,| 49 | 自带加湿器,| 50 | 但是还是觉得心里不踏实,| 51 | 评价比较晚,| 52 | 安全模式很好,| 53 | 冬天换尿不湿,| 54 | 叠小放在衣架上,| 55 | 也许只有抱到烤都不知道那些好评是钱买来的吧,| 56 | 还能不下床调整温度,| 57 | 有小孩晾衣服也很方便,| 58 | 觉得暖风机和油酊各有优势,| 59 | 用了几天不错买家太度好,| 60 | 用起来就不会干燥,| 61 | 没有那么好用,| 62 | 就是纳闷京东还随机赠送东西给我:一条划痕+一根头发其他评论可以看看上面的一条评论,外观 63 | 这次又买了一台对衡式的,| 64 | 每天早上豆把宝宝衣服烤暖和给他穿非常棒,制热效果 65 | 非常管用,| 66 | 同款买的这是第三个,| 67 | 安全其他特色:加湿盒和架子很有用处哦刚买回来兴奋的取快递,| 68 | 两个小时升了5度,| 69 | 在接近20平方的房间效果明显,| 70 | 南方广东,| 71 | 听不到太大噪音,| 72 | 比电暖气好用一点,| 73 | 安全设计:之前是先买的智米,| 74 | 开一晚上也感觉不到热,制热效果 75 | 挺好的放卧室里面,| 76 | 电费在*元左右静音效果:静音效果还是相当棒的,产品功耗 77 | 还没开到过高档,| 78 | 外观设计:出几口从下出来更好,| 79 | 取暖效果:在小房间还可以,| 80 | 睡眠不好的会被惊醒,| 81 | 这个价格很值得,| 82 | 外观设计:土,| 83 | 整个卧室加温的效果要等正式使用后才能知道,| 84 | 手机端购买的上面没有这个退货条款,| 85 | 系列也不错,| 86 | 收到后使用几天才来评价,| 87 | 夏天用三个空调最高才三四百的电费啊,产品功耗 88 | 插电就能用,| 89 | 虽然是石英热管,| 90 | 东西效果不是很好打开半个小时手放在上面一点也不烫,制热效果 91 | 夏天不好放,| 92 | 慢慢的升温,| 93 | 你得等几个小时才会回复你呢,| 94 | 最先收到的一个快递,| 95 | 从另一间进来能感受到温差,| 96 | 一直从京东自营买,| 97 | 放在桌子上面吹的话会很方便很多,| 98 | 真是太恶心人了,| 99 | 适合面积小点的,| 100 | 京东自营发货速度快,| 101 | 放盒水效果更好,制热效果 102 | 基本够用,| 103 | 有股难闻的怪味,味道 104 | 可以走个好评,| 105 | 浴室里能增加温度,| 106 | 静音效果:不太行,| 107 | 有防倾倒断电装置,| 108 | 买了没什么作用,制热效果 109 | 什么时候给我退款,| 110 | 使用感受说不上好不好,| 111 | 没有想象中的好,| 112 | 除了制热慢和用电高之外几乎就无缺点可找了,制热效果 113 | 重口难调,| 114 | 供热稳定,| 115 | 上温较快,| 116 | 白色散热片看起来干净、耐脏,| 117 | 洗澡不冷了,| 118 | 根本就暖和不起来,制热效果 119 | 热量大外观设计:小巧,| 120 | 外观设计:漂亮取暖效果:非常差,制热效果 121 | 用的也非常好,| 122 | 属于小巧型的,| 123 | 售后说不报销维修费,| 124 | 希望都能让购物更便捷,| 125 | 不良商家,| 126 | 更重要的是家里的电线总跳闸,| 127 | 安全设计:初用感觉有味道,味道 128 | 这款电暖风体积小,| 129 | 不到1分钟,| 130 | 宝宝衣物及大人都可以挂两边或上面烘干,| 131 | 看不明白了都,| 132 | 这样富有人性化和亲和力的店家,| 133 | 开窗换气,| 134 | 先用着吧,| 135 | 静音效果:非常耗电,产品功耗 136 | 其他特色:理论上是比暖风机好用,| 137 | 和我心里想的一模一样,| 138 | 联系客服感觉比老百姓找政府还要难,| 139 | 灯也不亮了,| 140 | 在办公室用非常好,| 141 | 适合小范围适用,| 142 | 特适合15平方的南方使用,| 143 | 除了有点费电之外没有别的缺点,产品功耗 144 | 老婆很喜欢,| 145 | 遥控的很方便,| 146 | 送给父母冬天最好的礼物了,| 147 | 结果这么闹心,| 148 | 这些评价都是水军吗,| 149 | 京东物流一如既往的给力,| 150 | 后面再来追评吧,| 151 | 不知道取暖效果怎么样,| 152 | 耗电情况:哇哦,产品功耗 153 | 就开始升温了,| 154 | 她说要我去售后,| 155 | 买过很多产品了,| 156 | 开机两秒就热了,| 157 | 牢骚有点多只是一些建议,| 158 | 不太在意它了,| 159 | 一点都不热、而且超费电的,制热效果/产品功耗 160 | 样子挺好的,| 161 | 也不怎么占位置安全设计:没看到有发热红色的,| 162 | 放在卧室开最高档3小时,| 163 | 京东的东西当天买当天送到,| 164 | 真心觉得挺棒的,| 165 | 另外还有加湿功能暂时还没用,| 166 | 朋友买了tcl那款,| 167 | 前两张图是客厅的,| 168 | 非常方便便携,| 169 | 就是制热太慢,制热效果 170 | 顺便提高下室内温度,| 171 | 是目前快递里最满意的快递,| 172 | 已经被很多人夸了,| 173 | 无味好好好,| 174 | 连小太阳都比不上,| 175 | 说没声音的都是瞎扯,| 176 | 衣服一会儿就烘干了,| 177 | 但是需要加湿器屋里会偏干,| 178 | 非常不错的宝贝哟,| 179 | 我还等了半天还慌得很,| 180 | 暖气已收到,| 181 | 信价比高,| 182 | 以后会再来你家的,| 183 | 使用很安全,| 184 | 都看不到,| 185 | 本身就是不愿意折腾的人,| 186 | 给家里都安排上了,| 187 | 你就知道为什么了,| 188 | 突然间不制热,制热效果 189 | 唯一缺点,| 190 | 不易接触水而漏电其他特色:吹冷风也是个实用设计,| 191 | 做得非常好,| 192 | 正面下有保护开关,| 193 | 一如既往地好用,| 194 | 加入购物车等活动,| 195 | 其他特色:风扇也是先锋,| 196 | 耗电情况:耗电适中,产品功耗 197 | 设计比较人性化,| 198 | 容易烫到人,| 199 | 声音比空调都大,| 200 | 可信赖取暖效果:爸妈买的,| 201 | 放在小房间里还可以,| 202 | 电暖器关机时显示室内温度25度,| 203 | 而且的话就是换了一个衣架,| 204 | 个人还是喜欢小太阳,| 205 | 品牌的东西还是靠得住,| 206 | 贵的一千多,| 207 | 有用是有用,| 208 | 可以小爱同学控制,| 209 | 局部取暖挺方便的,| 210 | 耗电情况:一小时不到两度电,产品功耗 211 | 对方除了让你跳转至其他页面,| 212 | 没想到还没等到确认收货,| 213 | 取暖效果:可能是我房间比较高比较大,| 214 | 试用了一晚上效果还不错,| 215 | 客服小姐姐态度也很好,| 216 | 能用多久不知道,| 217 | 使用过程中经常出现跳闸断电的情况,| 218 | 让买家使劲吹嘘店家的产品,| 219 | 无暖气,| 220 | 反射区也还行,| 221 | 可以可以不错,| 222 | 总体来说比较满意,| 223 | 放在身旁挺好,| 224 | 初步试了一下,| 225 | 取暖器包装精致,| 226 | 靠近都感觉甚微,| 227 | 一个月都不到,| 228 | 耗电在可接受范围,产品功耗 229 | 取暖效果:制热速度挺快,| 230 | 希望要买好好选别家的,| 231 | 客服让我找天猫客服,| 232 | 今年买了寄回老家,| 233 | 静音效果:看网上有人说声音大,| 234 | 这款的静音效果真的很好,制热效果/声音 235 | 如果在10平方的小房间的话开着会暖,| 236 | 因为之前买的是电热扇觉得用得不安全才换这个的,| 237 | 和美的的同款,| 238 | 找个位置囤起来,| 239 | 制热不行退货还扣我45快递,制热效果 240 | 我只能说瓦数绝对没有虚标,| 241 | 开最强档的话热的很快,| 242 | 而且没有空调干燥,| 243 | 主要用来烘半干的小件衣服,| 244 | 就是因为热胀冷缩会响,| 245 | 买第二台了、一直使用格力,| 246 | 如果我收到一件好货肯定不会去找你售后故意找你茬,| 247 | 取暖效果:可以静音效果:无音,| 248 | 又试了一下,| 249 | 只有挨着电暖器还可以,| 250 | 绝对睡不着,| 251 | 能不出门最好不出门,| 252 | 很好用非常暖和哦,制热效果 253 | 每一次都有很满意的购物经历,| 254 | 不会烫伤小孩,| 255 | 整个办公室里都是味道,| 256 | 满屋子都热,| 257 | 没想到寄回确要寄河北,| 258 | 比过去的油汀要安静的多,| 259 | 照片前面的标志盖板后面没有铝片隔热小孩大人不小心或则调皮碰一下就立马烫伤非常危险尤其自家孩子比较好奇我刚开就跑过来摸太危险了,| 260 | 但是快递小哥实在是好,| 261 | 吹不到,| 262 | 有暖暖的感觉耗电情况:一小时得2度电静音效果:没声音我是空调,产品功耗 263 | 把手能够调节方位,| 264 | 你们这有点骚扰了明白吗,| 265 | 这款带遥控器,| 266 | 取暖效果:暖风十足,| 267 | 暖脚足够用,| 268 | 不过开两次就没有了,| 269 | 前几年一直用空调,| 270 | 1分钟左右就温度基本就可以稳定,| 271 | 第一反应想退货,| 272 | 等到天冷再用,| 273 | *热速度够快,| 274 | 一个人搬6楼,| 275 | 金色蛮好看的,| 276 | 耗电情况:耗电量有点大,产品功耗 277 | 12号就到了,| 278 | 关键是我们都还没感受周边温度有任何变化,| 279 | 一下就热起来,| 280 | 希望能用更久时间,| 281 | 颜值很高哈,外观 282 | 本人使用20天的某一天,| 283 | 感觉离开半米就没什么效果,制热效果 284 | 静音效果:定时后一直有噪音的,| 285 | 买了一个还没用让朋友要走了,| 286 | 我觉得热度来得并不快,| 287 | 简直没法过呀,| 288 | 竟然比预计早一天送到,| 289 | 建议放通风处,| 290 | 期待已久的取暖器,| 291 | 东西不错就是不太大,| 292 | 用起来等别方便,| 293 | 我家的电器基本上都是在京东购的,| 294 | 热来的太慢了,| 295 | 小房间内升温很快,| 296 | 昨天降温,| 297 | 我是急用,| 298 | 晾衣支架很实用,| 299 | 下单之后,| 300 | 问了半天几个小时才回,| 301 | 真值得购买,| 302 | 而且还贼费电,产品功耗 303 | 199元我就当买个教训,| 304 | 想放哪就可以随手拎到哪.温度控制有三档,| 305 | 店家不退货,| 306 | 耗电情况:需要专用线,产品功耗 307 | 没有一点声音没有,声音 308 | 尤其是嘉实特别好用,| 309 | 售前推到售后,| 310 | 还需要配合空调使用,| 311 | 没拆开真的是不需要啊,| 312 | 我把取暖器放在身边用都没有一点暖意,| 313 | 可以很好用,| 314 | 可是它感受附近的温度上来了,| 315 | 打算退了,| 316 | 刚好家里有点事,| 317 | 静音效果:非常的小,| 318 | 不吃哦,| 319 | 外观设计:好看啊,| 320 | 有误导消费者,| 321 | 目前用着挺暖和的,制热效果 322 | 简约风很喜欢,| 323 | 静音效果:完全惊艳,| 324 | 不过可以调节风力大小,| 325 | 放在办公室比较隐蔽,| 326 | 分为三挡可调,| 327 | 很喜欢在京东自营买东西,| 328 | 相信大品牌,| 329 | 如果说房间大一点的话,| 330 | 大品牌做工就是不一样,| 331 | 说当天下午给我电话,| 332 | 开最大档声音有点大,| 333 | 而且没有退换,| 334 | 安全设计:还看不出来,| 335 | 整体升温效果很好,制热效果 336 | 安全设计:小朋友如果将取暖器拎起来,| 337 | 十几平米的卧室,| 338 | 小宝宝不怕磕碰,| 339 | 插座都烧坏了,| 340 | 30平的房间一下子就暖和了,制热效果 341 | 取暖效果:不暖和,制热效果 342 | 耐用也就有安全保障其他特色:冬天衣服难干,| 343 | 效果特别差,制热效果 344 | 方便不烫手,| 345 | 用质量很好,| 346 | 京东快递速度棒棒哒,| 347 | 就是不知道再冷点怎么样,| 348 | 为了凑评论字数,| 349 | 价钱便宜嘛,| 350 | 加热慢,制热效果 351 | 在13平的卧室里用效果可以,| 352 | 外观差不多,| 353 | 温度升的很快,| 354 | 真的挺好的,| 355 | 一直没有人处理,| 356 | 合算,| 357 | 风速太慢了,| 358 | 取暖效果:刚打开低档又换到高档,| 359 | 至少没旧之前,| 360 | 客服总是说反馈了,| 361 | 发货送货速度很快,| 362 | 今天早上10点就到货了,| 363 | 体积移动起来也很方便,| 364 | 之前自己家里用的一个,| 365 | 换挡就乱响,| 366 | 只有人靠在跟前才感觉到一点点热气,| 367 | 晚上开着也很安静,声音 368 | 性价比还是一件不错的产品,| 369 | 此款电暖器已是再次购买,| 370 | 静音效果:没的声音,| 371 | 大品牌效果好,制热效果 372 | 机器热,| 373 | 劝大家千万不要上当,| 374 | 空调不开用这个也可以过冬了,| 375 | 外观设计:挺好的不错,| 376 | 客服不够专业,| 377 | 真的还不如小太阳,制热范围 378 | 主要是厕所洗澡用的,| 379 | 其他特色:总之很不错,| 380 | 劝亲们别冒这个险,| 381 | 放在办公室脚下,| 382 | 刚好放在脚边,| 383 | 才二百多块钱,| 384 | 每天早上起床插上电宝宝起床就不冷了,| 385 | 很推荐,| 386 | 搭在架子上,| 387 | 费点电算什么,| 388 | 外型很美观,| 389 | 给孩子洗澡就不怕孩子冷了,| 390 | 一直有用小米的产品,| 391 | 外观设计:效果比较好,制热效果 392 | 和没开一样,| 393 | 基本开一档就行,| 394 | 十大功率输出而且插头不是家用插座,| 395 | *不到*元,| 396 | 20号还有催送货,| 397 | 买之前你问的时候特别积极,| 398 | 所以拿出来试机了一会,| 399 | 开机40分钟,| 400 | 我比较懒,| 401 | 插上电等等就暖和起来了,制热效果 402 | 外观设计:外观不很重要,| 403 | 耗电情况:没有整夜开过,产品功耗 404 | 开一会娃都嫌热,| 405 | 温度也就升了一点点,| 406 | 非常适合房间使用,| 407 | 没想象的暖和,制热效果 408 | 外观设计:漂亮取暖效果:好耗电情况:耗电大静音效果:静,产品功耗/制热效果 409 | 目前刚到货,| 410 | 到时候开着地暖晒太阳,| 411 | 艾美特的东西非常,| 412 | 不过这个油汀效果还是非常好,制热效果 413 | 咸鱼找我,| 414 | 今年买了一个先锋的,| 415 | 中国好配送员,| 416 | 在家里开着暖和多了,制热效果 417 | 也不是我的问题啊,| 418 | 就准备淘汰了,| 419 | 就开始慢慢挑选,| 420 | 不懂,| 421 | 买来有十来天了,| 422 | 冬天用了再来追评,| 423 | 外观设计:非常好收到很满意,| 424 | 应该比空调节约吧,| 425 | 而热量本来就是向上的,| 426 | 共分为三个档,| 427 | 打算买第二个,| 428 | 我主要是买来洗澡用的,| 429 | 100平的房子靠这个玩意儿,| 430 | 拿手上就感觉得出来,| 431 | 耗电是真的😭,产品功耗 432 | 外观设计:小巧美静音效果:声音小安全设计:倾斜就停了,| 433 | 艾美特的这款取暖器还是值得购买的,| 434 | 能感觉到热风往上吹,| 435 | 相对这个价格也算合理的,| 436 | 申请退货也不给我通过,| 437 | 产品质量很棒,| 438 | 静音效果:什么噪音,| 439 | 但是真的是暖和,制热效果 440 | 售后服务太差劲了,| 441 | 直接将屋里温度提升了,| 442 | 耗电情况:耗电一个小时在两度电左右而且不怎么暖和电暖气嘛也就耗电的确挺高的,产品功耗 443 | 缺点是开久了太干燥,| 444 | 晚上京东下单,| 445 | 届时补评,| 446 | 加个变动大,| 447 | 取暖发热速度很快,| 448 | 因为是给家里老人买的,| 449 | 我用了二晩坏了,| 450 | 不到一个月降了*-*,| 451 | 还不如买个烤脚炉,| 452 | 在家里使用增温快,| 453 | 店家服务的很周到店家的宝贝我很满意,| 454 | 整个房间真的是温度高几度的,| 455 | 今天妈妈收到了,| 456 | 颜色好看质感很赞,| 457 | 同时给其他买家作为参考,| 458 | 美的的售后给我打电话,| 459 | 打开很快就装好了,| 460 | 其他特色:包装完好无损送货人员服务态度很好,| 461 | 小孩冬天出生,| 462 | 耗电情况:未精测,产品功耗 463 | 我家就晚上开节能模式,| 464 | 40平的房间很快就暖和了,制热效果 465 | 给店家造成的困扰,| 466 | 电暖气烧热有气味,| 467 | 袜子??放上去一会就干了,| 468 | 办公条件限制,| 469 | 一点不用担心影响睡眠,| 470 | 彻底服了,| 471 | 一直是京东的忠实用户,| 472 | 那是温度达到了一定值自动开启待机模式,| 473 | 到手就拿来用了,| 474 | 拿到后就迫不及待的打开试了试,| 475 | 2014年买过一个艾美特的油汀,| 476 | 买的时候说是暖风机,| 477 | 所以特意模仿网友写下的这个模板,| 478 | 每**两度电,| 479 | 瓦开一天也废不了几度电,| 480 | 操作也简便,| 481 | 方便包宝玩耍,| 482 | 洗完澡进房间穿着睡衣一点都不冷了,| 483 | 京东加油^0^,| 484 | 外观设计:面板居然塑料拉丝了,| 485 | 很舒适因为房间较大没有想象中那么热,| 486 | 出风散热面积小,| 487 | 但是物流太慢了,| 488 | 安全设计:恰到好处,| 489 | 一直用这个品牌,| 490 | *买到这种,| 491 | 为了安全,| 492 | 今早就到了,| 493 | 需求各不同,| 494 | 这两个后来打开,| 495 | 两台正常发货,| 496 | 安全设计:自动断电功能,| 497 | 不算很贵,| 498 | 这个东东不太热,| 499 | 所以就是踢脚线取暖器了,| 500 | 什么垃圾产品,| 501 | 没多长时间屋里就暖和了,制热效果 502 | 也没了温度,| 503 | 比我妈买的那个*块的小太阳差太远了,| 504 | 取暖效果:很快就热还不错,| 505 | 浴室根本没用,| 506 | 京东最差的一次购物,| 507 | 给宝宝哄衣服的,| 508 | 其他特色:整体来说还不错,| 509 | 只是不需要开电热毯了,| 510 | 还可以烘干口罩等小衣物,| 511 | 还比较划算,| 512 | 放在卧室和卫生间逗挺适合的,| 513 | App连接使用也很方便,| 514 | 正好阴天派上用场,| 515 | 商家给我拒了,| 516 | 放卧室还可以,| 517 | 丹鸟快递真扯淡有多远给发多远还不送货,| 518 | 而且我们在农村,| 519 | 直接就是废品,制热效果 520 | 具体不清楚,| 521 | 稍微调一下温度就会一直自动关,| 522 | 卧室比较小,| 523 | 设计的不是特别复杂,| 524 | 机壳手摸不烫手,| 525 | 宝宝冲完凉用不错,| 526 | 不到一个月降*,| 527 | 很喜欢哦,| 528 | 三个功率,| 529 | 冬天终于不冷了,| 530 | 搬动容易,| 531 | 天冷换尿不湿也用,| 532 | 有嗡嗡声,| 533 | 还可以用券,| 534 | 安全设计:反正加热中我是一点不敢碰的,| 535 | 老妈买来要放在地下室的,| 536 | 打开整个房间温度有上去的,| 537 | 双十一前提前预购,| 538 | 但价格低,| 539 | 真特么服了,| 540 | 送货速度神速,| 541 | 好外观设计:好安全设计:好静音效果:可以,| 542 | 选择退货了,| 543 | 我只能说垃圾,| 544 | 联系客服也没人管,| 545 | 这是个幼稚园的想法,| 546 | 开着放在通风的地方吹一次就好了,| 547 | 20天下来就这么多,| 548 | 比吹风的好,| 549 | 房间就已经非常暖了,| 550 | 好货源,| 551 | 一点也没感觉,| 552 | 指头站在前面才有热气,| 553 | 桌底下等等,| 554 | 取暖效果:电烤炉取暖效果差不多,制热效果 555 | 格力很值得信赖,| 556 | 取暖效果:取暖效果比较理想,| 557 | 卧室十几平还开着暖气,| 558 | 卧室里根本没啥用,| 559 | 那个加湿盒感觉也没多大用,| 560 | 疯狂购物,| 561 | 推拉跟方便,| 562 | 散热量微热起不作用,| 563 | 而且吹出来的热风上升后整个人都很暖,| 564 | 外观我喜欢,| 565 | 升温忙,| 566 | 等你的票我都等跨年了,| 567 | 货还好,| 568 | 取暖设备必不可少,| 569 | 耗电情况:还没感受,产品功耗 570 | 相当凑活,| 571 | 工作完全没有声音,声音 572 | 等天气凉了在用,| 573 | 取暖效果棒棒极了,| 574 | 家里有小孩打空调总感觉干燥,| 575 | 开3档都不行,| 576 | 侧翻断电,| 577 | 东西外观好好,| 578 | 特地用了几天来评论,| 579 | 高档打开就只能感觉房间不冷,| 580 | 京东的快递无话可说,| 581 | 我很喜欢美的的产品,| 582 | 东西有质感,| 583 | 但是有了小孩子就不行了,| 584 | 如今像林依晨这样大胆爆出素颜照,| 585 | 联系了客服可以退差价,| 586 | 外观设计:简约美观取暖效果:重点推荐下,| 587 | 质量有待提高,| 588 | 跟空调一起开才稍微热一点,| 589 | 其他特色:质量不错哦,| 590 | 开关就是两个按键比较简单,| 591 | 不是京东,| 592 | 安全设计:安全也就一个倾倒断电,| 593 | 市面上这类产品很多,| 594 | 客服说会退差价,| 595 | 后来放卧室开2000勉强还有点温度,| 596 | 就是用电暖气比较干,| 597 | 还没用老爸说看起来还行,| 598 | 这是买的第2个取暖器了,| 599 | 连个电话都没有,| 600 | 相差三天,| 601 | 不过估计只能很小的地方用,| 602 | 可能也是应该习惯温度升起来了,| 603 | 京东这速度没谁了,| 604 | 实在是买得起,| 605 | 可根据需要选择功率,| 606 | 取暖效果:暖暖的陪我们过冬,| 607 | 给客服点赞??,| 608 | 屋里不会冷啦,| 609 | 直接和我说睡醒了再买就好,| 610 | 但好像很多地方里面都是空的,| 611 | 只能近距离的取暖,| 612 | 双十一期间买的,| 613 | 取暖效果:现在用是很暖和,制热效果 614 | 取暖烘衣一举两得,| 615 | 刚买一天就降价了,| 616 | 散热比较均匀的那种,| 617 | 25号到今天1.4号我用了200多元钱,| 618 | 这到底是什么原因,| 619 | 一冬天都很暖,| 620 | 无赖只有确认收货,| 621 | 小小的13片暖气,| 622 | 暂时总体很满意的,| 623 | 默认好评吧,| 624 | 取暖效果:15个平方的房间和还有一个空调房间打起来的效果一样,| 625 | 老人小孩都说这下不用在挨冻了,| 626 | 还是有小的声音,| 627 | 而且会发出很大的响声,| 628 | 做工比较好,| 629 | 热冷风2档选择,| 630 | 开的一档,| 631 | 商家和物流都很给力,| 632 | 要求退换货,| 633 | 一般够用耗电情况:具体没测过,产品功耗 634 | 感觉今年不冷,| 635 | 但是早几年前买的两个油汀都很好用,| 636 | 可惜制热效果和想象有差距,| 637 | 也比较容易调节到合适的档位,| 638 | 安静温暖如春,| 639 | 放在浴室正好,| 640 | 可是刚过双11就降20块,| 641 | 但收到后就有太阳了,| 642 | 京东价保很给力,| 643 | 才能有效果,| 644 | 用久了有点微热,| 645 | 也可放浴室,| 646 | 因为怕天天空调宝宝受不了,| 647 | 安全1500w的功率,| 648 | 满意不错,| 649 | 还可以当炉子的手把,| 650 | VCR做的好就是欺骗消费者的吗,| 651 | 刚好这个小东西可以满足需要,| 652 | 就是有气味,| 653 | 收货后试用,| 654 | 衣服就被烘干了,| 655 | 使用后再来评吧,| 656 | 我是真心想买,| 657 | 便宜了,| 658 | 联系客服推来推去的,| 659 | 安全设计:说是倾倒自动断电,| 660 | 申请说了48小时上门,| 661 | 况且退的话,| 662 | 以前在实体店买的效果很好,制热效果 663 | 已经使用了,| 664 | 哈哈??心动不如行动,| 665 | 好不容易熬到了天猫还有一天就可以退款给我了,| 666 | 待以后用了再评价,| 667 | 因为我买来房间用,| 668 | 应该比较安全,| 669 | 并没有评论说的那么好,| 670 | 不用再到外面的浴室去了,| 671 | 晚上下单第二天早上到家,| 672 | 应该看功率多少的问题,| 673 | 冬天没暖气用这个的话,| 674 | 能出来说句话吗,| 675 | 放办公室里用的,| 676 | 开一个晚上屋子都不暖,| 677 | 着急穿的衣服放在上面很快就烘干了,| 678 | 很轻便很方便,| 679 | 瞬间温度就提升了,| 680 | 至于电费嘛,产品功耗 681 | 取暖效果:升温快,| 682 | 比较满意这次购物,| 683 | 冬天最怕宝宝着凉了,| 684 | 效果不错整个房间都暖和了,制热效果 685 | 就是我客厅20平左右感觉温度升不上来,| 686 | 调温度开关还会闪光,| 687 | 商家跟我处理过了,| 688 | 物流更快,| 689 | 小房间保温保湿效果还不错,| 690 | 有点高估了,| 691 | 除了发货太慢太慢,| 692 | 比电风扇的噪音还大,| 693 | 要是能定时就更完美了,| 694 | 一米内有温度,| 695 | 使用效果还不错没燥音,| 696 | 买的第二台了非常方便主要没有声音,| 697 | 这就是你们解释的孕婴防烫吗,| 698 | 一次说登记差价等退款专员退款即可,| 699 | 这个已经买的第二个了,| 700 | 第一次使用有点味道,味道 701 | 收到货几天没用,| 702 | 后一位一般,| 703 | 现在现在沦为给小孩烘干衣物,| 704 | 这款的插头是16a的,| 705 | 取暖效果:取暖面积大效果好,制热效果 706 | 取暖效果:发热速度很快,| 707 | 取暖效果:刚开始用效果还挺好的,制热效果 708 | 此款电油汀性价比很高,| 709 | 安全设计:安全性可以,| 710 | 我刚买完就减了*,| 711 | 下次有需要的时候还会光顾的,| 712 | 就放在一边了,| 713 | 大概6s左右热气就迎面而来,| 714 | 仁者见仁吧,| 715 | 取暖效果:基本正常房间大小就已经很暖和了,制热效果 716 | 这东西比较鸡肋,| 717 | 用了几天没问题了才来评价的,| 718 | 我开一个晚上,| 719 | 客服晚上十二点多还在,| 720 | 100块上门自提需要咩,| 721 | 基本开启十几分钟,| 722 | 一会儿房间就暖和起来了,制热效果 723 | 就是味道重,味道 724 | 到货调试了一下,| 725 | 很好用已推荐同事们购买,| 726 | 要用两个小时以上,| 727 | 静音效果:比之前买的别的牌子声音小一些,| 728 | 很纠结,| 729 | 超值非常的好,| 730 | 看着非常大,| 731 | 这是买的第二台送给家人用,| 732 | 孔对的不是很标准呢,| 733 | 有高低功率之分,| 734 | 运费险就赔8块,| 735 | 这真的是在京东买的最吃亏的一件东西了,| 736 | 打开一看款式很高档,| 737 | 谢谢送的礼物,| 738 | 开一个晚上都还能冻**,| 739 | 很管用解决了供暖前期挨冻的问题,| 740 | 开启后只是上升温度不是太明显,| 741 | 平均每天就开两小时,| 742 | 送给老人家的礼物,| 743 | 但是就是感觉热的速度不够快,| 744 | 为何开启后很大的味道,| 745 | 比空调好用还省电,产品功耗 746 | 收到就试用了一下感觉不错,| 747 | 热度正好,| 748 | 有凉风档,| 749 | 应该还是比较经济吧,| 750 | 小朋友在厅里玩耍很舒服,| 751 | 希望能耐用,| 752 | 打开不一会房间就很暖了,| 753 | 一个正面回答都没有,| 754 | 客服态度也呵呵哒,| 755 | 而且包装非常精致,| 756 | 肯定的说上当了,| 757 | 整体感觉很不错,| 758 | 就变1档,| 759 | 关上的时候偶尔会有咔咔的响音,| 760 | 可还行,| 761 | 还是官方大牌子呢,| 762 | 回来用了一晚上目前看还是不错的,| 763 | 自己的问题,| 764 | 这第三代的应该跟棒的,| 765 | 价格依然美丽,| 766 | 还好吧,| 767 | 大的不行,| 768 | 不知要怎么处理,| 769 | 晾物品比直接放在暖气上更安全,| 770 | 也不觉得吵,| 771 | 有水盒,| 772 | 这就是品牌,| 773 | 现在很好用没什么问题,| 774 | 后续又陆续购买多次,| 775 | 还好格力的产品还是值得国人信赖的,| 776 | 不过不常开的,| 777 | 还好有强大的设备在手,| 778 | 耗电太多了,产品功耗 779 | 客服一直就说是铁片温差大导致的,| 780 | 很强,| 781 | 这样不怕冷了,| 782 | 品牌的品质就是不一样,| 783 | 东西是个好东西就是不知道费电不,产品功耗 784 | 家里有老人和小孩必备的,| 785 | 给宝宝洗澡挺好用,| 786 | 加热起来有味道,味道 787 | 已完美解决,| 788 | 邮回老家的,| 789 | 风口大,| 790 | 已经收到了取暖器,| 791 | 电源线在右边,| 792 | 晚上有点吵,| 793 | 冬天用什么都耗电的,产品功耗 794 | 夜深人静的时候比较明显,| 795 | 我娃是全村人的希望,| 796 | 大小高低刚刚好,| 797 | 也挺轻便,| 798 | 取暖兼烘干,| 799 | 稍微有点耗电,产品功耗 800 | 南方的冬天一般都很阴冷,| 801 | 开最大,| 802 | 放在家里也不占地方,体积大小 803 | 先锋的其他取暖器家里一直用,| 804 | 等一个小时大概都是只有25读的热度,| 805 | 符合要求,| 806 | 就是随便撺起来的物品,| 807 | 耗电情况:可以接受静音效果:几乎没声音安全设计:很安全,产品功耗 808 | 其他特色:可以暖袜子,| 809 | 不过格力品质应该不错吧,| 810 | 确实很好用,| 811 | 个人感觉比较鸡肋,| 812 | 退回去运费都*块,| 813 | 外观简洁耐看,| 814 | 侧面或者向上使用更方便,| 815 | 这双十二的活动力度也不小,| 816 | 耗电情况:最高档相当于空调的用电,产品功耗 817 | 死活说不影响使用而且是我逾期反应问题,| 818 | 办公室够用了,| 819 | 外观设计:圆润取暖效果:三档快静音效果:无噪音,声音 820 | 安全设计:方便安全,| 821 | )内在就是质量,| 822 | 简单定时孩子也喜欢,| 823 | 卧室一会儿就热了,| 824 | 可没用,| 825 | 收到有两天了,| 826 | 现在我每日必须的任务就是逛京东秒杀,| 827 | 买来试试看,| 828 | 目前为止用得挺好的,| 829 | 不过只能在小面积卧室用,| 830 | 耗电情况:一小时1-2.2度,产品功耗 831 | 怕冷的女生可以下手一台,| 832 | 在此再次表扬客服,| 833 | 散热效果很好,制热效果 834 | 见到大方,| 835 | 问了好几次客服,| 836 | 外观设计:简洁、流畅取暖效果:效果明显,| 837 | 稳亏不赚,| 838 | 老人家舍不得用不知道开箱了没有哈过年回去在现场核查看看,| 839 | 2档必须个点距离才行了,| 840 | 亮瞎眼,| 841 | 隐藏式烘衣架设计值得点赞,| 842 | 天冷了就可以用上了,| 843 | 取暖效果:用的效果很明显,| 844 | 白色很搭,| 845 | 八脉为之一畅,| 846 | 退货邮费100多,| 847 | 还要买一个给父母,| 848 | 这点比空调好外观设计:喜欢这种颜色,| 849 | 非常严实,| 850 | 应该比空调省电,产品功耗 851 | 大品牌的东西不错,| 852 | 前台很享受,| 853 | 要给个大大的赞,| 854 | 热的速度真的好快,| 855 | 客服夏兰很耐心,| 856 | 放在浴室给宝宝洗澡很不错,| 857 | 静音效果:静音还是非常静音的,声音 858 | 董明珠真有你的,| 859 | 颜色是那种灰白色的,| 860 | 没用几天,| 861 | 孩子还嫌弃太暖和了,制热效果 862 | 这两天上海阴天,| 863 | 耗电量在能接受的范围内,产品功耗 864 | 风扇也是非常的强,| 865 | 其他特色:唯一的缺点就是开关位置不好,| 866 | 靠近才会很热,| 867 | 知道会耽误别人的事情吗,| 868 | 第一感受还是不错的,| 869 | 其他特色:唯一刚开机有异味,| 870 | 最低档就很合适,| 871 | 安全设计:不稳也会自动关机,| 872 | 正好周一送到单位,| 873 | 我们退差价,| 874 | 老公很喜欢,| 875 | 整个房间暖起来很快,| 876 | 有种高级感,| 877 | 唯一的遗憾是没有电池,| 878 | 覆盖面积更大,| 879 | 感觉拆过非原装,| 880 | 桑心,| 881 | 着急给宝宝烘疝气带,| 882 | 从此洗澡不怕冷了,| 883 | 造型与众不同,| 884 | 还是很耐心,| 885 | 就看到艾美特,| 886 | 室内开最低档大约可以达到16度,| 887 | 第一个使用了这么长时间不曾出问题,| 888 | 没噪音很安静,声音 889 | 解答问题很及时,| 890 | 从外观颜色到做工那都是相当的美丽,| 891 | 估计是太重,| 892 | 制热(最高档)感觉也一般吧,| 893 | 比预想的好点,| 894 | 12月25号下单的,| 895 | 疫情期间也给配送,| 896 | 美的品牌真的值得购买,| 897 | 今天刚到货还没有使用,| 898 | 想退了换另外款的,| 899 | 超过一米的地方,| 900 | 头略低,| 901 | 安全性肯定更好,| 902 | 挺好的外观设计:简洁大方,| 903 | 再用就不行了,| 904 | 实在受不了,| 905 | 喜欢他的美观大气,| 906 | 没什么#,| 907 | 外观设计:小巧干净不占地方,体积大小 908 | 出乎想象,| 909 | 温暖十平方房间大概需要半小时以上,| 910 | 家里都有触电保护的,| 911 | 但是小巧实用,| 912 | 只好上墙插了,| 913 | 优秀的油丁它需要一个优秀的品牌,| 914 | 静音效果:真的棒,| 915 | 两年前买的机器现在还在使用中,| 916 | 取暖器上方三十厘米左右就感受不到热量了,| 917 | 其他特色:家里已经买了先锋的小太阳,| 918 | 活动好,| 919 | 安全设计:有防烫,| 920 | 提醒我倒了以后要正着放两到三个小时才能使用,| 921 | 适合十平米左右的小卧室或者办公室,| 922 | 效果很差一点也不暖和,制热效果 923 | 比较有发言权吧,| 924 | 体验过,| 925 | 适合不同需求,| 926 | 太坑了这个破玩意,| 927 | 没有设置一个卡扣,| 928 | 安全设计:断电设计很重要,| 929 | 风速也很大,| 930 | 外观设计:外观还是很漂亮的,| 931 | 直接寄回老家给妈妈了,| 932 | 本来还想再买两台,| 933 | 在这个屋子住过一晚,| 934 | 毕竟有一千多瓦静音效果:声音大,| 935 | 实现远程遥控,| 936 | 用了一个来月,| 937 | 取暖效果:取暖真的是超级赞,| 938 | 客服h小何是跟我玩呢吧,| 939 | 是因为在办公室有暖气我只要热脚底就行,| 940 | 暖风范围也不大,| 941 | 迫不及待的加入购物车,| 942 | 买来给宝宝穿衣服用的,| 943 | 整个空间从头到脚都暖和,制热效果 944 | 占地也不是很大取暖效果:插上立马就可以吹热风耗电情况:耗电肯定是大点的,产品功耗 945 | 感觉不碍事,| 946 | 放卫生间给孩子洗澡用的,| 947 | 可以温暖空间也还行就一块暖气片的作用,| 948 | 不会烫着,| 949 | 是非常失败的一次购物体验,| 950 | 家里的油汀一直是先锋的牌子,| 951 | 外观设计:十分完美,| 952 | 京东速度最给力,| 953 | 还有怪怪的气味,| 954 | 取暖效果:暖的很快静音效果:没有声音外观设计:好看,制热效果/声音 955 | 无法形容了,| 956 | 外观设计:漂亮取暖效果:好静音效果:没有声音其他特色:有加湿器空气不干,制热效果 957 | 莫名的瞪眼,| 958 | 到手价279,| 959 | 晾物架的设计有改良,| 960 | 冬天烘衣服特别好用的,| 961 | 强迫收货,| 962 | 外观设计:简单大器好用,| 963 | 主要是质量很好,| 964 | 客服贝拉米特别耐心,| 965 | 不影响入眠,| 966 | 客服回复及时,| 967 | 方便又暖和,制热效果 968 | 取暖效果:效果一般吧没有想象的好,制热效果/制热效果 969 | 建议京东不要再用这个物流,| 970 | 开到二十度以上才会暖和,制热效果 971 | 还想再来一台,| 972 | 第二不好修,| 973 | 开着**响,| 974 | 只唯一的缺点就是出口处防护罩挺烫,| 975 | 但是售后太差,| 976 | 我房间大了,| 977 | 等暖气来的时候就靠它了,| 978 | 这是我一直喜欢的一个暧机的品牌,| 979 | 热风小档和大档,| 980 | 送到合适的地方去用,| 981 | 小卧室用足够了,| 982 | 冬天坐月子不用担心着凉了,| 983 | 取暖器也购买艾美特,| 984 | 最值得吐槽的就是快递,| 985 | 这是最要命的点,| 986 | 皮肤也不会干燥,| 987 | 凭什么要用户出运费,| 988 | 产品加热性能不错,| 989 | 不是对着吹的,| 990 | 在浴室里加热很快,| 991 | 比其他的油汀窄很多,| 992 | 退货要邮费,| 993 | 可以用一下,| 994 | 很后悔买了,| 995 | 我就挑来挑去,| 996 | 不错超静音,声音 997 | 其他暂时没发现啥问题,| 998 | 客流大就增加客服啊,| 999 | 大概一个多小时就能感到室温升高了,| 1000 | 太幸运了,| 1001 | 没有比图片上看起来更小巧,| 1002 | 安全设计:一点点倾斜就自动断电了,| 1003 | 倾倒断电实测反应很快,| 1004 | 只试了一下功能都可以,| 1005 | 渴得快,| 1006 | 暖气出得非常快,| 1007 | 双十二的价格真的是可以说超级超级给力,| 1008 | 家里没温度计,| 1009 | 顶上的皮绳非常方便,| 1010 | 做工肯定比100带点点的好,| 1011 | 外观设计:扣手设计不太好,| 1012 | 20平方的房间2个小时都不能暖起来,| 1013 | 应该比空调要省电吧,产品功耗 1014 | 觉得不错自己也买了一个,| 1015 | 图片和实物一样,| 1016 | 取暖取暖器超级好用,| 1017 | 暖器收到,| 1018 | 升温效果不是很好,制热效果 1019 | 就不暖和了,制热效果 1020 | 只要打开,| 1021 | 刚好天气冷,| 1022 | 风力还挺大的,| 1023 | 希望可以多用几年,| 1024 | 用起来非常舒服,| 1025 | 开高风吹起还很烫,| 1026 | 没有其他品牌暖和,制热效果 1027 | 就是容易自动停止,| 1028 | 跑过去拿还要寄存费3元,| 1029 | 出风口向上的,| 1030 | 估计到冬天没太阳的时候烘衣服方便,| 1031 | 外观材质:外观大气,| 1032 | 10天200的电费,产品功耗 1033 | 心里总不是个滋味,| 1034 | 看着心烦,| 1035 | 外观设计比较简洁,| 1036 | 装配简单,| 1037 | 就是有时候**响,| 1038 | 静音效果:静音的话毕竟是油丁的,| 1039 | 而且安装也非常方便,| 1040 | 外观设计:简单美观,| 1041 | 放办公室正好使用,| 1042 | 布包装袋在,| 1043 | 看起来跟厚实,| 1044 | 外观设计:非常棒,| 1045 | 公司年会给员工抽奖,| 1046 | 机身手摸还是烫手的,| 1047 | 几个小时过去了温度一点么有,| 1048 | 报价也太差了,| 1049 | 但是用了一点也没有感觉,| 1050 | 耗电情况:非常的耗电,产品功耗 1051 | 而且还不像写的那样,| 1052 | 可能是刚开始使用的原因,| 1053 | 到货到服务点直接已签单,| 1054 | 裙子觉得非常实用,| 1055 | 这个最让我恶心??,| 1056 | 房间热的非常快,| 1057 | 供暖效果好,制热效果 1058 | 放房间试用了效果不好,制热效果/制热效果 1059 | 真的是很差劲,| 1060 | 不错小巧精致,| 1061 | 一个小时一度多的电,| 1062 | 明天到,| 1063 | 帮同事买的小组礼品,| 1064 | 几分钟房间就很暖了,| 1065 | 关键是代言人还帅??,| 1066 | 冬天就暖和了,制热效果 1067 | 说双十一如果更便宜会退我差价的,| 1068 | 曾经用过的油汀现在又回归这种取暖方式了,| 1069 | 效果还不如去年在店里买的功率小一点的,| 1070 | 一个人两个人使用还够,| 1071 | 估计他不会,| 1072 | 不重而且组装方便,| 1073 | 女朋友很开心,| 1074 | 就是占位置,| 1075 | 合起来不取*元,| 1076 | 配送速度超级快,| 1077 | 你们家为什么快递单贴在箱子底部,| 1078 | 价格优惠质量好,| 1079 | 连接小爱音响折腾了好久,| 1080 | 问客服为什么会响,| 1081 | 家中的空调等电器基本都是美的而且电器南昌城又是本省方便快捷,| 1082 | 体积虽小,| 1083 | 感觉广告夸大效果了,| 1084 | 甚至开长了还热,| 1085 | 放客厅都没感觉,| 1086 | 影响性能,| 1087 | 只适用于局部范围加热,| 1088 | 调至最高档,| 1089 | 小房间制热可以,| 1090 | 因为当时老人在家自己装不了,| 1091 | 东西大品牌不错,| 1092 | 外观设计:可以取暖效果:极差,制热效果 1093 | 但是效果要比电阻丝的要过好,制热效果 1094 | 而且很快就收到了,| 1095 | 耗电情况:还行吧,产品功耗 1096 | 暂未拆开,| 1097 | 变形严重,| 1098 | 很差的商家,| 1099 | 拿在手里有分量,| 1100 | 打开立即来了热,| 1101 | 只用智能来答非所问,| 1102 | 现在零上十一二度呢,| 1103 | 为什么关掉后会有声音,| 1104 | 用一小时后即无异味了,味道 1105 | 主要是之前在北方呆过几几年,| 1106 | 郑重声明质量一点都不好,| 1107 | 热度没有大的感觉高,| 1108 | 而且能耗也很底,| 1109 | 房间睡觉开一整晚能保持比外面高五度以上,| 1110 | 艾美特升温快一点温度稍微高一点,| 1111 | 取暖器收到非常方便实用,| 1112 | 外观设计:6666666漂亮的,| 1113 | 太大功率了,| 1114 | 因为相信大品牌,| 1115 | 售后说有一点气味,| 1116 | 那样才会暖一点,| 1117 | 样子还挺好看,| 1118 | 热气本来就是向上走的,| 1119 | 一开始以为没有说明书和保修卡呢,| 1120 | 买它回来过年,| 1121 | 小苹果客服很耐心,| 1122 | 取暖效果:屋子越小,| 1123 | 合不合格谁说清楚,| 1124 | 加上颜色深,| 1125 | 其他特色:支架功能很人性化,| 1126 | 连续几天阴雨天,| 1127 | 也不知道,| 1128 | 既方便又好用,| 1129 | 但是特别静音,| 1130 | 外观设计:简单大方颜色也是我喜欢的,| 1131 | 取暖效果:开了二档大概10秒内就热了静音效果:比之前买的声音小一些味道稍微有点大,味道 1132 | 谢谢处暑客服,| 1133 | 在买空调的时候送的,| 1134 | 但如果有电池的话感觉就很不一样了呢,| 1135 | 客厅、书房、卫生间各一个,| 1136 | 祝京东生意兴隆,| 1137 | 空气的话还是会偏干,| 1138 | 验货试用了下感觉还不错,| 1139 | 就一律发表情,| 1140 | 11.11活动时购买的价格实惠,| 1141 | 开了三挡升温后转2档,| 1142 | 开了一次,| 1143 | 开三挡暖的很快,| 1144 | 本来因为暖风机比较吵和干才买的,| 1145 | 小米真的很多家电蛮实用,| 1146 | 所以选择品牌是没得错的,| 1147 | 具有180度的摇头功能,| 1148 | 货未打开用,| 1149 | 持续输出热量,| 1150 | 要靠近才觉得暖,| 1151 | 在南方的冬天,| 1152 | 呼啦啦呼啦啦的暖气就来了,| 1153 | 洗衣服的时候我也放旁边,| 1154 | 美的油汀取暖非常好,| 1155 | 此物质量非常好,| 1156 | 外观的设计很不错,| 1157 | 还有质量问题,| 1158 | 开到最大也才那个效果,| 1159 | 而且发热也挺快的,| 1160 | 昨晚买今早到疯狂打电话,| 1161 | 就是我自己感觉噪音有点大,| 1162 | 希望可以长久的使用,| 1163 | 比较适合一切家庭电路使用,| 1164 | 不用在阳台上挂很多湿答答的衣服了,| 1165 | 20多平的屋子正好够用,| 1166 | 售后只会踢皮球不解决问题,| 1167 | 老婆说制热挺快的,| 1168 | 京东实惠方便,| 1169 | 主要是出热风的速度很快,| 1170 | 买回来试了下,| 1171 | 今后洗澡也不受冻了,| 1172 | 其他特色:贵,| 1173 | 南方的冬天实在是伤不起啊,| 1174 | 静音效果:开到最大声音非常小,| 1175 | 取暖效果:打开一分钟不到马上就有热气了,| 1176 | 空间大的可能稍微差一点,| 1177 | 静音效果:噪声很小,| 1178 | 比较一下哪个好用,| 1179 | 使用起来刚好合适,| 1180 | 犹豫下单,| 1181 | 洗澡的时候放旁边用,| 1182 | 换了挡不会变色的,| 1183 | 然后还送的晾衣架,| 1184 | 商品在黑名单内不参与价保,| 1185 | 加热效果多不行,| 1186 | 之前一直考虑买不买,| 1187 | 而且一买来上边的晾衣架就碎了,| 1188 | 但是一直觉得价格很贵,| 1189 | 强力推荐这款,| 1190 | 暖气上带的置物架很方便,| 1191 | 用节能模式会有哒哒的声响,| 1192 | 尺寸大,| 1193 | 总体来说性价比高,| 1194 | 必然消耗,| 1195 | 没有店家说的那么防烫,| 1196 | 强劲、一会功夫房间就暖暖的了噪音不大,| 1197 | 京东商城棒,| 1198 | 取暖效果:还行外观设计:时尚简约静音效果:挺安静,| 1199 | 非常满意需要的友友们可以放心购买,| 1200 | 非常非常不错非常非常不错啊,| 1201 | 安全性挺高的,| 1202 | 买到一个不能用的垃圾,| 1203 | 公司一直使用这个,| 1204 | 早晨开一会儿浑身暖和耗电情况:公用电,产品功耗 1205 | 比燃气那种暖气片会省很多,| 1206 | 怕熏到宝宝,| 1207 | 还发了一个朋友圈,| 1208 | 安装底座的材质要是金属的就更好,| 1209 | 不对着吹不热,| 1210 | 还有就是经常有叭叭声,| 1211 | 取暖器小面积使用还是挺好的,| 1212 | 朋友们可以选择,| 1213 | 买来给妈妈的房间取暖的,| 1214 | 可以放在台面上用,| 1215 | 离地倾倒马上断电很安全,| 1216 | 快递速度快,| 1217 | 希望冬天可以让老人家暖和点,制热效果 1218 | 现在京东送货不是送到家的,| 1219 | 小米可惜没有买到智能版的,| 1220 | 上面还有一个架子,| 1221 | 小小的还挺暖和,制热效果 1222 | 三挡调节风力调节,| 1223 | 这次仍然没让我失望,| 1224 | 温度很舒服,| 1225 | 满不满意,| 1226 | 取暖效果:加热速度还是比较快的,| 1227 | 一点都付责任,| 1228 | 活动的时候购买的,| 1229 | 好大一股胶味,| 1230 | 也费气费钱,| 1231 | 喜欢的可以放心购买哦做工很可以,| 1232 | 看中了这款美的的取暖器,| 1233 | 刚到货里面散发的味很大,| 1234 | 加热后没有噪音,声音 1235 | 只会把顾客推来推去,| 1236 | 昨天晚上才买的,| 1237 | 商家居然发的是二手货,| 1238 | 还没有感觉暖呢,| 1239 | 帮朋友再来一个,| 1240 | 窗子采用断桥铝材料,| 1241 | 不知道是新的还是什么原因,| 1242 | 要是再大一点就更好,| 1243 | 来这屋里温度比外面都暖和,制热效果 1244 | 靠得近暖和,制热效果 1245 | 还能玩,| 1246 | 昨晚拿回来就试了下,| 1247 | 京东有本事,| 1248 | 其他特色:还是那个,| 1249 | 其中暖气差价210元,| 1250 | 反正我觉得冬天室内室外温度不需要相差太大这样我能接受很好很好,| 1251 | 因为你会被冻死,| 1252 | 放在房间里真的特别适合,| 1253 | 白色的更好看,| 1254 | 139块刚买了一个星期,| 1255 | 开了几分钟就很热了,| 1256 | 质量可以说是非常好了,| 1257 | 并且倾斜自动关机,| 1258 | 开了几个小时只有在面前热,| 1259 | 优点:1)方便,| 1260 | 快递员小哥非常快,| 1261 | 很满意的商品,| 1262 | 转角圆润,| 1263 | 看起来很上档次,| 1264 | 给家人都买了,| 1265 | 不知道为什么这次的是薄片,| 1266 | 我替厂家羞愧,| 1267 | 风不够大,| 1268 | 但盒子挺大的,| 1269 | 但是我不用补差价,| 1270 | 价位而且比较平民,| 1271 | 但是一点也不干,| 1272 | 等了一个多星期没有任何进展,| 1273 | 跟北方的暖气片差不多,| 1274 | 静音效果:有哄哄声,| 1275 | 加热过程还可以接受,| 1276 | 安全设计:我完全没有体会到有这个自动跳的感觉,| 1277 | 整晚不干,| 1278 | 热气很快就没了,| 1279 | 不想空调那么干燥,| 1280 | 通过适当调节开关大小来控制电费,产品功耗 1281 | 取暖需求比较高的,| 1282 | 取暖效果:取暖速度特别快,| 1283 | 到现在也没人很我联系说补发的事,| 1284 | 客服非常棒,| 1285 | 很好哒,| 1286 | 只要能用就好了,| 1287 | 简洁取暖效果:快去制热耗电情况:比较耗电静音效果:没有声音安全设计:蛮不错其他特色:太费电,产品功耗 1288 | 回家使用了一下,| 1289 | 外观设计:小巧玲珑、美观,| 1290 | 安全性能非常人性化,| 1291 | 昨天晚上小孩要写作业已经拿出来用了今天让我退,| 1292 | 这个倒了自动断电的,| 1293 | 是想象的模样,| 1294 | 客厅差一点,| 1295 | 等到1月14了,| 1296 | 不错关键是噪音很小的,| 1297 | 这是真的格力产品吗,| 1298 | 电暖气收到试用了一下,| 1299 | 对于京东网上商城的服务很满意的,| 1300 | 对了还可以定时、调节温度,| 1301 | 外观设计:面板配色都不错取暖效果:加热快,制热效果 1302 | 外观设计:漂亮取暖效果:可以静音效果:声音很小安全设计:很好,制热效果/声音 1303 | 外观设计:外观简约又时尚,| 1304 | 一般5分钟卧室就暖和了,制热效果 1305 | 适合娃娃使用,| 1306 | 能退款吗,| 1307 | 客服小苹果声音很好听,| 1308 | 高档味道重,味道 1309 | 幸亏是运气好,| 1310 | 好评安全设计:触摸到了也不会非常烫手,| 1311 | 外观设计:出色还是很结实的,| 1312 | 放到小卧室内只有跟前有点热气,| 1313 | 而且只用来给宝宝换尿不湿用,| 1314 | 这个整体还不错,| 1315 | 可仰面可俯视很方面,| 1316 | 不用再纠结放哪个台面上,| 1317 | 天天用,| 1318 | 但是房间热的挺快的,制热效果 1319 | 物流配送速度很快,| 1320 | 直接安装使用,| 1321 | 已经烤了好几天了,| 1322 | 外观设计:小巧,| 1323 | 外观设计:大方明了,| 1324 | 外观材质:材质不错,| 1325 | 很快就好了,| 1326 | 老板服务态度很好,| 1327 | 这是我淘宝购物上一次愉快满意的购物,| 1328 | 浴室暖和真得很舒服,制热效果 1329 | 电暖气耗电太猛,产品功耗 1330 | 安装师傅服务非常好??,| 1331 | 我是直接搭在上面的,| 1332 | 安全设计:中间有温控设置,| 1333 | 按钮不太顺手,| 1334 | 能达到超快速度,| 1335 | 大家买的时候问清楚,| 1336 | 垃圾店铺垃圾商品,| 1337 | 通电五分钟这样,| 1338 | 孩子后以后写字不冷喽,| 1339 | 光运费就要100多,| 1340 | 反应问题不给退货,| 1341 | 防水自带倾倒断电保护,| 1342 | 取暖效果:取暖效果开出后就有风,| 1343 | 开了快一天,| 1344 | 配送员送货很快,| 1345 | 有了它我们都很少用空调了,| 1346 | 宝宝换纸尿裤时好用静音效果:没什么声音外观设计:时尚漂亮,| 1347 | 我觉得噪音稍大,| 1348 | 取暖效果:卧室里完全够用,| 1349 | 自己开车去提的,| 1350 | 刚开机有点声音启动起来就好了,| 1351 | 说明指向性比较好,| 1352 | 但是这味真的有点大,| 1353 | 你没能达到预期效果,| 1354 | 产品很不错,| 1355 | .,| 1356 | 这是官方回答,| 1357 | 以为是落地的,| 1358 | 退货却要退到广东去,| 1359 | 客服催了,| 1360 | 艾美特电暖器这些年市场占有率还比较高,| 1361 | 您们这款价位要知道是这样完全可以选择其它品牌,| 1362 | 选这款电暖气是有原因的,| 1363 | 几个小伙伴一起买的,| 1364 | 很安全取暖效果:很棒静音效果:有点噪音,| 1365 | 下次还会再买的,| 1366 | 耗电情况:3档可调节,产品功耗 1367 | 反正不满意,| 1368 | 一分钱一分货值得购买,| 1369 | 与商品描述相符,| 1370 | 应该还可以,| 1371 | 屋里暖烘烘,| 1372 | 开个3/5分钟就会觉得温度上来点了,| 1373 | 结果把我退货退款驳回,| 1374 | 网子也不烫手,| 1375 | 外观设计:好取暖效果:室温10°,| 1376 | 试了试升温不怎么快,| 1377 | 昨天收到,| 1378 | 京东自营的产品好,| 1379 | 慧米洛凡还有猫米白鸽还有好几个都很好,| 1380 | 用来烘干衣服不错,| 1381 | 放在边上也不占地方,体积大小 1382 | 又买了个螺丝刀回来拧它,| 1383 | 收到货保价失败,| 1384 | 特人性化,| 1385 | 不让用煤球炉子了,| 1386 | 十七八平的小客厅完全够用,| 1387 | 轮子不稳,滑轮提手 1388 | 其他特色:好好好,| 1389 | 是否有必要为了这个去买呢,| 1390 | 吸入冷的空气鼻塞到睡不着真的好难受,| 1391 | 声音好,| 1392 | 很方便物流快,| 1393 | 南方冬季阴雨天的福利,| 1394 | 还是骗人的美丽谎言,| 1395 | 耗电情况:确确实实比较耗电,产品功耗 1396 | 客服的态度也挺好,| 1397 | 外观设计:外观漂亮与落地风扇一样升降3档调节和左右摇头,外观 1398 | 为什么我觉得不是很热和呢,| 1399 | 开最大档最大功率,| 1400 | 这点确实做工可以,| 1401 | 洗澡时用非常不错,| 1402 | 白天再也不用艰难的摆脱温暖的被窝了,| 1403 | 价格买贵了*,| 1404 | 取暖效果:小范围内还是提温度较快的,| 1405 | 20平方开三档,| 1406 | 物流真快,| 1407 | 第一次买给老人的,| 1408 | 结果没有,| 1409 | 孩子们非常喜欢,| 1410 | 声音太大太吵,| 1411 | 取暖效果:坐在附近,| 1412 | 包装箱子都是完好无损,| 1413 | 拒绝理由却是保障已超时效,| 1414 | 最好点评快递:发货很快,| 1415 | 价格比较便宜,| 1416 | 尤其是生鲜类的,| 1417 | 只有靠近他50cm范围才有温度吧,| 1418 | 一天开10个小时就是20度电,| 1419 | 有了这台油丁衣服不愁每天干干爽爽了,| 1420 | 下边得放个凳子,| 1421 | 挺温暖,| 1422 | 格力看来也就做做空调,| 1423 | 开个6**W就足够热了,| 1424 | 这个很快就热了,| 1425 | 可能我更喜欢那种普通的小太阳多一点,| 1426 | 以验证真实有用不做假,| 1427 | 家里老人用了很满意,| 1428 | 给家里双方老人也买了,| 1429 | 这价格要什么自行车,| 1430 | 打开没一会儿就热了,| 1431 | 双十一228购入价,| 1432 | 运费险只能退20块钱,| 1433 | 油漆味道很大,味道 1434 | 双十一物流稍慢,| 1435 | 实用性真的很不错,| 1436 | 祝小康小暖阳每天开开心心,| 1437 | 旗舰店推给天猫,| 1438 | 明显是被压过的,| 1439 | 不占位子,| 1440 | 京东单独挑选的,| 1441 | 买的时候天气比较暖和就没有怎么使用,制热效果 1442 | 这款还不错,| 1443 | 也就覆盖20平的范围,| 1444 | 继续使用后再看,| 1445 | 平均15分钟停一次,| 1446 | 一个人烤还行,| 1447 | 过年没人来修,| 1448 | 都一个多礼拜了,| 1449 | 升温较慢,制热效果 1450 | 挨近才能取暖,| 1451 | 本来想给五星好评,| 1452 | 颜值很多,| 1453 | 取暖效果:看着没那么暖和,制热效果 1454 | 半个小时不到15平方的房间就比较温暖了,| 1455 | 由于在卧室里使用,| 1456 | 典型卖得好就质量越来越不行,| 1457 | 上次一个艾美特两年歇菜了,| 1458 | 聚划算,| 1459 | 老人也可以一学就会,| 1460 | 取暖效果:房间还可以,| 1461 | 白色+香槟金,| 1462 | 冷了一段时间,| 1463 | 用于浴室的,| 1464 | 最高档800+1200同时开,| 1465 | 但是要靠近才比较明显,| 1466 | 费电带回老家用的,产品功耗 1467 | 买来就从来没用过,| 1468 | 双十二优惠入的手,| 1469 | 一直对小米的东西印象不错,| 1470 | 着实让我觉得有点生气,| 1471 | 先给京东物流点个赞,| 1472 | 新机器一点声音也没有,| 1473 | 要用墙插,| 1474 | 我喜欢浅颜色的,| 1475 | 先锋质量不错??,| 1476 | 一般不好,| 1477 | 所谓对衡式加热就是,| 1478 | 慧米洛凡还有猫米白鸽还有慧米滑雨好几个都很好,| 1479 | 就是费电1500瓦的,产品功耗 1480 | 希望能用几年,| 1481 | 功能强大发热快,制热效果 1482 | 一直买这个品牌,| 1483 | 总之是一次很满意的购物,| 1484 | 外观设计:特别漂亮又实用匠心独特,| 1485 | 现在买比较合算,| 1486 | 也成了收货后没有再次找客服索要,| 1487 | 这款电暖气用了之后家里老人觉得很好,| 1488 | 取暖气在疫情间发货快,| 1489 | 安装设计有点欠佳,| 1490 | 外观设计:看上去高大尚,| 1491 | 期待了几天,| 1492 | 外观设计:艾美特大品牌,| 1493 | 有时还找不到人,| 1494 | 女儿有小暖宝宝了,| 1495 | 看着电影喝着啤酒美滋滋哈哈哈哈,声音 1496 | 给父母买的怕他们在取暖季到来之前太冷,| 1497 | 一天都三四十的电费,产品功耗 1498 | 加热温度大,| 1499 | (离近了当然能听出来),| 1500 | 先锋品牌高质量,| 1501 | 收到就是个这样的,| 1502 | 我觉得挺不错的,| 1503 | 更加好,| 1504 | 纸质发票还没收到,| 1505 | 用最低档开10分钟左右屋里面就很暖和了,制热效果 1506 | 开最大都没一点效果,| 1507 | 房间升温也快,| 1508 | 一般先开高档,| 1509 | 白色设计款,| 1510 | 发货特快,| 1511 | 安全设计:这个还不知道,| 1512 | 其他特色:安装容易方便,| 1513 | 且客服明明保证送货上门,| 1514 | 取暖效果:插上电热,| 1515 | 升温还算快的,| 1516 | 错过了7天试用,| 1517 | 物流小哥态度也不错,| 1518 | 外观设计:挺好看白色,| 1519 | 没想到这至少有两个单人枕头那么大,| 1520 | 所以再入手了,| 1521 | 一次买了好多个,| 1522 | 或者自己去售后网点购买配件,| 1523 | 无论学习还是工作就舒服了,| 1524 | 整整迟了一天,| 1525 | 加热了一宿后再用就没啥味道了,味道 1526 | 苏宁快递也给力,| 1527 | 消费者就那么好欺负,| 1528 | 就还是好评吧,| 1529 | 家里有宝宝也不觉得吵,| 1530 | 我要吐槽一下,| 1531 | 可能是快到春节的原因吧,| 1532 | 平方米左右,| 1533 | 衣服干的很快哦,| 1534 | 真的没用,| 1535 | 起热风很快,| 1536 | 希望可以耐用一点,| 1537 | 要看房间大小,| 1538 | 适合小平方的,| 1539 | 开启后升温很快,| 1540 | 而且它是可以分段定时的,| 1541 | 一旦你付了钱,| 1542 | 晾衣架不稳滑落,| 1543 | 虚报温度,| 1544 | 阳台烘衣服很给力,| 1545 | 前面没有热量,| 1546 | 不是那些杂牌货,| 1547 | 时间也赶得非常好,| 1548 | 用了近一月,| 1549 | 这次也没有让我失望,| 1550 | 外型也很漂亮看起来很精致,| 1551 | 还可以选择是否摇头,| 1552 | 就帮朋友又定啦一台,| 1553 | 耗电情况:节能款,产品功耗 1554 | 冷热风都好用,| 1555 | 鸡肋一样的东西,| 1556 | 暖度很快上升,| 1557 | 完美的产品,| 1558 | 冬天的福音啊,| 1559 | 我觉得太小了,体积大小 1560 | 正常使用中,| 1561 | 给宝宝洗澡用特别好,| 1562 | 外观设计:简洁大方取暖效果:优,| 1563 | 外观设计:好看取暖效果:非常快安全设计:全,| 1564 | 也太冷了,| 1565 | 外观大气干净,| 1566 | 不到三五折,| 1567 | 不象卖说的那么好,| 1568 | 冬天用起来很好,| 1569 | 很想象的差的太远了,| 1570 | 和我说正常,| 1571 | 没客服没售后,| 1572 | 防触电模式,| 1573 | 这个特价时候买的,| 1574 | 花了一百多运费换货,| 1575 | 还行总体不错,| 1576 | 1平米都吹不暖,制热范围 1577 | 距离五十多厘米完全没用,| 1578 | 都是拖吧,| 1579 | 很暖和耗电情况:中规中矩,产品功耗 1580 | 值得有需要的朋友购买哦,| 1581 | 适合夜里起来喂奶,| 1582 | 京东有这么一天,| 1583 | 比空调升温快,| 1584 | 据说还可以,| 1585 | 其他特色:新潮好看,| 1586 | 你细细品,| 1587 | 不过也就只能热一个十平方左右的屋子,| 1588 | 白搭100多就没退,| 1589 | 一直选不好买哪款电暖气,| 1590 | 之前看过测评,| 1591 | 两脚安装简易,| 1592 | 无论如何都没有实现,| 1593 | 买来烤猫的,| 1594 | 安全设计:把他推倒就自动的断电了,| 1595 | 擦,| 1596 | 小点的房间还是有制热效果的,| 1597 | 送过来是损坏的,| 1598 | 确实很费电,产品功耗 1599 | 需要维修部门出具故障单才能退换货,| 1600 | 我今天只能打淘宝客服讨要我的差价了,| 1601 | 所以建议商家,| 1602 | 不过肯定不能直接拿水浇吧,| 1603 | 晚上下单第二天上午就到了,| 1604 | 取暖效果:有3档风,| 1605 | 人家直接甩一句个人感觉问题,| 1606 | 打开后果不其然,| 1607 | 开十几分钟后就有感觉到暖和了,制热效果 1608 | 除了一堆复制粘贴的话,| 1609 | 线上是否有差别,| 1610 | 格力取暖器质量不错,| 1611 | 开着很舒服,| 1612 | 根本上没有返修率,| 1613 | 加湿器有水声(不过加湿效果不咋滴)其他特色:没有任何异味,| 1614 | 机器产生温度并不高,| 1615 | 这次第一个送过来还没开箱就已经漏油了,| 1616 | 差远,| 1617 | 其他特色:还有个水槽,| 1618 | 整屋加热应该费电些静音效果:有点声音,产品功耗 1619 | 这么细的铁杆还碍地方,| 1620 | 2、加热效果得开到最大才效果好,制热效果 1621 | 后期热效果不错,| 1622 | 室温达到20.5度,| 1623 | 需要的亲们可以赶快去下单了,| 1624 | 算是不小的优势,| 1625 | 科技改变生活,| 1626 | 还没开始用就发现降价了,| 1627 | 东西不好热不起来,| 1628 | 耗电情况:能小,产品功耗 1629 | 热的面积不大,| 1630 | 主要没风,| 1631 | 之前宝宝洗澡都是去母婴店,| 1632 | 打折买的话还是可以的,| 1633 | 房间感觉不到热度,制热效果 1634 | 有点异味,味道 1635 | 最高档一个小时两度电,| 1636 | 机器本身不沉方便移动,| 1637 | 再送两个字,| 1638 | 开一晚上也不热,| 1639 | 省的我大冬天骑车去镇上了,| 1640 | 外观质量都很好,| 1641 | 不用等很久,| 1642 | 使用感会更好的,| 1643 | 这大件东西还那么沉,| 1644 | 很安逸,| 1645 | 有遥控装置,| 1646 | 整个屋子非常温暖,| 1647 | 前几天刚买一个同一产品,| 1648 | 收到货后还一直没用,| 1649 | 有20来斤,| 1650 | 家里有孩子冬天换尿不湿怕感冒了,| 1651 | 物流也特别快,| 1652 | 东西很重,| 1653 | 闻完一杯,| 1654 | 凑合吧,| 1655 | 估计产品质量有问题,| 1656 | 就看以后质量如何,| 1657 | 今天最高气温28度,| 1658 | 起床前开会,| 1659 | 加遥控设计方便开关调节,| 1660 | 石墨烯客服介绍,| 1661 | 只是现在天气热,| 1662 | 这个取暖器就没有这种状况,| 1663 | 骗人的<,| 1664 | 还有最重要的一点是,| 1665 | 快去升温,| 1666 | 现在我要自己承担一百的运费呵呵了,| 1667 | 说实在话,| 1668 | 冬天直接不管用,| 1669 | 而且和暖气一样舒适,| 1670 | 感觉制热还可以,| 1671 | 16A的商家应该提醒,| 1672 | 差劲差劲,| 1673 | 自动-请求人工-等待-自动-请求人工-等待循环,| 1674 | 就是耗电多,产品功耗 1675 | 电暖风已收到质量很好声音很小,| 1676 | 不过质量得不到保证,| 1677 | 客服还说2-3天就没有了,| 1678 | 南方人家里没暖气就是苦逼,| 1679 | 也不是老用,| 1680 | 完全影响不到孩子睡觉,| 1681 | 用电非常大,| 1682 | 只适合一个人用,| 1683 | 对于冬天来说这款取暖器真的不错,| 1684 | 没人搭理,| 1685 | 京东这样的快递员这样的态度可不多的,| 1686 | 取暖器功能很强大,| 1687 | 双十一的时候抢了一个,| 1688 | 最近降温了,| 1689 | 这个房间里都很暖和,制热效果 1690 | 收到货就打开了,| 1691 | 间歇时间不同,| 1692 | 收到就退回,| 1693 | 油烟味很重,味道 1694 | 出暖风非常好,| 1695 | 以后再也不担心孩子洗澡感冒了,| 1696 | 单纯从赠品一事说起,| 1697 | 插上电出风立马就暖和了,制热效果 1698 | 估计是热胀开的,| 1699 | 快速热风,制热效果 1700 | 可以过个暖和的冬天,制热效果 1701 | 很小很轻,| 1702 | 油汀灯都长这样,| 1703 | 还是格力旗舰店,| 1704 | 唯一缺点:商家价格管理混乱,| 1705 | 终于换到一个质量不错的电暖气,| 1706 | 冬天多亏有它,| 1707 | 很美观漂亮、小巧不占地方节约空间,体积大小 1708 | 16平米的房子足够了,| 1709 | 热的速度有点慢,| 1710 | 按说明书上的图安装好,| 1711 | 不过还是不要指望能让一个房间暖和起来了,制热效果 1712 | 我家浴室的电位设置都比较高,| 1713 | 整个房间都不冷了,| 1714 | 耗了多少电也没有计,| 1715 | 送货安排很快,| 1716 | 没看评价就买了,| 1717 | 小电器没法上门修,| 1718 | 一直没人回复,| 1719 | 应该不会冷,| 1720 | 无法拍照,| 1721 | 性能一样,| 1722 | 所以我想了想还是要退货,| 1723 | 好久了才来评价,| 1724 | 联系了客服态度超好,| 1725 | 我相信你这种态度会让很多用户不舒服,| 1726 | 老妈说效果还可以,| 1727 | 买过手机、手环等,| 1728 | 外观设计时尚大方,| 1729 | 离开平面就自动断电,| 1730 | 一打开整个房间都是塑料味道,味道 1731 | 以前空调正常开两个月下来最多5、600的电费,产品功耗 1732 | 谢谢江河客服,| 1733 | 颜值还比较高,| 1734 | 仍然可联系客服退差价,| 1735 | 取暖效果:温度刚好,制热效果 1736 | 最重要的是升温效果可以,| 1737 | 解决问题还是比较给力的,| 1738 | 不过这次买的商标上没标注生产日期,| 1739 | 要垫凳子,| 1740 | 外观设计:我见到过的油汀基本都是这个造型,| 1741 | 今天收获,| 1742 | 买来发现惊喜啊,| 1743 | 目前没感觉有噪音安全设计:安装的时候看了一下里面就些丝状的东西,| 1744 | 我也真是无语了,| 1745 | 真厉害,| 1746 | 晚上用声音有点大,| 1747 | 不过只能放在卧室用,| 1748 | 不到一个月这个用了*,| 1749 | 颜值和功能担当,| 1750 | 宝贝换尿不湿时候开着,| 1751 | 好货杠杠的五分,| 1752 | 大牌取暖器很好用,| 1753 | 客服立夏也很热情nice 服务很好,| 1754 | 重鸭运费自理不合算鸭,| 1755 | 超乎想象的美好,| 1756 | 温度一会都上来了,| 1757 | 西安这两天晚上开一档房间就很热,| 1758 | 感觉比空调都管用,| 1759 | 有小朋友的更危险,| 1760 | 2.预热太慢,| 1761 | 春节假期送货也很快,| 1762 | 家里好多天没见太阳了,| 1763 | 245度,| 1764 | 而且价格也公道,| 1765 | 造型考虑了多种用途,| 1766 | 耗电情况:电耗有点高,产品功耗 1767 | 安全设计:安全这个要说一句,| 1768 | 只是我家房间太大,| 1769 | 倒了自动断电,| 1770 | 主要是家里没有安装暖气片,| 1771 | 售后方面也很到家,| 1772 | 赶紧买了这个,| 1773 | 冷暖两用3+1档温度,| 1774 | 开了一会会,| 1775 | 不开窗就是很臭,| 1776 | 美观非常满意,| 1777 | 关键家里有小宝随时要出门玩的,| 1778 | 最后机器长度有点长,| 1779 | 不会有危险的,| 1780 | 不太习惯,| 1781 | 取暖性太一般,| 1782 | 打开试一下,| 1783 | 油酊刚刚好,| 1784 | 运行期间一点声音都没有,| 1785 | 外观设计:13片专利热浪取暖器,| 1786 | 物流快递也很快,| 1787 | 抱着试试看的心态买的,| 1788 | 20多平一点都不暖和,制热范围 1789 | 有了这个很快烘干了,| 1790 | 买来还不到4天,| 1791 | 小范围取暖可以,| 1792 | 直接嘲讽客户讲20块是好多钱,| 1793 | 知道能穿着秋裤在房间里浪,| 1794 | 米家东西外观简洁大方,| 1795 | 取暖效果:多档可选,| 1796 | ***300块钱的东西我承担200运费还退**呢,| 1797 | 看着小,| 1798 | 好顶赞,| 1799 | 随便就断了,| 1800 | 遥控器不送电池,| 1801 | 真正的速热,制热效果 1802 | 只有周围有温度,| 1803 | 艾美特质量好,| 1804 | 就上面出风,| 1805 | 觉得推荐京造那款,| 1806 | 刚开始直接来高档的,| 1807 | 特意买了白色,| 1808 | 很合用,| 1809 | 静音效果:有噼里啪啦的声音,声音 1810 | 冬天用了两个月,| 1811 | 能正常使用,| 1812 | 亦可赛艇,| 1813 | 今天试用,| 1814 | 再来追评,| 1815 | 看朋友家在用这个,| 1816 | 用了一上午真的很不错,| 1817 | 已经用了一个了,| 1818 | 从试用情况来看还满意,| 1819 | 10平方内使用够了,| 1820 | 后来价格长上去了,| 1821 | 买来准备宝宝出生后给她洗澡用的,| 1822 | 商品未在身边,| 1823 | 物流很给力,| 1824 | 感谢京东这样的爱心平台??,| 1825 | 没用这个之前,| 1826 | 毕竟是吹风型,| 1827 | 好用实惠又不贵,| 1828 | 就是遥控器没有显示,| 1829 | *块钱很暖和,制热效果 1830 | 就是过于耗电,产品功耗 1831 | 不能洗澡,| 1832 | 一款可以远程,| 1833 | 一個使用上,| 1834 | 性能好也安全,| 1835 | 现在湖南常德才几度好吗,| 1836 | 取暖效果:常,| 1837 | 想房间暖和1得密封,制热效果 1838 | 小房间用适合,| 1839 | 冬天下雨天宝宝衣服不好干时还可以烘干衣服,衣物烘干 1840 | 这次买来估计用不了多久了,| 1841 | 温度没有想下的那么高,| 1842 | 包装都没拆,| 1843 | 如果不是平视的话基本算是没得光源亮度,| 1844 | 外观设计很美哦,| 1845 | 用了几天了感觉还不错,| 1846 | 外观设计:东西小取暖效果:可以其他特色:快递不错送货上门,| 1847 | 马上要睡着了,| 1848 | 然后现在双十一价格出来了,| 1849 | 真是骚操作,| 1850 | 看着比较小巧功能强大,| 1851 | 399那款因为缺货没有买到,| 1852 | 很好用家里有宝宝特别实用,| 1853 | 身边的朋友都有介绍此款,| 1854 | 退款不退,| 1855 | 耗电情况:耗电这个东西也没法测,产品功耗 1856 | 花了多少钱,| 1857 | 十几分钟房间就能感觉到温温的,| 1858 | 今年有了宝宝,| 1859 | 根本没有解决,| 1860 | 第一档冷风基本用不上,| 1861 | 取暖烘衣服两不误,| 1862 | 还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合还凑合,| 1863 | 打开10分钟就能感觉到房间里热乎乎的,| 1864 | 怕起火,| 1865 | 相信格力的大品牌不会有什么问题,| 1866 | 包装的很好不易损坏,| 1867 | 按客服提示换了放向也一样,| 1868 | 并且物流还慢,| 1869 | 之后追加,| 1870 | 没有噪声,| 1871 | 安全设计:有待提高,| 1872 | 但五星好评不会迟到,| 1873 | 问了三天客服会不会用一段时间就好了,| 1874 | 有风扇设计所致,| 1875 | 买了这个油汀,| 1876 | 不错非常感谢京东快递小哥不错非常感谢京东快递小哥,| 1877 | 颜值也比较高,| 1878 | 耗电情况:目前不太清楚,产品功耗 1879 | 电暖器的质量很好的哦非常好,| 1880 | 试用中后期再最追评,| 1881 | 物料也非常的快呢,| 1882 | 我再点几天看看,| 1883 | 颜色高端,| 1884 | 真的是很不错,| 1885 | 外观设计:美观就是有点大,| 1886 | 最近五年在深圳,| 1887 | 开起来的时候有点啪啪的响声,声音 1888 | 最后只有拿螺丝刀了,| 1889 | 太失望了??太失望了,| 1890 | 取暖效果:高效,| 1891 | 整体效果较为舒适,| 1892 | 还是比较优惠的了,| 1893 | 其他的都没用,| 1894 | 天气还挺热,| 1895 | 我是有点大所以不是很暖和,制热效果 1896 | 还贴心的送了加湿器,| 1897 | 看京东上面价格也合适,| 1898 | 四个风档,| 1899 | 平时房间小,| 1900 | 好点赞,| 1901 | 还会给你好评,| 1902 | 还没有测算电费,产品功耗 1903 | 很静音安全设计:没发现有安全隐患,| 1904 | 收到取暖器就试了一下,| 1905 | 结果209变179,| 1906 | 卧室效果不比空调取暖差,| 1907 | 暖和给力,制热效果 1908 | 如果有遥控器的话更好,| 1909 | 其他特色:有自动转头功能,| 1910 | 很多商家拉我刷好评,| 1911 | 以后刚开时就用最大档,| 1912 | 静音效果:没听出来有声音,| 1913 | 感觉很心疼电费,产品功耗 1914 | 孩子的衣服冬天也不担心干不了了,| 1915 | 外壳一点都不烫,| 1916 | 但是空调是很浪费电的,产品功耗 1917 | 温度上升还不错,| 1918 | 还是会很冷,| 1919 | 坏了只修不换,| 1920 | 冬季卧室值得拥有,| 1921 | 答应我,| 1922 | 这是买的第二个电暖器,| 1923 | 拍下价格502.55元(详见图1),| 1924 | 取暖效果:五六平方的空间,| 1925 | 等用了在追评,| 1926 | 简约风,| 1927 | 这次是给朋友推荐,| 1928 | 估计也就到10°,| 1929 | 而且广告说的防烫呢,| 1930 | 没什么用到,制热效果 1931 | 第一次开始用的时候有点味道,味道 1932 | 价格实惠便宜,| 1933 | 顺眼,| 1934 | 温度控制在35度,| 1935 | 电源线着实太短了一些,| 1936 | 整个房间升温一般,| 1937 | 真麻烦,| 1938 | 取暖效果:家人用了,| 1939 | 用了几天发现制热效果真快,| 1940 | 精美之物实用方便,| 1941 | 垃圾产品垃圾客服,| 1942 | 挺温暖的,| 1943 | 婆婆直夸我买的好,| 1944 | 最后是客服小白给将诶绝了问题,| 1945 | 他蛮横地很,| 1946 | 哈哈很好一直在喝这个牌子的牛奶宝贝也很喜欢喝总之生活用品食品啊都在京东上买了快递很给力很方便产品也便宜真的是太好了我爱京东购物太好了太方便了油盐酱醋茶想买什么有什么大的赞赞赞??米面粮衣服鞋你想买啥就有啥哈哈哈哈哈哈哈哈,| 1947 | 垃圾电商,| 1948 | 我根本不是不理由好不好,| 1949 | 其他特色:看了很多种,| 1950 | 其他特色:用过之后才知道,| 1951 | 高档还行,| 1952 | 30天内返差价,| 1953 | 很满意物流也很给力??,| 1954 | 而且温暖没什么感觉吧,| 1955 | 一个月用了400块电费,产品功耗 1956 | 拼多多才430元,| 1957 | 谢谢京东给我们非常时期带来实惠的价格,| 1958 | 有配遥控器,| 1959 | 领导们要奖励快递小哥,| 1960 | 宣传不实,| 1961 | 16平米的房间开2小时能暖十几度,| 1962 | 除了运费险报销10元,| 1963 | 相当好用,| 1964 | 而且这款还是升降的,| 1965 | 又可以触控又可以遥控返,| 1966 | 京东自营棒棒哒,| 1967 | 静音效果:无光无声音,| 1968 | 素雅,| 1969 | 断电后有响声,声音 1970 | 还是没有那种小小的热风机好用,| 1971 | 有点掉漆,| 1972 | 外观设计时尚、白色干净大方百搭、加热快、没有噪音、有加湿盒和烘干架、晾个袜子啥的很实用,制热效果/声音 1973 | 比空调差不了多少,| 1974 | 可以晾尿布,| 1975 | 24小时开着平均下来一小时2度电,| 1976 | 格力电器质量还是顶呱呱的,| 1977 | 使用了几天才来评价的,| 1978 | 加湿打开,| 1979 | 效果出色,| 1980 | 三十分钟后房间的制热还不明显,| 1981 | 昨天才拿卧室用,| 1982 | 上面的烘干架坏掉的,| 1983 | 外观设计:比一般的要骚猪一点,| 1984 | 刚收到货的时候发现有磕碰,| 1985 | 第二天上午就到了,| 1986 | 放在踢脚线旁边不占地方,体积大小 1987 | 取暖器已经试用过了,| 1988 | 开一会儿整个房间就暖和了,制热效果 1989 | 弄好设置,| 1990 | 外观材质:高到160成人膝盖附近,| 1991 | 主卧大概二十几平,| 1992 | 18平方卧室升温明显,| 1993 | 晚上到家就热了,| 1994 | 如果有超级差评价我真的想给,| 1995 | 这样空气也没那么干了,| 1996 | 不走心的商家,| 1997 | 老老实实用空调,| 1998 | 为我驱散了冬天的寒冷,| 1999 | 懒得麻烦就没退货了,| 2000 | 发现是电暖器的问题,| 2001 | 还没看到,| 2002 | -------------------------------------------------------------------------------- /data/vocabulary_label.txt: -------------------------------------------------------------------------------- 1 | | 2 | 互联互通 3 | 产品功耗 4 | 滑轮提手 5 | 声音 6 | APP操控性 7 | 呼吸灯 8 | 外观 9 | 底座 10 | 制热范围 11 | 遥控器电池 12 | 味道 13 | 制热效果 14 | 衣物烘干 15 | 体积大小 16 | -------------------------------------------------------------------------------- /hyperparameters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Nov 12 14:23:12 2018 4 | 5 | @author: cm 6 | """ 7 | 8 | 9 | import os 10 | pwd = os.path.dirname(os.path.abspath(__file__)) 11 | from classifier_multi_label.utils import load_vocabulary#,load_third_fourth_dict 12 | 13 | 14 | class Hyperparamters: 15 | # Train parameters 16 | num_train_epochs = 40 17 | print_step = 10 18 | batch_size = 32 19 | summary_step = 10 20 | num_saved_per_epoch = 3 21 | max_to_keep = 100 22 | logdir = 'logdir/model_01' 23 | file_save_model = 'model/model_01' 24 | 25 | # Predict model file 26 | file_model = 'model/saved_01' 27 | 28 | # Train/Test data 29 | data_dir = os.path.join(pwd,'data') 30 | train_data = 'train_onehot.csv' 31 | test_data = 'test_onehot.csv' 32 | 33 | # Load vocabulcary dict 34 | dict_id2label,dict_label2id = load_vocabulary(os.path.join(pwd,'data','vocabulary_label.txt')) 35 | label_vocabulary = list(dict_id2label.values()) 36 | 37 | # Optimization parameters 38 | warmup_proportion = 0.1 39 | use_tpu = None 40 | do_lower_case = True 41 | learning_rate = 5e-5 42 | 43 | # TextCNN parameters 44 | num_filters = 128 45 | filter_sizes = [2,3,4,5,6,7] 46 | embedding_size = 384 47 | keep_prob = 0.5 48 | 49 | # Sequence and Label 50 | sequence_length = 60 51 | num_labels = len(list(dict_id2label)) 52 | 53 | # ALBERT 54 | model = 'albert_small_zh_google' 55 | bert_path = os.path.join(pwd,model) 56 | vocab_file = os.path.join(pwd,model,'vocab_chinese.txt') 57 | init_checkpoint = os.path.join(pwd,model,'albert_model.ckpt') 58 | saved_model_path = os.path.join(pwd,'model') 59 | 60 | 61 | 62 | 63 | 64 | 65 | if __name__ == '__main__': 66 | hp = Hyperparamters() 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /image/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellonlp/classifier_multi_label/8cd68357d971a0cd53528b3a213e0e84a09c5b85/image/graph.png -------------------------------------------------------------------------------- /lamb_optimizer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Copyright 2018 The Google AI Team Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # Lint as: python2, python3 16 | """Functions and classes related to optimization (weight updates).""" 17 | 18 | from __future__ import absolute_import 19 | from __future__ import division 20 | from __future__ import print_function 21 | 22 | import re 23 | import six 24 | import tensorflow.compat.v1 as tf 25 | 26 | # pylint: disable=g-direct-tensorflow-import 27 | from tensorflow.python.ops import array_ops 28 | from tensorflow.python.ops import linalg_ops 29 | from tensorflow.python.ops import math_ops 30 | # pylint: enable=g-direct-tensorflow-import 31 | 32 | 33 | class LAMBOptimizer(tf.train.Optimizer): 34 | """LAMB (Layer-wise Adaptive Moments optimizer for Batch training).""" 35 | # A new optimizer that includes correct L2 weight decay, adaptive 36 | # element-wise updating, and layer-wise justification. The LAMB optimizer 37 | # was proposed by Yang You, Jing Li, Jonathan Hseu, Xiaodan Song, 38 | # James Demmel, and Cho-Jui Hsieh in a paper titled as Reducing BERT 39 | # Pre-Training Time from 3 Days to 76 Minutes (arxiv.org/abs/1904.00962) 40 | 41 | def __init__(self, 42 | learning_rate, 43 | weight_decay_rate=0.0, 44 | beta_1=0.9, 45 | beta_2=0.999, 46 | epsilon=1e-6, 47 | exclude_from_weight_decay=None, 48 | exclude_from_layer_adaptation=None, 49 | name="LAMBOptimizer"): 50 | """Constructs a LAMBOptimizer.""" 51 | super(LAMBOptimizer, self).__init__(False, name) 52 | 53 | self.learning_rate = learning_rate 54 | self.weight_decay_rate = weight_decay_rate 55 | self.beta_1 = beta_1 56 | self.beta_2 = beta_2 57 | self.epsilon = epsilon 58 | self.exclude_from_weight_decay = exclude_from_weight_decay 59 | # exclude_from_layer_adaptation is set to exclude_from_weight_decay if the 60 | # arg is None. 61 | # TODO(jingli): validate if exclude_from_layer_adaptation is necessary. 62 | if exclude_from_layer_adaptation: 63 | self.exclude_from_layer_adaptation = exclude_from_layer_adaptation 64 | else: 65 | self.exclude_from_layer_adaptation = exclude_from_weight_decay 66 | 67 | def apply_gradients(self, grads_and_vars, global_step=None, name=None): 68 | """See base class.""" 69 | assignments = [] 70 | for (grad, param) in grads_and_vars: 71 | if grad is None or param is None: 72 | continue 73 | 74 | param_name = self._get_variable_name(param.name) 75 | 76 | m = tf.get_variable( 77 | name=six.ensure_str(param_name) + "/adam_m", 78 | shape=param.shape.as_list(), 79 | dtype=tf.float32, 80 | trainable=False, 81 | initializer=tf.zeros_initializer()) 82 | v = tf.get_variable( 83 | name=six.ensure_str(param_name) + "/adam_v", 84 | shape=param.shape.as_list(), 85 | dtype=tf.float32, 86 | trainable=False, 87 | initializer=tf.zeros_initializer()) 88 | 89 | # Standard Adam update. 90 | next_m = ( 91 | tf.multiply(self.beta_1, m) + tf.multiply(1.0 - self.beta_1, grad)) 92 | next_v = ( 93 | tf.multiply(self.beta_2, v) + tf.multiply(1.0 - self.beta_2, 94 | tf.square(grad))) 95 | 96 | update = next_m / (tf.sqrt(next_v) + self.epsilon) 97 | 98 | # Just adding the square of the weights to the loss function is *not* 99 | # the correct way of using L2 regularization/weight decay with Adam, 100 | # since that will interact with the m and v parameters in strange ways. 101 | # 102 | # Instead we want ot decay the weights in a manner that doesn't interact 103 | # with the m/v parameters. This is equivalent to adding the square 104 | # of the weights to the loss with plain (non-momentum) SGD. 105 | if self._do_use_weight_decay(param_name): 106 | update += self.weight_decay_rate * param 107 | 108 | ratio = 1.0 109 | if self._do_layer_adaptation(param_name): 110 | w_norm = linalg_ops.norm(param, ord=2) 111 | g_norm = linalg_ops.norm(update, ord=2) 112 | ratio = array_ops.where(math_ops.greater(w_norm, 0), array_ops.where( 113 | math_ops.greater(g_norm, 0), (w_norm / g_norm), 1.0), 1.0) 114 | 115 | update_with_lr = ratio * self.learning_rate * update 116 | 117 | next_param = param - update_with_lr 118 | 119 | assignments.extend( 120 | [param.assign(next_param), 121 | m.assign(next_m), 122 | v.assign(next_v)]) 123 | return tf.group(*assignments, name=name) 124 | 125 | def _do_use_weight_decay(self, param_name): 126 | """Whether to use L2 weight decay for `param_name`.""" 127 | if not self.weight_decay_rate: 128 | return False 129 | if self.exclude_from_weight_decay: 130 | for r in self.exclude_from_weight_decay: 131 | if re.search(r, param_name) is not None: 132 | return False 133 | return True 134 | 135 | def _do_layer_adaptation(self, param_name): 136 | """Whether to do layer-wise learning rate adaptation for `param_name`.""" 137 | if self.exclude_from_layer_adaptation: 138 | for r in self.exclude_from_layer_adaptation: 139 | if re.search(r, param_name) is not None: 140 | return False 141 | return True 142 | 143 | def _get_variable_name(self, param_name): 144 | """Get the variable name from the tensor name.""" 145 | m = re.match("^(.*):\\d+$", six.ensure_str(param_name)) 146 | if m is not None: 147 | param_name = m.group(1) 148 | return param_name 149 | -------------------------------------------------------------------------------- /logdir/model_01/events.out.tfevents.1595940723.DESKTOP-QC1A83I: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellonlp/classifier_multi_label/8cd68357d971a0cd53528b3a213e0e84a09c5b85/logdir/model_01/events.out.tfevents.1595940723.DESKTOP-QC1A83I -------------------------------------------------------------------------------- /model/model_01/README.md: -------------------------------------------------------------------------------- 1 | # 当前目录为训练模型存储的文件夹:model/model_01 2 | # 预测时,使用另外一个文件夹加载模型。 3 | -------------------------------------------------------------------------------- /model/saved_01/README.md: -------------------------------------------------------------------------------- 1 | # 当前目录是存储预测(推理)模型用的文件夹:model/saved_01 2 | # 训练时,使用另外一个文件夹存储模型。 3 | -------------------------------------------------------------------------------- /model/saved_01/checkpoint: -------------------------------------------------------------------------------- 1 | model_checkpoint_path: "model_1_0.ckpt" 2 | -------------------------------------------------------------------------------- /modeling.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """The main ALBERT model and related functions. 3 | For a description of the algorithm, see https://arxiv.org/abs/1909.11942. 4 | """ 5 | 6 | from __future__ import absolute_import 7 | from __future__ import division 8 | from __future__ import print_function 9 | 10 | import collections 11 | import copy 12 | import json 13 | import math 14 | import re 15 | import numpy as np 16 | import six 17 | from six.moves import range 18 | import tensorflow as tf 19 | from tensorflow.contrib import layers as contrib_layers 20 | 21 | 22 | class AlbertConfig(object): 23 | """Configuration for `AlbertModel`. 24 | 25 | The default settings match the configuration of model `albert_xxlarge`. 26 | """ 27 | 28 | def __init__(self, 29 | vocab_size, 30 | embedding_size=128, 31 | hidden_size=4096, 32 | num_hidden_layers=12, 33 | num_hidden_groups=1, 34 | num_attention_heads=64, 35 | intermediate_size=16384, 36 | inner_group_num=1, 37 | down_scale_factor=1, 38 | hidden_act="gelu", 39 | hidden_dropout_prob=0, 40 | attention_probs_dropout_prob=0, 41 | max_position_embeddings=512, 42 | type_vocab_size=2, 43 | initializer_range=0.02): 44 | """Constructs AlbertConfig. 45 | 46 | Args: 47 | vocab_size: Vocabulary size of `inputs_ids` in `AlbertModel`. 48 | embedding_size: size of voc embeddings. 49 | hidden_size: Size of the encoder layers and the pooler layer. 50 | num_hidden_layers: Number of hidden layers in the Transformer encoder. 51 | num_hidden_groups: Number of group for the hidden layers, parameters in 52 | the same group are shared. 53 | num_attention_heads: Number of attention heads for each attention layer in 54 | the Transformer encoder. 55 | intermediate_size: The size of the "intermediate" (i.e., feed-forward) 56 | layer in the Transformer encoder. 57 | inner_group_num: int, number of inner repetition of attention and ffn. 58 | down_scale_factor: float, the scale to apply 59 | hidden_act: The non-linear activation function (function or string) in the 60 | encoder and pooler. 61 | hidden_dropout_prob: The dropout probability for all fully connected 62 | layers in the embeddings, encoder, and pooler. 63 | attention_probs_dropout_prob: The dropout ratio for the attention 64 | probabilities. 65 | max_position_embeddings: The maximum sequence length that this model might 66 | ever be used with. Typically set this to something large just in case 67 | (e.g., 512 or 1024 or 2048). 68 | type_vocab_size: The vocabulary size of the `token_type_ids` passed into 69 | `AlbertModel`. 70 | initializer_range: The stdev of the truncated_normal_initializer for 71 | initializing all weight matrices. 72 | """ 73 | self.vocab_size = vocab_size 74 | self.embedding_size = embedding_size 75 | self.hidden_size = hidden_size 76 | self.num_hidden_layers = num_hidden_layers 77 | self.num_hidden_groups = num_hidden_groups 78 | self.num_attention_heads = num_attention_heads 79 | self.inner_group_num = inner_group_num 80 | self.down_scale_factor = down_scale_factor 81 | self.hidden_act = hidden_act 82 | self.intermediate_size = intermediate_size 83 | self.hidden_dropout_prob = hidden_dropout_prob 84 | self.attention_probs_dropout_prob = attention_probs_dropout_prob 85 | self.max_position_embeddings = max_position_embeddings 86 | self.type_vocab_size = type_vocab_size 87 | self.initializer_range = initializer_range 88 | 89 | @classmethod 90 | def from_dict(cls, json_object): 91 | """Constructs a `AlbertConfig` from a Python dictionary of parameters.""" 92 | config = AlbertConfig(vocab_size=None) 93 | for (key, value) in six.iteritems(json_object): 94 | config.__dict__[key] = value 95 | return config 96 | 97 | @classmethod 98 | def from_json_file(cls, json_file): 99 | """Constructs a `AlbertConfig` from a json file of parameters.""" 100 | #with tf.gfile.GFile(json_file, "r") as reader: 101 | with tf.io.gfile.GFile(json_file, "r") as reader: 102 | text = reader.read() 103 | return cls.from_dict(json.loads(text)) 104 | 105 | def to_dict(self): 106 | """Serializes this instance to a Python dictionary.""" 107 | output = copy.deepcopy(self.__dict__) 108 | return output 109 | 110 | def to_json_string(self): 111 | """Serializes this instance to a JSON string.""" 112 | return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" 113 | 114 | 115 | class AlbertModel(object): 116 | """BERT model ("Bidirectional Encoder Representations from Transformers"). 117 | 118 | Example usage: 119 | 120 | ```python 121 | # Already been converted from strings into ids 122 | input_ids = tf.constant([[31, 51, 99], [15, 5, 0]]) 123 | input_mask = tf.constant([[1, 1, 1], [1, 1, 0]]) 124 | token_type_ids = tf.constant([[0, 0, 1], [0, 2, 0]]) 125 | 126 | config = modeling.AlbertConfig(vocab_size=32000, hidden_size=512, 127 | num_hidden_layers=8, num_attention_heads=6, intermediate_size=1024) 128 | 129 | model = modeling.AlbertModel(config=config, is_training=True, 130 | input_ids=input_ids, input_mask=input_mask, token_type_ids=token_type_ids) 131 | 132 | label_embeddings = tf.get_variable(...) 133 | pooled_output = model.get_pooled_output() 134 | logits = tf.matmul(pooled_output, label_embeddings) 135 | ... 136 | ``` 137 | """ 138 | 139 | def __init__(self, 140 | config, 141 | is_training, 142 | input_ids, 143 | input_mask=None, 144 | token_type_ids=None, 145 | use_one_hot_embeddings=False, 146 | scope=None): 147 | """Constructor for AlbertModel. 148 | 149 | Args: 150 | config: `AlbertConfig` instance. 151 | is_training: bool. true for training model, false for eval model. Controls 152 | whether dropout will be applied. 153 | input_ids: int32 Tensor of shape [batch_size, seq_length]. 154 | input_mask: (optional) int32 Tensor of shape [batch_size, seq_length]. 155 | token_type_ids: (optional) int32 Tensor of shape [batch_size, seq_length]. 156 | use_one_hot_embeddings: (optional) bool. Whether to use one-hot word 157 | embeddings or tf.embedding_lookup() for the word embeddings. 158 | scope: (optional) variable scope. Defaults to "bert". 159 | 160 | Raises: 161 | ValueError: The config is invalid or one of the input tensor shapes 162 | is invalid. 163 | """ 164 | config = copy.deepcopy(config) 165 | if not is_training: 166 | config.hidden_dropout_prob = 0.0 167 | config.attention_probs_dropout_prob = 0.0 168 | 169 | input_shape = get_shape_list(input_ids, expected_rank=2) 170 | batch_size = input_shape[0] 171 | seq_length = input_shape[1] 172 | 173 | if input_mask is None: 174 | input_mask = tf.ones(shape=[batch_size, seq_length], dtype=tf.int32) 175 | 176 | if token_type_ids is None: 177 | token_type_ids = tf.zeros(shape=[batch_size, seq_length], dtype=tf.int32) 178 | 179 | with tf.variable_scope(scope, default_name="albert"): 180 | with tf.variable_scope("embeddings"): 181 | # Perform embedding lookup on the word ids. 182 | (self.word_embedding_output, 183 | self.output_embedding_table) = embedding_lookup( 184 | input_ids=input_ids, 185 | vocab_size=config.vocab_size, 186 | embedding_size=config.embedding_size, 187 | initializer_range=config.initializer_range, 188 | word_embedding_name="word_embeddings", 189 | use_one_hot_embeddings=use_one_hot_embeddings) 190 | 191 | # Add positional embeddings and token type embeddings, then layer 192 | # normalize and perform dropout. 193 | self.embedding_output = embedding_postprocessor( 194 | input_tensor=self.word_embedding_output, 195 | use_token_type=True, 196 | token_type_ids=token_type_ids, 197 | token_type_vocab_size=config.type_vocab_size, 198 | token_type_embedding_name="token_type_embeddings", 199 | use_position_embeddings=True, 200 | position_embedding_name="position_embeddings", 201 | initializer_range=config.initializer_range, 202 | max_position_embeddings=config.max_position_embeddings, 203 | dropout_prob=config.hidden_dropout_prob) 204 | 205 | with tf.variable_scope("encoder"): 206 | 207 | # Run the stacked transformer. 208 | # `sequence_output` shape = [batch_size, seq_length, hidden_size]. 209 | self.all_encoder_layers = transformer_model( 210 | input_tensor=self.embedding_output, 211 | attention_mask=input_mask, 212 | hidden_size=config.hidden_size, 213 | num_hidden_layers=config.num_hidden_layers, 214 | num_hidden_groups=config.num_hidden_groups, 215 | num_attention_heads=config.num_attention_heads, 216 | intermediate_size=config.intermediate_size, 217 | inner_group_num=config.inner_group_num, 218 | intermediate_act_fn=get_activation(config.hidden_act), 219 | hidden_dropout_prob=config.hidden_dropout_prob, 220 | attention_probs_dropout_prob=config.attention_probs_dropout_prob, 221 | initializer_range=config.initializer_range, 222 | do_return_all_layers=True) 223 | 224 | self.sequence_output = self.all_encoder_layers[-1] 225 | # The "pooler" converts the encoded sequence tensor of shape 226 | # [batch_size, seq_length, hidden_size] to a tensor of shape 227 | # [batch_size, hidden_size]. This is necessary for segment-level 228 | # (or segment-pair-level) classification tasks where we need a fixed 229 | # dimensional representation of the segment. 230 | with tf.variable_scope("pooler"): 231 | # We "pool" the model by simply taking the hidden state corresponding 232 | # to the first token. We assume that this has been pre-trained 233 | first_token_tensor = tf.squeeze(self.sequence_output[:, 0:1, :], axis=1) 234 | self.pooled_output = tf.layers.dense( 235 | first_token_tensor, 236 | config.hidden_size, 237 | activation=tf.tanh, 238 | kernel_initializer=create_initializer(config.initializer_range)) 239 | 240 | def get_pooled_output(self): 241 | return self.pooled_output 242 | 243 | def get_sequence_output(self): 244 | """Gets final hidden layer of encoder. 245 | 246 | Returns: 247 | float Tensor of shape [batch_size, seq_length, hidden_size] corresponding 248 | to the final hidden of the transformer encoder. 249 | """ 250 | return self.sequence_output 251 | 252 | def get_all_encoder_layers(self): 253 | return self.all_encoder_layers 254 | 255 | def get_word_embedding_output(self): 256 | """Get output of the word(piece) embedding lookup. 257 | 258 | This is BEFORE positional embeddings and token type embeddings have been 259 | added. 260 | 261 | Returns: 262 | float Tensor of shape [batch_size, seq_length, hidden_size] corresponding 263 | to the output of the word(piece) embedding layer. 264 | """ 265 | return self.word_embedding_output 266 | 267 | def get_embedding_output(self): 268 | """Gets output of the embedding lookup (i.e., input to the transformer). 269 | 270 | Returns: 271 | float Tensor of shape [batch_size, seq_length, hidden_size] corresponding 272 | to the output of the embedding layer, after summing the word 273 | embeddings with the positional embeddings and the token type embeddings, 274 | then performing layer normalization. This is the input to the transformer. 275 | """ 276 | return self.embedding_output 277 | 278 | def get_embedding_table(self): 279 | return self.output_embedding_table 280 | 281 | 282 | def gelu(x): 283 | """Gaussian Error Linear Unit. 284 | 285 | This is a smoother version of the RELU. 286 | Original paper: https://arxiv.org/abs/1606.08415 287 | Args: 288 | x: float Tensor to perform activation. 289 | 290 | Returns: 291 | `x` with the GELU activation applied. 292 | """ 293 | cdf = 0.5 * (1.0 + tf.tanh( 294 | (np.sqrt(2 / np.pi) * (x + 0.044715 * tf.pow(x, 3))))) 295 | return x * cdf 296 | 297 | 298 | def get_activation(activation_string): 299 | """Maps a string to a Python function, e.g., "relu" => `tf.nn.relu`. 300 | 301 | Args: 302 | activation_string: String name of the activation function. 303 | 304 | Returns: 305 | A Python function corresponding to the activation function. If 306 | `activation_string` is None, empty, or "linear", this will return None. 307 | If `activation_string` is not a string, it will return `activation_string`. 308 | 309 | Raises: 310 | ValueError: The `activation_string` does not correspond to a known 311 | activation. 312 | """ 313 | 314 | # We assume that anything that"s not a string is already an activation 315 | # function, so we just return it. 316 | if not isinstance(activation_string, six.string_types): 317 | return activation_string 318 | 319 | if not activation_string: 320 | return None 321 | 322 | act = activation_string.lower() 323 | if act == "linear": 324 | return None 325 | elif act == "relu": 326 | return tf.nn.relu 327 | elif act == "gelu": 328 | return gelu 329 | elif act == "tanh": 330 | return tf.tanh 331 | else: 332 | raise ValueError("Unsupported activation: %s" % act) 333 | 334 | 335 | def get_assignment_map_from_checkpoint(tvars, init_checkpoint, num_of_group=0): 336 | """Compute the union of the current variables and checkpoint variables.""" 337 | assignment_map = {} 338 | initialized_variable_names = {} 339 | 340 | name_to_variable = collections.OrderedDict() 341 | for var in tvars: 342 | name = var.name 343 | m = re.match("^(.*):\\d+$", name) 344 | if m is not None: 345 | name = m.group(1) 346 | name_to_variable[name] = var 347 | init_vars = tf.train.list_variables(init_checkpoint) 348 | init_vars_name = [name for (name, _) in init_vars] 349 | 350 | if num_of_group > 0: 351 | assignment_map = [] 352 | for gid in range(num_of_group): 353 | assignment_map.append(collections.OrderedDict()) 354 | else: 355 | assignment_map = collections.OrderedDict() 356 | 357 | for name in name_to_variable: 358 | if name in init_vars_name: 359 | tvar_name = name 360 | elif (re.sub(r"/group_\d+/", "/group_0/", 361 | six.ensure_str(name)) in init_vars_name and 362 | num_of_group > 1): 363 | tvar_name = re.sub(r"/group_\d+/", "/group_0/", six.ensure_str(name)) 364 | elif (re.sub(r"/ffn_\d+/", "/ffn_1/", six.ensure_str(name)) 365 | in init_vars_name and num_of_group > 1): 366 | tvar_name = re.sub(r"/ffn_\d+/", "/ffn_1/", six.ensure_str(name)) 367 | elif (re.sub(r"/attention_\d+/", "/attention_1/", six.ensure_str(name)) 368 | in init_vars_name and num_of_group > 1): 369 | tvar_name = re.sub(r"/attention_\d+/", "/attention_1/", 370 | six.ensure_str(name)) 371 | else: 372 | tf.logging.info("name %s does not get matched", name) 373 | continue 374 | tf.logging.info("name %s match to %s", name, tvar_name) 375 | if num_of_group > 0: 376 | group_matched = False 377 | for gid in range(1, num_of_group): 378 | if (("/group_" + str(gid) + "/" in name) or 379 | ("/ffn_" + str(gid) + "/" in name) or 380 | ("/attention_" + str(gid) + "/" in name)): 381 | group_matched = True 382 | tf.logging.info("%s belongs to %dth", name, gid) 383 | assignment_map[gid][tvar_name] = name 384 | if not group_matched: 385 | assignment_map[0][tvar_name] = name 386 | else: 387 | assignment_map[tvar_name] = name 388 | initialized_variable_names[name] = 1 389 | initialized_variable_names[six.ensure_str(name) + ":0"] = 1 390 | 391 | return (assignment_map, initialized_variable_names) 392 | 393 | 394 | def dropout(input_tensor, dropout_prob): 395 | """Perform dropout. 396 | 397 | Args: 398 | input_tensor: float Tensor. 399 | dropout_prob: Python float. The probability of dropping out a value (NOT of 400 | *keeping* a dimension as in `tf.nn.dropout`). 401 | 402 | Returns: 403 | A version of `input_tensor` with dropout applied. 404 | """ 405 | if dropout_prob is None or dropout_prob == 0.0: 406 | return input_tensor 407 | 408 | output = tf.nn.dropout(input_tensor, rate=dropout_prob) 409 | return output 410 | 411 | 412 | def layer_norm(input_tensor, name=None): 413 | """Run layer normalization on the last dimension of the tensor.""" 414 | return contrib_layers.layer_norm( 415 | inputs=input_tensor, begin_norm_axis=-1, begin_params_axis=-1, scope=name) 416 | 417 | 418 | def layer_norm_and_dropout(input_tensor, dropout_prob, name=None): 419 | """Runs layer normalization followed by dropout.""" 420 | output_tensor = layer_norm(input_tensor, name) 421 | output_tensor = dropout(output_tensor, dropout_prob) 422 | return output_tensor 423 | 424 | 425 | def create_initializer(initializer_range=0.02): 426 | """Creates a `truncated_normal_initializer` with the given range.""" 427 | return tf.truncated_normal_initializer(stddev=initializer_range) 428 | 429 | 430 | def get_timing_signal_1d_given_position(channels, 431 | position, 432 | min_timescale=1.0, 433 | max_timescale=1.0e4): 434 | """Get sinusoids of diff frequencies, with timing position given. 435 | 436 | Adapted from add_timing_signal_1d_given_position in 437 | //third_party/py/tensor2tensor/layers/common_attention.py 438 | 439 | Args: 440 | channels: scalar, size of timing embeddings to create. The number of 441 | different timescales is equal to channels / 2. 442 | position: a Tensor with shape [batch, seq_len] 443 | min_timescale: a float 444 | max_timescale: a float 445 | 446 | Returns: 447 | a Tensor of timing signals [batch, seq_len, channels] 448 | """ 449 | num_timescales = channels // 2 450 | log_timescale_increment = ( 451 | math.log(float(max_timescale) / float(min_timescale)) / 452 | (tf.to_float(num_timescales) - 1)) 453 | inv_timescales = min_timescale * tf.exp( 454 | tf.to_float(tf.range(num_timescales)) * -log_timescale_increment) 455 | scaled_time = ( 456 | tf.expand_dims(tf.to_float(position), 2) * tf.expand_dims( 457 | tf.expand_dims(inv_timescales, 0), 0)) 458 | signal = tf.concat([tf.sin(scaled_time), tf.cos(scaled_time)], axis=2) 459 | signal = tf.pad(signal, [[0, 0], [0, 0], [0, tf.mod(channels, 2)]]) 460 | return signal 461 | 462 | 463 | def embedding_lookup(input_ids, 464 | vocab_size, 465 | embedding_size=128, 466 | initializer_range=0.02, 467 | word_embedding_name="word_embeddings", 468 | use_one_hot_embeddings=False): 469 | """Looks up words embeddings for id tensor. 470 | 471 | Args: 472 | input_ids: int32 Tensor of shape [batch_size, seq_length] containing word 473 | ids. 474 | vocab_size: int. Size of the embedding vocabulary. 475 | embedding_size: int. Width of the word embeddings. 476 | initializer_range: float. Embedding initialization range. 477 | word_embedding_name: string. Name of the embedding table. 478 | use_one_hot_embeddings: bool. If True, use one-hot method for word 479 | embeddings. If False, use `tf.nn.embedding_lookup()`. 480 | 481 | Returns: 482 | float Tensor of shape [batch_size, seq_length, embedding_size]. 483 | """ 484 | # This function assumes that the input is of shape [batch_size, seq_length, 485 | # num_inputs]. 486 | # 487 | # If the input is a 2D tensor of shape [batch_size, seq_length], we 488 | # reshape to [batch_size, seq_length, 1]. 489 | if input_ids.shape.ndims == 2: 490 | input_ids = tf.expand_dims(input_ids, axis=[-1]) 491 | 492 | embedding_table = tf.get_variable( 493 | name=word_embedding_name, 494 | shape=[vocab_size, embedding_size], 495 | initializer=create_initializer(initializer_range)) 496 | 497 | if use_one_hot_embeddings: 498 | flat_input_ids = tf.reshape(input_ids, [-1]) 499 | one_hot_input_ids = tf.one_hot(flat_input_ids, depth=vocab_size) 500 | output = tf.matmul(one_hot_input_ids, embedding_table) 501 | else: 502 | output = tf.nn.embedding_lookup(embedding_table, input_ids) 503 | 504 | input_shape = get_shape_list(input_ids) 505 | 506 | output = tf.reshape(output, 507 | input_shape[0:-1] + [input_shape[-1] * embedding_size]) 508 | return (output, embedding_table) 509 | 510 | 511 | def embedding_postprocessor(input_tensor, 512 | use_token_type=False, 513 | token_type_ids=None, 514 | token_type_vocab_size=16, 515 | token_type_embedding_name="token_type_embeddings", 516 | use_position_embeddings=True, 517 | position_embedding_name="position_embeddings", 518 | initializer_range=0.02, 519 | max_position_embeddings=512, 520 | dropout_prob=0.1): 521 | """Performs various post-processing on a word embedding tensor. 522 | 523 | Args: 524 | input_tensor: float Tensor of shape [batch_size, seq_length, 525 | embedding_size]. 526 | use_token_type: bool. Whether to add embeddings for `token_type_ids`. 527 | token_type_ids: (optional) int32 Tensor of shape [batch_size, seq_length]. 528 | Must be specified if `use_token_type` is True. 529 | token_type_vocab_size: int. The vocabulary size of `token_type_ids`. 530 | token_type_embedding_name: string. The name of the embedding table variable 531 | for token type ids. 532 | use_position_embeddings: bool. Whether to add position embeddings for the 533 | position of each token in the sequence. 534 | position_embedding_name: string. The name of the embedding table variable 535 | for positional embeddings. 536 | initializer_range: float. Range of the weight initialization. 537 | max_position_embeddings: int. Maximum sequence length that might ever be 538 | used with this model. This can be longer than the sequence length of 539 | input_tensor, but cannot be shorter. 540 | dropout_prob: float. Dropout probability applied to the final output tensor. 541 | 542 | Returns: 543 | float tensor with same shape as `input_tensor`. 544 | 545 | Raises: 546 | ValueError: One of the tensor shapes or input values is invalid. 547 | """ 548 | input_shape = get_shape_list(input_tensor, expected_rank=3) 549 | batch_size = input_shape[0] 550 | seq_length = input_shape[1] 551 | width = input_shape[2] 552 | 553 | output = input_tensor 554 | 555 | if use_token_type: 556 | if token_type_ids is None: 557 | raise ValueError("`token_type_ids` must be specified if" 558 | "`use_token_type` is True.") 559 | token_type_table = tf.get_variable( 560 | name=token_type_embedding_name, 561 | shape=[token_type_vocab_size, width], 562 | initializer=create_initializer(initializer_range)) 563 | # This vocab will be small so we always do one-hot here, since it is always 564 | # faster for a small vocabulary. 565 | flat_token_type_ids = tf.reshape(token_type_ids, [-1]) 566 | one_hot_ids = tf.one_hot(flat_token_type_ids, depth=token_type_vocab_size) 567 | token_type_embeddings = tf.matmul(one_hot_ids, token_type_table) 568 | token_type_embeddings = tf.reshape(token_type_embeddings, 569 | [batch_size, seq_length, width]) 570 | output += token_type_embeddings 571 | 572 | if use_position_embeddings: 573 | assert_op = tf.assert_less_equal(seq_length, max_position_embeddings) 574 | with tf.control_dependencies([assert_op]): 575 | full_position_embeddings = tf.get_variable( 576 | name=position_embedding_name, 577 | shape=[max_position_embeddings, width], 578 | initializer=create_initializer(initializer_range)) 579 | # Since the position embedding table is a learned variable, we create it 580 | # using a (long) sequence length `max_position_embeddings`. The actual 581 | # sequence length might be shorter than this, for faster training of 582 | # tasks that do not have long sequences. 583 | # 584 | # So `full_position_embeddings` is effectively an embedding table 585 | # for position [0, 1, 2, ..., max_position_embeddings-1], and the current 586 | # sequence has positions [0, 1, 2, ... seq_length-1], so we can just 587 | # perform a slice. 588 | position_embeddings = tf.slice(full_position_embeddings, [0, 0], 589 | [seq_length, -1]) 590 | num_dims = len(output.shape.as_list()) 591 | 592 | # Only the last two dimensions are relevant (`seq_length` and `width`), so 593 | # we broadcast among the first dimensions, which is typically just 594 | # the batch size. 595 | position_broadcast_shape = [] 596 | for _ in range(num_dims - 2): 597 | position_broadcast_shape.append(1) 598 | position_broadcast_shape.extend([seq_length, width]) 599 | position_embeddings = tf.reshape(position_embeddings, 600 | position_broadcast_shape) 601 | output += position_embeddings 602 | 603 | output = layer_norm_and_dropout(output, dropout_prob) 604 | return output 605 | 606 | 607 | def dense_layer_3d(input_tensor, 608 | num_attention_heads, 609 | head_size, 610 | initializer, 611 | activation, 612 | name=None): 613 | """A dense layer with 3D kernel. 614 | 615 | Args: 616 | input_tensor: float Tensor of shape [batch, seq_length, hidden_size]. 617 | num_attention_heads: Number of attention heads. 618 | head_size: The size per attention head. 619 | initializer: Kernel initializer. 620 | activation: Actication function. 621 | name: The name scope of this layer. 622 | 623 | Returns: 624 | float logits Tensor. 625 | """ 626 | 627 | input_shape = get_shape_list(input_tensor) 628 | hidden_size = input_shape[2] 629 | 630 | with tf.variable_scope(name): 631 | w = tf.get_variable( 632 | name="kernel", 633 | shape=[hidden_size, num_attention_heads * head_size], 634 | initializer=initializer) 635 | w = tf.reshape(w, [hidden_size, num_attention_heads, head_size]) 636 | b = tf.get_variable( 637 | name="bias", 638 | shape=[num_attention_heads * head_size], 639 | initializer=tf.zeros_initializer) 640 | b = tf.reshape(b, [num_attention_heads, head_size]) 641 | ret = tf.einsum("BFH,HND->BFND", input_tensor, w) 642 | ret += b 643 | if activation is not None: 644 | return activation(ret) 645 | else: 646 | return ret 647 | 648 | 649 | def dense_layer_3d_proj(input_tensor, 650 | hidden_size, 651 | head_size, 652 | initializer, 653 | activation, 654 | name=None): 655 | """A dense layer with 3D kernel for projection. 656 | 657 | Args: 658 | input_tensor: float Tensor of shape [batch,from_seq_length, 659 | num_attention_heads, size_per_head]. 660 | hidden_size: The size of hidden layer. 661 | num_attention_heads: The size of output dimension. 662 | head_size: The size of head. 663 | initializer: Kernel initializer. 664 | activation: Actication function. 665 | name: The name scope of this layer. 666 | 667 | Returns: 668 | float logits Tensor. 669 | """ 670 | input_shape = get_shape_list(input_tensor) 671 | num_attention_heads= input_shape[2] 672 | with tf.variable_scope(name): 673 | w = tf.get_variable( 674 | name="kernel", 675 | shape=[num_attention_heads * head_size, hidden_size], 676 | initializer=initializer) 677 | w = tf.reshape(w, [num_attention_heads, head_size, hidden_size]) 678 | b = tf.get_variable( 679 | name="bias", shape=[hidden_size], initializer=tf.zeros_initializer) 680 | ret = tf.einsum("BFND,NDH->BFH", input_tensor, w) 681 | ret += b 682 | if activation is not None: 683 | return activation(ret) 684 | else: 685 | return ret 686 | 687 | 688 | def dense_layer_2d(input_tensor, 689 | output_size, 690 | initializer, 691 | activation, 692 | num_attention_heads=1, 693 | name=None): 694 | """A dense layer with 2D kernel. 695 | 696 | Args: 697 | input_tensor: Float tensor with rank 3. 698 | output_size: The size of output dimension. 699 | initializer: Kernel initializer. 700 | activation: Activation function. 701 | num_attention_heads: number of attention head in attention layer. 702 | name: The name scope of this layer. 703 | 704 | Returns: 705 | float logits Tensor. 706 | """ 707 | del num_attention_heads # unused 708 | input_shape = get_shape_list(input_tensor) 709 | hidden_size = input_shape[2] 710 | with tf.variable_scope(name): 711 | w = tf.get_variable( 712 | name="kernel", 713 | shape=[hidden_size, output_size], 714 | initializer=initializer) 715 | b = tf.get_variable( 716 | name="bias", shape=[output_size], initializer=tf.zeros_initializer) 717 | ret = tf.einsum("BFH,HO->BFO", input_tensor, w) 718 | ret += b 719 | if activation is not None: 720 | return activation(ret) 721 | else: 722 | return ret 723 | 724 | 725 | def dot_product_attention(q, k, v, bias, dropout_rate=0.0): 726 | """Dot-product attention. 727 | 728 | Args: 729 | q: Tensor with shape [..., length_q, depth_k]. 730 | k: Tensor with shape [..., length_kv, depth_k]. Leading dimensions must 731 | match with q. 732 | v: Tensor with shape [..., length_kv, depth_v] Leading dimensions must 733 | match with q. 734 | bias: bias Tensor (see attention_bias()) 735 | dropout_rate: a float. 736 | 737 | Returns: 738 | Tensor with shape [..., length_q, depth_v]. 739 | """ 740 | logits = tf.matmul(q, k, transpose_b=True) # [..., length_q, length_kv] 741 | logits = tf.multiply(logits, 1.0 / math.sqrt(float(get_shape_list(q)[-1]))) 742 | if bias is not None: 743 | # `attention_mask` = [B, T] 744 | from_shape = get_shape_list(q) 745 | if len(from_shape) == 4: 746 | broadcast_ones = tf.ones([from_shape[0], 1, from_shape[2], 1], tf.float32) 747 | elif len(from_shape) == 5: 748 | # from_shape = [B, N, Block_num, block_size, depth]# 749 | broadcast_ones = tf.ones([from_shape[0], 1, from_shape[2], from_shape[3], 750 | 1], tf.float32) 751 | 752 | bias = tf.matmul(broadcast_ones, 753 | tf.cast(bias, tf.float32), transpose_b=True) 754 | 755 | # Since attention_mask is 1.0 for positions we want to attend and 0.0 for 756 | # masked positions, this operation will create a tensor which is 0.0 for 757 | # positions we want to attend and -10000.0 for masked positions. 758 | adder = (1.0 - bias) * -10000.0 759 | 760 | # Since we are adding it to the raw scores before the softmax, this is 761 | # effectively the same as removing these entirely. 762 | logits += adder 763 | else: 764 | adder = 0.0 765 | 766 | attention_probs = tf.nn.softmax(logits, name="attention_probs") 767 | attention_probs = dropout(attention_probs, dropout_rate) 768 | return tf.matmul(attention_probs, v) 769 | 770 | 771 | def attention_layer(from_tensor, 772 | to_tensor, 773 | attention_mask=None, 774 | num_attention_heads=1, 775 | query_act=None, 776 | key_act=None, 777 | value_act=None, 778 | attention_probs_dropout_prob=0.0, 779 | initializer_range=0.02, 780 | batch_size=None, 781 | from_seq_length=None, 782 | to_seq_length=None): 783 | """Performs multi-headed attention from `from_tensor` to `to_tensor`. 784 | 785 | Args: 786 | from_tensor: float Tensor of shape [batch_size, from_seq_length, 787 | from_width]. 788 | to_tensor: float Tensor of shape [batch_size, to_seq_length, to_width]. 789 | attention_mask: (optional) int32 Tensor of shape [batch_size, 790 | from_seq_length, to_seq_length]. The values should be 1 or 0. The 791 | attention scores will effectively be set to -infinity for any positions in 792 | the mask that are 0, and will be unchanged for positions that are 1. 793 | num_attention_heads: int. Number of attention heads. 794 | query_act: (optional) Activation function for the query transform. 795 | key_act: (optional) Activation function for the key transform. 796 | value_act: (optional) Activation function for the value transform. 797 | attention_probs_dropout_prob: (optional) float. Dropout probability of the 798 | attention probabilities. 799 | initializer_range: float. Range of the weight initializer. 800 | batch_size: (Optional) int. If the input is 2D, this might be the batch size 801 | of the 3D version of the `from_tensor` and `to_tensor`. 802 | from_seq_length: (Optional) If the input is 2D, this might be the seq length 803 | of the 3D version of the `from_tensor`. 804 | to_seq_length: (Optional) If the input is 2D, this might be the seq length 805 | of the 3D version of the `to_tensor`. 806 | 807 | Returns: 808 | float Tensor of shape [batch_size, from_seq_length, num_attention_heads, 809 | size_per_head]. 810 | 811 | Raises: 812 | ValueError: Any of the arguments or tensor shapes are invalid. 813 | """ 814 | from_shape = get_shape_list(from_tensor, expected_rank=[2, 3]) 815 | to_shape = get_shape_list(to_tensor, expected_rank=[2, 3]) 816 | size_per_head = int(from_shape[2]/num_attention_heads) 817 | 818 | if len(from_shape) != len(to_shape): 819 | raise ValueError( 820 | "The rank of `from_tensor` must match the rank of `to_tensor`.") 821 | 822 | if len(from_shape) == 3: 823 | batch_size = from_shape[0] 824 | from_seq_length = from_shape[1] 825 | to_seq_length = to_shape[1] 826 | elif len(from_shape) == 2: 827 | if (batch_size is None or from_seq_length is None or to_seq_length is None): 828 | raise ValueError( 829 | "When passing in rank 2 tensors to attention_layer, the values " 830 | "for `batch_size`, `from_seq_length`, and `to_seq_length` " 831 | "must all be specified.") 832 | 833 | # Scalar dimensions referenced here: 834 | # B = batch size (number of sequences) 835 | # F = `from_tensor` sequence length 836 | # T = `to_tensor` sequence length 837 | # N = `num_attention_heads` 838 | # H = `size_per_head` 839 | 840 | # `query_layer` = [B, F, N, H] 841 | q = dense_layer_3d(from_tensor, num_attention_heads, size_per_head, 842 | create_initializer(initializer_range), query_act, "query") 843 | 844 | # `key_layer` = [B, T, N, H] 845 | k = dense_layer_3d(to_tensor, num_attention_heads, size_per_head, 846 | create_initializer(initializer_range), key_act, "key") 847 | # `value_layer` = [B, T, N, H] 848 | v = dense_layer_3d(to_tensor, num_attention_heads, size_per_head, 849 | create_initializer(initializer_range), value_act, "value") 850 | q = tf.transpose(q, [0, 2, 1, 3]) 851 | k = tf.transpose(k, [0, 2, 1, 3]) 852 | v = tf.transpose(v, [0, 2, 1, 3]) 853 | if attention_mask is not None: 854 | attention_mask = tf.reshape( 855 | attention_mask, [batch_size, 1, to_seq_length, 1]) 856 | # 'new_embeddings = [B, N, F, H]' 857 | new_embeddings = dot_product_attention(q, k, v, attention_mask, 858 | attention_probs_dropout_prob) 859 | 860 | return tf.transpose(new_embeddings, [0, 2, 1, 3]) 861 | 862 | 863 | def attention_ffn_block(layer_input, 864 | hidden_size=768, 865 | attention_mask=None, 866 | num_attention_heads=1, 867 | attention_head_size=64, 868 | attention_probs_dropout_prob=0.0, 869 | intermediate_size=3072, 870 | intermediate_act_fn=None, 871 | initializer_range=0.02, 872 | hidden_dropout_prob=0.0): 873 | """A network with attention-ffn as sub-block. 874 | 875 | Args: 876 | layer_input: float Tensor of shape [batch_size, from_seq_length, 877 | from_width]. 878 | hidden_size: (optional) int, size of hidden layer. 879 | attention_mask: (optional) int32 Tensor of shape [batch_size, 880 | from_seq_length, to_seq_length]. The values should be 1 or 0. The 881 | attention scores will effectively be set to -infinity for any positions in 882 | the mask that are 0, and will be unchanged for positions that are 1. 883 | num_attention_heads: int. Number of attention heads. 884 | attention_head_size: int. Size of attention head. 885 | attention_probs_dropout_prob: float. dropout probability for attention_layer 886 | intermediate_size: int. Size of intermediate hidden layer. 887 | intermediate_act_fn: (optional) Activation function for the intermediate 888 | layer. 889 | initializer_range: float. Range of the weight initializer. 890 | hidden_dropout_prob: (optional) float. Dropout probability of the hidden 891 | layer. 892 | 893 | Returns: 894 | layer output 895 | """ 896 | 897 | with tf.variable_scope("attention_1"): 898 | with tf.variable_scope("self"): 899 | attention_output = attention_layer( 900 | from_tensor=layer_input, 901 | to_tensor=layer_input, 902 | attention_mask=attention_mask, 903 | num_attention_heads=num_attention_heads, 904 | attention_probs_dropout_prob=attention_probs_dropout_prob, 905 | initializer_range=initializer_range) 906 | 907 | # Run a linear projection of `hidden_size` then add a residual 908 | # with `layer_input`. 909 | with tf.variable_scope("output"): 910 | attention_output = dense_layer_3d_proj( 911 | attention_output, 912 | hidden_size, 913 | attention_head_size, 914 | create_initializer(initializer_range), 915 | None, 916 | name="dense") 917 | attention_output = dropout(attention_output, hidden_dropout_prob) 918 | attention_output = layer_norm(attention_output + layer_input) 919 | with tf.variable_scope("ffn_1"): 920 | with tf.variable_scope("intermediate"): 921 | intermediate_output = dense_layer_2d( 922 | attention_output, 923 | intermediate_size, 924 | create_initializer(initializer_range), 925 | intermediate_act_fn, 926 | num_attention_heads=num_attention_heads, 927 | name="dense") 928 | with tf.variable_scope("output"): 929 | ffn_output = dense_layer_2d( 930 | intermediate_output, 931 | hidden_size, 932 | create_initializer(initializer_range), 933 | None, 934 | num_attention_heads=num_attention_heads, 935 | name="dense") 936 | ffn_output = dropout(ffn_output, hidden_dropout_prob) 937 | ffn_output = layer_norm(ffn_output + attention_output) 938 | return ffn_output 939 | 940 | 941 | def transformer_model(input_tensor, 942 | attention_mask=None, 943 | hidden_size=768, 944 | num_hidden_layers=12, 945 | num_hidden_groups=12, 946 | num_attention_heads=12, 947 | intermediate_size=3072, 948 | inner_group_num=1, 949 | intermediate_act_fn="gelu", 950 | hidden_dropout_prob=0.1, 951 | attention_probs_dropout_prob=0.1, 952 | initializer_range=0.02, 953 | do_return_all_layers=False): 954 | """Multi-headed, multi-layer Transformer from "Attention is All You Need". 955 | 956 | This is almost an exact implementation of the original Transformer encoder. 957 | 958 | See the original paper: 959 | https://arxiv.org/abs/1706.03762 960 | 961 | Also see: 962 | https://github.com/tensorflow/tensor2tensor/blob/master/tensor2tensor/models/transformer.py 963 | 964 | Args: 965 | input_tensor: float Tensor of shape [batch_size, seq_length, hidden_size]. 966 | attention_mask: (optional) int32 Tensor of shape [batch_size, seq_length, 967 | seq_length], with 1 for positions that can be attended to and 0 in 968 | positions that should not be. 969 | hidden_size: int. Hidden size of the Transformer. 970 | num_hidden_layers: int. Number of layers (blocks) in the Transformer. 971 | num_hidden_groups: int. Number of group for the hidden layers, parameters 972 | in the same group are shared. 973 | num_attention_heads: int. Number of attention heads in the Transformer. 974 | intermediate_size: int. The size of the "intermediate" (a.k.a., feed 975 | forward) layer. 976 | inner_group_num: int, number of inner repetition of attention and ffn. 977 | intermediate_act_fn: function. The non-linear activation function to apply 978 | to the output of the intermediate/feed-forward layer. 979 | hidden_dropout_prob: float. Dropout probability for the hidden layers. 980 | attention_probs_dropout_prob: float. Dropout probability of the attention 981 | probabilities. 982 | initializer_range: float. Range of the initializer (stddev of truncated 983 | normal). 984 | do_return_all_layers: Whether to also return all layers or just the final 985 | layer. 986 | 987 | Returns: 988 | float Tensor of shape [batch_size, seq_length, hidden_size], the final 989 | hidden layer of the Transformer. 990 | 991 | Raises: 992 | ValueError: A Tensor shape or parameter is invalid. 993 | """ 994 | if hidden_size % num_attention_heads != 0: 995 | raise ValueError( 996 | "The hidden size (%d) is not a multiple of the number of attention " 997 | "heads (%d)" % (hidden_size, num_attention_heads)) 998 | 999 | attention_head_size = hidden_size // num_attention_heads 1000 | input_shape = get_shape_list(input_tensor, expected_rank=3) 1001 | input_width = input_shape[2] 1002 | 1003 | all_layer_outputs = [] 1004 | if input_width != hidden_size: 1005 | prev_output = dense_layer_2d( 1006 | input_tensor, hidden_size, create_initializer(initializer_range), 1007 | None, name="embedding_hidden_mapping_in") 1008 | else: 1009 | prev_output = input_tensor 1010 | with tf.variable_scope("transformer", reuse=tf.AUTO_REUSE): 1011 | for layer_idx in range(num_hidden_layers): 1012 | group_idx = int(layer_idx / num_hidden_layers * num_hidden_groups) 1013 | with tf.variable_scope("group_%d" % group_idx): 1014 | with tf.name_scope("layer_%d" % layer_idx): 1015 | layer_output = prev_output 1016 | for inner_group_idx in range(inner_group_num): 1017 | with tf.variable_scope("inner_group_%d" % inner_group_idx): 1018 | layer_output = attention_ffn_block( 1019 | layer_output, hidden_size, attention_mask, 1020 | num_attention_heads, attention_head_size, 1021 | attention_probs_dropout_prob, intermediate_size, 1022 | intermediate_act_fn, initializer_range, hidden_dropout_prob) 1023 | prev_output = layer_output 1024 | all_layer_outputs.append(layer_output) 1025 | if do_return_all_layers: 1026 | return all_layer_outputs 1027 | else: 1028 | return all_layer_outputs[-1] 1029 | 1030 | 1031 | def get_shape_list(tensor, expected_rank=None, name=None): 1032 | """Returns a list of the shape of tensor, preferring static dimensions. 1033 | 1034 | Args: 1035 | tensor: A tf.Tensor object to find the shape of. 1036 | expected_rank: (optional) int. The expected rank of `tensor`. If this is 1037 | specified and the `tensor` has a different rank, and exception will be 1038 | thrown. 1039 | name: Optional name of the tensor for the error message. 1040 | 1041 | Returns: 1042 | A list of dimensions of the shape of tensor. All static dimensions will 1043 | be returned as python integers, and dynamic dimensions will be returned 1044 | as tf.Tensor scalars. 1045 | """ 1046 | if name is None: 1047 | name = tensor.name 1048 | 1049 | if expected_rank is not None: 1050 | assert_rank(tensor, expected_rank, name) 1051 | 1052 | shape = tensor.shape.as_list() 1053 | 1054 | non_static_indexes = [] 1055 | for (index, dim) in enumerate(shape): 1056 | if dim is None: 1057 | non_static_indexes.append(index) 1058 | 1059 | if not non_static_indexes: 1060 | return shape 1061 | 1062 | dyn_shape = tf.shape(tensor) 1063 | for index in non_static_indexes: 1064 | shape[index] = dyn_shape[index] 1065 | return shape 1066 | 1067 | 1068 | def reshape_to_matrix(input_tensor): 1069 | """Reshapes a >= rank 2 tensor to a rank 2 tensor (i.e., a matrix).""" 1070 | ndims = input_tensor.shape.ndims 1071 | if ndims < 2: 1072 | raise ValueError("Input tensor must have at least rank 2. Shape = %s" % 1073 | (input_tensor.shape)) 1074 | if ndims == 2: 1075 | return input_tensor 1076 | 1077 | width = input_tensor.shape[-1] 1078 | output_tensor = tf.reshape(input_tensor, [-1, width]) 1079 | return output_tensor 1080 | 1081 | 1082 | def reshape_from_matrix(output_tensor, orig_shape_list): 1083 | """Reshapes a rank 2 tensor back to its original rank >= 2 tensor.""" 1084 | if len(orig_shape_list) == 2: 1085 | return output_tensor 1086 | 1087 | output_shape = get_shape_list(output_tensor) 1088 | 1089 | orig_dims = orig_shape_list[0:-1] 1090 | width = output_shape[-1] 1091 | 1092 | return tf.reshape(output_tensor, orig_dims + [width]) 1093 | 1094 | 1095 | def assert_rank(tensor, expected_rank, name=None): 1096 | """Raises an exception if the tensor rank is not of the expected rank. 1097 | 1098 | Args: 1099 | tensor: A tf.Tensor to check the rank of. 1100 | expected_rank: Python integer or list of integers, expected rank. 1101 | name: Optional name of the tensor for the error message. 1102 | 1103 | Raises: 1104 | ValueError: If the expected shape doesn't match the actual shape. 1105 | """ 1106 | if name is None: 1107 | name = tensor.name 1108 | 1109 | expected_rank_dict = {} 1110 | if isinstance(expected_rank, six.integer_types): 1111 | expected_rank_dict[expected_rank] = True 1112 | else: 1113 | for x in expected_rank: 1114 | expected_rank_dict[x] = True 1115 | 1116 | actual_rank = tensor.shape.ndims 1117 | if actual_rank not in expected_rank_dict: 1118 | scope_name = tf.get_variable_scope().name 1119 | raise ValueError( 1120 | "For the tensor `%s` in scope `%s`, the actual rank " 1121 | "`%d` (shape = %s) is not equal to the expected rank `%s`" % 1122 | (name, scope_name, actual_rank, str(tensor.shape), str(expected_rank))) 1123 | -------------------------------------------------------------------------------- /modules.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 30 21:01:45 2019 4 | 5 | @author: cm 6 | """ 7 | 8 | 9 | import tensorflow as tf 10 | from classifier_multi_label.hyperparameters import Hyperparamters as hp 11 | 12 | 13 | def cell_textcnn(inputs,is_training): 14 | # Add a dimension in final shape 15 | inputs_expand = tf.expand_dims(inputs, -1) 16 | # Create a convolution + maxpool layer for each filter size 17 | pooled_outputs = [] 18 | with tf.name_scope("TextCNN"): 19 | for i, filter_size in enumerate(hp.filter_sizes): 20 | with tf.name_scope("conv-maxpool-%s" % filter_size): 21 | # Convolution Layer 22 | filter_shape = [filter_size, hp.embedding_size, 1, hp.num_filters] 23 | W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1),dtype=tf.float32, name="W") 24 | b = tf.Variable(tf.constant(0.1, shape=[hp.num_filters]),dtype=tf.float32, name="b") 25 | conv = tf.nn.conv2d( 26 | inputs_expand, 27 | W, 28 | strides=[1, 1, 1, 1], 29 | padding="VALID", 30 | name="conv") 31 | # Apply nonlinearity 32 | h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu") 33 | # Maxpooling over the outputs 34 | pooled = tf.nn.max_pool( 35 | h, 36 | ksize=[1, hp.sequence_length - filter_size + 1, 1, 1], 37 | strides=[1, 1, 1, 1], 38 | padding='VALID', 39 | name="pool") 40 | pooled_outputs.append(pooled) 41 | # Combine all the pooled features 42 | num_filters_total = hp.num_filters * len(hp.filter_sizes) 43 | h_pool = tf.concat(pooled_outputs, 3) 44 | h_pool_flat = tf.reshape(h_pool, [-1, num_filters_total]) 45 | # Dropout 46 | h_pool_flat_dropout = tf.nn.dropout(h_pool_flat, keep_prob=hp.keep_prob if is_training else 1) 47 | return h_pool_flat_dropout 48 | 49 | 50 | -------------------------------------------------------------------------------- /networks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 30 20:44:42 2019 4 | 5 | @author: cm 6 | """ 7 | 8 | import os 9 | import tensorflow as tf 10 | from classifier_multi_label import modeling 11 | from classifier_multi_label import optimization 12 | from classifier_multi_label.utils import time_now_string 13 | from classifier_multi_label.hyperparameters import Hyperparamters as hp 14 | from classifier_multi_label.classifier_utils import ClassifyProcessor 15 | 16 | 17 | num_labels = hp.num_labels 18 | processor = ClassifyProcessor() 19 | bert_config_file = os.path.join(hp.bert_path,'albert_config.json') 20 | bert_config = modeling.AlbertConfig.from_json_file(bert_config_file) 21 | 22 | 23 | 24 | class NetworkAlbert(object): 25 | def __init__(self,is_training): 26 | # Training or not 27 | self.is_training = is_training 28 | 29 | # Placeholder 30 | self.input_ids = tf.placeholder(tf.int32, shape=[None, hp.sequence_length], name='input_ids') 31 | self.input_masks = tf.placeholder(tf.int32, shape=[None, hp.sequence_length], name='input_masks') 32 | self.segment_ids = tf.placeholder(tf.int32, shape=[None, hp.sequence_length], name='segment_ids') 33 | self.label_ids = tf.placeholder(tf.float32, shape=[None,hp.num_labels], name='label_ids') 34 | 35 | # Load BERT model 36 | self.model = modeling.AlbertModel( 37 | config=bert_config, 38 | is_training=self.is_training, 39 | input_ids=self.input_ids, 40 | input_mask=self.input_masks, 41 | token_type_ids=self.segment_ids, 42 | use_one_hot_embeddings=False) 43 | 44 | # Get the feature vector by BERT 45 | output_layer = self.model.get_pooled_output() 46 | 47 | # Hidden size 48 | hidden_size = output_layer.shape[-1].value 49 | 50 | with tf.name_scope("Full-connection"): 51 | output_weights = tf.get_variable( 52 | "output_weights", [num_labels, hidden_size], 53 | initializer=tf.truncated_normal_initializer(stddev=0.02)) 54 | output_bias = tf.get_variable( 55 | "output_bias", [num_labels], initializer=tf.zeros_initializer()) 56 | logits = tf.nn.bias_add(tf.matmul(output_layer, output_weights, transpose_b=True), output_bias) 57 | # Prediction sigmoid(Multi-label) 58 | self.probabilities = tf.nn.sigmoid(logits) 59 | 60 | 61 | with tf.variable_scope("Prediction"): 62 | # Prediction 63 | zero = tf.zeros_like(self.probabilities) 64 | one = tf.ones_like(self.probabilities) 65 | self.predictions = tf.where(self.probabilities < 0.5, x=zero, y=one) 66 | 67 | with tf.variable_scope("loss"): 68 | # Summary for tensorboard 69 | if self.is_training: 70 | self.accuracy = tf.reduce_mean(tf.to_float(tf.equal(self.predictions, self.label_ids))) 71 | tf.summary.scalar('accuracy', self.accuracy) 72 | 73 | # Initial embedding by BERT 74 | ckpt = tf.train.get_checkpoint_state(hp.saved_model_path) 75 | checkpoint_suffix = ".index" 76 | if ckpt and tf.gfile.Exists(ckpt.model_checkpoint_path + checkpoint_suffix): 77 | print('='*10,'Restoring model from checkpoint!','='*10) 78 | print("%s - Restoring model from checkpoint ~%s" % (time_now_string(), 79 | ckpt.model_checkpoint_path)) 80 | else: 81 | print('='*10,'First time load BERT model!','='*10) 82 | tvars = tf.trainable_variables() 83 | if hp.init_checkpoint: 84 | (assignment_map, initialized_variable_names) = \ 85 | modeling.get_assignment_map_from_checkpoint(tvars, 86 | hp.init_checkpoint) 87 | tf.train.init_from_checkpoint(hp.init_checkpoint, assignment_map) 88 | 89 | # Loss and Optimizer 90 | if self.is_training: 91 | # Global_step 92 | self.global_step = tf.Variable(0, name='global_step', trainable=False) 93 | per_example_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=self.label_ids,logits=logits) 94 | self.loss = tf.reduce_mean(per_example_loss) 95 | 96 | # Optimizer BERT 97 | train_examples = processor.get_train_examples(hp.data_dir) 98 | num_train_steps = int( 99 | len(train_examples) / hp.batch_size * hp.num_train_epochs) 100 | #num_train_steps = 10000 101 | num_warmup_steps = int(num_train_steps * hp.warmup_proportion) 102 | print('num_train_steps',num_train_steps) 103 | self.optimizer = optimization.create_optimizer(self.loss, 104 | hp.learning_rate, 105 | num_train_steps, 106 | num_warmup_steps, 107 | hp.use_tpu, 108 | Global_step=self.global_step) 109 | 110 | # Summary for tensorboard 111 | tf.summary.scalar('loss', self.loss) 112 | self.merged = tf.summary.merge_all() 113 | 114 | 115 | 116 | if __name__ == '__main__': 117 | # Load model 118 | albert = NetworkAlbert(is_training=True) 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /optimization.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Functions and classes related to optimization (weight updates).""" 3 | 4 | from __future__ import absolute_import 5 | from __future__ import division 6 | from __future__ import print_function 7 | import re 8 | import six 9 | from six.moves import zip 10 | import tensorflow.compat.v1 as tf 11 | from tensorflow.contrib import tpu as contrib_tpu 12 | from classifier_multi_label import lamb_optimizer 13 | 14 | 15 | def create_optimizer(loss, init_lr, num_train_steps, num_warmup_steps, use_tpu,Global_step, 16 | optimizer="adamw", poly_power=1.0, start_warmup_step=0): 17 | """Creates an optimizer training op.""" 18 | #global_step = tf.train.get_or_create_global_step() 19 | 20 | # by chenming 21 | if Global_step: 22 | global_step = Global_step 23 | else: 24 | global_step = tf.train.get_or_create_global_step() 25 | 26 | learning_rate = tf.constant(value=init_lr, shape=[], dtype=tf.float32) 27 | 28 | # Implements linear decay of the learning rate. 29 | learning_rate = tf.train.polynomial_decay( 30 | learning_rate, 31 | global_step, 32 | num_train_steps, 33 | end_learning_rate=0.0, 34 | power=poly_power, 35 | cycle=False) 36 | 37 | # Implements linear warmup. I.e., if global_step - start_warmup_step < 38 | # num_warmup_steps, the learning rate will be 39 | # `(global_step - start_warmup_step)/num_warmup_steps * init_lr`. 40 | if num_warmup_steps: 41 | tf.logging.info("++++++ warmup starts at step " + str(start_warmup_step) 42 | + ", for " + str(num_warmup_steps) + " steps ++++++") 43 | global_steps_int = tf.cast(global_step, tf.int32) 44 | start_warm_int = tf.constant(start_warmup_step, dtype=tf.int32) 45 | global_steps_int = global_steps_int - start_warm_int 46 | warmup_steps_int = tf.constant(num_warmup_steps, dtype=tf.int32) 47 | 48 | global_steps_float = tf.cast(global_steps_int, tf.float32) 49 | warmup_steps_float = tf.cast(warmup_steps_int, tf.float32) 50 | 51 | warmup_percent_done = global_steps_float / warmup_steps_float 52 | warmup_learning_rate = init_lr * warmup_percent_done 53 | 54 | is_warmup = tf.cast(global_steps_int < warmup_steps_int, tf.float32) 55 | learning_rate = ( 56 | (1.0 - is_warmup) * learning_rate + is_warmup * warmup_learning_rate) 57 | 58 | # It is OK that you use this optimizer for finetuning, since this 59 | # is how the model was trained (note that the Adam m/v variables are NOT 60 | # loaded from init_checkpoint.) 61 | # It is OK to use AdamW in the finetuning even the model is trained by LAMB. 62 | # As report in the Bert pulic github, the learning rate for SQuAD 1.1 finetune 63 | # is 3e-5, 4e-5 or 5e-5. For LAMB, the users can use 3e-4, 4e-4,or 5e-4 for a 64 | # batch size of 64 in the finetune. 65 | if optimizer == "adamw": 66 | tf.logging.info("using adamw") 67 | optimizer = AdamWeightDecayOptimizer( 68 | learning_rate=learning_rate, 69 | weight_decay_rate=0.01, 70 | beta_1=0.9, 71 | beta_2=0.999, 72 | epsilon=1e-6, 73 | exclude_from_weight_decay=["LayerNorm", "layer_norm", "bias"]) 74 | elif optimizer == "lamb": 75 | tf.logging.info("using lamb") 76 | optimizer = lamb_optimizer.LAMBOptimizer( 77 | learning_rate=learning_rate, 78 | weight_decay_rate=0.01, 79 | beta_1=0.9, 80 | beta_2=0.999, 81 | epsilon=1e-6, 82 | exclude_from_weight_decay=["LayerNorm", "layer_norm", "bias"]) 83 | else: 84 | raise ValueError("Not supported optimizer: ", optimizer) 85 | 86 | if use_tpu: 87 | optimizer = contrib_tpu.CrossShardOptimizer(optimizer) 88 | 89 | tvars = tf.trainable_variables() 90 | grads = tf.gradients(loss, tvars) 91 | 92 | # This is how the model was pre-trained. 93 | (grads, _) = tf.clip_by_global_norm(grads, clip_norm=1.0) 94 | 95 | train_op = optimizer.apply_gradients( 96 | list(zip(grads, tvars)), global_step=global_step) 97 | 98 | # Normally the global step update is done inside of `apply_gradients`. 99 | # However, neither `AdamWeightDecayOptimizer` nor `LAMBOptimizer` do this. 100 | # But if you use a different optimizer, you should probably take this line 101 | # out. 102 | new_global_step = global_step + 1 103 | train_op = tf.group(train_op, [global_step.assign(new_global_step)]) 104 | return train_op 105 | 106 | 107 | class AdamWeightDecayOptimizer(tf.train.Optimizer): 108 | """A basic Adam optimizer that includes "correct" L2 weight decay.""" 109 | 110 | def __init__(self, 111 | learning_rate, 112 | weight_decay_rate=0.0, 113 | beta_1=0.9, 114 | beta_2=0.999, 115 | epsilon=1e-6, 116 | exclude_from_weight_decay=None, 117 | name="AdamWeightDecayOptimizer"): 118 | """Constructs a AdamWeightDecayOptimizer.""" 119 | super(AdamWeightDecayOptimizer, self).__init__(False, name) 120 | 121 | self.learning_rate = learning_rate 122 | self.weight_decay_rate = weight_decay_rate 123 | self.beta_1 = beta_1 124 | self.beta_2 = beta_2 125 | self.epsilon = epsilon 126 | self.exclude_from_weight_decay = exclude_from_weight_decay 127 | 128 | def apply_gradients(self, grads_and_vars, global_step=None, name=None): 129 | """See base class.""" 130 | assignments = [] 131 | for (grad, param) in grads_and_vars: 132 | if grad is None or param is None: 133 | continue 134 | 135 | param_name = self._get_variable_name(param.name) 136 | 137 | m = tf.get_variable( 138 | name=six.ensure_str(param_name) + "/adam_m", 139 | shape=param.shape.as_list(), 140 | dtype=tf.float32, 141 | trainable=False, 142 | initializer=tf.zeros_initializer()) 143 | v = tf.get_variable( 144 | name=six.ensure_str(param_name) + "/adam_v", 145 | shape=param.shape.as_list(), 146 | dtype=tf.float32, 147 | trainable=False, 148 | initializer=tf.zeros_initializer()) 149 | 150 | # Standard Adam update. 151 | next_m = ( 152 | tf.multiply(self.beta_1, m) + tf.multiply(1.0 - self.beta_1, grad)) 153 | next_v = ( 154 | tf.multiply(self.beta_2, v) + tf.multiply(1.0 - self.beta_2, 155 | tf.square(grad))) 156 | 157 | update = next_m / (tf.sqrt(next_v) + self.epsilon) 158 | 159 | # Just adding the square of the weights to the loss function is *not* 160 | # the correct way of using L2 regularization/weight decay with Adam, 161 | # since that will interact with the m and v parameters in strange ways. 162 | # 163 | # Instead we want ot decay the weights in a manner that doesn't interact 164 | # with the m/v parameters. This is equivalent to adding the square 165 | # of the weights to the loss with plain (non-momentum) SGD. 166 | if self._do_use_weight_decay(param_name): 167 | update += self.weight_decay_rate * param 168 | 169 | update_with_lr = self.learning_rate * update 170 | 171 | next_param = param - update_with_lr 172 | 173 | assignments.extend( 174 | [param.assign(next_param), 175 | m.assign(next_m), 176 | v.assign(next_v)]) 177 | return tf.group(*assignments, name=name) 178 | 179 | def _do_use_weight_decay(self, param_name): 180 | """Whether to use L2 weight decay for `param_name`.""" 181 | if not self.weight_decay_rate: 182 | return False 183 | if self.exclude_from_weight_decay: 184 | for r in self.exclude_from_weight_decay: 185 | if re.search(r, param_name) is not None: 186 | return False 187 | return True 188 | 189 | def _get_variable_name(self, param_name): 190 | """Get the variable name from the tensor name.""" 191 | m = re.match("^(.*):\\d+$", six.ensure_str(param_name)) 192 | if m is not None: 193 | param_name = m.group(1) 194 | return param_name 195 | -------------------------------------------------------------------------------- /predict.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 30 17:12:37 2019 4 | 5 | @author: cm 6 | """ 7 | 8 | 9 | import os 10 | #os.environ["CUDA_VISIBLE_DEVICES"] = '-1' 11 | pwd = os.path.dirname(os.path.abspath(__file__)) 12 | import sys 13 | sys.path.append(os.path.dirname(os.path.dirname(__file__))) 14 | import numpy as np 15 | import tensorflow as tf 16 | from classifier_multi_label.networks import NetworkAlbert 17 | from classifier_multi_label.classifier_utils import get_feature_test,id2label 18 | from classifier_multi_label.hyperparameters import Hyperparamters as hp 19 | 20 | 21 | class Model(object,): 22 | """ 23 | Load NetworkAlbert model 24 | """ 25 | def __init__(self): 26 | self.albert, self.sess = self.load_model() 27 | @staticmethod 28 | def load_model(): 29 | with tf.Graph().as_default(): 30 | sess = tf.Session() 31 | with sess.as_default(): 32 | albert = NetworkAlbert(is_training=False) 33 | saver = tf.train.Saver() 34 | sess.run(tf.global_variables_initializer()) 35 | checkpoint_dir = os.path.abspath(os.path.join(pwd,hp.file_model)) 36 | print (checkpoint_dir) 37 | ckpt = tf.train.get_checkpoint_state(checkpoint_dir) 38 | saver.restore(sess, ckpt.model_checkpoint_path) 39 | return albert,sess 40 | 41 | MODEL = Model() 42 | print('Load model finished!') 43 | 44 | 45 | def get_label(sentence): 46 | """ 47 | Prediction of the sentence's label. 48 | """ 49 | feature = get_feature_test(sentence) 50 | fd = {MODEL.albert.input_ids: [feature[0]], 51 | MODEL.albert.input_masks: [feature[1]], 52 | MODEL.albert.segment_ids:[feature[2]], 53 | } 54 | prediction = MODEL.sess.run(MODEL.albert.predictions, feed_dict=fd)[0] 55 | return [id2label(l) for l in np.where(prediction==1)[0] if l!=0] 56 | 57 | 58 | if __name__ == '__main__': 59 | # Test 60 | sentences = ['耗电情况:整体来说耗电不是特别严重', 61 | '取暖效果:取暖效果好', 62 | '取暖效果:开到二挡很暖和', 63 | '一个小时房间仍然没暖和', 64 | '开着坐旁边才能暖和'] 65 | for sentence in sentences: 66 | print(sentence,get_label(sentence)) 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /tokenization.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Copyright 2018 The Google AI Team Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # Lint as: python2, python3 16 | # coding=utf-8 17 | """Tokenization classes.""" 18 | 19 | from __future__ import absolute_import 20 | from __future__ import division 21 | from __future__ import print_function 22 | 23 | import collections 24 | import re 25 | import unicodedata 26 | import six 27 | from six.moves import range 28 | import tensorflow.compat.v1 as tf 29 | #import tensorflow_hub as hub 30 | import sentencepiece as spm 31 | 32 | SPIECE_UNDERLINE = u"▁".encode("utf-8") 33 | 34 | 35 | def validate_case_matches_checkpoint(do_lower_case, init_checkpoint): 36 | """Checks whether the casing config is consistent with the checkpoint name.""" 37 | 38 | # The casing has to be passed in by the user and there is no explicit check 39 | # as to whether it matches the checkpoint. The casing information probably 40 | # should have been stored in the bert_config.json file, but it's not, so 41 | # we have to heuristically detect it to validate. 42 | 43 | if not init_checkpoint: 44 | return 45 | 46 | m = re.match("^.*?([A-Za-z0-9_-]+)/bert_model.ckpt", 47 | six.ensure_str(init_checkpoint)) 48 | if m is None: 49 | return 50 | 51 | model_name = m.group(1) 52 | 53 | lower_models = [ 54 | "uncased_L-24_H-1024_A-16", "uncased_L-12_H-768_A-12", 55 | "multilingual_L-12_H-768_A-12", "chinese_L-12_H-768_A-12" 56 | ] 57 | 58 | cased_models = [ 59 | "cased_L-12_H-768_A-12", "cased_L-24_H-1024_A-16", 60 | "multi_cased_L-12_H-768_A-12" 61 | ] 62 | 63 | is_bad_config = False 64 | if model_name in lower_models and not do_lower_case: 65 | is_bad_config = True 66 | actual_flag = "False" 67 | case_name = "lowercased" 68 | opposite_flag = "True" 69 | 70 | if model_name in cased_models and do_lower_case: 71 | is_bad_config = True 72 | actual_flag = "True" 73 | case_name = "cased" 74 | opposite_flag = "False" 75 | 76 | if is_bad_config: 77 | raise ValueError( 78 | "You passed in `--do_lower_case=%s` with `--init_checkpoint=%s`. " 79 | "However, `%s` seems to be a %s model, so you " 80 | "should pass in `--do_lower_case=%s` so that the fine-tuning matches " 81 | "how the model was pre-training. If this error is wrong, please " 82 | "just comment out this check." % (actual_flag, init_checkpoint, 83 | model_name, case_name, opposite_flag)) 84 | 85 | 86 | def preprocess_text(inputs, remove_space=True, lower=False): 87 | """preprocess data by removing extra space and normalize data.""" 88 | outputs = inputs 89 | if remove_space: 90 | outputs = " ".join(inputs.strip().split()) 91 | 92 | if six.PY2 and isinstance(outputs, str): 93 | try: 94 | outputs = six.ensure_text(outputs, "utf-8") 95 | except UnicodeDecodeError: 96 | outputs = six.ensure_text(outputs, "latin-1") 97 | 98 | outputs = unicodedata.normalize("NFKD", outputs) 99 | outputs = "".join([c for c in outputs if not unicodedata.combining(c)]) 100 | if lower: 101 | outputs = outputs.lower() 102 | 103 | return outputs 104 | 105 | 106 | def encode_pieces(sp_model, text, return_unicode=True, sample=False): 107 | """turn sentences into word pieces.""" 108 | 109 | if six.PY2 and isinstance(text, six.text_type): 110 | text = six.ensure_binary(text, "utf-8") 111 | 112 | if not sample: 113 | pieces = sp_model.EncodeAsPieces(text) 114 | else: 115 | pieces = sp_model.SampleEncodeAsPieces(text, 64, 0.1) 116 | new_pieces = [] 117 | for piece in pieces: 118 | piece = printable_text(piece) 119 | if len(piece) > 1 and piece[-1] == "," and piece[-2].isdigit(): 120 | cur_pieces = sp_model.EncodeAsPieces( 121 | six.ensure_binary(piece[:-1]).replace(SPIECE_UNDERLINE, b"")) 122 | if piece[0] != SPIECE_UNDERLINE and cur_pieces[0][0] == SPIECE_UNDERLINE: 123 | if len(cur_pieces[0]) == 1: 124 | cur_pieces = cur_pieces[1:] 125 | else: 126 | cur_pieces[0] = cur_pieces[0][1:] 127 | cur_pieces.append(piece[-1]) 128 | new_pieces.extend(cur_pieces) 129 | else: 130 | new_pieces.append(piece) 131 | 132 | # note(zhiliny): convert back to unicode for py2 133 | if six.PY2 and return_unicode: 134 | ret_pieces = [] 135 | for piece in new_pieces: 136 | if isinstance(piece, str): 137 | piece = six.ensure_text(piece, "utf-8") 138 | ret_pieces.append(piece) 139 | new_pieces = ret_pieces 140 | 141 | return new_pieces 142 | 143 | 144 | def encode_ids(sp_model, text, sample=False): 145 | pieces = encode_pieces(sp_model, text, return_unicode=False, sample=sample) 146 | ids = [sp_model.PieceToId(piece) for piece in pieces] 147 | return ids 148 | 149 | 150 | def convert_to_unicode(text): 151 | """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" 152 | if six.PY3: 153 | if type(text) in [str,bytes]: 154 | if isinstance(text, str): 155 | return text 156 | elif isinstance(text, bytes): 157 | return six.ensure_text(text, "utf-8", "ignore") 158 | else: 159 | raise ValueError("Unsupported string type: %s" % (type(text))) 160 | # by chenming 161 | else: 162 | return text 163 | elif six.PY2: 164 | if isinstance(text, str): 165 | return six.ensure_text(text, "utf-8", "ignore") 166 | elif isinstance(text, six.text_type): 167 | return text 168 | else: 169 | raise ValueError("Unsupported string type: %s" % (type(text))) 170 | else: 171 | raise ValueError("Not running on Python2 or Python 3?") 172 | 173 | 174 | 175 | def printale_text(text): 176 | """Returnsb text encoded in a way suitable for print or `tf.logging`.""" 177 | 178 | # These functions want `str` for both Python2 and Python3, but in one case 179 | # it's a Unicode string and in the other it's a byte string. 180 | if six.PY3: 181 | if isinstance(text, str): 182 | return text 183 | elif isinstance(text, bytes): 184 | return six.ensure_text(text, "utf-8", "ignore") 185 | else: 186 | raise ValueError("Unsupported string type: %s" % (type(text))) 187 | elif six.PY2: 188 | if isinstance(text, str): 189 | return text 190 | elif isinstance(text, six.text_type): 191 | return six.ensure_binary(text, "utf-8") 192 | else: 193 | raise ValueError("Unsupported string type: %s" % (type(text))) 194 | else: 195 | raise ValueError("Not running on Python2 or Python 3?") 196 | 197 | 198 | def load_vocab(vocab_file): 199 | """Loads a vocabulary file into a dictionary.""" 200 | vocab = collections.OrderedDict() 201 | with tf.gfile.GFile(vocab_file, "r") as reader: 202 | while True: 203 | token = convert_to_unicode(reader.readline()) 204 | if not token: 205 | break 206 | token = token.strip()#.split()[0] 207 | if token not in vocab: 208 | vocab[token] = len(vocab) 209 | return vocab 210 | 211 | 212 | 213 | 214 | def convert_by_vocab(vocab, items): 215 | """Converts a sequence of [tokens|ids] using the vocab.""" 216 | output = [] 217 | for item in items: 218 | output.append(vocab[item]) 219 | return output 220 | 221 | 222 | def convert_tokens_to_ids(vocab, tokens): 223 | return convert_by_vocab(vocab, tokens) 224 | 225 | 226 | def convert_ids_to_tokens(inv_vocab, ids): 227 | return convert_by_vocab(inv_vocab, ids) 228 | 229 | 230 | def whitespace_tokenize(text): 231 | """Runs basic whitespace cleaning and splitting on a piece of text.""" 232 | text = text.strip() 233 | if not text: 234 | return [] 235 | tokens = text.split() 236 | return tokens 237 | 238 | 239 | class FullTokenizer(object): 240 | """Runs end-to-end tokenziation.""" 241 | 242 | def __init__(self, vocab_file, do_lower_case=True, spm_model_file=None): 243 | self.vocab = None 244 | self.sp_model = None 245 | if spm_model_file: 246 | self.sp_model = spm.SentencePieceProcessor() 247 | tf.logging.info("loading sentence piece model") 248 | self.sp_model.Load(spm_model_file) 249 | # Note(mingdachen): For the purpose of consisent API, we are 250 | # generating a vocabulary for the sentence piece tokenizer. 251 | self.vocab = {self.sp_model.IdToPiece(i): i for i 252 | in range(self.sp_model.GetPieceSize())} 253 | else: 254 | self.vocab = load_vocab(vocab_file) 255 | self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) 256 | self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) 257 | self.inv_vocab = {v: k for k, v in self.vocab.items()} 258 | 259 | @classmethod 260 | def from_scratch(cls, vocab_file, do_lower_case, spm_model_file): 261 | return FullTokenizer(vocab_file, do_lower_case, spm_model_file) 262 | 263 | # @classmethod 264 | # def from_hub_module(cls, hub_module, spm_model_file): 265 | # """Get the vocab file and casing info from the Hub module.""" 266 | # with tf.Graph().as_default(): 267 | # albert_module = hub.Module(hub_module) 268 | # tokenization_info = albert_module(signature="tokenization_info", 269 | # as_dict=True) 270 | # with tf.Session() as sess: 271 | # vocab_file, do_lower_case = sess.run( 272 | # [tokenization_info["vocab_file"], 273 | # tokenization_info["do_lower_case"]]) 274 | # return FullTokenizer( 275 | # vocab_file=vocab_file, do_lower_case=do_lower_case, 276 | # spm_model_file=spm_model_file) 277 | 278 | def tokenize(self, text): 279 | if self.sp_model: 280 | split_tokens = encode_pieces(self.sp_model, text, return_unicode=False) 281 | else: 282 | split_tokens = [] 283 | for token in self.basic_tokenizer.tokenize(text): 284 | for sub_token in self.wordpiece_tokenizer.tokenize(token): 285 | split_tokens.append(sub_token) 286 | 287 | return split_tokens 288 | 289 | def convert_tokens_to_ids(self, tokens): 290 | if self.sp_model: 291 | tf.logging.info("using sentence piece tokenzier.") 292 | return [self.sp_model.PieceToId( 293 | printable_text(token)) for token in tokens] 294 | else: 295 | return convert_by_vocab(self.vocab, tokens) 296 | 297 | def convert_ids_to_tokens(self, ids): 298 | if self.sp_model: 299 | tf.logging.info("using sentence piece tokenzier.") 300 | return [self.sp_model.IdToPiece(id_) for id_ in ids] 301 | else: 302 | return convert_by_vocab(self.inv_vocab, ids) 303 | 304 | 305 | class BasicTokenizer(object): 306 | """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" 307 | 308 | def __init__(self, do_lower_case=True): 309 | """Constructs a BasicTokenizer. 310 | 311 | Args: 312 | do_lower_case: Whether to lower case the input. 313 | """ 314 | self.do_lower_case = do_lower_case 315 | 316 | def tokenize(self, text): 317 | """Tokenizes a piece of text.""" 318 | text = convert_to_unicode(text) 319 | text = self._clean_text(text) 320 | 321 | # This was added on November 1st, 2018 for the multilingual and Chinese 322 | # models. This is also applied to the English models now, but it doesn't 323 | # matter since the English models were not trained on any Chinese data 324 | # and generally don't have any Chinese data in them (there are Chinese 325 | # characters in the vocabulary because Wikipedia does have some Chinese 326 | # words in the English Wikipedia.). 327 | text = self._tokenize_chinese_chars(text) 328 | 329 | orig_tokens = whitespace_tokenize(text) 330 | split_tokens = [] 331 | for token in orig_tokens: 332 | if self.do_lower_case: 333 | token = token.lower() 334 | token = self._run_strip_accents(token) 335 | split_tokens.extend(self._run_split_on_punc(token)) 336 | 337 | output_tokens = whitespace_tokenize(" ".join(split_tokens)) 338 | return output_tokens 339 | 340 | def _run_strip_accents(self, text): 341 | """Strips accents from a piece of text.""" 342 | text = unicodedata.normalize("NFD", text) 343 | output = [] 344 | for char in text: 345 | cat = unicodedata.category(char) 346 | if cat == "Mn": 347 | continue 348 | output.append(char) 349 | return "".join(output) 350 | 351 | def _run_split_on_punc(self, text): 352 | """Splits punctuation on a piece of text.""" 353 | chars = list(text) 354 | i = 0 355 | start_new_word = True 356 | output = [] 357 | while i < len(chars): 358 | char = chars[i] 359 | if _is_punctuation(char): 360 | output.append([char]) 361 | start_new_word = True 362 | else: 363 | if start_new_word: 364 | output.append([]) 365 | start_new_word = False 366 | output[-1].append(char) 367 | i += 1 368 | 369 | return ["".join(x) for x in output] 370 | 371 | def _tokenize_chinese_chars(self, text): 372 | """Adds whitespace around any CJK character.""" 373 | output = [] 374 | for char in text: 375 | cp = ord(char) 376 | if self._is_chinese_char(cp): 377 | output.append(" ") 378 | output.append(char) 379 | output.append(" ") 380 | else: 381 | output.append(char) 382 | return "".join(output) 383 | 384 | def _is_chinese_char(self, cp): 385 | """Checks whether CP is the codepoint of a CJK character.""" 386 | # This defines a "chinese character" as anything in the CJK Unicode block: 387 | # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) 388 | # 389 | # Note that the CJK Unicode block is NOT all Japanese and Korean characters, 390 | # despite its name. The modern Korean Hangul alphabet is a different block, 391 | # as is Japanese Hiragana and Katakana. Those alphabets are used to write 392 | # space-separated words, so they are not treated specially and handled 393 | # like the all of the other languages. 394 | if ((cp >= 0x4E00 and cp <= 0x9FFF) or # 395 | (cp >= 0x3400 and cp <= 0x4DBF) or # 396 | (cp >= 0x20000 and cp <= 0x2A6DF) or # 397 | (cp >= 0x2A700 and cp <= 0x2B73F) or # 398 | (cp >= 0x2B740 and cp <= 0x2B81F) or # 399 | (cp >= 0x2B820 and cp <= 0x2CEAF) or 400 | (cp >= 0xF900 and cp <= 0xFAFF) or # 401 | (cp >= 0x2F800 and cp <= 0x2FA1F)): # 402 | return True 403 | 404 | return False 405 | 406 | def _clean_text(self, text): 407 | """Performs invalid character removal and whitespace cleanup on text.""" 408 | output = [] 409 | for char in text: 410 | cp = ord(char) 411 | if cp == 0 or cp == 0xfffd or _is_control(char): 412 | continue 413 | if _is_whitespace(char): 414 | output.append(" ") 415 | else: 416 | output.append(char) 417 | return "".join(output) 418 | 419 | 420 | class WordpieceTokenizer(object): 421 | """Runs WordPiece tokenziation.""" 422 | 423 | def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200): 424 | self.vocab = vocab 425 | self.unk_token = unk_token 426 | self.max_input_chars_per_word = max_input_chars_per_word 427 | 428 | def tokenize(self, text): 429 | """Tokenizes a piece of text into its word pieces. 430 | 431 | This uses a greedy longest-match-first algorithm to perform tokenization 432 | using the given vocabulary. 433 | 434 | For example: 435 | input = "unaffable" 436 | output = ["un", "##aff", "##able"] 437 | 438 | Args: 439 | text: A single token or whitespace separated tokens. This should have 440 | already been passed through `BasicTokenizer. 441 | 442 | Returns: 443 | A list of wordpiece tokens. 444 | """ 445 | 446 | text = convert_to_unicode(text) 447 | 448 | output_tokens = [] 449 | for token in whitespace_tokenize(text): 450 | chars = list(token) 451 | if len(chars) > self.max_input_chars_per_word: 452 | output_tokens.append(self.unk_token) 453 | continue 454 | 455 | is_bad = False 456 | start = 0 457 | sub_tokens = [] 458 | while start < len(chars): 459 | end = len(chars) 460 | cur_substr = None 461 | while start < end: 462 | substr = "".join(chars[start:end]) 463 | if start > 0: 464 | substr = "##" + six.ensure_str(substr) 465 | if substr in self.vocab: 466 | cur_substr = substr 467 | break 468 | end -= 1 469 | if cur_substr is None: 470 | is_bad = True 471 | break 472 | sub_tokens.append(cur_substr) 473 | start = end 474 | 475 | if is_bad: 476 | output_tokens.append(self.unk_token) 477 | else: 478 | output_tokens.extend(sub_tokens) 479 | return output_tokens 480 | 481 | 482 | def _is_whitespace(char): 483 | """Checks whether `chars` is a whitespace character.""" 484 | # \t, \n, and \r are technically control characters but we treat them 485 | # as whitespace since they are generally considered as such. 486 | if char == " " or char == "\t" or char == "\n" or char == "\r": 487 | return True 488 | cat = unicodedata.category(char) 489 | if cat == "Zs": 490 | return True 491 | return False 492 | 493 | 494 | def _is_control(char): 495 | """Checks whether `chars` is a control character.""" 496 | # These are technically control characters but we count them as whitespace 497 | # characters. 498 | if char == "\t" or char == "\n" or char == "\r": 499 | return False 500 | cat = unicodedata.category(char) 501 | if cat in ("Cc", "Cf"): 502 | return True 503 | return False 504 | 505 | 506 | def _is_punctuation(char): 507 | """Checks whether `chars` is a punctuation character.""" 508 | cp = ord(char) 509 | # We treat all non-letter/number ASCII as punctuation. 510 | # Characters such as "^", "$", and "`" are not in the Unicode 511 | # Punctuation class but we treat them as punctuation anyways, for 512 | # consistency. 513 | if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or 514 | (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)): 515 | return True 516 | cat = unicodedata.category(char) 517 | if cat.startswith("P"): 518 | return True 519 | return False 520 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 30 21:42:07 2019 4 | 5 | @author: cm 6 | """ 7 | 8 | import os 9 | #os.environ["CUDA_VISIBLE_DEVICES"] = '-1' 10 | import numpy as np 11 | import tensorflow as tf 12 | from classifier_multi_label.networks import NetworkAlbert 13 | from classifier_multi_label.classifier_utils import get_features 14 | from classifier_multi_label.hyperparameters import Hyperparamters as hp 15 | from classifier_multi_label.utils import select,shuffle_one,time_now_string 16 | 17 | 18 | pwd = os.path.dirname(os.path.abspath(__file__)) 19 | MODEL = NetworkAlbert(is_training=True) 20 | 21 | 22 | # Get data features 23 | input_ids,input_masks,segment_ids,label_ids = get_features() 24 | num_train_samples = len(input_ids) 25 | indexs = np.arange(num_train_samples) 26 | num_batchs = int((num_train_samples - 1) /hp.batch_size) + 1 27 | print('Number of batch:',num_batchs) 28 | 29 | # Set up the graph 30 | saver = tf.train.Saver(max_to_keep=hp.max_to_keep) 31 | sess = tf.Session() 32 | sess.run(tf.global_variables_initializer()) 33 | 34 | # Load model saved before 35 | MODEL_SAVE_PATH = os.path.join(pwd, hp.file_save_model) 36 | ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH) 37 | if ckpt and ckpt.model_checkpoint_path: 38 | saver.restore(sess, ckpt.model_checkpoint_path) 39 | print('Restored model!') 40 | 41 | 42 | with sess.as_default(): 43 | # Tensorboard writer 44 | writer = tf.summary.FileWriter(hp.logdir, sess.graph) 45 | for i in range(hp.num_train_epochs): 46 | np.random.shuffle(indexs) 47 | for j in range(num_batchs-1): 48 | # Get ids selected 49 | i1 = indexs[j * hp.batch_size:min((j + 1) * hp.batch_size, num_train_samples)] 50 | 51 | # Get features 52 | input_id_ = select(input_ids,i1) 53 | input_mask_ = select(input_masks,i1) 54 | segment_id_ = select(segment_ids,i1) 55 | label_id_ = select(label_ids,i1) 56 | 57 | # Feed dict 58 | fd = {MODEL.input_ids: input_id_, 59 | MODEL.input_masks: input_mask_, 60 | MODEL.segment_ids:segment_id_, 61 | MODEL.label_ids:label_id_} 62 | 63 | # Optimizer 64 | sess.run(MODEL.optimizer, feed_dict = fd) 65 | 66 | # Tensorboard 67 | if j%hp.summary_step==0: 68 | summary,glolal_step = sess.run([MODEL.merged,MODEL.global_step], feed_dict = fd) 69 | writer.add_summary(summary, glolal_step) 70 | 71 | # Save Model 72 | if j%(num_batchs//hp.num_saved_per_epoch)==0: 73 | if not os.path.exists(os.path.join(pwd, hp.file_save_model)): 74 | os.makedirs(os.path.join(pwd, hp.file_save_model)) 75 | saver.save(sess, os.path.join(pwd, hp.file_save_model, 'model'+'_%s_%s.ckpt'%(str(i),str(j)))) 76 | 77 | # Log 78 | if j % hp.print_step == 0: 79 | fd = {MODEL.input_ids: input_id_, 80 | MODEL.input_masks: input_mask_, 81 | MODEL.segment_ids:segment_id_, 82 | MODEL.label_ids:label_id_} 83 | loss = sess.run(MODEL.loss, feed_dict = fd) 84 | print('Time:%s, Epoch:%s, Batch number:%s/%s, Loss:%s'%(time_now_string(),str(i),str(j),str(num_batchs),str(loss))) 85 | print('Train finished') 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri May 25 23:43:39 2018 4 | 5 | @author: cm 6 | """ 7 | 8 | 9 | import time 10 | import numpy as np 11 | import pandas as pd 12 | 13 | 14 | 15 | def time_now_string(): 16 | return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime( time.time() )) 17 | 18 | 19 | def select(data,ids): 20 | return [data[i] for i in ids] 21 | 22 | 23 | def shuffle_one(a1): 24 | ran = np.arange(len(a1)) 25 | np.random.shuffle(ran) 26 | return np.array(a1)[ran].tolist() 27 | 28 | 29 | def shuffle_two(a1,a2): 30 | ran = np.arange(len(a1)) 31 | np.random.shuffle(ran) 32 | return [a1[l] for l in ran], [a2[l] for l in ran] 33 | 34 | 35 | def load_txt(file): 36 | with open(file,encoding='utf-8',errors='ignore') as fp: 37 | lines = fp.readlines() 38 | lines = [l.strip() for l in lines] 39 | print("Load data from file (%s) finished !"%file) 40 | return lines 41 | 42 | 43 | def save_txt(file,lines): 44 | lines = [l+'\n' for l in lines] 45 | with open(file,'w+',encoding='utf-8') as fp:#a+添加 46 | fp.writelines(lines) 47 | return "Write data to txt finished !" 48 | 49 | 50 | def load_csv(file,encoding='utf-8',header=0): 51 | return pd.read_csv(file,encoding=encoding,header=header,error_bad_lines=False)#,encoding='gbk' 52 | 53 | 54 | def save_csv(dataframe,file,header=True,index=None,encoding="utf-8-sig"): 55 | return dataframe.to_csv(file, 56 | mode='w+', 57 | header=header, 58 | index=index, 59 | encoding=encoding) 60 | 61 | 62 | def save_excel(dataframe,file,header=True,sheetname='Sheet1'): 63 | return dataframe.to_excel(file, 64 | header=header, 65 | sheet_name=sheetname) 66 | 67 | 68 | def load_excel(file,header=0): 69 | return pd.read_excel(file, 70 | header=header, 71 | ) 72 | 73 | 74 | def cut_list(data,size): 75 | """ 76 | data: a list 77 | size: the size of cut 78 | """ 79 | return [data[i * size:min((i + 1) * size, len(data))] for i in range(int(len(data)-1)//size + 1)] 80 | 81 | 82 | 83 | def load_vocabulary(file_vocabulary_label): 84 | """ 85 | Load vocabulary to dict 86 | """ 87 | vocabulary = load_txt(file_vocabulary_label) 88 | dict_id2label,dict_label2id = {},{} 89 | for i,l in enumerate(vocabulary): 90 | dict_id2label[str(i)] = str(l) 91 | dict_label2id[str(l)] = str(i) 92 | return dict_id2label,dict_label2id 93 | 94 | 95 | 96 | if __name__ == '__main__': 97 | print('') 98 | 99 | 100 | 101 | 102 | --------------------------------------------------------------------------------