├── LICENSE ├── README.md ├── data ├── label2id.json └── stopwords.txt └── src ├── __init__.py ├── bert_dataset.py ├── dictionary.py ├── distill_bert ├── config │ ├── fastbert_cls.json │ └── fastbert_cls_ernie.json ├── count_label .py ├── data_utils │ ├── dataset_preparing.py │ └── tokenization.py ├── infer.py ├── model_define │ ├── model_bert.py │ ├── model_fastbert.py │ └── optimization.py ├── predict.py ├── pretrained_model │ └── bert │ │ └── config.json ├── run_scripts │ ├── script_eval.sh │ ├── script_infer.sh │ ├── script_train_stage0.sh │ └── script_train_stage1.sh ├── sample │ └── ChnSentiCorp │ │ ├── dev.tsv │ │ ├── test.tsv │ │ ├── test_slim.tsv │ │ └── train.tsv ├── train.py └── utils.py ├── finetune_bert ├── run_language_model_bert.py ├── run_language_model_roberta.py ├── train_script.sh ├── train_script_roberta.sh ├── wiki_test.txt └── wiki_train.txt ├── models ├── RNN.py ├── TextCNN.py ├── TextDGCNN.py └── bert.py ├── prepare_dataset.py ├── prepare_distill_dataset.py ├── rnn_dataset.py ├── train.py ├── train_fasttext.py └── utils ├── config.py ├── convert_tf_torch.py └── tools.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 laomagic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **THUCNews**中文文本分类数据集的处理,该数据集包含 84 万篇新闻文档,总计 14 类;在数据集的基础上可以进行文本分类、词向量的训练等任务。数据集的下载地址:[http://thuctc.thunlp.org/](http://thuctc.thunlp.org/) 2 | 3 | ## 1.bert预训练模型的介绍 4 | 5 | wobert/**wonezha**:以词为单位的中文 bert 模型,具体详情见:[https://github.com/ZhuiyiTechnology/WoBERT](https://github.com/ZhuiyiTechnology/WoBERT) 6 | 7 | google/Roberta:以字为粒度的中文 bert,Roberta 的模型地址:[https://github.com/ymcui/Chinese-BERT-wwm](https://github.com/ymcui/Chinese-BERT-wwm),roberta 的模型为:`RoBERTa-wwm-ext, Chinese` 8 | 9 | google 中文 bert 的地址:[https://github.com/google-research/bert](https://github.com/google-research/bert),google 的为中文的 bert 模型 10 | 11 | 其中,四种版本的bert链接内部均有较详细的介绍。 12 | 13 | albert是在bert基础上进行参数缩减的版本,[https://github.com/brightmart/albert_zh](https://github.com/brightmart/albert_zh) (tiny:albert_chinese_tiny base:albert_chinese_base large:albert_chinese_large) 14 | 15 | ## 2.bert模型的在数据集上测试 16 | 17 | 在THUCNews数据集上进行文本的分类任务,bert在数据集上微调过程中每 1000batch,在测试集上验证一次,待验证的loss损失在5batch内不在下降,停止训练。 18 | 19 | bert 测试参数设置:Bert 模型:12-layer,768-hidden,12-heads 20 | 21 | ```plain 22 | batch_size = 64 23 | hidden_size = 768 24 | dropout = 0.3 25 | eps = 1e-8 26 | learning_rate = 2e-5 # 学习率 27 | ``` 28 | **1)语料为词粒度版本,参数:word=True(**语料使用jieba分词工具进行分词**)** 29 | 30 | ```plain 31 | acc loss 32 | roberta 78.27 0.69 33 | wonezha 92.24 0.25 34 | wobert 93.83 0.2 35 | google 77.39 0.72 36 | tiny 73.83 0.82 37 | base 75 0.79 38 | large 76.35 0.74 39 | ``` 40 | (其中google指的goole版本的中文bert-base,如1介绍的bert对应的基础版本,本次测试的bert版本链接,百度网盘:链接:https://pan.baidu.com/s/10kX5kC20ggJo7ztz4h3rLA ;提取码:vnxk ) 41 | 从测试的结果中可以看出,wobert/wonezha(词粒度版本)的bert在THUCNews文本分类的任务上的表现要远好于google/roberta(字粒度版本),而wobert的acc比wonezha也要高1.59%。 42 | 43 | **2)语料为字粒度版本,参数:word=False** 44 | 45 | ```plain 46 | acc loss 47 | roberta 54.01 1.4 48 | wonezha 70.33 0.94 49 | wobert 73.29 0.83 50 | google 54.09 1.4 51 | ``` 52 | 未使用分词工具对语料进行分词,bert默认使用的wordpiece的工具处理文本,字粒度的文本方式改变了词汇之间的相关联系,文本分类的效果总体较差,而且词粒度版本的bert比字粒度的bert的acc高16~19%,说明词粒度版本的词中一定程度上包含了char的语义信息。 53 | 54 | ## **3.FastBert蒸馏后的效果** 55 | 56 | **FastBert的论文地址:**[https://arxiv.org/pdf/2004.02178.pdf](https://arxiv.org/pdf/2004.02178.pdf),代码地址:[https://github.com/BitVoyage/FastBERT](https://github.com/BitVoyage/FastBERT) 57 | 58 | 59 | ### 3.1代码的使用步骤 60 | 61 | ```python 62 | 1.到数据集的地址下载原始数据进行解压,放到data目录下 63 | 运行prepare_dataset.py data文件下生成训练数据train.csv test.csv valid.csv 以及标签到id的映射文件label2id.json 64 | 运行prepare_distill_dataset.py 生成蒸馏数据train_distill_new.tsv test_distill_new.tsv dev_distill_new.tsv 65 | 2.下载预训练的bert模型 放到model目录下 66 | 3.修改utils/config.py配置文件的模型路径等参数,运行train.py文件进行文本分类任务 67 | 68 | ``` 69 | 70 | ```python 71 | 模型的蒸馏,可以参考FastBert提供的代码,主要步骤: 72 | 1. 初始训练:进行文本分类的微调训练 73 | sh run_scripts/script_train_stage0.sh 74 | 2. 蒸馏训练:transformer每层的student classifier学习teacher classifier的分布 75 | sh run_scripts/script_train_stage1.sh 76 | **注意**:蒸馏阶段输入数据为无监督数据,可依据需要引入更多数据提升鲁棒性 77 | 3. 推理:调节speed,观察推理速度和准确率之间的关系 78 | sh run_scripts/script_infer.sh 79 | 其中 inference_speed参数(0.0~1.0)控制加速程度 80 | 4. 部署使用 81 | python3 predict.py 82 | ``` 83 | ### 3.2蒸馏的效果 84 | git提供的数据集上的复现效果,使用作者提供的bert模型: 85 | ```plain 86 | speed_arg:0.0, time_per_record:0.0365, acc:0.9392, 基准 87 | speed_arg:0.1, time_per_record:0.0332, acc:0.9400, 1.10倍 88 | speed_arg:0.5, time_per_record:0.0237, acc:0.9333, 1.54倍 89 | speed_arg:0.8, time_per_record:0.0176, acc:0.9100, 2.07倍 90 | ``` 91 | 推理的acc指标和git提供的结果基本一致,但是推理速度,并没有作者测试的那么好。 92 | 93 | 与2同样的数据集上,使用git提供的代码,测试代码链接:[https://github.com/laomagic/TextFastBert](https://github.com/laomagic/TextFastBert),其中修改数据预处理文件,对train.py添加早停功能,然后进行蒸馏,蒸馏时使用的模型为wobert版本的bert,对语料使用结巴进行了分词,结果如下: 94 | 95 | ```plain 96 | speed 0 time 0.0380 acc 0.9441 基准 97 | speed 0.5 time 0.0364 acc 0.9441 1.04 98 | speed 0.8 time 0.0338 acc 0.9438 1.12 99 | speed 0.85 time 0.0267 acc 0.9432 1.42 100 | speed 0.9 time 0.0137 acc 0.9406 2.77 101 | speed 0.95 time 0.0080 acc 0.9229 4.75 102 | speed 1 time 0.0062 acc 0.8974 6.13 103 | ``` 104 | 未蒸馏的分类效果:acc:0.9466 loss:0.2970 105 | 106 | 未蒸馏的模型和蒸馏后的模型(speed=0),acc相差0.0025;蒸馏后的模型,speed为0.5时,acc保持不变,而speed为0.9时,acc下降0.006,推理速度提升2.77倍,speed为1时,acc下降0.0467,推理速度提升6.14倍;表明FastBert蒸馏能保证较低精度损失的同时,一定程度能够提升模型的推理速度。 -------------------------------------------------------------------------------- /data/label2id.json: -------------------------------------------------------------------------------- 1 | {"\u80a1\u7968": 0, "\u65f6\u5c1a": 1, "\u5bb6\u5c45": 2, "\u6e38\u620f": 3, "\u4f53\u80b2": 4, "\u793e\u4f1a": 5, "\u65f6\u653f": 6, "\u623f\u4ea7": 7, "\u661f\u5ea7": 8, "\u5f69\u7968": 9, "\u5a31\u4e50": 10, "\u8d22\u7ecf": 11, "\u6559\u80b2": 12, "\u79d1\u6280": 13} -------------------------------------------------------------------------------- /data/stopwords.txt: -------------------------------------------------------------------------------- 1 | 2 | ——— 3 | 》), 4 | )÷(1- 5 | ”, 6 | )、 7 | =( 8 | : 9 | → 10 | ℃ 11 | & 12 | * 13 | 一一 14 | ~~~~ 15 | ’ 16 | . 17 | 『 18 | .一 19 | ./ 20 | -- 21 | 』 22 | =″ 23 | 【 24 | [*] 25 | }> 26 | [⑤]] 27 | [①D] 28 | c] 29 | ng昉 30 | * 31 | // 32 | [ 33 | ] 34 | [②e] 35 | [②g] 36 | ={ 37 | } 38 | ,也 39 | ‘ 40 | A 41 | [①⑥] 42 | [②B] 43 | [①a] 44 | [④a] 45 | [①③] 46 | [③h] 47 | ③] 48 | 1. 49 | -- 50 | [②b] 51 | ’‘ 52 | ××× 53 | [①⑧] 54 | 0:2 55 | =[ 56 | [⑤b] 57 | [②c] 58 | [④b] 59 | [②③] 60 | [③a] 61 | [④c] 62 | [①⑤] 63 | [①⑦] 64 | [①g] 65 | ∈[ 66 | [①⑨] 67 | [①④] 68 | [①c] 69 | [②f] 70 | [②⑧] 71 | [②①] 72 | [①C] 73 | [③c] 74 | [③g] 75 | [②⑤] 76 | [②②] 77 | 一. 78 | [①h] 79 | .数 80 | [] 81 | [①B] 82 | 数/ 83 | [①i] 84 | [③e] 85 | [①①] 86 | [④d] 87 | [④e] 88 | [③b] 89 | [⑤a] 90 | [①A] 91 | [②⑧] 92 | [②⑦] 93 | [①d] 94 | [②j] 95 | 〕〔 96 | ][ 97 | :// 98 | ′∈ 99 | [②④ 100 | [⑤e] 101 | 12% 102 | b] 103 | ... 104 | ................... 105 | …………………………………………………③ 106 | ZXFITL 107 | [③F] 108 | 」 109 | [①o] 110 | ]∧′=[ 111 | ∪φ∈ 112 | ′| 113 | {- 114 | ②c 115 | } 116 | [③①] 117 | R.L. 118 | [①E] 119 | Ψ 120 | -[*]- 121 | ↑ 122 | .日 123 | [②d] 124 | [② 125 | [②⑦] 126 | [②②] 127 | [③e] 128 | [①i] 129 | [①B] 130 | [①h] 131 | [①d] 132 | [①g] 133 | [①②] 134 | [②a] 135 | f] 136 | [⑩] 137 | a] 138 | [①e] 139 | [②h] 140 | [②⑥] 141 | [③d] 142 | [②⑩] 143 | e] 144 | 〉 145 | 】 146 | 元/吨 147 | [②⑩] 148 | 2.3% 149 | 5:0 150 | [①] 151 | :: 152 | [②] 153 | [③] 154 | [④] 155 | [⑤] 156 | [⑥] 157 | [⑦] 158 | [⑧] 159 | [⑨] 160 | …… 161 | —— 162 | ? 163 | 、 164 | 。 165 | “ 166 | ” 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 | ③ 203 | ④ 204 | ⑤ 205 | ⑥ 206 | ⑦ 207 | ⑧ 208 | ⑨ 209 | ⑩ 210 | Ⅲ 211 | В 212 | " 213 | ; 214 | # 215 | @ 216 | γ 217 | μ 218 | φ 219 | φ. 220 | × 221 | Δ 222 | ■ 223 | ▲ 224 | sub 225 | exp 226 | sup 227 | sub 228 | Lex 229 | # 230 | % 231 | & 232 | ' 233 | + 234 | +ξ 235 | ++ 236 | - 237 | -β 238 | < 239 | <± 240 | <Δ 241 | <λ 242 | <φ 243 | << 244 | = 245 | = 246 | =☆ 247 | =- 248 | > 249 | >λ 250 | _ 251 | ~± 252 | ~+ 253 | [⑤f] 254 | [⑤d] 255 | [②i] 256 | ≈ 257 | [②G] 258 | [①f] 259 | LI 260 | ㈧ 261 | [- 262 | ...... 263 | 〉 264 | [③⑩] 265 | 第二 266 | 一番 267 | 一直 268 | 一个 269 | 一些 270 | 许多 271 | 种 272 | 有的是 273 | 也就是说 274 | 末##末 275 | 啊 276 | 阿 277 | 哎 278 | 哎呀 279 | 哎哟 280 | 唉 281 | 俺 282 | 俺们 283 | 按 284 | 按照 285 | 吧 286 | 吧哒 287 | 把 288 | 罢了 289 | 被 290 | 本 291 | 本着 292 | 比 293 | 比方 294 | 比如 295 | 鄙人 296 | 彼 297 | 彼此 298 | 边 299 | 别 300 | 别的 301 | 别说 302 | 并 303 | 并且 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 | 但是 341 | 当 342 | 当着 343 | 到 344 | 得 345 | 的 346 | 的话 347 | 等 348 | 等等 349 | 地 350 | 第 351 | 叮咚 352 | 对 353 | 对于 354 | 多 355 | 多少 356 | 而 357 | 而况 358 | 而且 359 | 而是 360 | 而外 361 | 而言 362 | 而已 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 | 何 397 | 何处 398 | 何况 399 | 何时 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 | 继而 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 | 来 465 | 来着 466 | 离 467 | 例如 468 | 哩 469 | 连 470 | 连同 471 | 两者 472 | 了 473 | 临 474 | 另 475 | 另外 476 | 另一方面 477 | 论 478 | 嘛 479 | 吗 480 | 慢说 481 | 漫说 482 | 冒 483 | 么 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 | 那样 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 | 且 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 | 时候 576 | 什么 577 | 什么样 578 | 使得 579 | 是 580 | 是的 581 | 首先 582 | 谁 583 | 谁知 584 | 顺 585 | 顺着 586 | 似的 587 | 虽 588 | 虽然 589 | 虽说 590 | 虽则 591 | 随 592 | 随着 593 | 所 594 | 所以 595 | 他 596 | 他们 597 | 他人 598 | 它 599 | 它们 600 | 她 601 | 她们 602 | 倘 603 | 倘或 604 | 倘然 605 | 倘若 606 | 倘使 607 | 腾 608 | 替 609 | 通过 610 | 同 611 | 同时 612 | 哇 613 | 万一 614 | 往 615 | 望 616 | 为 617 | 为何 618 | 为了 619 | 为什么 620 | 为着 621 | 喂 622 | 嗡嗡 623 | 我 624 | 我们 625 | 呜 626 | 呜呼 627 | 乌乎 628 | 无论 629 | 无宁 630 | 毋宁 631 | 嘻 632 | 吓 633 | 相对而言 634 | 像 635 | 向 636 | 向着 637 | 嘘 638 | 呀 639 | 焉 640 | 沿 641 | 沿着 642 | 要 643 | 要不 644 | 要不然 645 | 要不是 646 | 要么 647 | 要是 648 | 也 649 | 也罢 650 | 也好 651 | 一 652 | 一般 653 | 一旦 654 | 一方面 655 | 一来 656 | 一切 657 | 一样 658 | 一则 659 | 依 660 | 依照 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 | 于是 686 | 于是乎 687 | 与 688 | 与此同时 689 | 与否 690 | 与其 691 | 越是 692 | 云云 693 | 哉 694 | 再说 695 | 再者 696 | 在 697 | 在下 698 | 咱 699 | 咱们 700 | 则 701 | 怎 702 | 怎么 703 | 怎么办 704 | 怎么样 705 | 怎样 706 | 咋 707 | 照 708 | 照着 709 | 者 710 | 这 711 | 这边 712 | 这儿 713 | 这个 714 | 这会儿 715 | 这就是说 716 | 这里 717 | 这么 718 | 这么点儿 719 | 这么些 720 | 这么样 721 | 这时 722 | 这些 723 | 这样 724 | 正如 725 | 吱 726 | 之 727 | 之类 728 | 之所以 729 | 之一 730 | 只是 731 | 只限 732 | 只要 733 | 只有 734 | 至 735 | 至于 736 | 诸位 737 | 着 738 | 着呢 739 | 自 740 | 自从 741 | 自个儿 742 | 自各儿 743 | 自己 744 | 自家 745 | 自身 746 | 综上所述 747 | 总的来看 748 | 总的来说 749 | 总的说来 750 | 总而言之 751 | 总之 752 | 纵 753 | 纵令 754 | 纵然 755 | 纵使 756 | 遵照 757 | 作为 758 | 兮 759 | 呃 760 | 呗 761 | 咚 762 | 咦 763 | 喏 764 | 啐 765 | 喔唷 766 | 嗬 767 | 嗯 768 | 嗳 769 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 5 | sys.path.append(root_path) 6 | print(root_path) -------------------------------------------------------------------------------- /src/bert_dataset.py: -------------------------------------------------------------------------------- 1 | from torch.utils.data import Dataset 2 | import pandas as pd 3 | from src.utils import config 4 | from src.utils.tools import clean_symbols, query_cut, processes_data, multiprocess_data 5 | from tqdm import tqdm 6 | import torch 7 | 8 | tqdm.pandas() 9 | 10 | 11 | class BertDataset(Dataset): 12 | """数据集的创建""" 13 | 14 | def __init__(self, path, tokenizer=None, word=False, debug=False, max_length=128): 15 | super(BertDataset, self).__init__() 16 | self.word = word 17 | self.data = pd.read_csv(path, sep='\t').dropna() 18 | if debug: 19 | self.data = self.data.sample(n=1000, replace=True) 20 | self.data = multiprocess_data(self.data, processes_data, worker=10, word=word) # 处理数据 21 | # # self.data["sentence"] = self.data['title'] 22 | # # self.data["sentence"] = self.data['title'] + self.data['content'] 23 | # self.data["sentence"] = self.data['content'] 24 | # self.data['clean_sentence'] = self.data['sentence'].progress_apply(clean_symbols) 25 | # self.data["cut_sentence"] = self.data['clean_sentence'] 26 | # # 标签映射到id 27 | # self.data['category_id'] = self.data['label'].progress_apply(lambda x: x.strip()).map(config.label2id) 28 | # # char粒度 29 | # if self.word: 30 | # self.data["cut_sentence"] = self.data['clean_sentence'].progress_apply(query_cut) 31 | self.tokenizer = tokenizer 32 | self.max_length = max_length 33 | 34 | def __getitem__(self, i): 35 | data = self.data.iloc[i] 36 | text = data['cut_sentence'].split(' ') # text数据 37 | labels = int(data['category_id']) 38 | text_dict = self.tokenizer.encode_plus( 39 | text, 40 | add_special_token=True, 41 | max_length=self.max_length, 42 | ad_to_max_length=True, 43 | return_attention_mask=True) 44 | input_ids, attention_mask, token_type_ids = text_dict['input_ids'], \ 45 | text_dict['attention_mask'], \ 46 | text_dict['token_type_ids'] 47 | output = { 48 | "token_ids": input_ids, 49 | 'attention_mask': attention_mask, 50 | "token_type_ids": token_type_ids, 51 | "labels": labels 52 | } 53 | return output 54 | 55 | def __len__(self): 56 | return self.data.shape[0] 57 | 58 | 59 | def collate_fn(batch): 60 | """ 61 | 动态padding,返回Tensor 62 | :param batch: 63 | :return: 每个batch id和label 64 | """ 65 | 66 | def padding(indice, max_length, pad_idx=0): 67 | """ 68 | 填充每个batch的句子长度 69 | """ 70 | pad_indice = [item + [pad_idx] * max(0, max_length - len(item)) for item in indice] 71 | return torch.tensor(pad_indice) 72 | 73 | token_ids = [data["token_ids"] for data in batch] 74 | max_length = max([len(t) for t in token_ids]) # batch中样本的最大的长度 75 | labels = torch.tensor([data["labels"] for data in batch]) 76 | token_type_ids = [data["token_type_ids"] for data in batch] 77 | attention_mask = [data["attention_mask"] for data in batch] 78 | # 填充每个batch的sample 79 | token_ids_padded = padding(token_ids, max_length) 80 | token_type_ids_padded = padding(token_type_ids, max_length) 81 | attention_mask_padded = padding(attention_mask, max_length) 82 | 83 | return token_ids_padded, attention_mask_padded, token_type_ids_padded, labels 84 | -------------------------------------------------------------------------------- /src/dictionary.py: -------------------------------------------------------------------------------- 1 | import jieba 2 | import os 3 | import re 4 | jieba.initialize() 5 | from string import punctuation 6 | from collections import Counter 7 | from tqdm import tqdm 8 | import joblib 9 | import pandas as pd 10 | from src.tools import processes_data, multiprocess_data 11 | 12 | 13 | def clean_symbols(text): 14 | """ 15 | 对特殊符号做一些处理 16 | """ 17 | text = re.sub('[0-9]+', " NUM ", str(text)) 18 | text = re.sub('[!!]+', " ", text) 19 | # text = re.sub('!', '', text) 20 | text = re.sub('[??]+', " ", text) 21 | text = re.sub("[a-zA-Z#$%&\'()*+,-./:;:<=>@,。★、…【】《》“”‘’'!'[\\]^_`{|}~]+", " OOV ", text) 22 | return re.sub("\s+", " ", text) 23 | 24 | class Dictionary: 25 | """构建字典""" 26 | 27 | def __init__(self, max_size=50000, start_end_token=True, min_count=2): 28 | self.max_size = max_size 29 | self.start_end_tokens = start_end_token 30 | self.pad_token = '' 31 | self.min_count = min_count 32 | 33 | def build_dictionary(self, data): 34 | self.word2id, self.id2word, self.vocab_words, self.word2count = self._build_dictionary(data) 35 | self.vocabulary_size = len(self.vocab_words) 36 | 37 | def _build_dictionary(self, data): 38 | vocab_words = [self.pad_token, ''] 39 | vocab_size = 2 40 | if self.start_end_tokens: 41 | vocab_words += ['', ''] 42 | vocab_size += 2 43 | data = [word for sen in tqdm(data) for word in sen.split(' ')] 44 | word2count = Counter(data) 45 | # 词典的容量 46 | if self.max_size: 47 | word2count = {word: count for word, count in word2count.most_common(self.max_size - vocab_size)} 48 | # 过滤低频词 49 | if self.min_count: 50 | word2count = {word: count for word, count in word2count.items() if count > self.min_count} 51 | vocab_words += list(sorted(word2count.keys())) 52 | word2id = dict(zip(vocab_words, range(len(vocab_words)))) 53 | id2word = vocab_words 54 | 55 | return word2id, id2word, vocab_words, word2count 56 | 57 | def indexer(self, word): 58 | """根据词查询id""" 59 | try: 60 | id = self.word2id[word] 61 | except: 62 | id = self.word2id[''] 63 | return id 64 | 65 | 66 | if __name__ == '__main__': 67 | data = pd.read_csv(config.train_path, sep='\t').dropna() 68 | test = pd.read_csv(config.test_path, sep='\t') 69 | valid = pd.read_csv(config.valid_path, sep='\t') 70 | data = pd.concat([train, test, valid], axis=0).dropna() 71 | word = True 72 | data = multiprocess_data(data,processes_data,worker=10,word=True) 73 | if word: 74 | data = data["cut_sentence"].values.tolist() 75 | else: 76 | data = data['raw_words'].values.tolist() 77 | dictionary = Dictionary() 78 | dictionary.build_dictionary(data) 79 | print(dictionary.vocab_words[:20]) 80 | joblib.dump(dictionary, open('dict.pkl', 'wb')) 81 | # dictionary = joblib.load('dict.pkl') 82 | # id = dictionary.indexer('世界') 83 | # print(id) 84 | -------------------------------------------------------------------------------- /src/distill_bert/config/fastbert_cls.json: -------------------------------------------------------------------------------- 1 | { 2 | "vocab_file" : "./pretrained_model/public/vocab.txt", 3 | "bert_config_path" : "./pretrained_model/public/config.json", 4 | "bert_pretrained_model_path" : "./pretrained_model/public/pytorch_model.bin", 5 | 6 | "num_class":119, 7 | "cls_hidden_size": 128, 8 | "cls_num_attention_heads": 8, 9 | "max_seq_len" : 128, 10 | 11 | "num_tuning_layers" : 12, 12 | "init_lr" : 2e-5, 13 | "gradient_accumulation_steps" : 1, 14 | "warmup_proportion" : 0.1 15 | } -------------------------------------------------------------------------------- /src/distill_bert/config/fastbert_cls_ernie.json: -------------------------------------------------------------------------------- 1 | { 2 | "vocab_file" : "./pretrained_model/ernie/ERNIE_stable-1.0.1-pytorch/vocab.txt", 3 | "bert_config_path" : "./pretrained_model/ernie/ERNIE_stable-1.0.1-pytorch/config.json", 4 | "bert_pretrained_model_path" : "./pretrained_model/ernie/ERNIE_stable-1.0.1-pytorch/pytorch_model.bin", 5 | 6 | "num_class":2, 7 | "cls_hidden_size": 128, 8 | "cls_num_attention_heads": 8, 9 | "max_seq_len" : 128, 10 | 11 | "num_tuning_layers" : 12, 12 | "init_lr" : 1e-5, 13 | "gradient_accumulation_steps" : 1, 14 | "warmup_proportion" : 0.1 15 | } 16 | -------------------------------------------------------------------------------- /src/distill_bert/count_label .py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | def main(args): 5 | raw_data = open(args.train_data, 'r').readlines() 6 | labels = [] 7 | for line in raw_data: 8 | row = line.strip("\n").split("\t") 9 | labels.append(row[0]) 10 | unique_label = set(labels) 11 | print('数据集标签:{} 标签总数:{}'.format(unique_label,len(unique_label))) 12 | 13 | if __name__ == "__main__": 14 | parser = argparse.ArgumentParser(description="Count dataset label.") 15 | parser.add_argument("--train_data", dest="train_data", action="store", help="") 16 | parsed_args = parser.parse_args() 17 | main(parsed_args) 18 | -------------------------------------------------------------------------------- /src/distill_bert/data_utils/dataset_preparing.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import data_utils.tokenization as tokenization 3 | import jieba 4 | jieba.initialize() 5 | from transformers import BertTokenizer 6 | import pandas as pd 7 | 8 | 9 | def text_collate_fn(batch, padding): 10 | texts = [instance["text"] for instance in batch] 11 | tokens = [instance["tokens"] for instance in batch] 12 | segment_ids = [[0] * len(token) for token in tokens] 13 | attn_masks = [[1] * len(token) for token in tokens] 14 | labels = [instance["label"] for instance in batch] 15 | max_len = max([len(token) for token in tokens]) 16 | for i, token in enumerate(tokens): 17 | token.extend([padding] * (max_len - len(token))) 18 | segment_ids[i].extend([0] * (max_len - len(segment_ids[i]))) 19 | attn_masks[i].extend([0] * (max_len - len(attn_masks[i]))) 20 | tokens = torch.LongTensor(tokens) 21 | segment_ids = torch.LongTensor(segment_ids) 22 | attn_masks = torch.LongTensor(attn_masks) 23 | labels = torch.LongTensor(labels) 24 | return {"texts":texts, "tokens" : tokens, "segment_ids" : segment_ids, 25 | "attn_masks" : attn_masks, "labels" : labels} 26 | 27 | 28 | class TextCollate(): 29 | def __init__(self, dataset, tag_padding=0): 30 | self.padding = dataset.tokenizer.vocab["[PAD]"] 31 | 32 | def __call__(self, batch): 33 | return text_collate_fn(batch, self.padding) 34 | 35 | # class PrepareDataset(torch.utils.data.Dataset): 36 | 37 | # def __init__(self, num_class, max_seq_len, data_file=None, vocab_file=None, do_lower_case=True): 38 | # self.max_seq_len = max_seq_len 39 | # self.num_class = num_class 40 | 41 | # self.tokenizer = tokenization.FullTokenizer( 42 | # vocab_file=vocab_file, do_lower_case=do_lower_case) 43 | 44 | # if data_file != None: 45 | # self.raw_data = open(data_file, 'r').readlines() 46 | 47 | 48 | # def __len__(self): 49 | # return len(self.raw_data) 50 | 51 | 52 | # def __getitem__(self, index): 53 | # line = self.raw_data[index] 54 | # row = line.strip("\n").split("\t") 55 | # if len(row) != 2: 56 | # raise RuntimeError("Data is illegal: " + line) 57 | 58 | # # __label__0, __label__1 59 | # if len(row[0]) == 10: 60 | # label = int(row[0][-1]) 61 | # # 0, 1 62 | # else: 63 | # label = int(row[0]) 64 | # if label > self.num_class - 1: 65 | # raise RuntimeError("data label is illegal: " + line) 66 | 67 | # tokens = self.tokenizer.tokenize(row[1]) 68 | # tokens = tokens[:(self.max_seq_len - 1)] 69 | # tokens = ["[CLS]"] + tokens 70 | # tokens = self.tokenizer.convert_tokens_to_ids(tokens) 71 | # return {"text":row[1], "tokens" : tokens, "label" : label} 72 | class PrepareDataset(torch.utils.data.Dataset): 73 | 74 | def __init__(self, num_class, max_seq_len, data_file=None, vocab_file=None, do_lower_case=True): 75 | self.max_seq_len = max_seq_len 76 | self.num_class = num_class 77 | 78 | # self.tokenizer = tokenization.FullTokenizer( 79 | # vocab_file=vocab_file, do_lower_case=do_lower_case) 80 | self.tokenizer = BertTokenizer.from_pretrained(vocab_file) 81 | 82 | if data_file != None: 83 | # self.raw_data = open(data_file, 'r').readlines() 84 | self.raw_data = pd.read_csv(data_file,sep='\t').dropna() 85 | 86 | 87 | def __len__(self): 88 | return len(self.raw_data) 89 | 90 | 91 | def __getitem__(self, index): 92 | # line = self.raw_data[index] 93 | line = self.raw_data.iloc[index] 94 | # row = line.strip("\n").split("\t") 95 | # if len(row) != 2: 96 | # raise RuntimeError("Data is illegal: " + line) 97 | 98 | # # __label__0, __label__1 99 | # if len(row[0]) == 10: 100 | # label = int(row[0][-1]) 101 | # # 0, 1 102 | # else: 103 | # label = int(row[0]) 104 | # if label > self.num_class - 1: 105 | # raise RuntimeError("data label is illegal: " + line) 106 | label = int(line['label']) 107 | # tokens = self.tokenizer.tokenize(row[1]) 108 | # tokens = jieba.lcut(line['content']) 109 | tokens = jieba.lcut(line['text']) 110 | tokens = tokens[:(self.max_seq_len - 1)] 111 | tokens = ["[CLS]"] + tokens 112 | tokens = self.tokenizer.convert_tokens_to_ids(tokens) 113 | return {"text":line['text'], "tokens" : tokens, "label" : label} 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/distill_bert/data_utils/tokenization.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Tokenization classes.""" 3 | 4 | from __future__ import absolute_import 5 | from __future__ import division 6 | from __future__ import print_function 7 | 8 | import collections 9 | import unicodedata 10 | import six 11 | 12 | 13 | def convert_to_unicode(text): 14 | """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" 15 | if six.PY3: 16 | if isinstance(text, str): 17 | return text 18 | elif isinstance(text, bytes): 19 | return text.decode("utf-8", "ignore") 20 | else: 21 | raise ValueError("Unsupported string type: %s" % (type(text))) 22 | elif six.PY2: 23 | if isinstance(text, str): 24 | return text.decode("utf-8", "ignore") 25 | elif isinstance(text, unicode): 26 | return text 27 | else: 28 | raise ValueError("Unsupported string type: %s" % (type(text))) 29 | else: 30 | raise ValueError("Not running on Python2 or Python 3?") 31 | 32 | 33 | def printable_text(text): 34 | """Returns text encoded in a way suitable for print or `tf.logging`.""" 35 | 36 | # These functions want `str` for both Python2 and Python3, but in one case 37 | # it's a Unicode string and in the other it's a byte string. 38 | if six.PY3: 39 | if isinstance(text, str): 40 | return text 41 | elif isinstance(text, bytes): 42 | return text.decode("utf-8", "ignore") 43 | else: 44 | raise ValueError("Unsupported string type: %s" % (type(text))) 45 | elif six.PY2: 46 | if isinstance(text, str): 47 | return text 48 | elif isinstance(text, unicode): 49 | return text.encode("utf-8") 50 | else: 51 | raise ValueError("Unsupported string type: %s" % (type(text))) 52 | else: 53 | raise ValueError("Not running on Python2 or Python 3?") 54 | 55 | 56 | def load_vocab(vocab_file): 57 | """Loads a vocabulary file into a dictionary.""" 58 | vocab = collections.OrderedDict() 59 | index = 0 60 | with open(vocab_file, "r", encoding='utf8') as reader: 61 | while True: 62 | token = convert_to_unicode(reader.readline()) 63 | if not token: 64 | break 65 | token = token.strip() 66 | vocab[token] = index 67 | index += 1 68 | return vocab 69 | 70 | 71 | def convert_tokens_to_ids(vocab, tokens): 72 | """Converts a sequence of tokens into ids using the vocab.""" 73 | ids = [] 74 | for token in tokens: 75 | ids.append(vocab[token]) 76 | return ids 77 | 78 | 79 | def whitespace_tokenize(text): 80 | """Runs basic whitespace cleaning and splitting on a peice of text.""" 81 | text = text.strip() 82 | if not text: 83 | return [] 84 | tokens = text.split() 85 | return tokens 86 | 87 | 88 | class FullTokenizer(object): 89 | """Runs end-to-end tokenziation.""" 90 | 91 | def __init__(self, vocab_file, do_lower_case=True): 92 | self.vocab = load_vocab(vocab_file) 93 | self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) 94 | self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) 95 | 96 | def tokenize(self, text): 97 | split_tokens = [] 98 | for token in self.basic_tokenizer.tokenize(text): 99 | for sub_token in self.wordpiece_tokenizer.tokenize(token): 100 | split_tokens.append(sub_token) 101 | 102 | return split_tokens 103 | 104 | def convert_tokens_to_ids(self, tokens): 105 | return convert_tokens_to_ids(self.vocab, tokens) 106 | 107 | 108 | class BasicTokenizer(object): 109 | """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" 110 | 111 | def __init__(self, do_lower_case=True): 112 | """Constructs a BasicTokenizer. 113 | 114 | Args: 115 | do_lower_case: Whether to lower case the input. 116 | """ 117 | self.do_lower_case = do_lower_case 118 | 119 | def tokenize(self, text): 120 | """Tokenizes a piece of text.""" 121 | text = convert_to_unicode(text) 122 | text = self._clean_text(text) 123 | # This was added on November 1st, 2018 for the multilingual and Chinese 124 | # models. This is also applied to the English models now, but it doesn't 125 | # matter since the English models were not trained on any Chinese data 126 | # and generally don't have any Chinese data in them (there are Chinese 127 | # characters in the vocabulary because Wikipedia does have some Chinese 128 | # words in the English Wikipedia.). 129 | text = self._tokenize_chinese_chars(text) 130 | orig_tokens = whitespace_tokenize(text) 131 | split_tokens = [] 132 | for token in orig_tokens: 133 | if self.do_lower_case: 134 | token = token.lower() 135 | token = self._run_strip_accents(token) 136 | split_tokens.extend(self._run_split_on_punc(token)) 137 | 138 | output_tokens = whitespace_tokenize(" ".join(split_tokens)) 139 | return output_tokens 140 | 141 | def _run_strip_accents(self, text): 142 | """Strips accents from a piece of text.""" 143 | text = unicodedata.normalize("NFD", text) 144 | output = [] 145 | for char in text: 146 | cat = unicodedata.category(char) 147 | if cat == "Mn": 148 | continue 149 | output.append(char) 150 | return "".join(output) 151 | 152 | def _run_split_on_punc(self, text): 153 | """Splits punctuation on a piece of text.""" 154 | chars = list(text) 155 | i = 0 156 | start_new_word = True 157 | output = [] 158 | while i < len(chars): 159 | char = chars[i] 160 | if _is_punctuation(char): 161 | output.append([char]) 162 | start_new_word = True 163 | else: 164 | if start_new_word: 165 | output.append([]) 166 | start_new_word = False 167 | output[-1].append(char) 168 | i += 1 169 | 170 | return ["".join(x) for x in output] 171 | 172 | def _tokenize_chinese_chars(self, text): 173 | """Adds whitespace around any CJK character.""" 174 | output = [] 175 | for char in text: 176 | cp = ord(char) 177 | if self._is_chinese_char(cp): 178 | output.append(" ") 179 | output.append(char) 180 | output.append(" ") 181 | else: 182 | output.append(char) 183 | return "".join(output) 184 | 185 | def _is_chinese_char(self, cp): 186 | """Checks whether CP is the codepoint of a CJK character.""" 187 | # This defines a "chinese character" as anything in the CJK Unicode block: 188 | # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) 189 | # 190 | # Note that the CJK Unicode block is NOT all Japanese and Korean characters, 191 | # despite its name. The modern Korean Hangul alphabet is a different block, 192 | # as is Japanese Hiragana and Katakana. Those alphabets are used to write 193 | # space-separated words, so they are not treated specially and handled 194 | # like the all of the other languages. 195 | if ((cp >= 0x4E00 and cp <= 0x9FFF) or # 196 | (cp >= 0x3400 and cp <= 0x4DBF) or # 197 | (cp >= 0x20000 and cp <= 0x2A6DF) or # 198 | (cp >= 0x2A700 and cp <= 0x2B73F) or # 199 | (cp >= 0x2B740 and cp <= 0x2B81F) or # 200 | (cp >= 0x2B820 and cp <= 0x2CEAF) or 201 | (cp >= 0xF900 and cp <= 0xFAFF) or # 202 | (cp >= 0x2F800 and cp <= 0x2FA1F)): # 203 | return True 204 | 205 | return False 206 | 207 | def _clean_text(self, text): 208 | """Performs invalid character removal and whitespace cleanup on text.""" 209 | output = [] 210 | for char in text: 211 | cp = ord(char) 212 | if cp == 0 or cp == 0xfffd or _is_control(char): 213 | continue 214 | if _is_whitespace(char): 215 | output.append(" ") 216 | else: 217 | output.append(char) 218 | return "".join(output) 219 | 220 | 221 | class WordpieceTokenizer(object): 222 | """Runs WordPiece tokenization.""" 223 | 224 | def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=100): 225 | self.vocab = vocab 226 | self.unk_token = unk_token 227 | self.max_input_chars_per_word = max_input_chars_per_word 228 | 229 | def tokenize(self, text): 230 | """Tokenizes a piece of text into its word pieces. 231 | 232 | This uses a greedy longest-match-first algorithm to perform tokenization 233 | using the given vocabulary. 234 | 235 | For example: 236 | input = "unaffable" 237 | output = ["un", "##aff", "##able"] 238 | 239 | Args: 240 | text: A single token or whitespace separated tokens. This should have 241 | already been passed through `BasicTokenizer. 242 | 243 | Returns: 244 | A list of wordpiece tokens. 245 | """ 246 | 247 | text = convert_to_unicode(text) 248 | 249 | output_tokens = [] 250 | for token in whitespace_tokenize(text): 251 | chars = list(token) 252 | if len(chars) > self.max_input_chars_per_word: 253 | output_tokens.append(self.unk_token) 254 | continue 255 | 256 | is_bad = False 257 | start = 0 258 | sub_tokens = [] 259 | while start < len(chars): 260 | end = len(chars) 261 | cur_substr = None 262 | while start < end: 263 | substr = "".join(chars[start:end]) 264 | if start > 0: 265 | substr = "##" + substr 266 | if substr in self.vocab: 267 | cur_substr = substr 268 | break 269 | end -= 1 270 | if cur_substr is None: 271 | is_bad = True 272 | break 273 | sub_tokens.append(cur_substr) 274 | start = end 275 | 276 | if is_bad: 277 | output_tokens.append(self.unk_token) 278 | else: 279 | output_tokens.extend(sub_tokens) 280 | return output_tokens 281 | 282 | 283 | def _is_whitespace(char): 284 | """Checks whether `chars` is a whitespace character.""" 285 | # \t, \n, and \r are technically contorl characters but we treat them 286 | # as whitespace since they are generally considered as such. 287 | if char == " " or char == "\t" or char == "\n" or char == "\r": 288 | return True 289 | cat = unicodedata.category(char) 290 | if cat == "Zs": 291 | return True 292 | return False 293 | 294 | 295 | def _is_control(char): 296 | """Checks whether `chars` is a control character.""" 297 | # These are technically control characters but we count them as whitespace 298 | # characters. 299 | if char == "\t" or char == "\n" or char == "\r": 300 | return False 301 | cat = unicodedata.category(char) 302 | if cat.startswith("C"): 303 | return True 304 | return False 305 | 306 | 307 | def _is_punctuation(char): 308 | """Checks whether `chars` is a punctuation character.""" 309 | cp = ord(char) 310 | # We treat all non-letter/number ASCII as punctuation. 311 | # Characters such as "^", "$", and "`" are not in the Unicode 312 | # Punctuation class but we treat them as punctuation anyways, for 313 | # consistency. 314 | if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or 315 | (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)): 316 | return True 317 | cat = unicodedata.category(char) 318 | if cat.startswith("P"): 319 | return True 320 | return False 321 | -------------------------------------------------------------------------------- /src/distill_bert/infer.py: -------------------------------------------------------------------------------- 1 | #Auth:lshzhang@tencent.com 2 | #Implementation of FastBERT, paper refer:https://arxiv.org/pdf/2004.02178.pdf 3 | 4 | import argparse 5 | import json 6 | import time 7 | import logging 8 | 9 | import torch 10 | import torch.utils.data as data 11 | import numpy as np 12 | from tqdm import tqdm 13 | from tensorboardX import SummaryWriter 14 | 15 | from model_define.model_fastbert import FastBertModel, BertConfig 16 | from data_utils.dataset_preparing import PrepareDataset, TextCollate 17 | import torch.nn.functional as F 18 | from utils import load_json_config, init_bert_adam_optimizer, load_saved_model, save_model, eval_pr 19 | 20 | #随机数固定,RE-PRODUCIBLE 21 | seed = 9999 22 | torch.manual_seed(seed) 23 | torch.cuda.manual_seed(seed) 24 | torch.cuda.manual_seed_all(seed) 25 | np.random.seed(seed) 26 | 27 | 28 | logging.basicConfig(level=logging.INFO, 29 | format="%(asctime)s - %(levelname)s - %(message)s") 30 | debug_break = False 31 | 32 | def infer_model(master_gpu_id, model, dataset, 33 | use_cuda=False, num_workers=1, inference_speed=None, dump_info_file=None): 34 | global global_step 35 | global debug_break 36 | model.eval() 37 | infer_dataloader = data.DataLoader(dataset=dataset, 38 | collate_fn=TextCollate(dataset), 39 | pin_memory=use_cuda, 40 | batch_size=1, 41 | num_workers=num_workers, 42 | shuffle=False) 43 | correct_sum = 0 44 | num_sample = infer_dataloader.dataset.__len__() 45 | predicted_probs = [] 46 | true_labels = [] 47 | infos = [] 48 | logging.info("Inference Model...") 49 | cnt = 0 50 | stime_all = time.time() 51 | for step, batch in enumerate(tqdm(infer_dataloader, unit="batch", ncols=100, desc="Inference process: ")): 52 | texts = batch["texts"] 53 | tokens = batch["tokens"].cuda(master_gpu_id) if use_cuda else batch["tokens"] 54 | segment_ids = batch["segment_ids"].cuda(master_gpu_id) if use_cuda else batch["segment_ids"] 55 | attn_masks = batch["attn_masks"].cuda(master_gpu_id) if use_cuda else batch["attn_masks"] 56 | labels = batch["labels"].cuda(master_gpu_id) if use_cuda else batch["labels"] 57 | with torch.no_grad(): 58 | probs, layer_idxes, uncertain_infos = model(tokens, token_type_ids=segment_ids, attention_mask=attn_masks, 59 | inference=True, inference_speed=inference_speed) 60 | _, top_index = probs.topk(1) 61 | 62 | correct_sum += (top_index.view(-1) == labels).sum().item() 63 | cnt += 1 64 | if cnt == 1: 65 | stime = time.time() 66 | if dump_info_file != None: 67 | for label, pred, prob, layer_i, text in zip(labels, top_index.view(-1), probs, [layer_idxes], texts): 68 | infos.append((label.item(), pred.item(), prob.cpu().numpy(), layer_i, text)) 69 | if debug_break and step > 50: 70 | break 71 | 72 | time_per = (time.time() - stime)/(cnt - 1) 73 | time_all = time.time() - stime_all 74 | acc = format(correct_sum / num_sample, "0.4f") 75 | logging.info("speed_arg:%s, time_per_record:%s, acc:%s, total_time:%s", 76 | inference_speed, format(time_per, '0.4f'), acc, format(time_all, '0.4f')) 77 | if dump_info_file != None and len(dump_info_file) != 0: 78 | with open(dump_info_file, 'w') as fw: 79 | for label, pred, prob, layer_i, text in infos: 80 | fw.write('\t'.join([str(label), str(pred), str(layer_i), text])+'\n') 81 | 82 | if probs.shape[1] == 2: 83 | labels_pr = [info[0] for info in infos] 84 | preds_pr = [info[1] for info in infos] 85 | precise, recall = eval_pr(labels_pr, preds_pr) 86 | logging.info("precise:%s, recall:%s", format(precise, '0.4f'), format(recall, '0.4f')) 87 | 88 | 89 | def main(args): 90 | config = load_json_config(args.model_config_file) 91 | logging.info(json.dumps(config, indent=2, sort_keys=True)) 92 | logging.info("Load HyperParameters Done") 93 | 94 | #---------------------MODEL GRAPH INIT--------------------------# 95 | bert_config = BertConfig.from_json_file(config.get("bert_config_path")) 96 | model = FastBertModel(bert_config, config) 97 | load_saved_model(model, args.save_model_path) 98 | 99 | logging.info(model) 100 | logging.info("Initialize Model Done".center(60, "=")) 101 | 102 | #-----------GPU SETTING, INFER Only Support Max 1 GPU-----------# 103 | use_cuda = args.gpu_ids != '-1' 104 | device = torch.device('cuda' if use_cuda else 'cpu') 105 | model.to(device) 106 | master_gpu_id = 0 107 | # if len(args.gpu_ids) == 1 and use_cuda: 108 | # master_gpu_id = int(args.gpu_ids) 109 | # model = model.cuda(int(args.gpu_ids)) if use_cuda else model 110 | # elif not use_cuda: 111 | # master_gpu_id = None 112 | # else: 113 | # raise RuntimeError("GPU Mode not support, INFER Only Support Max 1 GPU: " + args.gpu_ids) 114 | 115 | #-----------------------Dataset Init---------------------------# 116 | infer_dataset = PrepareDataset(vocab_file=config.get("vocab_file"), 117 | max_seq_len=config.get("max_seq_len"), 118 | num_class=config.get("num_class"), 119 | data_file=args.infer_data) 120 | logging.info("Load INFER Dataset Done, Total eval line: %s", infer_dataset.__len__()) 121 | 122 | #-----------------------Running Mode Start, Batch Size Only Support 1--------------------------------# 123 | infer_model(master_gpu_id, model, infer_dataset, 124 | use_cuda=use_cuda, num_workers=args.data_load_num_workers, 125 | inference_speed=args.inference_speed, dump_info_file=args.dump_info_file) 126 | 127 | 128 | if __name__ == "__main__": 129 | parser = argparse.ArgumentParser( 130 | description="Textclassification training script arguments.") 131 | parser.add_argument("--model_config_file", dest="model_config_file", action="store", 132 | help="The path of configuration json file.") 133 | 134 | parser.add_argument("--save_model_path", dest="save_model_path", action="store", 135 | help="The path of trained checkpoint model.") 136 | 137 | parser.add_argument("--infer_data", dest="infer_data", action="store", help="") 138 | parser.add_argument("--dump_info_file", dest="dump_info_file", action="store", help="") 139 | 140 | parser.add_argument("--inference_speed", dest="inference_speed", action="store", 141 | type=float, default=1.0, help="") 142 | 143 | # -1 for NO GPU 144 | parser.add_argument("--gpu_ids", dest="gpu_ids", action="store", default="0", 145 | help="Device ids of used gpus, split by ',' , IF -1 then no gpu") 146 | 147 | parser.add_argument("--data_load_num_workers", dest="data_load_num_workers", action="store",type=int, default=1, help="") 148 | parser.add_argument("--debug_break", dest="debug_break", action="store", type=int, default=0, 149 | help="Running debug_break, 0 or 1.") 150 | 151 | parsed_args = parser.parse_args() 152 | debug_break = (parsed_args.debug_break == 1) 153 | main(parsed_args) 154 | -------------------------------------------------------------------------------- /src/distill_bert/model_define/model_bert.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """PyTorch BERT model.""" 3 | 4 | from __future__ import absolute_import 5 | from __future__ import division 6 | from __future__ import print_function 7 | 8 | import copy 9 | import json 10 | import math 11 | import six 12 | import torch 13 | import torch.nn as nn 14 | from torch.nn import CrossEntropyLoss 15 | 16 | def gelu(x): 17 | """Implementation of the gelu activation function. 18 | For information: OpenAI GPT's gelu is slightly different (and gives slightly different results): 19 | 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3)))) 20 | """ 21 | return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0))) 22 | 23 | 24 | ACT2FN = {"gelu": gelu, "relu": torch.nn.functional.relu} 25 | 26 | 27 | class BertConfig(object): 28 | """Configuration class to store the configuration of a `BertModel`. 29 | """ 30 | def __init__(self, 31 | vocab_size, 32 | hidden_size=768, 33 | num_hidden_layers=12, 34 | num_attention_heads=12, 35 | intermediate_size=3072, 36 | hidden_act="gelu", 37 | hidden_dropout_prob=0.1, 38 | attention_probs_dropout_prob=0.1, 39 | max_position_embeddings=512, 40 | type_vocab_size=16, 41 | initializer_range=0.02): 42 | """Constructs BertConfig. 43 | 44 | Args: 45 | vocab_size: Vocabulary size of `inputs_ids` in `BertModel`. 46 | hidden_size: Size of the encoder layers and the pooler layer. 47 | num_hidden_layers: Number of hidden layers in the Transformer encoder. 48 | num_attention_heads: Number of attention heads for each attention layer in 49 | the Transformer encoder. 50 | intermediate_size: The size of the "intermediate" (i.e., feed-forward) 51 | layer in the Transformer encoder. 52 | hidden_act: The non-linear activation function (function or string) in the 53 | encoder and pooler. 54 | hidden_dropout_prob: The dropout probabilitiy for all fully connected 55 | layers in the embeddings, encoder, and pooler. 56 | attention_probs_dropout_prob: The dropout ratio for the attention 57 | probabilities. 58 | max_position_embeddings: The maximum sequence length that this model might 59 | ever be used with. Typically set this to something large just in case 60 | (e.g., 512 or 1024 or 2048). 61 | type_vocab_size: The vocabulary size of the `token_type_ids` passed into 62 | `BertModel`. 63 | initializer_range: The sttdev of the truncated_normal_initializer for 64 | initializing all weight matrices. 65 | """ 66 | self.vocab_size = vocab_size 67 | self.hidden_size = hidden_size 68 | self.num_hidden_layers = num_hidden_layers 69 | self.num_attention_heads = num_attention_heads 70 | self.hidden_act = hidden_act 71 | self.intermediate_size = intermediate_size 72 | self.hidden_dropout_prob = hidden_dropout_prob 73 | self.attention_probs_dropout_prob = attention_probs_dropout_prob 74 | self.max_position_embeddings = max_position_embeddings 75 | self.type_vocab_size = type_vocab_size 76 | self.initializer_range = initializer_range 77 | 78 | @classmethod 79 | def from_dict(cls, json_object): 80 | """Constructs a `BertConfig` from a Python dictionary of parameters.""" 81 | config = BertConfig(vocab_size=None) 82 | for (key, value) in six.iteritems(json_object): 83 | config.__dict__[key] = value 84 | return config 85 | 86 | @classmethod 87 | def from_json_file(cls, json_file): 88 | """Constructs a `BertConfig` from a json file of parameters.""" 89 | with open(json_file, "r") as reader: 90 | text = reader.read() 91 | return cls.from_dict(json.loads(text)) 92 | 93 | def to_dict(self): 94 | """Serializes this instance to a Python dictionary.""" 95 | output = copy.deepcopy(self.__dict__) 96 | return output 97 | 98 | def to_json_string(self): 99 | """Serializes this instance to a JSON string.""" 100 | return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" 101 | 102 | 103 | class BERTLayerNorm(nn.Module): 104 | def __init__(self, config, variance_epsilon=1e-12): 105 | """Construct a layernorm module in the TF style (epsilon inside the square root). 106 | """ 107 | super(BERTLayerNorm, self).__init__() 108 | self.gamma = nn.Parameter(torch.ones(config.hidden_size)) 109 | self.beta = nn.Parameter(torch.zeros(config.hidden_size)) 110 | self.variance_epsilon = variance_epsilon 111 | 112 | def forward(self, x): 113 | u = x.mean(-1, keepdim=True) 114 | s = (x - u).pow(2).mean(-1, keepdim=True) 115 | x = (x - u) / torch.sqrt(s + self.variance_epsilon) 116 | return self.gamma * x + self.beta 117 | 118 | class BERTEmbeddings(nn.Module): 119 | def __init__(self, config): 120 | super(BERTEmbeddings, self).__init__() 121 | """Construct the embedding module from word, position and token_type embeddings. 122 | """ 123 | self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size) 124 | self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size) 125 | self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size) 126 | 127 | # self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load 128 | # any TensorFlow checkpoint file 129 | self.LayerNorm = BERTLayerNorm(config) 130 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 131 | 132 | def forward(self, input_ids, token_type_ids=None): 133 | seq_length = input_ids.size(1) 134 | position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device) 135 | position_ids = position_ids.unsqueeze(0).expand_as(input_ids) 136 | if token_type_ids is None: 137 | token_type_ids = torch.zeros_like(input_ids) 138 | 139 | words_embeddings = self.word_embeddings(input_ids) 140 | position_embeddings = self.position_embeddings(position_ids) 141 | token_type_embeddings = self.token_type_embeddings(token_type_ids) 142 | 143 | embeddings = words_embeddings + position_embeddings + token_type_embeddings 144 | embeddings = self.LayerNorm(embeddings) 145 | embeddings = self.dropout(embeddings) 146 | return embeddings 147 | 148 | 149 | class BERTSelfAttention(nn.Module): 150 | def __init__(self, config): 151 | super(BERTSelfAttention, self).__init__() 152 | if config.hidden_size % config.num_attention_heads != 0: 153 | raise ValueError( 154 | "The hidden size (%d) is not a multiple of the number of attention " 155 | "heads (%d)" % (config.hidden_size, config.num_attention_heads)) 156 | self.num_attention_heads = config.num_attention_heads 157 | self.attention_head_size = int(config.hidden_size / config.num_attention_heads) 158 | self.all_head_size = self.num_attention_heads * self.attention_head_size 159 | 160 | self.query = nn.Linear(config.hidden_size, self.all_head_size) 161 | self.key = nn.Linear(config.hidden_size, self.all_head_size) 162 | self.value = nn.Linear(config.hidden_size, self.all_head_size) 163 | 164 | self.dropout = nn.Dropout(config.attention_probs_dropout_prob) 165 | 166 | def transpose_for_scores(self, x): 167 | new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) 168 | x = x.view(*new_x_shape) 169 | return x.permute(0, 2, 1, 3) 170 | 171 | def forward(self, hidden_states, attention_mask): 172 | mixed_query_layer = self.query(hidden_states) 173 | mixed_key_layer = self.key(hidden_states) 174 | mixed_value_layer = self.value(hidden_states) 175 | 176 | query_layer = self.transpose_for_scores(mixed_query_layer) 177 | key_layer = self.transpose_for_scores(mixed_key_layer) 178 | value_layer = self.transpose_for_scores(mixed_value_layer) 179 | 180 | # Take the dot product between "query" and "key" to get the raw attention scores. 181 | attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) 182 | attention_scores = attention_scores / math.sqrt(self.attention_head_size) 183 | # Apply the attention mask is (precomputed for all layers in BertModel forward() function) 184 | attention_scores = attention_scores + attention_mask 185 | 186 | # Normalize the attention scores to probabilities. 187 | attention_probs = nn.Softmax(dim=-1)(attention_scores) 188 | 189 | # This is actually dropping out entire tokens to attend to, which might 190 | # seem a bit unusual, but is taken from the original Transformer paper. 191 | attention_probs = self.dropout(attention_probs) 192 | 193 | context_layer = torch.matmul(attention_probs, value_layer) 194 | context_layer = context_layer.permute(0, 2, 1, 3).contiguous() 195 | new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) 196 | context_layer = context_layer.view(*new_context_layer_shape) 197 | return context_layer 198 | 199 | 200 | class BERTSelfOutput(nn.Module): 201 | def __init__(self, config): 202 | super(BERTSelfOutput, self).__init__() 203 | self.dense = nn.Linear(config.hidden_size, config.hidden_size) 204 | self.LayerNorm = BERTLayerNorm(config) 205 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 206 | 207 | def forward(self, hidden_states, input_tensor): 208 | hidden_states = self.dense(hidden_states) 209 | hidden_states = self.dropout(hidden_states) 210 | hidden_states = self.LayerNorm(hidden_states + input_tensor) 211 | return hidden_states 212 | 213 | 214 | class BERTAttention(nn.Module): 215 | def __init__(self, config): 216 | super(BERTAttention, self).__init__() 217 | self.self = BERTSelfAttention(config) 218 | self.output = BERTSelfOutput(config) 219 | 220 | def forward(self, input_tensor, attention_mask): 221 | self_output = self.self(input_tensor, attention_mask) 222 | attention_output = self.output(self_output, input_tensor) 223 | return attention_output 224 | 225 | 226 | class BERTIntermediate(nn.Module): 227 | def __init__(self, config): 228 | super(BERTIntermediate, self).__init__() 229 | self.dense = nn.Linear(config.hidden_size, config.intermediate_size) 230 | self.intermediate_act_fn = ACT2FN[config.hidden_act] \ 231 | if isinstance(config.hidden_act, str) else config.hidden_act 232 | 233 | def forward(self, hidden_states): 234 | hidden_states = self.dense(hidden_states) 235 | hidden_states = self.intermediate_act_fn(hidden_states) 236 | return hidden_states 237 | 238 | 239 | class BERTOutput(nn.Module): 240 | def __init__(self, config): 241 | super(BERTOutput, self).__init__() 242 | self.dense = nn.Linear(config.intermediate_size, config.hidden_size) 243 | self.LayerNorm = BERTLayerNorm(config) 244 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 245 | 246 | def forward(self, hidden_states, input_tensor): 247 | hidden_states = self.dense(hidden_states) 248 | hidden_states = self.dropout(hidden_states) 249 | hidden_states = self.LayerNorm(hidden_states + input_tensor) 250 | return hidden_states 251 | 252 | 253 | class BERTLayer(nn.Module): 254 | def __init__(self, config): 255 | super(BERTLayer, self).__init__() 256 | self.attention = BERTAttention(config) 257 | self.intermediate = BERTIntermediate(config) 258 | self.output = BERTOutput(config) 259 | 260 | def forward(self, hidden_states, attention_mask): 261 | attention_output = self.attention(hidden_states, attention_mask) 262 | intermediate_output = self.intermediate(attention_output) 263 | layer_output = self.output(intermediate_output, attention_output) 264 | return layer_output 265 | 266 | 267 | class BERTEncoder(nn.Module): 268 | def __init__(self, config): 269 | super(BERTEncoder, self).__init__() 270 | layer = BERTLayer(config) 271 | self.layer = nn.ModuleList([copy.deepcopy(layer) for _ in range(config.num_hidden_layers)]) 272 | 273 | def forward(self, hidden_states, attention_mask): 274 | all_encoder_layers = [] 275 | for layer_module in self.layer: 276 | hidden_states = layer_module(hidden_states, attention_mask) 277 | all_encoder_layers.append(hidden_states) 278 | return all_encoder_layers 279 | 280 | 281 | class BERTPooler(nn.Module): 282 | def __init__(self, config): 283 | super(BERTPooler, self).__init__() 284 | self.dense = nn.Linear(config.hidden_size, config.hidden_size) 285 | self.activation = nn.Tanh() 286 | 287 | def forward(self, hidden_states): 288 | # We "pool" the model by simply taking the hidden state corresponding 289 | # to the first token. 290 | first_token_tensor = hidden_states[:, 0] 291 | #print('--', hidden_states.shape) 292 | #print(first_token_tensor.shape) 293 | pooled_output = self.dense(first_token_tensor) 294 | pooled_output = self.activation(pooled_output) 295 | return pooled_output 296 | 297 | 298 | class BertModel(nn.Module): 299 | """BERT model ("Bidirectional Embedding Representations from a Transformer"). 300 | 301 | Example usage: 302 | ```python 303 | # Already been converted into WordPiece token ids 304 | input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]]) 305 | input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]]) 306 | token_type_ids = torch.LongTensor([[0, 0, 1], [0, 2, 0]]) 307 | 308 | config = modeling.BertConfig(vocab_size=32000, hidden_size=512, 309 | num_hidden_layers=8, num_attention_heads=6, intermediate_size=1024) 310 | 311 | model = modeling.BertModel(config=config) 312 | all_encoder_layers, pooled_output = model(input_ids, token_type_ids, input_mask) 313 | ``` 314 | """ 315 | def __init__(self, config: BertConfig): 316 | """Constructor for BertModel. 317 | 318 | Args: 319 | config: `BertConfig` instance. 320 | """ 321 | super(BertModel, self).__init__() 322 | self.embeddings = BERTEmbeddings(config) 323 | self.encoder = BERTEncoder(config) 324 | self.pooler = BERTPooler(config) 325 | 326 | def forward(self, input_ids, token_type_ids=None, attention_mask=None): 327 | if attention_mask is None: 328 | attention_mask = torch.ones_like(input_ids) 329 | if token_type_ids is None: 330 | token_type_ids = torch.zeros_like(input_ids) 331 | 332 | # We create a 3D attention mask from a 2D tensor mask. 333 | # Sizes are [batch_size, 1, 1, to_seq_length] 334 | # So we can broadcast to [batch_size, num_heads, from_seq_length, to_seq_length] 335 | # this attention mask is more simple than the triangular masking of causal attention 336 | # used in OpenAI GPT, we just need to prepare the broadcast dimension here. 337 | extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) 338 | 339 | # Since attention_mask is 1.0 for positions we want to attend and 0.0 for 340 | # masked positions, this operation will create a tensor which is 0.0 for 341 | # positions we want to attend and -10000.0 for masked positions. 342 | # Since we are adding it to the raw scores before the softmax, this is 343 | # effectively the same as removing these entirely. 344 | extended_attention_mask = extended_attention_mask.float() 345 | extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 346 | 347 | embedding_output = self.embeddings(input_ids, token_type_ids) 348 | all_encoder_layers = self.encoder(embedding_output, extended_attention_mask) 349 | #print(len(all_encoder_layers)) 350 | sequence_output = all_encoder_layers[-1] 351 | pooled_output = self.pooler(sequence_output) 352 | return all_encoder_layers, pooled_output 353 | -------------------------------------------------------------------------------- /src/distill_bert/model_define/model_fastbert.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """PyTorch FastBERT model modify based on HugginFace Work.""" 3 | 4 | from __future__ import absolute_import 5 | from __future__ import division 6 | from __future__ import print_function 7 | 8 | import copy 9 | import re 10 | import json 11 | import math 12 | import six 13 | import torch 14 | import torch.nn as nn 15 | from torch.nn import CrossEntropyLoss 16 | import torch.nn.functional as F 17 | 18 | def gelu(x): 19 | """Implementation of the gelu activation function. 20 | For information: OpenAI GPT's gelu is slightly different (and gives slightly different results): 21 | 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3)))) 22 | """ 23 | return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0))) 24 | 25 | 26 | ACT2FN = {"gelu": gelu, "relu": torch.nn.functional.relu} 27 | 28 | 29 | class BertConfig(object): 30 | """Configuration class to store the configuration of a `BertModel`. 31 | """ 32 | def __init__(self, 33 | vocab_size, 34 | hidden_size=768, 35 | num_hidden_layers=12, 36 | num_attention_heads=12, 37 | intermediate_size=3072, 38 | hidden_act="gelu", 39 | hidden_dropout_prob=0.1, 40 | attention_probs_dropout_prob=0.1, 41 | max_position_embeddings=512, 42 | type_vocab_size=16, 43 | initializer_range=0.02): 44 | """Constructs BertConfig. 45 | 46 | Args: 47 | vocab_size: Vocabulary size of `inputs_ids` in `BertModel`. 48 | hidden_size: Size of the encoder layers and the pooler layer. 49 | num_hidden_layers: Number of hidden layers in the Transformer encoder. 50 | num_attention_heads: Number of attention heads for each attention layer in 51 | the Transformer encoder. 52 | intermediate_size: The size of the "intermediate" (i.e., feed-forward) 53 | layer in the Transformer encoder. 54 | hidden_act: The non-linear activation function (function or string) in the 55 | encoder and pooler. 56 | hidden_dropout_prob: The dropout probabilitiy for all fully connected 57 | layers in the embeddings, encoder, and pooler. 58 | attention_probs_dropout_prob: The dropout ratio for the attention 59 | probabilities. 60 | max_position_embeddings: The maximum sequence length that this model might 61 | ever be used with. Typically set this to something large just in case 62 | (e.g., 512 or 1024 or 2048). 63 | type_vocab_size: The vocabulary size of the `token_type_ids` passed into 64 | `BertModel`. 65 | initializer_range: The sttdev of the truncated_normal_initializer for 66 | initializing all weight matrices. 67 | """ 68 | self.vocab_size = vocab_size 69 | self.hidden_size = hidden_size 70 | self.num_hidden_layers = num_hidden_layers 71 | self.num_attention_heads = num_attention_heads 72 | self.hidden_act = hidden_act 73 | self.intermediate_size = intermediate_size 74 | self.hidden_dropout_prob = hidden_dropout_prob 75 | self.attention_probs_dropout_prob = attention_probs_dropout_prob 76 | self.max_position_embeddings = max_position_embeddings 77 | self.type_vocab_size = type_vocab_size 78 | self.initializer_range = initializer_range 79 | 80 | @classmethod 81 | def from_dict(cls, json_object): 82 | """Constructs a `BertConfig` from a Python dictionary of parameters.""" 83 | config = BertConfig(vocab_size=None) 84 | for (key, value) in six.iteritems(json_object): 85 | config.__dict__[key] = value 86 | return config 87 | 88 | @classmethod 89 | def from_json_file(cls, json_file): 90 | """Constructs a `BertConfig` from a json file of parameters.""" 91 | with open(json_file, "r") as reader: 92 | text = reader.read() 93 | return cls.from_dict(json.loads(text)) 94 | 95 | def to_dict(self): 96 | """Serializes this instance to a Python dictionary.""" 97 | output = copy.deepcopy(self.__dict__) 98 | return output 99 | 100 | def to_json_string(self): 101 | """Serializes this instance to a JSON string.""" 102 | return json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n" 103 | 104 | 105 | class BERTLayerNorm(nn.Module): 106 | def __init__(self, config, variance_epsilon=1e-12): 107 | """Construct a layernorm module in the TF style (epsilon inside the square root). 108 | """ 109 | super(BERTLayerNorm, self).__init__() 110 | self.gamma = nn.Parameter(torch.ones(config.hidden_size)) 111 | self.beta = nn.Parameter(torch.zeros(config.hidden_size)) 112 | self.variance_epsilon = variance_epsilon 113 | 114 | def forward(self, x): 115 | u = x.mean(-1, keepdim=True) 116 | s = (x - u).pow(2).mean(-1, keepdim=True) 117 | x = (x - u) / torch.sqrt(s + self.variance_epsilon) 118 | return self.gamma * x + self.beta 119 | 120 | class BERTEmbeddings(nn.Module): 121 | def __init__(self, config): 122 | super(BERTEmbeddings, self).__init__() 123 | """Construct the embedding module from word, position and token_type embeddings. 124 | """ 125 | self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size) 126 | self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size) 127 | self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size) 128 | 129 | # self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load 130 | # any TensorFlow checkpoint file 131 | self.LayerNorm = BERTLayerNorm(config) 132 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 133 | 134 | def forward(self, input_ids, token_type_ids=None): 135 | seq_length = input_ids.size(1) 136 | position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device) 137 | position_ids = position_ids.unsqueeze(0).expand_as(input_ids) 138 | if token_type_ids is None: 139 | token_type_ids = torch.zeros_like(input_ids) 140 | 141 | words_embeddings = self.word_embeddings(input_ids) 142 | position_embeddings = self.position_embeddings(position_ids) 143 | token_type_embeddings = self.token_type_embeddings(token_type_ids) 144 | 145 | embeddings = words_embeddings + position_embeddings + token_type_embeddings 146 | embeddings = self.LayerNorm(embeddings) 147 | embeddings = self.dropout(embeddings) 148 | return embeddings 149 | 150 | 151 | class BERTSelfAttention(nn.Module): 152 | def __init__(self, config, hidden_size=None, num_attention_heads=None): 153 | super(BERTSelfAttention, self).__init__() 154 | if hidden_size == None: 155 | hidden_size = config.hidden_size 156 | if num_attention_heads == None: 157 | num_attention_heads = config.num_attention_heads 158 | 159 | if hidden_size % num_attention_heads != 0: 160 | raise ValueError( 161 | "The hidden size (%d) is not a multiple of the number of attention " 162 | "heads (%d)" % (hidden_size, num_attention_heads)) 163 | 164 | self.num_attention_heads = num_attention_heads 165 | self.attention_head_size = int(hidden_size / self.num_attention_heads) 166 | self.all_head_size = self.num_attention_heads * self.attention_head_size 167 | 168 | self.query = nn.Linear(hidden_size, self.all_head_size) 169 | self.key = nn.Linear(hidden_size, self.all_head_size) 170 | self.value = nn.Linear(hidden_size, self.all_head_size) 171 | 172 | self.dropout = nn.Dropout(config.attention_probs_dropout_prob) 173 | 174 | def transpose_for_scores(self, x): 175 | new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) 176 | x = x.view(*new_x_shape) 177 | return x.permute(0, 2, 1, 3) 178 | 179 | def forward(self, hidden_states, attention_mask, use_attention_mask=True): 180 | mixed_query_layer = self.query(hidden_states) 181 | mixed_key_layer = self.key(hidden_states) 182 | mixed_value_layer = self.value(hidden_states) 183 | 184 | query_layer = self.transpose_for_scores(mixed_query_layer) 185 | key_layer = self.transpose_for_scores(mixed_key_layer) 186 | value_layer = self.transpose_for_scores(mixed_value_layer) 187 | 188 | # Take the dot product between "query" and "key" to get the raw attention scores. 189 | attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) 190 | attention_scores = attention_scores / math.sqrt(self.attention_head_size) 191 | # Apply the attention mask is (precomputed for all layers in BertModel forward() function) 192 | if use_attention_mask: 193 | attention_scores = attention_scores + attention_mask 194 | 195 | # Normalize the attention scores to probabilities. 196 | attention_probs = nn.Softmax(dim=-1)(attention_scores) 197 | 198 | # This is actually dropping out entire tokens to attend to, which might 199 | # seem a bit unusual, but is taken from the original Transformer paper. 200 | attention_probs = self.dropout(attention_probs) 201 | 202 | context_layer = torch.matmul(attention_probs, value_layer) 203 | context_layer = context_layer.permute(0, 2, 1, 3).contiguous() 204 | new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) 205 | context_layer = context_layer.view(*new_context_layer_shape) 206 | return context_layer 207 | 208 | 209 | class BERTSelfOutput(nn.Module): 210 | def __init__(self, config): 211 | super(BERTSelfOutput, self).__init__() 212 | self.dense = nn.Linear(config.hidden_size, config.hidden_size) 213 | self.LayerNorm = BERTLayerNorm(config) 214 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 215 | 216 | def forward(self, hidden_states, input_tensor): 217 | hidden_states = self.dense(hidden_states) 218 | hidden_states = self.dropout(hidden_states) 219 | hidden_states = self.LayerNorm(hidden_states + input_tensor) 220 | return hidden_states 221 | 222 | 223 | class BERTAttention(nn.Module): 224 | def __init__(self, config): 225 | super(BERTAttention, self).__init__() 226 | self.self = BERTSelfAttention(config) 227 | self.output = BERTSelfOutput(config) 228 | 229 | def forward(self, input_tensor, attention_mask): 230 | self_output = self.self(input_tensor, attention_mask) 231 | #print(self_output.shape) 232 | attention_output = self.output(self_output, input_tensor) 233 | return attention_output 234 | 235 | 236 | class BERTIntermediate(nn.Module): 237 | def __init__(self, config): 238 | super(BERTIntermediate, self).__init__() 239 | self.dense = nn.Linear(config.hidden_size, config.intermediate_size) 240 | self.intermediate_act_fn = ACT2FN[config.hidden_act] \ 241 | if isinstance(config.hidden_act, str) else config.hidden_act 242 | 243 | def forward(self, hidden_states): 244 | hidden_states = self.dense(hidden_states) 245 | hidden_states = self.intermediate_act_fn(hidden_states) 246 | return hidden_states 247 | 248 | 249 | class BERTOutput(nn.Module): 250 | def __init__(self, config): 251 | super(BERTOutput, self).__init__() 252 | self.dense = nn.Linear(config.intermediate_size, config.hidden_size) 253 | self.LayerNorm = BERTLayerNorm(config) 254 | self.dropout = nn.Dropout(config.hidden_dropout_prob) 255 | 256 | def forward(self, hidden_states, input_tensor): 257 | hidden_states = self.dense(hidden_states) 258 | hidden_states = self.dropout(hidden_states) 259 | hidden_states = self.LayerNorm(hidden_states + input_tensor) 260 | return hidden_states 261 | 262 | 263 | class BERTLayer(nn.Module): 264 | def __init__(self, config): 265 | super(BERTLayer, self).__init__() 266 | self.attention = BERTAttention(config) 267 | self.intermediate = BERTIntermediate(config) 268 | self.output = BERTOutput(config) 269 | 270 | def forward(self, hidden_states, attention_mask): 271 | attention_output = self.attention(hidden_states, attention_mask) 272 | intermediate_output = self.intermediate(attention_output) 273 | layer_output = self.output(intermediate_output, attention_output) 274 | return layer_output 275 | 276 | 277 | class FastBERTClassifier(nn.Module): 278 | def __init__(self, config, op_config): 279 | super(FastBERTClassifier, self).__init__() 280 | 281 | cls_hidden_size = op_config["cls_hidden_size"] 282 | num_attention_heads = op_config['cls_num_attention_heads'] 283 | num_class = op_config["num_class"] 284 | 285 | self.dense_narrow = nn.Linear(config.hidden_size, cls_hidden_size) 286 | self.selfAttention = BERTSelfAttention(config, hidden_size=cls_hidden_size, num_attention_heads=num_attention_heads) 287 | self.dense_prelogits = nn.Linear(cls_hidden_size, cls_hidden_size) 288 | self.dense_logits = nn.Linear(cls_hidden_size, num_class) 289 | 290 | def forward(self, hidden_states): 291 | states_output = self.dense_narrow(hidden_states) 292 | states_output = self.selfAttention(states_output, None, use_attention_mask=False) 293 | token_cls_output = states_output[:, 0] 294 | prelogits = self.dense_prelogits(token_cls_output) 295 | logits = self.dense_logits(prelogits) 296 | return logits 297 | 298 | 299 | class BERTPooler(nn.Module): 300 | def __init__(self, config): 301 | super(BERTPooler, self).__init__() 302 | self.dense = nn.Linear(config.hidden_size, config.hidden_size) 303 | self.activation = nn.Tanh() 304 | 305 | def forward(self, hidden_states): 306 | # We "pool" the model by simply taking the hidden state corresponding 307 | # to the first token. 308 | first_token_tensor = hidden_states[:, 0] 309 | pooled_output = self.dense(first_token_tensor) 310 | pooled_output = self.activation(pooled_output) 311 | return pooled_output 312 | 313 | 314 | class CommonClassifier(nn.Module): 315 | def __init__(self, drop_prob, hidden_size, num_labels): 316 | super(CommonClassifier, self).__init__() 317 | self.dropout = nn.Dropout(drop_prob) 318 | self.classifier = nn.Linear(hidden_size, num_labels) 319 | 320 | def forward(self, pooled_output): 321 | pooled_output = self.dropout(pooled_output) 322 | logits = self.classifier(pooled_output) 323 | return logits 324 | 325 | 326 | 327 | class FastBERTGraph(nn.Module): 328 | def __init__(self, bert_config, op_config): 329 | super(FastBERTGraph, self).__init__() 330 | bert_layer = BERTLayer(bert_config) 331 | self.layers = nn.ModuleList([copy.deepcopy(bert_layer) for _ in range(bert_config.num_hidden_layers)]) 332 | 333 | self.layer_classifier = FastBERTClassifier(bert_config, op_config) 334 | self.layer_classifiers = nn.ModuleDict() 335 | for i in range(bert_config.num_hidden_layers - 1): 336 | self.layer_classifiers['branch_classifier_'+str(i)] = copy.deepcopy(self.layer_classifier) 337 | self.layer_classifiers['final_classifier'] = copy.deepcopy(self.layer_classifier) 338 | 339 | self.ce_loss_fct = nn.CrossEntropyLoss() 340 | self.num_class = torch.tensor(op_config["num_class"], dtype=torch.float32) 341 | 342 | 343 | def forward(self, hidden_states, attention_mask, labels=None, inference=False, inference_speed=0.5, training_stage=0): 344 | #-----Inference阶段,第i层student不确定性低则动态提前返回----# 345 | if inference: 346 | uncertain_infos = [] 347 | for i, (layer_module, (k, layer_classifier_module)) in enumerate(zip(self.layers, self.layer_classifiers.items())): 348 | hidden_states = layer_module(hidden_states, attention_mask) 349 | logits = layer_classifier_module(hidden_states) 350 | prob = F.softmax(logits, dim=-1) 351 | log_prob = F.log_softmax(logits, dim=-1) 352 | uncertain = torch.sum(prob * log_prob, 1) / (-torch.log(self.num_class)) 353 | uncertain_infos.append([uncertain, prob]) 354 | 355 | #提前返回结果 356 | if uncertain < inference_speed: 357 | return prob, i, uncertain_infos 358 | return prob, i, uncertain_infos 359 | #------训练阶段, 第一阶段初始训练, 第二阶段蒸馏训练--------# 360 | else: 361 | #初始训练,和普通训练一致 362 | if training_stage == 0: 363 | for layer_module in self.layers: 364 | hidden_states = layer_module(hidden_states, attention_mask) 365 | logits = self.layer_classifier(hidden_states) 366 | loss = self.ce_loss_fct(logits, labels) 367 | return loss, logits 368 | #蒸馏训练,每层的student和teacher的KL散度作为loss 369 | else: 370 | all_encoder_layers = [] 371 | for layer_module in self.layers: 372 | hidden_states = layer_module(hidden_states, attention_mask) 373 | all_encoder_layers.append(hidden_states) 374 | 375 | all_logits = [] 376 | for encoder_layer, (k, layer_classifier_module) in zip(all_encoder_layers, self.layer_classifiers.items()): 377 | layer_logits = layer_classifier_module(encoder_layer) 378 | all_logits.append(layer_logits) 379 | 380 | #NOTE:debug if freezed 381 | #print(self.layer_classifiers['final_classifier'].dense_narrow.weight) 382 | 383 | loss = 0.0 384 | teacher_log_prob = F.log_softmax(all_logits[-1], dim=-1) 385 | for student_logits in all_logits[:-1]: 386 | student_prob = F.softmax(student_logits, dim=-1) 387 | student_log_prob = F.log_softmax(student_logits, dim=-1) 388 | uncertain = torch.sum(student_prob * student_log_prob, 1) / (-torch.log(self.num_class)) 389 | #print('uncertain:', uncertain[0]) 390 | 391 | D_kl = torch.sum(student_prob * (student_log_prob - teacher_log_prob), 1) 392 | D_kl = torch.mean(D_kl) 393 | loss += D_kl 394 | return loss, all_logits 395 | 396 | 397 | class FastBertModel(nn.Module): 398 | def __init__(self, bert_config: BertConfig, op_config): 399 | super(FastBertModel, self).__init__() 400 | self.embeddings = BERTEmbeddings(bert_config) 401 | self.graph = FastBERTGraph(bert_config, op_config) 402 | 403 | def forward(self, input_ids, token_type_ids=None, attention_mask=None, 404 | inference=False, inference_speed=0.5, labels=None, training_stage=0): 405 | if attention_mask is None: 406 | attention_mask = torch.ones_like(input_ids) 407 | if token_type_ids is None: 408 | token_type_ids = torch.zeros_like(input_ids) 409 | 410 | # We create a 3D attention mask from a 2D tensor mask. 411 | # Sizes are [batch_size, 1, 1, to_seq_length] 412 | # So we can broadcast to [batch_size, num_heads, from_seq_length, to_seq_length] 413 | # this attention mask is more simple than the triangular masking of causal attention 414 | # used in OpenAI GPT, we just need to prepare the broadcast dimension here. 415 | extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) 416 | 417 | # Since attention_mask is 1.0 for positions we want to attend and 0.0 for 418 | # masked positions, this operation will create a tensor which is 0.0 for 419 | # positions we want to attend and -10000.0 for masked positions. 420 | # Since we are adding it to the raw scores before the softmax, this is 421 | # effectively the same as removing these entirely. 422 | extended_attention_mask = extended_attention_mask.float() 423 | extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 424 | 425 | embedding_output = self.embeddings(input_ids, token_type_ids) 426 | 427 | #if Inference=True:res=prob, else: res=loss 428 | res = self.graph(embedding_output, extended_attention_mask, 429 | inference=inference, inference_speed=inference_speed, 430 | labels=labels, training_stage=training_stage) 431 | return res 432 | 433 | 434 | @classmethod 435 | def load_pretrained_bert_model(cls, config: BertConfig, op_config, pretrained_model_path, 436 | *inputs, **kwargs): 437 | model = cls(config, op_config, *inputs, **kwargs) 438 | pretrained_model_weights = torch.load(pretrained_model_path, 439 | map_location='cpu') 440 | rename_weights = {} 441 | for k, v in pretrained_model_weights.items(): 442 | k = re.sub(r'^bert\.', '', k) 443 | k = re.sub(r'LayerNorm\.weight$', 'LayerNorm.gamma', k) 444 | k = re.sub(r'LayerNorm\.bias$', 'LayerNorm.beta', k) 445 | k = re.sub(r'^encoder', 'graph', k) 446 | k = re.sub(r'^graph\.layer', 'graph.layers', k) 447 | k = re.sub(r'^pooler\.dense', 'graph.pooler.dense', k) 448 | #print(k) 449 | rename_weights[k] = v 450 | 451 | #Strict可以Debug参数 452 | #model.load_state_dict(rename_weights) 453 | model.load_state_dict(rename_weights, strict=False) 454 | return model 455 | 456 | 457 | 458 | -------------------------------------------------------------------------------- /src/distill_bert/model_define/optimization.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """PyTorch optimization for BERT model.""" 3 | 4 | import math 5 | import torch 6 | from torch.optim import Optimizer 7 | from torch.nn.utils import clip_grad_norm_ 8 | 9 | def warmup_cosine(x, warmup=0.002): 10 | if x < warmup: 11 | return x/warmup 12 | return 0.5 * (1.0 + torch.cos(math.pi * x)) 13 | 14 | def warmup_constant(x, warmup=0.002): 15 | if x < warmup: 16 | return x/warmup 17 | return 1.0 18 | 19 | def warmup_linear(x, warmup=0.002): 20 | if x < warmup: 21 | return x/warmup 22 | return 1.0 - x 23 | 24 | SCHEDULES = { 25 | 'warmup_cosine':warmup_cosine, 26 | 'warmup_constant':warmup_constant, 27 | 'warmup_linear':warmup_linear, 28 | } 29 | 30 | 31 | class BERTAdam(Optimizer): 32 | """Implements BERT version of Adam algorithm with weight decay fix (and no ). 33 | Params: 34 | lr: learning rate 35 | warmup: portion of t_total for the warmup, -1 means no warmup. Default: -1 36 | t_total: total number of training steps for the learning 37 | rate schedule, -1 means constant learning rate. Default: -1 38 | schedule: schedule to use for the warmup (see above). Default: 'warmup_linear' 39 | b1: Adams b1. Default: 0.9 40 | b2: Adams b2. Default: 0.999 41 | e: Adams epsilon. Default: 1e-6 42 | weight_decay_rate: Weight decay. Default: 0.01 43 | max_grad_norm: Maximum norm for the gradients (-1 means no clipping). Default: 1.0 44 | """ 45 | def __init__(self, params, lr, warmup=-1, t_total=-1, schedule='warmup_linear', 46 | b1=0.9, b2=0.999, e=1e-6, weight_decay_rate=0.01, 47 | max_grad_norm=1.0): 48 | if not lr >= 0.0: 49 | raise ValueError("Invalid learning rate: {} - should be >= 0.0".format(lr)) 50 | if schedule not in SCHEDULES: 51 | raise ValueError("Invalid schedule parameter: {}".format(schedule)) 52 | if not 0.0 <= warmup < 1.0 and not warmup == -1: 53 | raise ValueError("Invalid warmup: {} - should be in [0.0, 1.0[ or -1".format(warmup)) 54 | if not 0.0 <= b1 < 1.0: 55 | raise ValueError("Invalid b1 parameter: {} - should be in [0.0, 1.0[".format(b1)) 56 | if not 0.0 <= b2 < 1.0: 57 | raise ValueError("Invalid b2 parameter: {} - should be in [0.0, 1.0[".format(b2)) 58 | if not e >= 0.0: 59 | raise ValueError("Invalid epsilon value: {} - should be >= 0.0".format(e)) 60 | defaults = dict(lr=lr, schedule=schedule, warmup=warmup, t_total=t_total, 61 | b1=b1, b2=b2, e=e, weight_decay_rate=weight_decay_rate, 62 | max_grad_norm=max_grad_norm) 63 | super(BERTAdam, self).__init__(params, defaults) 64 | 65 | def get_lr(self): 66 | lr = [] 67 | for group in self.param_groups: 68 | for p in group['params']: 69 | state = self.state[p] 70 | if len(state) == 0: 71 | return [0] 72 | if group['t_total'] != -1: 73 | schedule_fct = SCHEDULES[group['schedule']] 74 | lr_scheduled = group['lr'] * schedule_fct(state['step']/group['t_total'], group['warmup']) 75 | else: 76 | lr_scheduled = group['lr'] 77 | lr.append(lr_scheduled) 78 | return lr 79 | 80 | def step(self, closure=None): 81 | """Performs a single optimization step. 82 | 83 | Arguments: 84 | closure (callable, optional): A closure that reevaluates the model 85 | and returns the loss. 86 | """ 87 | loss = None 88 | if closure is not None: 89 | loss = closure() 90 | 91 | for group in self.param_groups: 92 | for p in group['params']: 93 | if p.grad is None: 94 | continue 95 | grad = p.grad.data 96 | if grad.is_sparse: 97 | raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead') 98 | 99 | state = self.state[p] 100 | 101 | # State initialization 102 | if len(state) == 0: 103 | state['step'] = 0 104 | # Exponential moving average of gradient values 105 | state['next_m'] = torch.zeros_like(p.data) 106 | # Exponential moving average of squared gradient values 107 | state['next_v'] = torch.zeros_like(p.data) 108 | 109 | next_m, next_v = state['next_m'], state['next_v'] 110 | beta1, beta2 = group['b1'], group['b2'] 111 | 112 | # Add grad clipping 113 | if group['max_grad_norm'] > 0: 114 | clip_grad_norm_(p, group['max_grad_norm']) 115 | 116 | # Decay the first and second moment running average coefficient 117 | # In-place operations to update the averages at the same time 118 | next_m.mul_(beta1).add_(1 - beta1, grad) 119 | next_v.mul_(beta2).addcmul_(1 - beta2, grad, grad) 120 | update = next_m / (next_v.sqrt() + group['e']) 121 | 122 | # Just adding the square of the weights to the loss function is *not* 123 | # the correct way of using L2 regularization/weight decay with Adam, 124 | # since that will interact with the m and v parameters in strange ways. 125 | # 126 | # Instead we want ot decay the weights in a manner that doesn't interact 127 | # with the m/v parameters. This is equivalent to adding the square 128 | # of the weights to the loss with plain (non-momentum) SGD. 129 | if group['weight_decay_rate'] > 0.0: 130 | update += group['weight_decay_rate'] * p.data 131 | 132 | if group['t_total'] != -1: 133 | schedule_fct = SCHEDULES[group['schedule']] 134 | lr_scheduled = group['lr'] * schedule_fct(state['step']/group['t_total'], group['warmup']) 135 | else: 136 | lr_scheduled = group['lr'] 137 | 138 | update_with_lr = lr_scheduled * update 139 | p.data.add_(-update_with_lr) 140 | 141 | state['step'] += 1 142 | 143 | # step_size = lr_scheduled * math.sqrt(bias_correction2) / bias_correction1 144 | # bias_correction1 = 1 - beta1 ** state['step'] 145 | # bias_correction2 = 1 - beta2 ** state['step'] 146 | 147 | return loss 148 | -------------------------------------------------------------------------------- /src/distill_bert/predict.py: -------------------------------------------------------------------------------- 1 | #Auth:zhanglusheng@outlook.com 2 | #Implementation of FastBERT, paper refer:https://arxiv.org/pdf/2004.02178.pdf 3 | import argparse 4 | import pickle as pkl 5 | import numpy as np 6 | import json 7 | import logging 8 | 9 | import torch 10 | 11 | from model_define.model_fastbert import FastBertModel, BertConfig 12 | from data_utils import tokenization 13 | from utils import load_json_config, load_saved_model 14 | 15 | logging.basicConfig(level=logging.INFO, 16 | format="%(asctime)s - %(levelname)s - %(message)s") 17 | 18 | 19 | class ModelPredictor: 20 | def __init__(self, model_config_file, save_model_path, inference_speed, gpu_id): 21 | self.init_config(model_config_file) 22 | 23 | self.save_model_path = save_model_path 24 | self.max_seq_len = self.config.get('max_seq_len') 25 | self.inference_speed = inference_speed 26 | 27 | self.gpu_id = gpu_id 28 | self.use_cuda = gpu_id != -1 29 | 30 | self.init_model() 31 | self.tokenizer = tokenization.FullTokenizer( 32 | vocab_file=self.config.get("vocab_file"), do_lower_case=True) 33 | 34 | 35 | def init_config(self, config_file): 36 | logging.info("Loading HyperParameters".center(60, "=")) 37 | self.config = load_json_config(config_file) 38 | logging.info(json.dumps(self.config, indent=2, sort_keys=True)) 39 | logging.info("Load HyperParameters Done".center(60, "=")) 40 | 41 | 42 | def init_model(self): 43 | bert_config = BertConfig.from_json_file(self.config.get("bert_config_path")) 44 | self.model = FastBertModel(bert_config, self.config) 45 | logging.info(self.model) 46 | logging.info("Initialize Model Done".center(60, "=")) 47 | 48 | logging.info("Load saved model from: " + self.save_model_path) 49 | load_saved_model(self.model, self.save_model_path) 50 | logging.info("Load Saved Model Done".center(60, "=")) 51 | 52 | if self.use_cuda: 53 | self.model = self.model.cuda(self.gpu_id) 54 | 55 | self.model.eval() 56 | 57 | 58 | def preproc_text(self, text): 59 | tokens = self.tokenizer.tokenize(text) 60 | tokens = tokens[:(self.max_seq_len - 1)] 61 | tokens = ["[CLS]"] + tokens 62 | tokens = self.tokenizer.convert_tokens_to_ids(tokens) 63 | segment_ids = [0] * (len(tokens)) 64 | attn_masks = [1] * (len(tokens)) 65 | 66 | tokens = torch.LongTensor([tokens]) 67 | segment_ids = torch.LongTensor([segment_ids]) 68 | attn_masks = torch.LongTensor([attn_masks]) 69 | return tokens, segment_ids, attn_masks 70 | 71 | 72 | def predict(self, text): 73 | tokens, segment_ids, attn_masks = self.preproc_text(text) 74 | 75 | if self.use_cuda: 76 | tokens = tokens.cuda(self.gpu_id) 77 | segment_ids = segment_ids.cuda(self.gpu_id) 78 | attn_masks = attn_masks.cuda(self.gpu_id) 79 | with torch.no_grad(): 80 | probs, layer_idxes, uncertain_infos = self.model(tokens, token_type_ids=segment_ids, attention_mask=attn_masks, 81 | inference=True, inference_speed=self.inference_speed) 82 | top_probs, top_index = probs.topk(1) 83 | return probs.cpu().numpy()[0], top_index.cpu().numpy()[0] 84 | 85 | 86 | if __name__=="__main__": 87 | model_config_file = './config/fastbert_cls.json' 88 | save_model_path = 'saved_model/fastbert_test' 89 | inference_speed = 0.5 90 | gpu_id = -1 91 | model_predictor = ModelPredictor(model_config_file, save_model_path, inference_speed, gpu_id) 92 | 93 | text = '一场鸿门宴,十句话语出惊人,自毁前程,网友:都是喝酒惹的祸' 94 | prob, pred = model_predictor.predict(text) 95 | print(pred, prob, text) 96 | -------------------------------------------------------------------------------- /src/distill_bert/pretrained_model/bert/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "attention_probs_dropout_prob": 0.1, 3 | "directionality": "bidi", 4 | "hidden_act": "gelu", 5 | "hidden_dropout_prob": 0.1, 6 | "hidden_size": 768, 7 | "initializer_range": 0.02, 8 | "intermediate_size": 3072, 9 | "max_position_embeddings": 512, 10 | "num_attention_heads": 12, 11 | "num_hidden_layers": 12, 12 | "pooler_fc_size": 768, 13 | "pooler_num_attention_heads": 12, 14 | "pooler_num_fc_layers": 3, 15 | "pooler_size_per_head": 128, 16 | "pooler_type": "first_token_transform", 17 | "type_vocab_size": 2, 18 | "vocab_size": 33586 19 | } 20 | -------------------------------------------------------------------------------- /src/distill_bert/run_scripts/script_eval.sh: -------------------------------------------------------------------------------- 1 | python3 train.py \ 2 | --model_config_file='config/fastbert_cls.json' \ 3 | --save_model_path='saved_model/fastbert_test' \ 4 | --run_mode=eval \ 5 | --eval_data='./sample/ChnSentiCorp/dev.tsv' \ 6 | --batch_size=32 \ 7 | --data_load_num_workers=2 \ 8 | --gpu_ids='0,1' \ 9 | --debug_break=0 10 | -------------------------------------------------------------------------------- /src/distill_bert/run_scripts/script_infer.sh: -------------------------------------------------------------------------------- 1 | python3 infer.py \ 2 | --model_config_file='config/fastbert_cls.json' \ 3 | --save_model_path='saved_model/fastbert_test_distill' \ 4 | --inference_speed=0.5 \ 5 | --infer_data='./sample/ChnSentiCorp/dev.tsv' \ 6 | --dump_info_file='infer_info.txt' \ 7 | --data_load_num_workers=2 \ 8 | --gpu_ids=1 \ 9 | --debug_break=0 10 | -------------------------------------------------------------------------------- /src/distill_bert/run_scripts/script_train_stage0.sh: -------------------------------------------------------------------------------- 1 | python3 train.py \ 2 | --model_config_file='config/fastbert_cls.json' \ 3 | --save_model_path='saved_model/fastbert_test' \ 4 | --run_mode=train \ 5 | --train_stage=0 \ 6 | --train_data='./sample/ChnSentiCorp/train.tsv' \ 7 | --eval_data='./sample/ChnSentiCorp/dev.tsv' \ 8 | --epochs=8 \ 9 | --batch_size=32 \ 10 | --data_load_num_workers=2 \ 11 | --gpu_ids='0' \ 12 | --debug_break=0 13 | -------------------------------------------------------------------------------- /src/distill_bert/run_scripts/script_train_stage1.sh: -------------------------------------------------------------------------------- 1 | python3 train.py \ 2 | --model_config_file='config/fastbert_cls.json' \ 3 | --save_model_path='saved_model/fastbert_test' \ 4 | --save_model_path_distill='saved_model/fastbert_test_distill' \ 5 | --run_mode=train \ 6 | --train_stage=1 \ 7 | --train_data='./sample/ChnSentiCorp/train.tsv' \ 8 | --eval_data='./sample/ChnSentiCorp/dev.tsv' \ 9 | --epochs=3 \ 10 | --batch_size=32 \ 11 | --data_load_num_workers=2 \ 12 | --gpu_ids='0,1' \ 13 | --debug_break=0 14 | -------------------------------------------------------------------------------- /src/distill_bert/train.py: -------------------------------------------------------------------------------- 1 | #Auth:zhanglusheng@outlook.com 2 | #Implementation of FastBERT, paper refer:https://arxiv.org/pdf/2004.02178.pdf 3 | 4 | import argparse 5 | import json 6 | import time 7 | import logging 8 | 9 | import torch 10 | import torch.utils.data as data 11 | import numpy as np 12 | from tqdm import tqdm 13 | from tensorboardX import SummaryWriter 14 | 15 | from model_define.model_fastbert import FastBertModel, BertConfig 16 | from data_utils.dataset_preparing import PrepareDataset, TextCollate 17 | import torch.nn.functional as F 18 | from utils import load_json_config, init_bert_adam_optimizer, load_saved_model, save_model 19 | 20 | #随机数固定,RE-PRODUCIBLE 21 | seed = 9999 22 | torch.manual_seed(seed) 23 | torch.cuda.manual_seed(seed) 24 | torch.cuda.manual_seed_all(seed) 25 | np.random.seed(seed) 26 | 27 | 28 | logging.basicConfig(level=logging.INFO, 29 | format="%(asctime)s - %(levelname)s - %(message)s") 30 | debug_break = False 31 | 32 | 33 | def eval_model(train_stage, master_gpu_id, model, dataset, batch_size=1, 34 | use_cuda=False, num_workers=1): 35 | global global_step 36 | global debug_break 37 | model.eval() 38 | dataloader = data.DataLoader(dataset=dataset, 39 | collate_fn=TextCollate(dataset), 40 | pin_memory=use_cuda, 41 | batch_size=batch_size, 42 | num_workers=num_workers, 43 | shuffle=False) 44 | total_loss = 0.0 45 | correct_sum = 0 46 | proc_sum = 0 47 | num_sample = dataloader.dataset.__len__() 48 | num_batch = dataloader.__len__() 49 | predicted_probs = [] 50 | true_labels = [] 51 | logging.info("Evaluating Model...") 52 | infos = [] 53 | for step, batch in enumerate(tqdm(dataloader, unit="batch", ncols=100, desc="Evaluating process: ")): 54 | texts = batch["texts"] 55 | tokens = batch["tokens"].cuda(master_gpu_id) if use_cuda else batch["tokens"] 56 | segment_ids = batch["segment_ids"].cuda(master_gpu_id) if use_cuda else batch["segment_ids"] 57 | attn_masks = batch["attn_masks"].cuda(master_gpu_id) if use_cuda else batch["attn_masks"] 58 | labels = batch["labels"].cuda(master_gpu_id) if use_cuda else batch["labels"] 59 | with torch.no_grad(): 60 | loss, logits = model(tokens, token_type_ids=segment_ids, attention_mask=attn_masks, labels=labels, 61 | training_stage=train_stage, inference=False) 62 | loss = loss.mean() 63 | loss_val = loss.item() 64 | total_loss += loss_val 65 | #writer.add_scalar('eval/loss', total_loss/num_batch, global_step) 66 | if debug_break and step > 50: 67 | break 68 | if train_stage == 0: 69 | _, top_index = logits.topk(1) 70 | correct_sum += (top_index.view(-1) == labels).sum().item() 71 | proc_sum += labels.shape[0] 72 | logging.info('eval total avg loss:%s', format(total_loss/num_batch, "0.4f")) 73 | if train_stage == 0: 74 | logging.info("Correct Prediction: " + str(correct_sum)) 75 | logging.info("Accuracy Rate: " + format(correct_sum / proc_sum, "0.4f")) 76 | 77 | 78 | def train_epoch(train_stage, master_gpu_id, model, optimizer, dataloader, gradient_accumulation_steps, use_cuda, dump_info=False): 79 | global global_step 80 | global debug_break 81 | model.train() 82 | dataloader.dataset.is_training = True 83 | 84 | total_loss = 0.0 85 | correct_sum = 0 86 | proc_sum = 0 87 | num_batch = dataloader.__len__() 88 | num_sample = dataloader.dataset.__len__() 89 | pbar = tqdm(dataloader, unit="batch", ncols=100) 90 | pbar.set_description('train step loss') 91 | for step, batch in enumerate(pbar): 92 | texts = batch["texts"] 93 | tokens = batch["tokens"].cuda(master_gpu_id) if use_cuda else batch["tokens"] 94 | segment_ids = batch["segment_ids"].cuda(master_gpu_id) if use_cuda else batch["segment_ids"] 95 | attn_masks = batch["attn_masks"].cuda(master_gpu_id) if use_cuda else batch["attn_masks"] 96 | labels = batch["labels"].cuda(master_gpu_id) if use_cuda else batch["labels"] 97 | loss, logits = model(tokens, token_type_ids=segment_ids, attention_mask=attn_masks, labels=labels, 98 | training_stage=train_stage, inference=False) 99 | if train_stage == 0 and dump_info: 100 | probs = F.softmax(logits, dim=-1) 101 | loss = loss.mean() 102 | if gradient_accumulation_steps > 1: 103 | loss /= gradient_accumulation_steps 104 | loss.backward() 105 | if (step + 1) % gradient_accumulation_steps == 0: 106 | optimizer.step() 107 | model.zero_grad() 108 | loss_val = loss.item() 109 | total_loss += loss_val 110 | if train_stage == 0: 111 | _, top_index = logits.topk(1) 112 | correct_sum += (top_index.view(-1) == labels).sum().item() 113 | proc_sum += labels.shape[0] 114 | 115 | #writer.add_scalar('train/loss', loss_val, global_step) 116 | pbar.set_description('train step loss '+format(loss_val, "0.4f")) 117 | if debug_break and step > 50: 118 | break 119 | pbar.close() 120 | 121 | logging.info("Total Training Samples:%s ", num_sample) 122 | logging.info('train total avg loss:%s', total_loss/num_batch) 123 | if train_stage == 0: 124 | logging.info("Correct Prediction: " + str(correct_sum)) 125 | logging.info("Accuracy Rate: " + format(correct_sum / proc_sum, "0.4f")) 126 | return total_loss / num_batch 127 | 128 | 129 | def train_model(train_stage, save_model_path, master_gpu_id, model, optimizer, epochs, 130 | train_dataset, eval_dataset, 131 | batch_size=1, gradient_accumulation_steps=1, 132 | use_cuda=False, num_workers=1): 133 | logging.info("Start Training".center(60, "=")) 134 | training_dataloader = data.DataLoader(dataset=train_dataset, 135 | collate_fn=TextCollate(train_dataset), 136 | pin_memory=use_cuda, 137 | batch_size=batch_size, 138 | num_workers=num_workers, 139 | shuffle=True) 140 | for epoch in range(1, epochs + 1): 141 | logging.info("Training Epoch: " + str(epoch)) 142 | avg_loss = train_epoch(train_stage, master_gpu_id, model, optimizer, training_dataloader, 143 | gradient_accumulation_steps, use_cuda) 144 | logging.info("Average Loss: " + format(avg_loss, "0.4f")) 145 | eval_model(train_stage, master_gpu_id, model, eval_dataset, batch_size=batch_size, use_cuda=use_cuda, num_workers=num_workers) 146 | save_model(save_model_path, model, epoch) 147 | 148 | 149 | def main(args): 150 | config = load_json_config(args.model_config_file) 151 | logging.info(json.dumps(config, indent=2, sort_keys=True)) 152 | logging.info("Load HyperParameters Done") 153 | 154 | #---------------------MODEL GRAPH INIT--------------------------# 155 | bert_config = BertConfig.from_json_file(config.get("bert_config_path")) 156 | if args.run_mode == 'train': 157 | #初始训练 158 | if args.train_stage == 0: 159 | model = FastBertModel.load_pretrained_bert_model(bert_config, config, 160 | pretrained_model_path=config.get("bert_pretrained_model_path")) 161 | save_model_path_for_train = args.save_model_path 162 | #蒸馏训练 163 | elif args.train_stage == 1: 164 | model = FastBertModel(bert_config, config) 165 | load_saved_model(model, args.save_model_path) 166 | save_model_path_for_train = args.save_model_path_distill 167 | 168 | #Freeze Part Model 169 | for name, p in model.named_parameters(): 170 | if "branch_classifier" not in name: 171 | p.requires_grad = False 172 | logging.info("Main Graph and Teacher Classifier Freezed, Student Classifier will Distilling") 173 | else: 174 | raise RuntimeError('Operation Train Stage(0 or 1) not Legal') 175 | 176 | elif args.run_mode == 'eval': 177 | model = FastBertModel(bert_config, config) 178 | load_saved_model(model, args.save_model_path) 179 | else: 180 | raise RuntimeError('Operation Mode not Legal') 181 | 182 | logging.info(model) 183 | logging.info("Initialize Model Done".center(60, "=")) 184 | 185 | #---------------------GPU SETTING--------------------------# 186 | # device = torch.device('cuda' if torch.cuda else 'cpu') 187 | # model.to(device) 188 | # master_gpu_id = 0 189 | use_cuda = args.gpu_ids != '-1' 190 | if len(args.gpu_ids) == 1 and use_cuda: 191 | master_gpu_id = int(args.gpu_ids) 192 | model = model.cuda(int(args.gpu_ids)) if use_cuda else model 193 | elif use_cuda: 194 | gpu_ids = [int(each) for each in args.gpu_ids.split(",")] 195 | master_gpu_id = gpu_ids[0] 196 | model = model.cuda(gpu_ids[0]) 197 | logging.info("Start multi-gpu dataparallel training/evaluating...") 198 | model = torch.nn.DataParallel(model, device_ids=gpu_ids) 199 | else: 200 | master_gpu_id = None 201 | 202 | #-----------------------Dataset Init --------------------------------# 203 | if args.train_data: 204 | train_dataset = PrepareDataset(vocab_file=config.get("vocab_file"), 205 | max_seq_len=config.get("max_seq_len"), 206 | num_class=config.get("num_class"), 207 | data_file=args.train_data) 208 | logging.info("Load Training Dataset Done, Total training line: %s", train_dataset.__len__()) 209 | if args.eval_data: 210 | eval_dataset = PrepareDataset(vocab_file=config.get("vocab_file"), 211 | max_seq_len=config.get("max_seq_len"), 212 | num_class=config.get("num_class"), 213 | data_file=args.eval_data) 214 | logging.info("Load Eval Dataset Done, Total eval line: %s", eval_dataset.__len__()) 215 | 216 | #-----------------------Running Mode Start--------------------------------# 217 | if args.run_mode == "train": 218 | optimizer = init_bert_adam_optimizer(model, train_dataset.__len__(), args.epochs, args.batch_size, 219 | config.get("gradient_accumulation_steps"), 220 | config.get("init_lr"), config.get("warmup_proportion")) 221 | train_model(args.train_stage, 222 | save_model_path_for_train, 223 | master_gpu_id, model, 224 | optimizer, args.epochs, 225 | train_dataset, eval_dataset, 226 | batch_size=args.batch_size, 227 | gradient_accumulation_steps=config.get("gradient_accumulation_steps"), 228 | use_cuda=use_cuda, num_workers=args.data_load_num_workers) 229 | elif args.run_mode == "eval": 230 | eval_model(args.train_stage, master_gpu_id, model, eval_dataset, batch_size=args.batch_size, 231 | use_cuda=use_cuda, num_workers=args.data_load_num_workers) 232 | else: 233 | raise RuntimeError("Mode not support: " + args.mode) 234 | 235 | 236 | if __name__ == "__main__": 237 | parser = argparse.ArgumentParser( 238 | description="Textclassification training script arguments.") 239 | parser.add_argument("--model_config_file", dest="model_config_file", action="store", 240 | help="The path of configuration json file.") 241 | 242 | parser.add_argument("--run_mode", dest="run_mode", action="store", default="train", 243 | help="Running mode: train or eval") 244 | parser.add_argument("--train_stage", dest="train_stage", action="store", type=int, default=0, 245 | help="Running train stage, 0 or 1.") 246 | 247 | parser.add_argument("--save_model_path", dest="save_model_path", action="store", 248 | help="The path of trained checkpoint model.") 249 | parser.add_argument("--save_model_path_distill", dest="save_model_path_distill", action="store", 250 | help="The path of trained checkpoint model.") 251 | 252 | parser.add_argument("--train_data", dest="train_data", action="store", help="") 253 | parser.add_argument("--eval_data", dest="eval_data", action="store", help="") 254 | 255 | parser.add_argument("--inference_speed", dest="inference_speed", action="store", 256 | type=float, default=1.0, help="") 257 | 258 | # -1 for NO GPU 259 | parser.add_argument("--gpu_ids", dest="gpu_ids", action="store", default="0", 260 | help="Device ids of used gpus, split by ',' , IF -1 then no gpu") 261 | 262 | parser.add_argument("--epochs", dest="epochs", action="store", type=int, default=1, help="") 263 | parser.add_argument("--batch_size", dest="batch_size", action="store",type=int, default=32, help="") 264 | parser.add_argument("--data_load_num_workers", dest="data_load_num_workers", action="store",type=int, default=1, help="") 265 | parser.add_argument("--debug_break", dest="debug_break", action="store", type=int, default=0, 266 | help="Running debug_break, 0 or 1.") 267 | 268 | parsed_args = parser.parse_args() 269 | debug_break = (parsed_args.debug_break == 1) 270 | main(parsed_args) 271 | -------------------------------------------------------------------------------- /src/distill_bert/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf8 3 | # Provide some utilities for task finetune scripts. 4 | 5 | 6 | import collections 7 | import json 8 | import os 9 | import re 10 | import torch 11 | import torch.utils.data as data 12 | from model_define.optimization import BERTAdam 13 | import logging 14 | 15 | 16 | def eval_pr(labels, preds): 17 | TP, TN, FP, FN = 0, 0, 0, 0 18 | for label, pred in zip(labels, preds): 19 | if label == 1 and pred == 1: 20 | TP += 1 21 | elif label == 0 and pred == 0: 22 | TN += 1 23 | elif label == 1 and pred == 0: 24 | FP += 1 25 | elif label == 0 and pred == 1: 26 | FN += 1 27 | #print('TP', TP) 28 | #print('TN', TN) 29 | #print('FP', FP) 30 | #print('FN', FN) 31 | precise = TP/(TP+FN+0.0001) 32 | recall = TP/(TP+FP+0.0001) 33 | return precise, recall 34 | 35 | 36 | def load_dict_from_file(file_dir): 37 | """ 38 | Load the dictionary from file, every line is a key info 39 | in file, and each line must at least have one word for KEY. 40 | The value is the index of KEY in file. 41 | """ 42 | d = collections.OrderedDict() 43 | index = 0 44 | with open(file_dir, "r", encoding="utf-8") as f: 45 | for line in f: 46 | token = line.strip().split("\t")[0] 47 | d[token] = index 48 | index += 1 49 | return d 50 | 51 | 52 | def load_json_config(config_path): 53 | with open(config_path, "r") as f: 54 | config = json.load(f) 55 | return config 56 | 57 | 58 | def save_model(path, model, epoch): 59 | if not os.path.exists(path): 60 | os.mkdir(path) 61 | model_weight = model.state_dict() 62 | new_state_dict = collections.OrderedDict() 63 | for k, v in model_weight.items(): 64 | if k.startswith("module"): 65 | name = k[7:] 66 | else: 67 | name = k 68 | new_state_dict[name] = v 69 | model_name = "Epoch_" + str(epoch) + ".bin" 70 | model_file = os.path.join(path, model_name) 71 | torch.save(new_state_dict, model_file) 72 | logging.info('dumped model file to:%s', model_file) 73 | 74 | 75 | def load_saved_model(model, saved_model_path, model_file=None): 76 | if model_file == None: 77 | files = os.listdir(saved_model_path) 78 | max_idx = 0 79 | max_fname = '' 80 | for fname in files: 81 | idx = re.sub('Epoch_|\.bin', '',fname) 82 | if int(idx) > max_idx: 83 | max_idx = int(idx) 84 | max_fname = fname 85 | model_file = max_fname 86 | model_file = os.path.join(saved_model_path, model_file) 87 | model_weight = torch.load(model_file, map_location="cpu") 88 | new_state_dict = collections.OrderedDict() 89 | for k, v in model_weight.items(): 90 | if k.startswith("module"): 91 | name = k[7:] 92 | else: 93 | name = k 94 | new_state_dict[name] = v 95 | model.load_state_dict(new_state_dict) 96 | logging.info('loaded saved model file:%s', model_file) 97 | return model_file 98 | 99 | 100 | def init_bert_adam_optimizer(model, training_data_len, epoch, batch_size, 101 | gradient_accumulation_steps, init_lr, warmup_proportion): 102 | no_decay = ["bias", "gamma", "beta"] 103 | optimizer_parameters = [ 104 | {"params" : [p for name, p in model.named_parameters() \ 105 | if name not in no_decay], "weight_decay_rate" : 0.01}, 106 | {"params" : [p for name, p in model.named_parameters() \ 107 | if name in no_decay], "weight_decay_rate" : 0.0} 108 | ] 109 | num_train_steps = int(training_data_len / batch_size / \ 110 | gradient_accumulation_steps * epoch) 111 | optimizer = BERTAdam(optimizer_parameters, 112 | lr=init_lr, 113 | warmup=warmup_proportion, 114 | t_total=num_train_steps) 115 | return optimizer 116 | 117 | -------------------------------------------------------------------------------- /src/finetune_bert/run_language_model_bert.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Fine-tuning the library models for language modeling on a text file (GPT, GPT-2, CTRL, BERT, RoBERTa, XLNet). 4 | GPT, GPT-2 and CTRL are fine-tuned using a causal language modeling (CLM) loss. BERT and RoBERTa are fine-tuned 5 | using a masked language modeling (MLM) loss. XLNet is fine-tuned using a permutation language modeling (PLM) loss. 6 | """ 7 | 8 | 9 | import logging 10 | import math 11 | import os 12 | from dataclasses import dataclass, field 13 | from typing import Optional 14 | 15 | from transformers import ( 16 | CONFIG_MAPPING, 17 | MODEL_WITH_LM_HEAD_MAPPING, 18 | AutoConfig, 19 | AutoModelWithLMHead, 20 | AutoTokenizer, 21 | DataCollatorForLanguageModeling, 22 | HfArgumentParser, 23 | LineByLineTextDataset, 24 | PreTrainedTokenizer, 25 | TextDataset, 26 | Trainer, 27 | TrainingArguments, 28 | set_seed, 29 | ) 30 | 31 | 32 | logger = logging.getLogger(__name__) 33 | 34 | 35 | MODEL_CONFIG_CLASSES = list(MODEL_WITH_LM_HEAD_MAPPING.keys()) 36 | MODEL_TYPES = tuple(conf.model_type for conf in MODEL_CONFIG_CLASSES) 37 | 38 | 39 | @dataclass 40 | class ModelArguments: 41 | """ 42 | Arguments pertaining to which model/config/tokenizer we are going to fine-tune, or train from scratch. 43 | """ 44 | 45 | model_name_or_path: Optional[str] = field( 46 | default=None, 47 | metadata={ 48 | "help": "The model checkpoint for weights initialization. Leave None if you want to train a model from scratch." 49 | }, 50 | ) 51 | model_type: Optional[str] = field( 52 | default=None, 53 | metadata={"help": "If training from scratch, pass a model type from the list: " + ", ".join(MODEL_TYPES)}, 54 | ) 55 | config_name: Optional[str] = field( 56 | default=None, metadata={"help": "Pretrained config name or path if not the same as model_name"} 57 | ) 58 | tokenizer_name: Optional[str] = field( 59 | default=None, metadata={"help": "Pretrained tokenizer name or path if not the same as model_name"} 60 | ) 61 | cache_dir: Optional[str] = field( 62 | default=None, metadata={"help": "Where do you want to store the pretrained models downloaded from s3"} 63 | ) 64 | 65 | 66 | @dataclass 67 | class DataTrainingArguments: 68 | """ 69 | Arguments pertaining to what data we are going to input our model for training and eval. 70 | """ 71 | 72 | train_data_file: Optional[str] = field( 73 | default=None, metadata={"help": "The input training data file (a text file)."} 74 | ) 75 | eval_data_file: Optional[str] = field( 76 | default=None, 77 | metadata={"help": "An optional input evaluation data file to evaluate the perplexity on (a text file)."}, 78 | ) 79 | line_by_line: bool = field( 80 | default=False, 81 | metadata={"help": "Whether distinct lines of text in the dataset are to be handled as distinct sequences."}, 82 | ) 83 | 84 | mlm: bool = field( 85 | default=False, metadata={"help": "Train with masked-language modeling loss instead of language modeling."} 86 | ) 87 | mlm_probability: float = field( 88 | default=0.15, metadata={"help": "Ratio of tokens to mask for masked language modeling loss"} 89 | ) 90 | plm_probability: float = field( 91 | default=1 / 6, 92 | metadata={ 93 | "help": "Ratio of length of a span of masked tokens to surrounding context length for permutation language modeling." 94 | }, 95 | ) 96 | max_span_length: int = field( 97 | default=5, metadata={"help": "Maximum length of a span of masked tokens for permutation language modeling."} 98 | ) 99 | 100 | block_size: int = field( 101 | default=-1, 102 | metadata={ 103 | "help": "Optional input sequence length after tokenization." 104 | "The training dataset will be truncated in block of this size for training." 105 | "Default to the model max input length for single sentence inputs (take into account special tokens)." 106 | }, 107 | ) 108 | overwrite_cache: bool = field( 109 | default=False, metadata={"help": "Overwrite the cached training and evaluation sets"} 110 | ) 111 | 112 | 113 | def get_dataset(args: DataTrainingArguments, tokenizer: PreTrainedTokenizer, evaluate=False): 114 | file_path = args.eval_data_file if evaluate else args.train_data_file 115 | if args.line_by_line: 116 | return LineByLineTextDataset(tokenizer=tokenizer, file_path=file_path, block_size=args.block_size) 117 | else: 118 | return TextDataset( 119 | tokenizer=tokenizer, file_path=file_path, block_size=args.block_size, overwrite_cache=args.overwrite_cache 120 | ) 121 | 122 | 123 | def main(): 124 | # See all possible arguments in src/transformers/training_args.py 125 | # or by passing the --help flag to this script. 126 | # We now keep distinct sets of args, for a cleaner separation of concerns. 127 | 128 | parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments)) 129 | model_args, data_args, training_args = parser.parse_args_into_dataclasses() 130 | 131 | if data_args.eval_data_file is None and training_args.do_eval: 132 | raise ValueError( 133 | "Cannot do evaluation without an evaluation data file. Either supply a file to --eval_data_file " 134 | "or remove the --do_eval argument." 135 | ) 136 | 137 | if ( 138 | os.path.exists(training_args.output_dir) 139 | and os.listdir(training_args.output_dir) 140 | and training_args.do_train 141 | and not training_args.overwrite_output_dir 142 | ): 143 | raise ValueError( 144 | f"Output directory ({training_args.output_dir}) already exists and is not empty. Use --overwrite_output_dir to overcome." 145 | ) 146 | 147 | # Setup logging 148 | logging.basicConfig( 149 | format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", 150 | datefmt="%m/%d/%Y %H:%M:%S", 151 | level=logging.INFO if training_args.local_rank in [-1, 0] else logging.WARN, 152 | ) 153 | logger.warning( 154 | "Process rank: %s, device: %s, n_gpu: %s, distributed training: %s, 16-bits training: %s", 155 | training_args.local_rank, 156 | training_args.device, 157 | training_args.n_gpu, 158 | bool(training_args.local_rank != -1), 159 | training_args.fp16, 160 | ) 161 | logger.info("Training/evaluation parameters %s", training_args) 162 | 163 | # Set seed 164 | set_seed(training_args.seed) 165 | 166 | # Load pretrained model and tokenizer 167 | # 168 | # Distributed training: 169 | # The .from_pretrained methods guarantee that only one local process can concurrently 170 | # download model & vocab. 171 | 172 | if model_args.config_name: 173 | config = AutoConfig.from_pretrained(model_args.config_name, cache_dir=model_args.cache_dir) 174 | elif model_args.model_name_or_path: 175 | config = AutoConfig.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 176 | else: 177 | config = CONFIG_MAPPING[model_args.model_type]() 178 | logger.warning("You are instantiating a new config instance from scratch.") 179 | 180 | if model_args.tokenizer_name: 181 | tokenizer = AutoTokenizer.from_pretrained(model_args.tokenizer_name, cache_dir=model_args.cache_dir) 182 | elif model_args.model_name_or_path: 183 | tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 184 | else: 185 | raise ValueError( 186 | "You are instantiating a new tokenizer from scratch. This is not supported, but you can do it from another script, save it," 187 | "and load it from here, using --tokenizer_name" 188 | ) 189 | 190 | if model_args.model_name_or_path: 191 | model = AutoModelWithLMHead.from_pretrained( 192 | model_args.model_name_or_path, 193 | from_tf=bool(".ckpt" in model_args.model_name_or_path), 194 | config=config, 195 | cache_dir=model_args.cache_dir, 196 | ) 197 | else: 198 | logger.info("Training new model from scratch") 199 | model = AutoModelWithLMHead.from_config(config) 200 | 201 | model.resize_token_embeddings(len(tokenizer)) 202 | 203 | if config.model_type in ["bert", "roberta", "distilbert", "camembert"] and not data_args.mlm: 204 | raise ValueError( 205 | "BERT and RoBERTa-like models do not have LM heads but masked LM heads. They must be run using the" 206 | "--mlm flag (masked language modeling)." 207 | ) 208 | 209 | if data_args.block_size <= 0: 210 | data_args.block_size = tokenizer.max_len 211 | # Our input block size will be the max possible for the model 212 | else: 213 | data_args.block_size = min(data_args.block_size, tokenizer.max_len) 214 | 215 | # Get datasets 216 | 217 | train_dataset = get_dataset(data_args, tokenizer=tokenizer) if training_args.do_train else None 218 | eval_dataset = get_dataset(data_args, tokenizer=tokenizer, evaluate=True) if training_args.do_eval else None 219 | 220 | data_collator = DataCollatorForLanguageModeling( 221 | tokenizer=tokenizer, mlm=data_args.mlm, mlm_probability=data_args.mlm_probability 222 | ) 223 | 224 | # Initialize our Trainer 225 | trainer = Trainer( 226 | model=model, 227 | args=training_args, 228 | data_collator=data_collator, 229 | train_dataset=train_dataset, 230 | eval_dataset=eval_dataset, 231 | prediction_loss_only=True, 232 | ) 233 | 234 | # Training 235 | if training_args.do_train: 236 | model_path = ( 237 | model_args.model_name_or_path 238 | if model_args.model_name_or_path is not None and os.path.isdir(model_args.model_name_or_path) 239 | else None 240 | ) 241 | trainer.train(model_path=model_path) 242 | trainer.save_model() 243 | # For convenience, we also re-save the tokenizer to the same directory, 244 | # so that you can share your model easily on huggingface.co/models =) 245 | if trainer.is_world_master(): 246 | tokenizer.save_pretrained(training_args.output_dir) 247 | 248 | # Evaluation 249 | results = {} 250 | if training_args.do_eval: 251 | logger.info("*** Evaluate ***") 252 | 253 | eval_output = trainer.evaluate() 254 | 255 | perplexity = math.exp(eval_output["eval_loss"]) 256 | result = {"perplexity": perplexity} 257 | 258 | output_eval_file = os.path.join(training_args.output_dir, "eval_results_lm.txt") 259 | if trainer.is_world_master(): 260 | with open(output_eval_file, "w") as writer: 261 | logger.info("***** Eval results *****") 262 | for key in sorted(result.keys()): 263 | logger.info(" %s = %s", key, str(result[key])) 264 | writer.write("%s = %s\n" % (key, str(result[key]))) 265 | 266 | results.update(result) 267 | 268 | return results 269 | 270 | 271 | def _mp_fn(index): 272 | # For xla_spawn (TPUs) 273 | main() 274 | 275 | 276 | if __name__ == "__main__": 277 | main() -------------------------------------------------------------------------------- /src/finetune_bert/run_language_model_roberta.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Fine-tuning the library models for language modeling on a text file (GPT, GPT-2, CTRL, BERT, RoBERTa, XLNet). 4 | GPT, GPT-2 and CTRL are fine-tuned using a causal language modeling (CLM) loss. BERT and RoBERTa are fine-tuned 5 | using a masked language modeling (MLM) loss. XLNet is fine-tuned using a permutation language modeling (PLM) loss. 6 | """ 7 | 8 | 9 | import logging 10 | import math 11 | import os 12 | from dataclasses import dataclass, field 13 | from typing import Optional 14 | from transformers import BertTokenizer, BertForMaskedLM, BertConfig, BertLMHeadModel 15 | 16 | from transformers import ( 17 | CONFIG_MAPPING, 18 | MODEL_WITH_LM_HEAD_MAPPING, 19 | AutoConfig, 20 | AutoModelWithLMHead, 21 | AutoTokenizer, 22 | DataCollatorForLanguageModeling, 23 | HfArgumentParser, 24 | LineByLineTextDataset, 25 | PreTrainedTokenizer, 26 | TextDataset, 27 | Trainer, 28 | TrainingArguments, 29 | set_seed, 30 | ) 31 | 32 | 33 | logger = logging.getLogger(__name__) 34 | 35 | 36 | MODEL_CONFIG_CLASSES = list(MODEL_WITH_LM_HEAD_MAPPING.keys()) 37 | MODEL_TYPES = tuple(conf.model_type for conf in MODEL_CONFIG_CLASSES) 38 | 39 | 40 | @dataclass 41 | class ModelArguments: 42 | """ 43 | Arguments pertaining to which model/config/tokenizer we are going to fine-tune, or train from scratch. 44 | """ 45 | 46 | model_name_or_path: Optional[str] = field( 47 | default=None, 48 | metadata={ 49 | "help": "The model checkpoint for weights initialization. Leave None if you want to train a model from scratch." 50 | }, 51 | ) 52 | model_type: Optional[str] = field( 53 | default=None, 54 | metadata={"help": "If training from scratch, pass a model type from the list: " + ", ".join(MODEL_TYPES)}, 55 | ) 56 | config_name: Optional[str] = field( 57 | default=None, metadata={"help": "Pretrained config name or path if not the same as model_name"} 58 | ) 59 | tokenizer_name: Optional[str] = field( 60 | default=None, metadata={"help": "Pretrained tokenizer name or path if not the same as model_name"} 61 | ) 62 | cache_dir: Optional[str] = field( 63 | default=None, metadata={"help": "Where do you want to store the pretrained models downloaded from s3"} 64 | ) 65 | 66 | 67 | @dataclass 68 | class DataTrainingArguments: 69 | """ 70 | Arguments pertaining to what data we are going to input our model for training and eval. 71 | """ 72 | 73 | train_data_file: Optional[str] = field( 74 | default=None, metadata={"help": "The input training data file (a text file)."} 75 | ) 76 | eval_data_file: Optional[str] = field( 77 | default=None, 78 | metadata={"help": "An optional input evaluation data file to evaluate the perplexity on (a text file)."}, 79 | ) 80 | line_by_line: bool = field( 81 | default=True, 82 | metadata={"help": "Whether distinct lines of text in the dataset are to be handled as distinct sequences."}, 83 | ) 84 | 85 | mlm: bool = field( 86 | default=False, metadata={"help": "Train with masked-language modeling loss instead of language modeling."} 87 | ) 88 | mlm_probability: float = field( 89 | default=0.15, metadata={"help": "Ratio of tokens to mask for masked language modeling loss"} 90 | ) 91 | plm_probability: float = field( 92 | default=1 / 6, 93 | metadata={ 94 | "help": "Ratio of length of a span of masked tokens to surrounding context length for permutation language modeling." 95 | }, 96 | ) 97 | max_span_length: int = field( 98 | default=5, metadata={"help": "Maximum length of a span of masked tokens for permutation language modeling."} 99 | ) 100 | 101 | block_size: int = field( 102 | default=512, 103 | metadata={ 104 | "help": "Optional input sequence length after tokenization." 105 | "The training dataset will be truncated in block of this size for training." 106 | "Default to the model max input length for single sentence inputs (take into account special tokens)." 107 | }, 108 | ) 109 | overwrite_cache: bool = field( 110 | default=False, metadata={"help": "Overwrite the cached training and evaluation sets"} 111 | ) 112 | 113 | 114 | def get_dataset(args: DataTrainingArguments, tokenizer: PreTrainedTokenizer, evaluate=False): 115 | file_path = args.eval_data_file if evaluate else args.train_data_file 116 | if args.line_by_line: 117 | return LineByLineTextDataset(tokenizer=tokenizer, file_path=file_path, block_size=args.block_size) 118 | else: 119 | return TextDataset( 120 | tokenizer=tokenizer, file_path=file_path, block_size=args.block_size, overwrite_cache=args.overwrite_cache 121 | ) 122 | 123 | 124 | def main(): 125 | # See all possible arguments in src/transformers/training_args.py 126 | # or by passing the --help flag to this script. 127 | # We now keep distinct sets of args, for a cleaner separation of concerns. 128 | 129 | parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments)) 130 | model_args, data_args, training_args = parser.parse_args_into_dataclasses() 131 | # model_args.config_name = 132 | if data_args.eval_data_file is None and training_args.do_eval: 133 | raise ValueError( 134 | "Cannot do evaluation without an evaluation data file. Either supply a file to --eval_data_file " 135 | "or remove the --do_eval argument." 136 | ) 137 | 138 | if ( 139 | os.path.exists(training_args.output_dir) 140 | and os.listdir(training_args.output_dir) 141 | and training_args.do_train 142 | and not training_args.overwrite_output_dir 143 | ): 144 | raise ValueError( 145 | f"Output directory ({training_args.output_dir}) already exists and is not empty. Use --overwrite_output_dir to overcome." 146 | ) 147 | 148 | # Setup logging 149 | logging.basicConfig( 150 | format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", 151 | datefmt="%m/%d/%Y %H:%M:%S", 152 | level=logging.INFO if training_args.local_rank in [-1, 0] else logging.WARN, 153 | ) 154 | logger.warning( 155 | "Process rank: %s, device: %s, n_gpu: %s, distributed training: %s, 16-bits training: %s", 156 | training_args.local_rank, 157 | training_args.device, 158 | training_args.n_gpu, 159 | bool(training_args.local_rank != -1), 160 | training_args.fp16, 161 | ) 162 | logger.info("Training/evaluation parameters %s", training_args) 163 | 164 | # Set seed 165 | set_seed(training_args.seed) 166 | 167 | # Load pretrained model and tokenizer 168 | # 169 | # Distributed training: 170 | # The .from_pretrained methods guarantee that only one local process can concurrently 171 | # download model & vocab. 172 | 173 | if model_args.config_name: 174 | config = AutoConfig.from_pretrained(model_args.config_name, cache_dir=model_args.cache_dir) 175 | elif model_args.model_name_or_path: 176 | config = BertConfig.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 177 | # config = AutoConfig.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 178 | else: 179 | config = CONFIG_MAPPING[model_args.model_type]() 180 | logger.warning("You are instantiating a new config instance from scratch.") 181 | 182 | if model_args.tokenizer_name: 183 | tokenizer = AutoTokenizer.from_pretrained(model_args.tokenizer_name, cache_dir=model_args.cache_dir) 184 | elif model_args.model_name_or_path: 185 | tokenizer = BertTokenizer.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 186 | # tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir) 187 | else: 188 | raise ValueError( 189 | "You are instantiating a new tokenizer from scratch. This is not supported, but you can do it from another script, save it," 190 | "and load it from here, using --tokenizer_name" 191 | ) 192 | 193 | if model_args.model_name_or_path: 194 | # model = AutoModelWithLMHead.from_pretrained( 195 | # model_args.model_name_or_path, 196 | # from_tf=bool(".ckpt" in model_args.model_name_or_path), 197 | # config=config, 198 | # cache_dir=model_args.cache_dir, 199 | # ) 200 | model = BertForMaskedLM.from_pretrained( 201 | model_args.model_name_or_path, 202 | from_tf=bool(".ckpt" in model_args.model_name_or_path), 203 | config=config, 204 | cache_dir=model_args.cache_dir, 205 | ) 206 | else: 207 | logger.info("Training new model from scratch") 208 | model = AutoModelWithLMHead.from_config(config) 209 | 210 | model.resize_token_embeddings(len(tokenizer)) 211 | 212 | if config.model_type in ["bert", "roberta", "distilbert", "camembert"] and not data_args.mlm: 213 | raise ValueError( 214 | "BERT and RoBERTa-like models do not have LM heads but masked LM heads. They must be run using the" 215 | "--mlm flag (masked language modeling)." 216 | ) 217 | 218 | if data_args.block_size <= 0: 219 | data_args.block_size = tokenizer.max_len 220 | # Our input block size will be the max possible for the model 221 | else: 222 | data_args.block_size = min(data_args.block_size, tokenizer.max_len) 223 | 224 | # Get datasets 225 | print('data args--------------------', data_args) 226 | train_dataset = get_dataset(data_args, tokenizer=tokenizer) if training_args.do_train else None 227 | eval_dataset = get_dataset(data_args, tokenizer=tokenizer, evaluate=True) if training_args.do_eval else None 228 | # print('examples----------------', train_dataset.examples) 229 | data_collator = DataCollatorForLanguageModeling( 230 | tokenizer=tokenizer, mlm=data_args.mlm, mlm_probability=data_args.mlm_probability 231 | ) 232 | 233 | # Initialize our Trainer 234 | trainer = Trainer( 235 | model=model, 236 | args=training_args, 237 | data_collator=data_collator, 238 | train_dataset=train_dataset, 239 | eval_dataset=eval_dataset, 240 | prediction_loss_only=True, 241 | ) 242 | 243 | # Training 244 | if training_args.do_train: 245 | model_path = ( 246 | model_args.model_name_or_path 247 | if model_args.model_name_or_path is not None and os.path.isdir(model_args.model_name_or_path) 248 | else None 249 | ) 250 | trainer.train(model_path=model_path) 251 | trainer.save_model() 252 | # For convenience, we also re-save the tokenizer to the same directory, 253 | # so that you can share your model easily on huggingface.co/models =) 254 | if trainer.is_world_master(): 255 | tokenizer.save_pretrained(training_args.output_dir) 256 | 257 | # Evaluation 258 | results = {} 259 | if training_args.do_eval: 260 | logger.info("*** Evaluate ***") 261 | 262 | eval_output = trainer.evaluate() 263 | 264 | perplexity = math.exp(eval_output["eval_loss"]) 265 | result = {"perplexity": perplexity} 266 | 267 | output_eval_file = os.path.join(training_args.output_dir, "eval_results_lm.txt") 268 | if trainer.is_world_master(): 269 | with open(output_eval_file, "w") as writer: 270 | logger.info("***** Eval results *****") 271 | for key in sorted(result.keys()): 272 | logger.info(" %s = %s", key, str(result[key])) 273 | writer.write("%s = %s\n" % (key, str(result[key]))) 274 | 275 | results.update(result) 276 | 277 | return results 278 | 279 | 280 | def _mp_fn(index): 281 | # For xla_spawn (TPUs) 282 | main() 283 | 284 | 285 | if __name__ == "__main__": 286 | main() -------------------------------------------------------------------------------- /src/finetune_bert/train_script.sh: -------------------------------------------------------------------------------- 1 | python run_language_model_bert.py \ 2 | --output_dir="output" \ 3 | --model_type="bert" \ 4 | --model_name_or_path="./english_bert" \ 5 | --do_train \ 6 | --train_data_file="wiki_train.txt" \ 7 | --do_eval \ 8 | --eval_data_file="wiki_eval.txt" \ 9 | --mlm \ 10 | --line_by_line \ 11 | --per_device_train_batch_size=8 -------------------------------------------------------------------------------- /src/finetune_bert/train_script_roberta.sh: -------------------------------------------------------------------------------- 1 | python run_language_model_roberta.py \ 2 | --output_dir="output/roberta/" \ 3 | --model_type="bert" \ 4 | --model_name_or_path="roberta_ext" \ 5 | --do_train \ 6 | --train_data_file="wiki_train.txt" \ 7 | --do_eval \ 8 | --eval_data_file="wiki_eval.txt" \ 9 | --mlm \ 10 | --per_device_train_batch_size=8 -------------------------------------------------------------------------------- /src/finetune_bert/wiki_test.txt: -------------------------------------------------------------------------------- 1 | 香港性产业是香港的性服务行业及相关产业的统称。 2 | 在香港性交易本身是合法的,但组织及操纵性交易活动是非法的。 3 | 相关法制类似于英国,性工作者及接受性服务的个体都是合法的,操控性工作者则属违法。 4 | 香港并没有设立法定的红灯区供有关行业经营性服务,但较著名的性服务经营地区,包括旺角的砵兰街、上海街和新填地街、深水埗福华街、油麻地庙街及尖沙咀香槟大厦等地。 5 | 无固定场所,主要在网站卖广告及提供联络电话,按客人要求上门(应召)或在指定酒店提供性服务的性工作者。 6 | 私钟妹性质与应召相同,是香港妓女的一种。 7 | 「私钟」指自由工作,收入都是归自己,无须与夜总会或「公司」分拆钟钱。 8 | 私钟妹通常应召出动外卖,地点有马槛、酒店等。 9 | 私钟妹之中又有高钟妹,即高级私钟妹,原指质素较高,兼职性质,对客人有基本要求的私钟妹;通常外卖至酒店等地方,部份亦有伴游、饭局、派对等社交服务。 10 | 收费比一般私钟妹贵三成至数倍不等。 11 | 亦可能有马伕(俗称「爹哋」)带领,定时在酒店内的酒吧出没。 12 | 有些时候,一些私钟妹亦会在香港的中英文报章(例如:东方日报、HK Magazine等)刊登伴游或交友广告。 13 | 「鱼蛋妹」在香港旧日的俗语中,指于「鱼蛋档」工作的妓女,大部份为未成年少女。 14 | 虽是未成年少女,但指的其实是「女学生」,她们的工作地方称为「鱼蛋档」,靠出卖身体为客人服务。 15 | 但在一般情况下,不会在「鱼蛋档」内为客人有任何性服务,包括手淫,只会让客人在衣服内四处游走,称为「摷嘢」,如客人想有进一步的行为,可与「鱼蛋妹」私下协议,在「鱼蛋档」外的地方进行交易。 16 | 当年的「鱼蛋档」原本称为「康乐中心」。 17 | 因「鱼蛋档」通常是以楼上茶室为招牌,而为免警方故意找麻烦,在初期楼上是一定没有任何时租酒店的,但后期开了很多「鱼蛋档」,再加上「一楼一凤」的盛行,使得这种只摸不做的黄色行业没落。 18 | 另外,又因为客人在触摸女性乳房时的动作,有点像「鱼蛋师傅」在做「鱼蛋球」的动作,故得名「鱼蛋妹」。 19 | (另有一种讲法指「鱼蛋妹」只能用手碰,不能性交。 20 | 而这种用手碰的行为也被称为「笃鱼蛋」,而那些有这种球形胸的也被称为「鱼蛋胸」。 21 | )「老泥妹」是1990年代香港对某些边缘少女的称呼,带有贬义,据说由1994年的《东周刊》首先使用。 22 | 粤语「老泥」意思是人体表皮上的污垢,一般这种污垢是没有洗澡所形成。 23 | 「老泥妹」因离家出走,只能靠与其他人进行性行为时才有机会洗澡,此现象其后更成为电影题材,如《老泥妹》、《老泥妹之四大天后》等。 24 | 1990年代中以后,社会应用这种称呼已不多。 25 | 一般指为「老泥妹」的特征有:离家出走没洗澡、滥交、说粗话、吸烟、经常在尖东和旺角一带出没。 26 | 穿著性感的女子站在街上招客,有时会用眼神及动作吸引行人注意。 27 | 多在旺角、油麻地及佐敦一带,包括佐敦道、上海街、新填地街及庙街,深水埗的鸭寮街及福华街招客,再到时钟酒店或套房交易。 28 | 部分上流人士或男明星会供养一名或多名妓女,性欲发作或召开性派对时上门工作。 29 | 此外,亦有贪污罪犯供养妓女用作行贿。 30 | 援助交际一词源自日本,亦即不定时、无固定交易场所、兼职从事性工作的少女。 31 | 在香港自2000年代援交网站纷纷出现,少女得以在网上与客人讨价及相约,援助交际开始成为一个严重的社会问题,但自王嘉梅命案后才真正被社会人士重视。 32 | 2007年10月14日,循道卫理杨震社会服务处油尖旺青少年综合发展中心发表名为《香港少女援助交际现象初探》的研究报告,指出大部份援交少女大都因缺乏家人关怀或为满足购买名牌等的物质生活,而投身援交行列。 33 | 而报告亦同时指出互联网关于援交的资讯日渐增加,间接令更多人投入援交行列。 34 | 活跃于两岸三地的已故著名电影导演柯受良曾拍摄电影《老泥妹》,讲述这一班在香港的援交少女的故事。 35 | 姐姐仔会是香港首个由性工作者自己成立的组织,希望帮助同行间守望相助的团体,在2006年11月由紫藤协助下成立。 36 | 不少国家如澳洲、柬埔寨等早有性工作者工会,如2000年成立、英国总工会旗下的(IUSW),便是当中著名的一个例子。 37 | 但根据香港法例,操控性工作者是非法行为,性工作者不能有合法雇主,因此姐姐仔会只取得社团注册,而并非法律上的正式工会。 38 | 39 | 睡虎地秦简,亦称云梦秦简,为1975年12月于中国湖北省云梦县城关睡虎地十一号墓出土的秦代竹简,记录当时的法律及公文,经整理的竹简内容,被收入《睡虎地秦墓竹简》一书。 40 | 此批竹简是研究战国晚期至秦始皇时期政治、经济、文化、法律、军事的珍贵史料,也是校核古籍的依据。 41 | 据考证,该墓的主人名叫“喜”,生前曾担任过县的令吏,参与过“治狱”,这些竹简可能是墓主人生前根据工作需要对秦朝的法律和法律文书所作的抄录。 42 | 睡虎地秦墓竹简计1155枚,残片80枚,现将其分类整理为十部分内容,包括:《秦律十八种》、《效律》、《秦律杂抄》、《法律答问》、《封诊式》、《编年记》、《语书》、《为吏之道》、《日书》甲种与《日书》乙种。 43 | 其中《语书》、《效律》、《封诊式》、《日书》为原书标题,其他均为后人整理拟定。 44 | 竹简长23.1~27.8厘米,宽0.5~0.8厘米,内文为墨书秦隶,写于战国晚期及秦始皇时期。 45 | 计202简,位于墓主身体右侧,简长27.5厘米,宽0.6厘米。 46 | 包括《田律》、《厩苑律》、《仓律》、《金布律》、《关市律》、《工律》、《工人程》、《均工》、《徭律》、《司空》、《置吏律》、《效》、《军爵律》、《传食律》、《行书》、《内史杂》、《尉杂》、《属邦》等18种,律名或其简称写于每条律文尾端,内容涉及农业、仓库、货币、贸易、徭役、置吏、军爵、手工业等方面。 47 | 每种律文均为摘录,非全文。 48 | 计61简,位于墓主腹部,简长27厘米,宽0.6厘米。 49 | 标题写在第一支简的背面。 50 | 规定了对核验县和都官物资帐目作了详细规定,律中对兵器、铠甲、皮革等军备物资的管理尤为严格,也对度量衡的制式、误差作了明确规定。 51 | 计42简,位于墓主腹部,简长27.5厘米,宽0.6厘米。 52 | 包括:《除吏律》、《游士律》、《除弟子律》、《中劳律》、《藏律》、《公车司马猎律》、《牛羊课》、《傅律》、《屯表律》、《捕盗律》、《戍律》等墓主人生前抄录的11种律文,其中与军事相关的律文较多。 53 | 计210简,位于墓主颈部右侧,简长25.5厘米,宽0.6厘米。 54 | 以问答形式对秦律的条文、术语及律文的意图所作解释,相当于现时的法律解释。 55 | 主要是解释秦律的主体部分(即刑法),也有关于诉讼程序的说明。 56 | 计98简,位于墓主头部右侧,简长25.4厘米,宽0.5厘米。 57 | 标题写在最后一支简的背面。 58 | 简文分25节,每节第一简简首写有小标题,包含:《治狱》、《讯狱》、《封守》、《有鞫》、《覆》、《盗自告》、《□捕》、《盗马》、《争牛》、《群盗》、《夺首》、《告臣》、《黥妾》、《迁子》、《告子》、《疠》、《贼死》、《经死》、《穴盗》、《出子》、《毒言》、《奸》、《亡自出》等,还有两个小标题字迹模糊无法辨认。 59 | 封诊式是关于审判原则及对案件进行调查、勘验、审讯、查封等方面的规定和案例。 60 | 计53简,位于墓主头下,简长23.2厘米,宽0.6厘米。 61 | 简文分上、下两栏书写,逐年记载秦昭王元年(前306年)至秦始皇三十年(前217年)秦灭六国之战大事及墓主的生平经历等。 62 | 计14简,位于墓主腹下部,简长27.8厘米,宽0.6厘米。 63 | 标题写在最后一支简的背面。 64 | 计51简,位于墓主腹下,简长27.5厘米,宽0.6厘米。 65 | 内容主要是关于处世做官的规矩,供官吏学习。 66 | 甲种《日书》计166简,位于墓主头部的右侧,简长25厘米,宽0.5厘米,两面书字。 67 | 乙种《日书》计257简,位于墓主的足部,简长23厘米,宽0.6厘米,最后一简简背有“日书”标题。 68 | 甲种《日书》载有秦、楚纪月对照。 69 | 70 | 浦安修,江苏嘉定人,生于北京。 71 | 中华人民共和国元帅彭德怀的妻子(后主动要求离异)。 72 | 青年时就读国立北平师范大学,曾参与“一二‧九”爱国学生运动。 73 | 1936年参加妇女救国会,从事抗日活动。 74 | 同年加入中国共产党。 75 | 1938年4月任陕北公学党总支妇女干事和中共中央组织部训练班秘书。 76 | 同年10月10日在延安与彭德怀结婚,彭私下昵称其为“大学生”。 77 | 1957年任北京师范大学党委副书记。 78 | 1959年7月,庐山会议之后,彭德怀受到错误批判,被指为成立“军事俱乐部”,她受到株连,被免去职务。 79 | 1962年浦安修主动提出与彭德怀离婚。 80 | 文化大革命中,浦安修被列为全校批斗的重点对象,遭受迫害。 81 | 1967年8月31日,浦安修投昆明湖自杀未遂。 82 | 1974年9月,彭德怀病危,希望能见妻子一面。 83 | 浦安修没去见他最后一面。 84 | 1978年12月中国共产党召开十一届三中全会,与彭德怀获得平反,恢复了学校党委副书记的职务。 85 | 彭德怀平反后,在追悼会的准备期间,浦安修请求恢复彭德怀夫人身份参加追悼活动,但彭梅魁等人坚决反对。 86 | 最终中央以离婚报告未批准为由,还是认定浦安修是彭德怀夫人。 87 | 日后整理成《彭德怀自述》一书。 88 | 1991年5月2日在北京逝世。 89 | 大姐浦洁修。 90 | 二姐浦熙修。 91 | 92 | 李心洁(英文名:,),马来西亚歌手、演员,出生于马来西亚吉打州亚罗士打。 93 | 丈夫是香港著名悬疑片导演彭顺。 94 | 李心洁18岁时参加新人征选被张艾嘉发掘到台湾发展,和滚石签约成为歌手,在1998年发了个人音乐专辑「少女标本」后转往电影发展。 95 | 曾拍摄多出惊栗电影,《见鬼》、《救命》、《鬼域》及《深海寻人》。 96 | 成名作《见鬼》使她连夺多个电影颁奖典礼最佳女主角奖项,有「鬼后」之称。 97 | 在第39届金马奖,李心洁以《见鬼》与周迅、梅艳芳与郑秀文等女星一起角逐金马奖最佳女主角的奖项,最后也打败3位前辈荣获最佳女主角奖。 98 | 当时的评审所说,李心洁在《见鬼》的演技极为出色,在恐怖片中是难得的发挥,评审们也看见了李心洁在电影《见鬼》之外做的努力与投射,以此奖鼓励李心洁的电影之路。 99 | 2010年2月6日,与导演彭顺结婚。 100 | 2014年5月,彭顺被香港媒体拍到与艺人李悦彤的接吻影片,彭顺与李心洁的4年婚姻面临危机。 101 | 5月28日,李心洁在个人脸书及微博以夫妻联名的方式共同发表声明,表示两人会一起面对挫折。 102 | 2016年7月8日在马来西亚吉隆坡生下一对双胞胎儿子,长子彭济云,幼子彭子然。 103 | 104 | 有意义学习是由美国心理学家奥苏伯尔提出,与机械学习相对的概念,指符号所代表的新知识与学习者认知结构中已有的知识建立实质性的和非人为的联系。 105 | 106 | 林永-{升}-,又名翼升,字钟卿,福建侯官人。 107 | 清朝北洋海军将领,左翼左营副将。 108 | 1894年甲午战争黄海海战中为巡洋舰经远号管带,在战事中被日军炮弹击中殉国。 109 | 1867年考入福建船政学堂,于后学堂习驾驶,1871年首届毕业,上建威训练舰,游经南洋各地。 110 | 之后以千总阶留学堂任教习。 111 | 1876年获选派往英国留学,入格林威治皇家海军学院,再上英国地中海舰队铁甲舰上实习。 112 | 1880年回中国后调往北洋舰队,先任镇中号炮船管带,之后又任康济号管带。 113 | 1887年往德国接经远号巡洋舰回国。 114 | 同年北洋海军编成,任左翼左营副将,经远号管带。 115 | 1894年9月17日黄海海战中,林永-{升}-指挥经远号配合定远舰、来远舰作战。 116 | 经远号中弹甚多,逐渐脱离队形,被四艘日舰围攻。 117 | 但在林永-{升}-指挥下仍沉著作战。 118 | 后来林永-{升}-在舰上被炮弹击中,即场身亡。 119 | 之后舰上大副、二副亦先后中弹身亡,经远号沉没。 120 | 战后清廷以在战事中作战英勇,照提督例抚恤,追赠太子少保。 121 | 122 | 亚历山大·卡特来特(Alexander Joy Cartwright, Jr.,),历史上最早之棒球规则制定者。 123 | 1845年史上第一个棒球俱乐部尼克巴克「Knicker Bocker」在纽约成立,并由负责人卡特来特制定适合当时背景的棒球规则,并在翌年修订,即为史上最早之棒球规则,成为目前棒球规则之骨干。 124 | 卡莱特是一位银行家也是位土地测量员,是尼克巴克棒球俱乐部的一员。 125 | 他订定垒间距离为90呎,首次以扁平的垒包代替原来的木桩,每个垒设置守备员,于二、三垒间设置一位守备员称为游击手,规定九名球员出场比赛,三名于外野区,比赛以那一队先超过21分即比赛结束。 126 | 又规定三好球构成一出局,三出局构成一局球赛,亦订定捕手未正规捕接第三好球时之「不死三振」与比赛中打击者的打击顺序不能变更等规则。 127 | 由这些规则的订定人们可以想像出他对美国棒球的贡献。 128 | 129 | 马森,华人戏剧家与小说家。 130 | 1932年10月3日生于山东省齐河县。 131 | 父亲马超群,母亲孙希然。 132 | 少年时期曾于济南、北京、淡水、宜兰等地就读中学。 133 | 毕业于国立台湾师范大学国文系及国文研究所,1961年赴法国巴黎电影高级研究院(Institut des hautes études cinématographiques)研究电影与戏剧,并在巴黎大学汉学院博士班肄业。 134 | 1965年在巴黎创办、主编《欧洲杂志》。 135 | 然后继续赴加拿大研究社会学,获不列颠哥伦比亚大学(University of British Columbia)哲学博士学位。 136 | 现旅居加拿大。 137 | 138 | 朱大可,祖籍福建武平,生于上海,当代文化批评家、小说家、随笔作家。 139 | 当过制造照相机的技工,1979年考进华东师范大学中文系,澳洲悉尼理工大学哲学博士,现任同济大学教授。 140 | 以苛评闻名,和李泽厚、刘再复、刘晓波等人齐名。 141 | 是中国当代的批评家之一。 142 | 著有《燃烧的迷津》、《逃亡者档案》、《聒噪的时代》、《十作家批判书》、《话语的狂欢》《记忆的红皮书》等。 143 | 144 | 芝,日语中原为草地的意思,是东京都港区南部地名与町名,曾经是东京都的一个区,昭和22年(1947年)合并入港区。 145 | 2013年(平成25年)7月1日为止的人口有13,007人。 146 | 狭义的芝是指现今的芝一丁目至五丁目,广义范围指旧芝区。 147 | 芝的范围大致是从是沿新桥,滨松町,田町,到品川南北方向的狭长地带,地势西高东低。 148 | 芝在江户时代是大名(藩主)在江户居所的聚集地,有众多的历史古迹,其中有德川幕府的家庙增上寺,其正对东方的门称为大门或芝大门,为主讨仇的赤穗浪人而切腹自杀的大石良雄等47人的墓地泉岳寺,东京铁塔也在此处,其下的公园称为芝公园,靠东京湾的东面部分称为芝浦,日本著名的电器公司东芝的本社就在此处,是东芝商标的由来。 149 | 废藩置县后大名领地逐渐回收,很多被改为外国驻日本的使领馆,是东京外国使领馆最为密集的地区之一。 150 | 位于东京都港区东部,町内有JR山手线与京滨东北线的田町站、都营地下铁浅草线与三田线的三田站,另有国道15号与日比谷通路口,为重要交通枢纽。 151 | 田町站与三田站周边为繁荣的商业用地、餐饮区、商务区,主要道路内混杂古老的住宅街。 152 | 另外,同港区其他地区,町内有许多大使馆,并有许多的教育与宗教设施。 153 | 随著近年的再开发计划,、本社、(NEC本社)等大企业总部迁入,高级高层公寓也逐渐增加。 154 | 邮递区号为105-0014(芝一丁目、芝二丁目、芝三丁目,属)、108-0014(芝四丁目、芝五丁目,属)。 155 | 现在的芝在江户时代属丰岛郡柴村、荏原郡金杉村与荏原郡上高轮村。 156 | 地名「芝」最早可追溯到15世纪后半的文献。 157 | 地名由来众说纷纭。 158 | 有一说是来自海苔的柴枝,或说是源自于守护职「斯波」(しば),也有人认为是来自芝草,近年则出现代表河口三角洲地形的看法。 159 | 160 | 伯灵顿郡为美国纽泽西州的一个郡。 161 | 其郡政府现在坐落于蒙荷里 ()。 162 | 其郡政府以前曾经位于伯灵顿市,但因为人口渐渐地自德拉瓦河往内陆移动所以便决定搬移郡政府。 163 | 根据2000年的人口统计资料,有423,394人居住于该郡。 164 | 此郡位于德拉瓦河地区(Delaware River Region)此郡。 165 | 166 | 德克·尼可拉斯·艾德沃卡特(,),暱称为迪克·艾德沃卡特前荷兰足球运动员,曾经是英超新特兰主教练,目前担任荷兰国家足球队主教练,这是他第三次执教荷兰国家足球队。 167 | 艾德沃卡特以前是一名勤奋而情绪化的中场球员,曾效力荷兰联赛的海牙、罗达、鹿特丹斯巴达和乌得勒支,职业生涯晚期则效力美国联赛的芝加哥蜂螫(Chicago Sting)。 168 | 艾禾卡特在仅仅32岁便转任教练,首先任教哈勒姆及S.V.V.,稍后在荷兰国家足球队协助传奇教练里努斯·米歇尔斯。 169 | 由于米高斯绰号为「将军」,故艾禾卡特被称为「小将军」以示师承米高斯。 170 | 随后他接手荷兰国家队主教练一职,带领荷兰国家队进军1994年世界杯。 171 | 在国家队在八强赛不敌巴西后,艾禾卡特离任,转教荷兰班霸球会PSV燕豪芬,曾带领燕豪芬获得1997年荷甲联赛冠军。 172 | 1998年远赴苏格兰执教当地班霸格拉斯哥流浪,为该球会连赢几届苏超联赛冠军。 173 | 2002年他再出任荷兰国家队一职,带领国家队进军2004年欧洲国家杯。 174 | 当届荷兰国家队在外围赛并不顺利,仅能取得附加赛资格。 175 | 2004年荷兰进军欧洲国家杯,在小组赛出线对著瑞典,凭互射十二码成功淘汰瑞典。 176 | 可惜四强面对主办国葡萄牙,却以1比2落败。 177 | 国人皆对此大加批评,而艾禾卡特更收到死亡恐吓,结果他只好辞职。 178 | 之后他执教了德甲球会慕逊加柏、阿联酋国家足球队和南韩国家足球队,但日子皆不长。 179 | 2006年转在俄超联赛球会辛尼特执教,2008年协助球会晋身欧洲足协杯决赛,晋级阶段先后击败马赛,利华古逊及拜仁慕尼黑多支欧洲劲旅,最后决赛击败苏格兰的格拉斯哥流浪首次夺得欧洲赛事冠军,2009年10月,他接任比利时国家足球队教练一职,2010年,他亦接任俄罗斯国家足球队教练一职。 180 | 2015年,艾禾卡特执教英超球会新特兰,接替被解雇的-{zh-hans:波耶特;zh-hant:普耶;}-,出任暂代总教练到季尾,他最终带领新特兰护级成功,原先原定计划退休,但艾禾卡特被新特兰球迷的支持下而诚意打动便答应决定续约一年。 181 | 但是到2015/2016球季,开季成续未如理想,头八场只得3分,最终艾禾卡特引咎辞职。 182 | 2016年5月15日,荷兰足协任命艾德沃卡特为荷兰国家队助理教练,辅佐主教练-{zh-hans:布林德; zh-hk:白兰特; zh-hant:布林德;}-,但仅仅三个月后,艾德沃卡特便辞去国家队助理教练职务,并于2016年8月17日与土超球队费内巴切签订了为期一年的合同,成为费内巴切的新任主教练。 183 | 2017年5月7日,艾德沃卡特第三次被任命为荷兰国家队主教练,-{zh-hans:古利特; zh-hk:古烈治;zh-tw:古利特;}-将作为他的助理教练。 184 | 185 | 白朗字明心,幼名六儿,是中华民国初年河南马贼、农民军领袖,河南宝丰人。 186 | 白朗清末时是低级军官,因响应局势而反清,靠劫富济贫获得穷人支持,并收编众多绿林匪盗,后又成为积极反抗袁世凯之地方民变领袖,最终战死沙场。 187 | 白朗清末时曾在陆军第六镇统制吴禄贞手下充当参谋军官。 188 | 1911年辛亥革命爆发,吴被暗杀,白朗在河南宝丰起兵,起初仅有二十余士兵和一支步枪,后很快招募周围团练,至1912年,已有500余人,在河南西部一带游击,每到一处,白朗军劫掠官家及绅士、富豪财物,布施穷人,并鼓励穷人造反,自称为「中原扶汉军大都督」。 189 | 1913年5月31日,白郎发动农民起义攻占唐县,反对袁世凯政权,威胁京汉铁路的安全。 190 | 因民国初年,河南连年饥荒,加上北洋政府的河南都督张镇芳横征暴敛,常有被裁士兵、贫民、饥民铤而走险,成为马贼。 191 | 这些盗匪中有许多人响应白朗的起义。 192 | 为保证这条交通干线的畅通,袁世凯调集湖北、河南和陕西三省联军对白朗进行围剿。 193 | 不料联军中的陕西王生歧部阵前投敌,参加白朗军,使得白朗军的军力和装备都得到了很大的改善。 194 | 白朗遂自称“中华民国扶汉讨袁司令大都督”。 195 | 1914年初,白朗率众突破袁军的包围圈,进入安徽。 196 | 2月13日,袁世凯任命段祺瑞为总司令,率领北洋精锐追击白朗,并派出了4架飞机对白朗军进行侦察,。 197 | 白朗迫于压力,离皖入鄂。 198 | 3月8日,白朗军占领老河口,全军发展到2万余人。 199 | 在老河口,白朗军打死挪威基督教传教士费兰德医生,打伤沙麻牧师,酿成“老河口案”,引起西方国家对袁世凯的强烈抗议。 200 | 此后,白朗决意西进,3月17日入秦,后多次击败当地驻军,经天水、岷县等地进入甘肃南部藏族聚居地区。 201 | 由于无法在当地立足,粮食与弹药也无处补充,白朗只得率军返回河南。 202 | 白朗军虽然成功突破了袁军刘镇华的三道防线,于6月返回河南,但是途中损失惨重,只得分散进行游击战。 203 | 不久,白朗军被袁军各个击破。 204 | 白朗也于8月3日的宝丰战斗阵亡。 205 | 白朗死后被刘镇华斩下首级,函首燕京。 206 | 白朗在河南地区所领导的反袁战争在河南当地引起轰动,被视为英雄人物传颂一时,尤其是对当时年纪尚小的马尚德触动很大。 207 | 马尚德以白朗为偶像,立志长大以后也像白朗一样通过革命斗争的道路改变社会的旧面貌。 208 | 马尚德年轻时领导过河南当地的红军部队,后又化名“杨靖宇”领导了中国东北抗日联军,并成为抗战时期中国东北地区最著名的抗日将领。 209 | 210 | 商南县是中国陕西省商洛市下辖的一个县,位于陕西东南部,秦岭以南,与湖北河南交界,面积2307平方公里,山地为主。 211 | 全县辖11镇5乡,人口23.85万。 212 | 商南的矿产资源主要有镁橄榄石(5亿吨)、金红石(200万吨)、钾钠长石(5500万吨)、钒(储量710万吨)和水晶(全国四大富矿区之一)等。 213 | 生态旅游资源丰富,境内有国家级4A级旅游景区、国家级森林公园—金丝大峡谷。 214 | 水力资源丰富,境内有一江三库。 215 | 312国道、西合铁路、沪陕高速过境。 216 | 明朝洪武七年(1374年)五月,商州降为商县。 217 | 成化十三年(1477年)三月,仍为商州。 218 | 同月,以商县的层峰驿置商南县。 219 | 下辖1个街道办事处、9个镇: 220 | 221 | 筱云山庄位于台湾台中市神冈区三角里大丰路116号,兴建于同治五年(1866年),为神冈三角仔吕家在三角仔庄的所建立的第二座宅第,因此又称吕家新厝或第二公厝。 222 | 目前与相隔不到1000公尺的吕家顶瓦厝同列为市定古迹。 223 | 为目前台湾少数保存状态良好的清代私人宅第,而吕氏家族是清朝时代台湾文人士大夫的典型代表家族。 224 | 根据学者研究筱云山庄具有以下特色:第一建有书斋筱云轩,藏书丰富,以文会友最有名;第二同时拥有中国风格与日式风格的庭园,有山有水,风水佳,是二进六护龙的四合院建筑;第三建有兼具防卫、登高望远、壮观和风水功用的独立门楼;第四保存良好的交趾陶、彩画、木刻和砖雕等装饰艺术;第五文物珍贵,水墨画与诗句、联对的书法艺术俯拾皆是。 225 | 建筑学者李干朗将其列为清代台湾十大传统民居,中华邮政并在民国99年将筱云山庄与林安泰古厝、芦洲李宅与雾峰林家宅园列为台湾古厝邮票第二辑的内容。 226 | 筱云山庄主人吕氏,原籍福建漳州诏安县,诏安位于闽南与粤东交界地带,自唐代以后,人文荟萃。 227 | 清初乾隆三十六年(1771年),吕氏的北田房派下第十二世吕祥省搬眷渡台,定居于当时彰化县拣东上堡瓦窑仔庄(即今台中市潭子区)定居。 228 | 至乾隆五十五年(1790年)惊动全台的林爽文事件之后,十三世移居三角仔庄(今神冈区三角里)建屋定居。 229 | 同治五年(1866年),吕炳南为奉养张太夫人,斥资建造别业一座(即筱云山庄)。 230 | 落成时,吕炳南声望达于顶峰。 231 | 炳南有三子汝玉、汝修与汝诚,俱中秀才,举人吴子光称许为「海东三凤」。 232 | 同治八年(1869年)吕炳南创建「文英书院」,其后汝玉及汝修皆承继支助。 233 | 光绪十四年(1888年)吕汝修又于中举,获有「文魁」匾额。 234 | 这三位秀才兄弟,有《海东三凤诗集》、《餐霞子遗稿》。 235 | 筱云山庄建成后,其时广东嘉应州鸿儒吴子光客寓吕家,吕家聘为西席。 236 | 吴子光在岸里文英书院讲学,著有《一肚皮集》十八卷。 237 | 又参与《淡水厅志》编撰。 238 | 光绪四年(1878年)筱云山庄藏书室落成时,吴子光题对联曰「筱环老屋三分水,云护名山万卷书」,加以筱云轩庋藏书籍丰富,包括经、史、子、集等多达二万一千多卷。 239 | 吕家兴建的文英书院(文英社)及筱云轩遂有促成台湾中部文风益加鼎盛之美誉,并以孕育出一位进士:苗栗丘逢甲,四位举人:雾峰林文钦(林献堂之父)、清水蔡时超、鹿港施士浩及筱云吕赓年。 240 | 其后连雅堂等名儒皆来过筱云轩,可惜近代藏书逐渐散佚。 241 | 吕氏以书香传家,后人在文化艺术方面皆有所成,吕厚庵以诗名,与栎社唱和密切;吕孟津精于画,擅画骏马;吕泉生则以音乐闻名,其名作《摇婴仔歌》是台湾家喻户晓的闽南语 摇篮曲,其摇篮所在地即筱云山庄。 242 | 1999年5月,台中县文化局将筱云山庄清代建筑部份登录为县定古迹。 243 | 6月24日,再将南庭洋风日治建筑(筱云别馆)划入古迹范围。 244 | 2010年12月20日,再将北庭的日式建筑划入古迹范围。 245 | 筱云山庄方位座北朝南,北侧有小丘陵,左侧有沟渠环护,颇具风水上的意义,当时人文荟萃,促使台湾中部的文风益加鼎盛的发源地。 246 | 筱云山庄前方具一个燕尾脊之大型门楼作为入口导引,兼具防卫、远眺的功能并具有风水上的意义,门楼上具有房间,推断为当时遭逢地方变乱,为提供兵勇及后来守更者使用,门楼右侧有一马房,作为出远门使用之交通工具。 247 | 建筑主体为两院落多护龙格局的四合院,正堂为出廓起形式,与各护龙间以「过水」相接,可同时居住多人,可守望相助增加防御功能,筱云山庄的护龙比中央门厅「笃庆堂」凸出一间,使正面看起来像三合院,与台湾各地的四合院作法较为不同。 248 | 筱云山庄在清同治五年(1866年)初建时的规模,应包括门楼,第一进笃庆堂,第二进五常堂,左右各有一列护龙。 249 | 外护龙于光绪年间陆续完成,目前所见的护龙左右各有三条,总共六条护龙。 250 | 门楼附近的迎宾馆则为清末或日治时期完成,到了昭和八年(1933年)又完成近代洋风建筑,使之成为现存的建筑群组。 251 | 筱云山庄的建筑材料多来自于福建,木头主要以杉木、樟木为主,所用的砖块多为红甓及颜只砖,石雕风格较为朴实,属泉州白石。 252 | 木构方面,笃庆堂及正堂采穿斗式架构,屋面则以传统闽南仰合瓦铺设,以剪黏及泥塑装饰屋脊,内部装饰则以现今仍保存有完整的交趾陶,最为特别,交趾陶为色彩丰富的低温陶,不容易在阳光雨水下保存,筱云山庄仍保有,足以见得当时匠师技艺超群。 253 | 在整体风格上具闽、粤混和风格,「笃庆堂」前方设有一座半月池,水池具有消防及调节气候的作用,较接近客家风格,推测乃因地缘接近客家地区,而受其影响。 254 | 在左边的护龙外,则设有曲水流殇的园林,主要功能为藏书之用,为台湾园林建筑中,较为少见的例子。 255 | 筱云山庄在日治时期仍有增建,多分布于基地的南边与北边,所采用的建筑材料与设计式样皆引进西洋式的造型,具有闽洋折衷风格、和洋混和风格等多种式样,呈现较多时代的变化,装饰材料以洗石子及磨石子为主,墙体结构则为传统的土埆墙,所用的元素及空间构思参杂了传统工法及西方元素,庭园亦采用中国与日本融合的风格,是当时日治仕绅住宅的典型作法。 256 | 257 | 王若飞,原名运生,字继任,贵州安顺人,中国共产党革命烈士。 258 | 王于1903年迁居贵阳,跟随其舅黄齐生生活。 259 | 1919年后,先后赴日本、法国留学。 260 | 1922年在巴黎加入中国少年共产党,后转入中国共产党。 261 | 1923年赴苏联莫斯科学习,1925年回国。 262 | 回国后,李大钊派遣其负责共产党在河南的工作。 263 | 1926年调往上海党中央机关,出任中共中央秘书部主任,协助陈独秀工作。 264 | 1927年4月初,随中共中央机关迁至武汉办公,出席中国共产党第五次全国代表大会,负责大会的总务工作。 265 | 1927年中国国民党和中国共产党决裂。 266 | 八七会议之后,王负责在江苏南部发起了几次农民暴动。 267 | 1928年,王赴莫斯科出席中国共产党第六次全国代表大会,在会上,王为陈独秀作了辩解,并提名其依旧出任中央委员,遭到其他代表的抨击,险些被开除出党。 268 | 此后,王留莫斯科,进入列宁学院学习。 269 | 1931年回国,负责在西北为共产党开展活动。 270 | 同年在包头被捕入狱,1937年国共合作之后方在太原被释放。 271 | 之后在延安历任中共陕甘宁边区党委统战部长、宣传部长、中央军委参谋长,中共中央华北华中工作委员会秘书长,中央党务研究室主任,中共中央秘书长等职。 272 | 1944年后,负责与国民党的联系和谈判。 273 | 1945年同毛泽东、周恩来等一同参加重庆谈判。 274 | 1946年4月8日,同博古、叶挺、黄齐生等人一同返延安时因飞机失事在山西遇难。 275 | 276 | 高拱,字肃卿,号中玄,河南新郑县人,祖籍山西洪洞县,明朝政治人物,官至吏部尚书、中极殿大学士,为内阁首辅。 277 | 先世避元末乱迁徙新郑。 278 | 曾祖父高旺,祖父高魁,为成化二十二年(1486年)举人,官至工部虞衡司郎中。 279 | 父高尚贤,为嘉靖二十年(1541年)进士,官至光禄寺少卿。 280 | 高拱幼聪颖,“五岁善对偶,八岁诵千言”。 281 | 嘉靖二十年(1541年)进士,选庶吉士,一年后授翰林院编修。 282 | 穆宗为裕王时,任侍讲学士,在裕邸九年全力维护裕王地位,君臣形同密友。 283 | 嘉靖四十五年(1565年)因首辅徐阶推荐,拜文渊阁大学士。 284 | 然性情高傲,“性迫急,不能容物,又不能藏蓄需忍,有所忤触之立碎。 285 | 每张目怒视,恶声继之,即左右皆为之辟易”,与徐阶相抗,竟至相互攻讦,后为胡应嘉、欧阳一敬所逼退。 286 | 隆庆二年(1568年)七月,徐阶退休归乡,隔年冬天,张居正与李芳等奏请复起高拱,以内阁兼吏部尚书领吏部事,尽改徐阶旧制,“授诸司以籍,使署贤否,志爵里姓氏”,有官员被黜,高拱必亲“告以故”,黜者“无不慑服”。 287 | 隆庆四年(1570年)俺答汗之孙把汗那吉来降,高拱和张居正采纳宣大总督王崇古建议,先授予其指挥使官衔,乘机与俺答达成封贡互市协议,史称俺答封贡。 288 | 又推荐殷正茂为总督平定云南苗变,推荐潘季驯治理黄河。 289 | 改兵部一尚书二侍郎制为一尚书四侍郎制,侍郎们分工。 290 | 高拱与张居正供职时国子监相识,二人志趣相投,相期将来入内阁匡扶社稷。 291 | 但当明世宗去世当天,当时的内阁首辅徐阶只召自己门生张居正共同写就嘉靖遗诏,并未通知已是阁臣的对手高拱,二人顿生龃龉。 292 | 此外,在隆庆五年(1571年)高拱的亲信传言张居正接受徐阶三万两白银贿赂以救他的三个儿子,二人心中误解日益加深。 293 | 张居正暗中和对高拱不满的秉笔太监冯保同盟。 294 | 隆庆六年(1572年)正月,以高拱为柱国,专横更甚,日渐狎奢,给事中曹大野上书言及高拱不忠十事。 295 | 是年五月,穆宗病危,召高拱、张居正及高仪三人为顾命大臣,穆宗握著高拱的手说:“以天下累先生”。 296 | 穆宗去世后因遗诏问题,高拱计划发动百僚驱逐冯保,双方正式决战。 297 | 明神宗朱翊钧即位后,张居正指使神宗「大伴」司礼监秉笔太监冯保在皇贵妃和陈皇后面前将高拱曾在内阁说过的一句话“十岁太子,如何治天下!”改为“十岁孩子,如何作人主。 298 | ”宣称高拱谋拥立藩王,皇贵妃和陈皇后大惊,连明神宗听闻也闻之色变。 299 | 六月十六日早朝,「召内阁、五府、六部众至。 300 | 」,切责高拱擅权无君,到会极门,太监王榛捧旨宣读:「今有大学士高拱专权擅政,把朝廷威福都强夺自专,通不许皇帝主专。 301 | 不知他要何为?我母子三人惊惧不宁。 302 | 高拱著回籍闲住,不许停留。 303 | 」致被罢官,高拱听旨后,“面色如死灰”,“汗陡下如雨,伏不能起”,从后赶来的张居正将他扶起。 304 | 冯保又造「王大臣」事件,说误闯宫禁的逃兵「王大臣」,是高拱派来的,欲置高拱于死地。 305 | 幸赖吏部尚书杨博、御史葛守礼等力救,张居正“贻书相慰安,乃止。 306 | ” 高拱得免于难。 307 | 万历五年(1577年)张居正回故乡湖北江陵葬父,路过高拱的故里,专程探望高拱,两人相见掩面而泣,感慨不已。 308 | 临终前高拱写了《》四卷,记述张居正勾结冯保阴夺首辅之位的经过,将张居正描述为阴险刻毒的人物,大骂张居正“又做师婆又做鬼,吹笛捏眼打鼓弄琵琶”。 309 | 万历六年(1578年)十二月卒于家,葬县城北郊今阁老坟村。 310 | 万历七年(1579年)以“高某担当受降,北虏称臣,功不可泯”,赠复原官。 311 | 张居正死后,《病榻遗言》刊刻,此书在北京广为流传,催化了万历帝对张居正的清算。 312 | 有人则认为《病榻遗言》是在万历三十年(1602年)高拱赠为太师以后,才公开发行的,而张居正案是在万历十一年(1583年),对张居正抄家案不会产生影响。 313 | 万历三十年(1602年)诏赠高拱为太师,谥文襄,荫其子高务观尚宝司司丞。 314 | 著有《高文襄公集》。 315 | 高拱思想受同时代思想家王廷相的影响,不相信灾异之说,否定天人感应之论。 316 | 他反对朱子学,批评朱熹的「穷理论」令圣贤之道更晦昧不通。 317 | 高拱提倡天理与人情是一致的。 318 | 政治上,他受张居正和冯保排挤,深知现实政治借「理」之名而多行不义,因而主张通达人情,使「理」务归平实。 319 | 他偏重「性」,主张「尽心必由于知性」。 320 | 高拱也不赞同王阳明的「知行合一」和「良知」说,不同意人心本身「完全自足」,认为善恶不能由心自由决定,放任自心会导致恶念滋生,必须阻止人心放恣而无所节制。 321 | 百姓的艰苦值得同情,但士人与平民的区别仍必须严格遵守。 322 | 圣人有其特殊性,只有圣人才能运用当然之则。 323 | 朱鸿林:〈高拱与明穆宗的经筵讲读初探〉。 324 | 朱鸿林:〈高拱经筵内外的经说异同〉。 325 | 326 | 萨利克法(英语:Salic law; 拉丁语:lex Salica),是中世纪以来西欧通行的法典。 327 | 此法限制女性继承。 328 | 萨利克法发源于法兰克人萨利克部族中通行的各种习惯法,并因此而得名。 329 | 在公元6世纪初期,这些习惯法被法兰克帝国国王克洛维一世汇编为法律。 330 | 萨利克法是查理曼帝国法律的基础。 331 | 萨利克法主要是一部刑法典和程序法典,极其详细规定了列举了各种违法犯罪应科处的赔偿金,其中对于人身伤害、财产损害、偷盗和侮辱的赔偿规定尤为详细,并规定受害者所得到的赔偿金的三分之一应交给王室。 332 | 萨利克法也包括一些民法的法令,其中包括女性后裔不得继承土地的条款。 333 | 但是6世纪下半叶,法兰克国王希尔佩里克一世曾经颁布过一道修改萨利克法的敕令,规定死者如无子嗣,土地可改由其女儿继承,而不再交还公社。 334 | 萨利克法中有关女性继承权的规定随着法兰克帝国的分裂和联姻扩散到大多数欧洲的天主教国家中,女性无权继承土地的规定逐渐演变为对女性继承权的剥夺,并对中世纪和近代欧洲历史产生了很大的影响。 335 | 西班牙的历次卡洛斯战争,其起源都是旁系男性继承人对直系女性继承人权利的争议。 336 | 英法百年战争诱因之一,就是法国卡佩王朝查理四世死后没有男性继承人,英格兰国王爱德华三世因是查理四世妹妹伊莎贝拉的儿子要求得到法国国王宝座,法国方面则认定萨利克法不支持女性系后裔的继承权,查理四世的堂兄腓力六世随之加冕,开创法国的瓦卢瓦王朝,爱德华三世虽然妥协,但冲突的火种已然埋下。 337 | 英国允许女性继承,但汉诺威实行萨利克法,因此汉诺威王朝的维多利亚女王继为英国君主时,不得不将汉诺威王位转让给其叔父恩斯特亲王。 338 | 此外,在唯一仍由英国统治的原诺曼第公国领土——海峡群岛上,英国女王伊丽莎白二世的头衔是诺曼第公爵(Duke of Normandy),而非“女公爵”(Duchess)。 339 | 为了回避萨利克法的不利影响,波兰女王雅德维加在1384年继承波兰王位时,宣布自己为国王(Hedvig Rex Poloniæa),而非女王(Hedvig Regina Poloniæa)。 340 | 在被废黜的王室中,萨利克法仍有影响。 341 | 俄罗斯皇位觊觎者弗拉基米尔·基里洛维奇仅有一女玛利亚·弗拉基米洛夫娜。 342 | 他逝世后,因对女性继承权的分歧,俄罗斯皇位觊觎者分为两人,一即玛利亚·弗拉基米洛夫娜,另为季米特里·罗曼诺维奇。 343 | 罗马尼亚王国末代国王米哈伊一世在1947年退位,因为没有儿子,长女玛格丽塔在1997年成为王储,但这并不符合罗马尼亚王国法律。 344 | 345 | Celeron D,或者称为赛扬D,是属于Intel赛扬处理器家族的一系列桌面处理器。 346 | 自Celeron系列推出以来,不论核心如何随着同期奔腾处理器而升级换代,在名称上从未有任何如Pentium II、Pentium III之于Pentium的更改,全系列桌面处理器一直沿用Celeron标志——直到Celeron D系列推出,才打破这一惯例。 347 | 最早的Celeron D使用Prescott核心,拥有256KB二级缓存,采用NetBurst架构,虽然有在Pentium系列上的测试显示,Prescott并不比之前的Northwood核心有更高的执行效率,甚至单位频率性能有所下降,但拜256KB二级缓存所赐,性能较上一代"Northwood" “赛扬4”有明显提高。 348 | 在玩家中掀起新一轮的“赛扬热”。 349 | 由于Intel此期不成熟的90nm工艺,"Prescott" Celeron D的功耗未能比上一代130nm Celeron减少,相反,消费者不得不为Celeron D而好好考虑考虑CPU的散热系统。 350 | 高功耗的现象直到Intel向Celeron D引入65nm工艺才有所缓解。 351 | 从赛扬D开始,Intel开始向微处理器引入性能指数指标,以一些消费者难以直观了解的数字来区分同族处理器的不同型号。 352 | SIS651 和Intel 845G晶片组不支援 353 | Intel官方解释说Celeron D的“D”代表Desktop,即桌面应用。 354 | 反观移动平台,移动赛扬处理器,Intel也不再仅仅称呼其为“Mobile Celeron”,而命名为Celeron M。 355 | 这个D标号与Pentium D的不同,Pentium D为双核心处理器。 356 | 目前赛扬也推出双核心,型号为Celeron E1000/E3000系列,第一颗双核心赛扬型号为E1200,L2快取为512KB。 357 | E3000系列则为45nm、L2为1MB。 358 | 359 | 1949年的世界大赛是由纽约洋基与布鲁克林道奇交手,洋基队以4胜1负的战绩,三年内两度击败道奇,拿下队史第12座世界大赛奖杯。 360 | 总教练:(洋基)、(道奇)。 361 | 裁判:(美联)、(国联)、(美联)、(国联)、(美联,外野审)、(国联,外野审)。 362 | 电视转播:NBC(由负责播报)。 363 | 美联 纽约洋基(4)- 国联 布鲁克林道奇(1) 364 | 365 | 1950年的世界大赛是由卫冕队纽约洋基出战费城费城人。 366 | 费城人这一年是由一群年青的球员组成,因此被称为「神童队」。 367 | 他们神奇地在球季最后一天才赢得国联的冠军奖杯,距离上一次已经有35年了。 368 | 不过很不幸地,他们的灰姑娘传奇在这次世界大赛很快地就画上句点。 369 | 洋基的重砲部队轻易地摧毁了费城人疲惫的投手群,以四战全胜的佳绩完成二连霸,也是队史第13座奖杯。 370 | 总教练:(洋基)、(费城人)。 371 | 裁判:(国联)、(美联)、(国联)、(美联)、(国联,外野审)、(美联,外野审)。 372 | 电视转播:NBC(播报员包括:以及)。 373 | 美联 纽约洋基(4)- 国联 费城费城人(0) 374 | 375 | 1951年的世界大赛是由再度卫冕的纽约洋基出战纽约巨人,后者在三场国联冠军赛加赛中惊险地击败布鲁克林道奇(归功于巴比·汤森的全垒打,又叫做「全世界都听得到的那一击」(""))。 376 | 洋基队以4胜2负的战绩完成三连霸,与队史第14座奖杯。 377 | 这是乔·狄马乔所参与的最后一次世界大赛(赛事结束后随即宣布退休),也是菜鸟威利·梅斯与的第一次。 378 | 这次的世界大赛也上演了美国职棒史上两位最伟大与最具传奇色彩的总教练,洋基的与巨人的,间的对决。 379 | 总教练:(洋基)、(巨人)。 380 | 裁判:(美联)、(国联)、(美联)、(国联)、(美联,外野审)、(国联,外野审)。 381 | 电视转播:NBC(播报员包括:、、以及)。 382 | 美联 纽约洋基(4)- 国联 纽约巨人(2) 383 | 384 | 1952年的世界大赛是由三度卫冕的纽约洋基再度迎战布鲁克林道奇,洋基队最后以4胜3负险胜,第二次(第一次是1936年至1939年)完成四连霸,以及队史第15座奖杯。 385 | 这也是洋基队六年内第三度击败道奇队封王。 386 | 洋基队的二垒手在第七战贡献了一次再见接杀。 387 | 总教练:(洋基)、(道奇)。 388 | 裁判:(国联)、(美联)、(国联)、(美联)、(国联,外野审)、(美联,外野审)。 389 | 电视转播:NBC(播报员包括:以及)。 390 | 美联 纽约洋基(4)- 国联 布鲁克林道奇(3) 391 | 392 | 1953年的世界大赛是由四度卫冕的纽约洋基再度与布鲁克林道奇交手,这次洋基只花了六场就击败对手,完成大联盟史上唯一一次五连霸,以及队史第16座奖杯。 393 | 这也是洋基队七年内第四度击败道奇队封王。 394 | 洋基队的在第六战击出他在这个系列战中的第12支安打,将送回来得分(再见安打)。 395 | 虽然洋基赢了,不过道奇队投手也创下了世界大赛的单场三振纪录——他在第三战三振了14名洋基队打者。 396 | 这个纪录到了1968年才被所打破。 397 | 总教练:(洋基)、(道奇)。 398 | 裁判:(美联)、(国联)、(美联)、(国联)、(美联,外野审)、(国联,外野审)。 399 | 电视转播:NBC(播报员包括:以及)。 400 | 美联 纽约洋基(4)- 国联 布鲁克林道奇(2) 401 | 402 | 郭素沁,女,马来西亚华人,祖籍广东省惠州市客家人氏。 403 | 马来西亚政治人物,为民主行动党副秘书长兼吉隆坡国会议员,现任马来西亚原产业部长。 404 | 郭素沁生于1964年3月3日在吉隆坡出世,祖籍广东省惠州县。 405 | 她曾于吉隆坡坤成小学、芙蓉中华中学就,并在毕业于马来西亚理科大学修读大众传播系,并拥有马来亚大学政治学学位与哲学系硕士学位。 406 | 她是在1990年加入民主行动党,参与政治社会关怀工作,现任该党国际事务秘书等多个党职。 407 | 她是最早一批当选为国会议员的女性反对党人士之一。 408 | 自1999年开始,郭素沁成为吉隆坡国会选区议员至今。 409 | 她同时也是民主行动党宣传秘书, 行动党妇女组全国秘书与行动党纪律委员会成员。 410 | 2005年,郭素沁因揭发马来西亚警方虐待扣留犯的“裸蹲事件”而名声大噪。 411 | 2008年3月8日,郭素沁在第12届全国大选中蝉联国会议员的同时,也赢得雪兰莪州议会议席,出任雪兰莪州行政议员和投资与贸工常务委员会主席。 412 | 雪兰莪州在是次选举中首次实现政党轮替,行动党、公正党及回教党夺下州执政权,组成民联政府。 413 | 郭素沁受推举为州行政议员,在州政府中担任投资与贸工常务委员会主席一职。 414 | 2014年2月,郭素沁推出《马来犀利啊!》短片,讽刺马华公会,被指攻击马来人及伊斯兰教,其服务中心多次因事受到滋扰,同属民主行动党的林吉祥指国阵内马来人被其阵营内之华人领袖误导。 415 | 2018年7月2日,受委为联邦政府内阁成员,出任马来西亚原产业部长。 416 | 417 | 葡萄牙杯,获冠名赞助称为「千禧葡萄牙杯」("Taça de Portugal Millennium"),是由葡萄牙足球总会主办的一项全国性男子淘汰制足球杯赛。 418 | 葡萄牙杯的前身为成立于1922年的「葡萄牙锦标赛」("Campeonato de Portugal"),1938/39年度葡萄牙足总将葡萄牙锦标赛更名为葡萄牙杯。 419 | 现时由葡萄牙足总所管理的四个联赛组别的属下所有足球会(包括葡萄牙超级联赛、葡萄牙荣誉联赛、葡萄牙乙组联赛及葡萄牙丙组联赛)均获得参加葡萄牙杯资格,而上季度在地区联赛获得亚军的球队亦获准参加第一圈赛事。 420 | 直到2007年连同决赛葡萄牙杯共有9圈赛事,第三级联赛球会在第二圈加入,第二级联赛球会在第三圈加入,而顶级联赛球会在第四圈才加入参赛,由2008/09年球季开始准决赛采用两回合主客制,而一场过的决赛则在中立的国家体育场(Estádio Nacional)举行。 421 | 422 | 许子根,马来西亚民政党全国主席,曾在1990年至2008年期间出任槟城第八任首席部长。 423 | 随后在2009年出任首相署部长,主管团结事务及表现指标,至2013年大选后卸任。 424 | 许子根是拿督许平等的儿子。 425 | 1982年,以华教人士的身份,并打著「打进国阵,纠正国阵」的口号,代表董总参政,这之前,他是理科大学的理工院副院长;同年,他代表民政党中选为丹绒区国会议员。 426 | 1986年,林吉祥移师槟州后受到挫折,那年他被林吉祥在大选射下马来;随后,他受委为槟州首席部长敦林苍佑的政治秘书,过著四年的「政治潜伏期」。 427 | 1990年,大选,他奉派取代邱武扬,出战丹绒武雅州选区告捷。 428 | 这一届大选,林苍佑医生以八百多张票败在林吉祥手中。 429 | 国阵过后虽仍以十九个议席执政,但马华全军覆没,林苍佑落败,在群龙无首情况下,许子根以一个初当州议员的槟州新雀,被推选为槟州首席部长。 430 | 许子根在九零年上任。 431 | 1995年,当年大选许子根领导的槟州国阵取得狂胜,在卅三个州席中赢得卅二席,而民政党的11席也保住了10,许子根也再度出任槟州首席部长。 432 | 1996年,民政党选,中央更示意他更上一层楼,终在州民政党党选「气走」陈锦华,当上州主席。 433 | 同年的民政党中央改选,许子根原本提名角逐总秘书以取代陈松德,但后来因他被发觉不是支部代表而失去竞选资格。 434 | 无论如何,许子根在选举后受委为全国副主席。 435 | 2008年,大选前一周,宣布卸下首席部长职,不竞选州议席,只攻国会议席,为进入内阁中央铺路,然而,在3月8日举行的第12届国会大选中,却以九千多票之差败给了行动党新人拉马沙米,民政党也遭到创党以来最大的败绩,和马华公会一起在槟城全军覆没。 436 | 这是槟城政权继1969年之后第二次易手。 437 | 选举后,许子根大方的召开记者招待会承认败选。 438 | 3日之后的3月11日大方的出席民主行动党籍的林冠英宣誓出任第4届槟城首席部长,并向对方祝贺。 439 | 然而选举后,许子根曾经宣告天下不会接受成为受委国会上议院议员身份当上内阁部长。 440 | 虽然如此,许子根仍然在2009年4月获国阵领袖推荐出任国会上议院议员,受委入阁出任首相署部长,主管团结及表现指标等事务。 441 | 此举也因而被政治对手批评为“走后门部长”。 442 | 2013年5月5日第13届全国大选后,民政党进一步受挫,许子根卸下首相署部长职务。 443 | 11日宣布不进入新的内阁,只保留上议员职务,并引咎辞去党主席职,由署理主席郑可扬代理,至10月26日党全国代表大会选出新任主席马袖强为止。 444 | 2014年9月,许子根提前卸下上议员职务,其空缺由民政党吉兰丹州联委会主席的黄真贞接替。 445 | !Colspan="3" style="background: #ccccff"| 槟城州行政议会职务 446 | !Colspan="3" style="background: #ccccff"| 槟城州立法议会职务 447 | !Colspan="3" style="background: #ccccff"|国会下议院职务 448 | !Colspan="3" style="background: #ff4400"| 民政党职务 449 | 450 | 歌赋街是在香港上环的一条街道,东接鸭巴甸街,西接城皇街。 451 | 自从邻近的中环兰桂坊及中环苏豪区食店租金成天价之后,此处西式餐厅开始出现,成为上环一个美食区,吸引到不少名人光顾。 452 | 而近年中环整体街舖租户变化很大,现时新租户以国际连锁时装店及潮流家居摆设装饰等店舖为主。 453 | 歌赋街之道路,自1840年开辟,命名来自当年英国来华之海军少将休·歌赋(Hugh Gough)。 454 | 清朝末年时期,四大寇常在香港上环歌赋街杨鹤龄祖产商店「杨耀记」处会面并议论中国时政,大谈反清大事。 455 | 此外,曾经位于歌赋街四十四号的香港首间官立学校中央书院,是中华民国国父孙中山先生曾接受教育的地方。 456 | 该街道主打商户的印刷公司,现只剩一间。 457 | 458 | 《末日迷踪》〈Left Behind〉是末世小说系列中的小说,由黎曦庭(Tim LaHaye)及曾健时(Jerry B. Jenkins)所著。 459 | 这是整个系列中的第一本书,2014年改编成电影《末日迷踪》。 460 | 461 | 瓦西里科·康斯坦丁诺维奇 Василько Константинович(1209年~1238年)古罗斯王公,罗斯托夫公爵(1218年~1238年在位)。 462 | 瓦西里科·康斯坦丁诺维奇为弗拉基米尔大公康斯坦丁·弗谢沃洛多维奇之子,母亲是一位斯摩棱斯克公主。 463 | 1218年康斯坦丁·弗谢沃洛多维奇在去世前,由于明知弗拉基米尔大公之位将落入其弟尤里·弗谢沃洛多维奇之手,为了保住其子女的封地,命令瓦西里科·康斯坦丁诺维奇到弗拉基米尔去向尤里宣誓效忠。 464 | 于是后者承诺将保证瓦西里科的后代将保有罗斯托夫。 465 | 在瓦西里科·康斯坦丁诺维奇的整个统治时期,他都严格按照其叔父尤里·弗谢沃洛多维奇大公的指示办事。 466 | 根据尤里的意思,瓦西里科于1220年与保加利亚人开战,又曾两次进攻摩尔多瓦人(1227年,1228年)。 467 | 1223年,罗斯南部诸王公集体参加了帮助波洛韦茨人抵抗蒙古人入侵的战斗。 468 | 在这次发生于迦勒河沿岸的战役中,罗斯联军被第一次遭遇的蒙古骑兵彻底击溃,许多王公被杀;然而瓦西里科·康斯坦丁诺维奇却由于未能及时参战而幸免于难。 469 | 之后他前往诺夫哥罗德,与库尔斯克王公奥列格·斯维亚托斯拉维奇爆发了一场战斗。 470 | 1229年,在尤里·弗谢沃洛多维奇与瓦西里科·康斯坦丁诺维奇叔侄之间发生了唯一的一次争执,不过很快就和平解决了。 471 | 1238年,瓦西里科·康斯坦丁诺维奇与其他王公一起参加了反抗蒙古人入侵的战役。 472 | 他在锡塔河附近的战斗中被俘,不久遭蒙古人杀害。 473 | 编年史记载瓦西里科·康斯坦丁诺维奇是一位品质高尚的人。 474 | 475 | 利尼史特朗体育会(Lillestrøm Sportsklubb),是一间位于北欧国家挪威利勒斯特罗姆的足球会,于1917年4月2日成立。 476 | 他们的主场馆是阿拉森球场,有 12,250 个座位。 477 | 利尼史特朗乃于1917年4月2日成立,由该市两家球会合并。 478 | 利尼史特朗拥有一项最长时间停留于挪超联赛而没有降班的纪录。 479 | 这段期间球会约有40名球员曾代表挪威国家队比赛,同时也拥有很多代表其他国家的国脚,计有瑞典、冰岛、塞内加尔、芬兰、马尔他、澳洲、南非、斯洛文尼亚、突尼斯、加拿大和尼日利亚。 480 | 利尼史特朗曾经五次胜出挪超联赛,最近一次在1989年,其余的就在1986年、1977年、1976年和1959年。 481 | 另外,利尼史特朗也在1977年、1978年、1981年、1985年胜出挪威杯。 482 | 虽然利尼史特朗的主场馆──阿拉森球场有12,250个座位,但平均每场入场人数只有略多于8,000人。 483 | 在1977年9月14日,利尼史特朗和阿积士在欧洲联赛冠军杯对阵,当时利尼史特朗在乌利华球场以2-0击败对手。 484 | 这场赛事吸引了超过20,000人入场,是利尼史特朗一直保持国内最多入场的纪录。 485 | 但是利尼史特朗在次回合在阿积士主场以0-4见负。 486 | 487 | 秀英竹(学名:" ",英文名称:""),是香港原生独有的禾本科植物,是中国境内易危物种。 488 | 于1981年在尖山首次发现,并以胡秀英博士之名而命名。 489 | 因为仅见分布于尖山及中文大学校园,所以特别具有科学研究价值。 490 | 根状茎细长型。 491 | 秆复丛生,竹叶翠绿,身高约4至6米,节间幼时常显现紫色斑点。 492 | 竹竿约高达4-6米,直径约1-2厘米。 493 | 节间长约22-38厘米,节间幼时常有紫色斑点显现并长有疏生短刺毛,节下常披一圈白粉。 494 | 竹竿中部分枝常为3枝,中、下部有分枝之一侧扁平,主枝稍粗。 495 | 秆箨早落或脱落,箨鞘黄绿色,基部略带褐色,先端渐狭而截平,背部近无毛或具淡棕色刺毛,边缘披纤毛。 496 | 箨耳细小,边缘有2或3条刚毛;箨舌截平,高约1毫米,边缘被白色短纤毛;箨片直立,卵状披针形或披针形,绿带褐色,基部宽度约为箨鞘顶端宽度的一半。 497 | 叶片线状披针形,两面无毛,叶下表面有狭长方格状小横脉,长约12-20厘米,宽约8-13毫米。 498 | 叶鞘无毛,常带有紫色小斑点,叶约5-9片生长于开花的枝条上;叶耳不发达,叶鞘口有2-3条刚毛。 499 | 叶舌暗紫色,背部有毛,高约0.5-1毫米。 500 | 花为总状或简单圆锥状花序,小穗2-4枚,披针状,含小花5-15朵,长约8-9毫米,宽约4.5-5毫米。 501 | 502 | 1941年的世界大赛是由纽约洋基与布鲁克林道奇交手,洋基以4胜1败赢得6年来第5座,也是队史第9座世界大赛奖杯。 503 | 这是地铁大战这个名词首度用来称呼由两个同样位于纽约市球队所参与的世界大赛。 504 | 这个系列战的转捩点在第四战的九局下,道奇以4比3领先。 505 | 两出局后,眼看著就要获胜,主场观众等著要狂欢庆祝两队以2胜2负平手的时候,道奇队捕手居然漏接了投手所投出角度变化相当大的第三个好球(曲球,不过也有人怀疑是唾液球),打者靠著这记不死三振上到一垒。 506 | 之后洋基大爆发,连得四分形成了大局,也赢得了这场比赛,取得3胜1败的优势。 507 | 总教练:(洋基)、(道奇)。 508 | 裁判:(美联)、(国联)、(美联)、(国联)。 509 | 美联 纽约洋基(4)- 国联 布鲁克林道奇(1) 510 | 511 | 1943年的世界大赛是由卫冕队圣路易红雀再度与纽约洋基交手,洋基以4胜1负赢得21年来第10座奖杯。 512 | 这也是洋基教头的最后一座世界大赛奖杯。 513 | 由于战时实施的旅行限制,所以这一年的世界大赛采用3-4主客场制(先在洋基主场三连战,再到红雀主场四连战)。 514 | 总教练:(洋基)、(红雀)。 515 | 裁判:(美联)、(国联)、(美联)、(国联)。 516 | 美联 纽约洋基(4)- 国联 圣路易红雀(1) 517 | 518 | 1947年的世界大赛是由纽约洋基与布鲁克林道奇交手,洋基以4胜3败赢得1943年之后的第一座,也是队史第11座世界大赛奖杯。 519 | 洋基教头自1924年率领华盛顿参议员夺冠之后,总算再度拿下世界大赛奖杯。 520 | 在第四战里,洋基投手只差一个出局数就完成无安打比赛,不过被道奇队的的两分打点安打所破坏,也把他从胜投候选人一棒打成败战投手。 521 | 道奇队以3比2神奇地赢了这一场。 522 | 在执行长的指示下,这是首度有六名裁判于世界大赛中执法。 523 | 从1918年到1946年期间,只有四名裁判轮流担任内野审。 524 | 有多指派两位替代裁判,以备紧急状况发生时上场,不过从来没有发生过这样的情况。 525 | 不过觉得多找两位裁判负责外野会比较好。 526 | 直到1964年,原本专职的外野审和其他裁判一起轮值。 527 | 这是大联盟史上第一次电视转播世界大赛,不过收视地区只有纽约市及其周围地带。 528 | 这也是首次有非白人球员出场——杰基·罗宾森于1947年球季开始就以先发一垒手的身分于道奇队出赛。 529 | 总教练:(洋基)、(道奇)。 530 | 裁判:(美联)、(国联)、(美联)、(国联)、(美联,外野审)、(国联,外野审)。 531 | 电视转播:NBC。 532 | 美联 纽约洋基(4)- 国联 布鲁克林道奇(3)1947年世界大赛的第四战又被称为库奇剧场。 533 | 在之前的三场比赛,洋基以2胜1败领先,准备朝著奖杯再向前一步。 534 | 洋基的先发投手,主投8又3分之2局没让对方击出任何一支安打,洋基也以2比1领先。 535 | 眼看著就要投出史上第一场季后赛(以及世界大赛里的)无安打比赛,并且让洋基取得第3胜听牌的时候,剧情突然往另外一个方向发展。 536 | 连续保送了两名打者,与,下一棒原本是,换上来代打。 537 | 一坏球之后,瞄准一颗快速球攻击,把球击向右外野。 538 | 球打到墙上,然后弹到洋基右外野手的肩上。 539 | 和回来得分,无安打比赛破功,道奇队也反败为胜。 540 | 以下引用自道奇队播报员的转播内容: 541 | 虽然这支安打让道奇免于1胜3败的劣势,以及成为史上最有意义的安打之一,不过洋基最后仍拿下决定性的第七战胜利以及世界大赛的奖杯。 542 | 奇怪的是,这支安打后来成为生涯的最后一支。 543 | 544 | 和平车站位于台湾花莲县秀林乡太鲁阁族为主的和平村,邻近和平工业区及和平电厂,为台湾铁路管理局北回线的铁路车站,主要办理水泥原料运输服务,属于货物列车编组站。 545 | 目前无公车路线行经此站,仅有社区巴士提供区域运输服务 546 | 547 | 艾迪·墨菲(Eddie Murphy,),是一位非裔美国人喜剧演员,歌手和电影演员。 548 | 本名为爱德华·里根·墨菲(Edward Regan Murphy),出生于纽约布鲁克林。 549 | 艾迪墨菲在16岁就开始了他的喜剧演员生涯。 550 | 19岁时,就在他从罗斯福中学毕业后不久,他成为了NBC电视台的电视节目《周末夜现场》(美国名人智力竞答搞笑娱乐节目)的一个演员。 551 | 他演过的角色包括模仿弗莱德·罗杰丝以及电影《小淘气》里的角色巴克韦特。 552 | 前任“周末夜现场”作家玛格丽特·杭菲特说过墨菲和比尔·默里是演艺史上最有天分的两个人。 553 | 墨菲在1983-1984演出季度中途退出了演出,在该季度余下的时间内出现在电影速写中。 554 | 之后,墨菲在许多喜剧电影中演出,包括《比佛利山超级警探》(又名妙探出更),他因此片而获得金球奖最佳喜剧男演员。 555 | 其他演出的电影还包括接下来几年的《你整我、我整你》(Trading Places)和《来去美国》。 556 | 他是一个著名的配音员,曾经为电影《史瑞克》系列里的驴子配音,也为迪士尼电影《花木兰》中为“木须”配音。 557 | 墨菲也出演了许多的电影续集,包括1987年的《轰天雷》,1990年《四十八小时续集》,1994年的《霹雳炮》(又名妙探出更3),2001年《怪医杜立德2》,2000年《随身变2》。 558 | 在他的许多电影当中,墨菲除了出演主角之外还扮演了许多配角。 559 | 一个例子是《随身变》,翻拍当年裘利·路易的电影。 560 | 墨菲在其中扮演了克乐普家庭的许多成员,以及薜曼克乐普的知己,巴迪·劳夫。 561 | 另外墨菲的一个招牌动作就是他那深沉,有感染力,但又有点傻气的笑容。 562 | 在2005年的寻找“喜剧演员的喜剧演员”投票中,墨菲被年轻喜剧演员和其他喜剧界人士选为有史以来最伟大的50名喜剧演员之一。 563 | 艾迪·墨菲的哥哥,查理·墨菲,也是一个演员,最近因为《查普尔的演出》而出名。 564 | 2016年,墨菲赢得了好莱坞职业成就奖。 565 | 墨菲的生父在他很小的时候就去世了。 566 | 艾迪·墨菲、他哥哥查理·墨菲、还有属于K9说唱组的继兄小弗农,都是被身为电话公司雇员的妈妈莉莲·墨菲以及身为Breyers冰淇淋公司工头继父弗农·林奇抚养大的。 567 | 墨菲被认为是一个聪明的儿童,但他花费了大量的时间在印象和单口说笑的喜剧剧目上,而不是在学习上。 568 | 15岁的时候,他已经开始创作和演出他自己的剧目,通常在青年中心,本地酒吧或者罗斯福中学的礼堂里。 569 | 必须说明的是在那时候,他写作和演出的剧本糟透了。 570 | 一开始,舆论通常把墨菲称为“永不会有成就的无用表演者”。 571 | 渐渐的,他成功的在曼哈顿的某个地方亮相,那就是The Comic Strip。 572 | 这个俱乐部的合伙人,罗伯特·华什和理查德·廷肯,深深的被墨菲对声名的感悟和他的人生观所打动。 573 | 因此他们俩决定负责墨菲的职业生涯。 574 | 墨菲在就读罗斯福中学的时候被选为“最受欢迎的人”,因为他在学校礼堂里表演的单口说笑的喜剧剧目,以及他在午餐时给同学们讲的笑话。 575 | 然后,在他开始他的演员生涯之前,墨菲进入了纽约州东花园市的拿骚社区大学。 576 | 墨菲在罗宾·威廉斯和琥碧·戈柏(当时还在以其真实姓名卡林·约翰逊进行演出)同在的湾区喜剧俱乐部里表演脱口秀的喜剧。 577 | 他的早期喜剧活泼而生动,和被他誉为带给他喜剧灵感的理查德·波依尔的风格类似。 578 | 虽然波依尔在他的自传中写道他总是觉得墨菲的戏剧太过做作,但是由于被人形容成经常骂脏话和以同性恋,歌手和其他人来取乐,墨菲在某种意义上成为80年代的波依尔。 579 | 墨菲在他的脱口秀的喜剧中关于同性恋和艾滋病的评论被认为是非常恶毒的,以致于几年之后他要为此而道歉。 580 | 在他最受欢迎的时期,墨菲出现在《》(1983年)和《野马秀》(1987年)这两部音乐电影中。 581 | “发狂”包括了一个不出名的喜剧片段,墨菲在其中饰演《蜜月伴侣》中的人物拉夫·克兰登以及埃德·诺顿,以及其他著名的角色例如同性恋T先生。 582 | 在1983年,墨菲凭借他的喜剧专辑“喜剧演员”获得格莱美奖。 583 | 1980年秋天,当时还未出名的19岁的墨菲纠缠和乞求有才华的协调员内尔·利维给他一个演出的机会。 584 | 利维以演出人员已满为由拒绝了他好几次,但是墨菲继续恳求利维,说他有好几个兄弟姐妹借钱给他以实现他出镜的愿望。 585 | 利维最后让步了,并给墨菲一个试听的机会。 586 | 这场试听演出非常成功,因此内尔·利维开始主张新的执行制片人琼·道曼尼安(她在1979-1980季度之后接替洛恩·迈克尔斯的位置)让墨菲参加演出。 587 | 道曼尼安一开始拒绝,理由是另一个演员罗伯特·汤塞德已经被定为将要出演“无关紧要的黑小子”的角色,同时演出拮据的预算也无法让更多的演员加入。 588 | 道曼尼安的想法在她看到墨菲试听她自己的演出之后发生改变,在这之后道曼尼安也开始恳求电视网络允许墨菲演出。 589 | NBC最终在确定汤塞德还没有签下合同的时候才同意,此时墨菲作为一个客串演员参与演出。 590 | 墨菲在马尔科姆·麦克道尔主持的1980-1981季度的第二部作品中完成了他的处女演出。 591 | 他是作为小喜剧“寻找黑人[共和主义]者”中的一个额外角色出现的。 592 | 两周之后,墨菲在“周末更新”中以拉希姆·阿卜杜·穆哈姆德作为他的第一个演讲角色。 593 | 他给人留下了很深刻的印象,因此后来他在后续作品中的出镜的次数越来越多,也很快成为全职演员。 594 | 如果不考虑墨菲的参与,1980-1981季度可以被称为一个灾难,因为NBC解雇了琼·道曼尼安和剧组中的所有人,唯独除了墨菲和乔·皮斯柯波。 595 | 因为墨菲在道曼尼安在位的时候很少客串演出,因此他在道曼尼安的接替者,迪克·埃本索上任之后,成为了一颗闪耀的新星。 596 | 墨菲直线上升的人气帮助这个节目重新获得高的了[尼尔森]指数。 597 | 他还创造了一些当时最好的角色,包括前童星比利·汤姆斯以及和原物一样大小的的Gumby玩具角色。 598 | 墨菲扮演了一把怪诞的史提夫·汪达(他出现在一个虚假的佳能相机广告中)。 599 | 《周末夜现场》在1981到1983年间几乎是一个两人节目,由墨菲和皮斯柯波扮演一系列的重要人物。 600 | 其他所有的剧组成员都只是当配角,因此遭到制作人非常没耐心的对待。 601 | 1982年,墨菲第一次出现在银幕上,那就是与尼克·诺特一起主演的罪案片《48小时》。 602 | 这部电影因为两个片断最值得回味:第一个是有一幕墨菲因为要和尼克打赌,威胁一间“红脖子”酒吧。 603 | 第二个片断是墨菲在一间囚室内一边用耳机听着一边唱The Police的Roxanne,并且还要走音。 604 | 《48小时》在1982年圣诞期间被首次推出的时候被证明是一个重量级的作品。 605 | 它被一些人认为是错配警察冒险动作片的套路的始祖,且被之后的许多电影例如《致命武器》、《绝地战警》以及《尖峰时刻》等所追随。 606 | 尼克·诺特按计划是要主持《周末夜现场》的1982圣诞节部分,也就是12月11日。 607 | 但是他因为病而不能主持,因此墨菲接过了他的工作。 608 | 墨菲成为唯一一个既是剧组演员又是主持人的人。 609 | 在这个节目的开场,墨菲说道:“纽约实况,这是艾迪·墨菲的演出!”让墨菲来主持的决定后来被发现让剧组的其他人很烦恼。 610 | 接下来的一年,墨菲与年轻的“周末夜现场”校友丹·阿克罗伊德搭档演出了《交易所》。 611 | 这部电影也成为墨菲与导演约翰·兰迪斯(他执导了墨菲演出的《来到美国》和《妙探出更3》)的首部合作电影。 612 | 它也被证明成为比《48小时》更成功的票房灵丹。 613 | 1983年,墨菲出演了百万票房奇迹的《比佛利山警探》。 614 | 这部电影,有争议的成为墨菲的第一次完全成型的电影演出,因为该电影本来计划由西尔维斯特·史泰龙出演。 615 | 《比佛利山警探》获得了超过2亿美元的票房收入(也因此奠定了墨菲作为票房的信心保证),就算考虑通货膨胀因素,它也在2005年荣获40部最高票房收入的电影之一。 616 | 同在1984年,墨菲出现在电影《最佳防卫》中,与达德利·穆尔共同主演。 617 | 墨菲作为“特别嘉宾”在电影最初完成的版本试映时受到观众恶评之后才被加入到影片中。 618 | 《最佳防卫》是一部令舆论与市场失望的电影,但是墨菲是其中最少受指责的部分,因为整个电影的重心没有落在他的肩上。 619 | 当墨菲主持“周末夜现场”的时候,他加入到诋毁《最佳防卫》的队伍当中,并称这部电影为“有史以来最差的电影”。 620 | 与此同时,他也指出“如果他们付钱让你对他们付钱让我做的‘最佳防卫’作‘最佳防卫’,那你们都会完成‘最佳防卫’。 621 | 据传言,艾迪·墨菲最开始是一些受欢迎电影的参演人员之一,例如《捉鬼敢死队》(由墨菲在《交易所》里的搭档丹·阿克罗伊德和年轻的“周末夜现场”校友比尔·莫瑞出演)。 622 | 本来创作时打算以墨菲为对象的角色最终让俄尼·哈德森出演。 623 | 墨菲也被传原本有份参加1986年电影《星空奇遇记4:回到地球》的演出,但是最后这个角色落入《第七天堂》的主演凯瑟琳·希克斯手中。 624 | 也是在1986年,墨菲出演了一部超自然的喜剧《金童子》。 625 | 这部电影本来是计划让梅尔·吉布森来主演的一部严肃电影。 626 | 但是随着吉布森拒绝演出之后,这个角色就让给了墨菲。 627 | 也因此,剧本被重写而带有部分喜剧色彩。 628 | 虽然《金童子》也还算得上是一部大作(一些令人回味的点滴,例如墨菲的“我要这把刀”段子),但它并不如《48小时》、《交易所》和《比佛利山警探》那样受好评。 629 | 《金童子》被认为是墨菲步调的一种改变,因为其超自然的基调与墨菲之前“街头智慧”的格调正好相反。 630 | 一年之后,墨菲在《比佛利山警探2》中再度出演他的角色“艾思豪·佛利”。 631 | 虽然这部电影不如它的前一集那般受好评(《比佛利山警探2》因为其卑鄙下流的基调和总的结构而遭到舆论抨击),但是也算是一部票房冠军电影,因其总共收入了150万美元以上的票房。 632 | 据报道,制片人曾经想要将“比佛利山警探”和约转成一个每周的电视剧集。 633 | 墨菲拒绝了这个电视剧的邀请,但是却愿意接拍电影的续集。 634 | 艾迪·墨菲是最后一批与制片厂签订独占协议的电影演员之一。 635 | 在这里,派拉蒙电影公司发行了墨菲所有的早期电影。 636 | 墨菲同时也是一个歌手,并且在80年代有两首畅销歌曲:“一直聚会吧!”(由里克·詹姆斯制作)以及“把你的嘴凑上来”。 637 | 前者比后者更加出名,也因此被人误认为是墨菲唯一的畅销歌。 638 | 2004年,VH-1和Blender投票将“一直聚会吧”选为“五十首最差的歌曲”第七位。 639 | 墨菲在90年代初期也录制了一张唱片,名字叫做“Love's Alright”。 640 | 在这张专辑中他与迈克尔·杰克逊一起出演了歌曲“你怎么了”的视频。 641 | 在1999年,由墨菲和杰克逊在一个色彩鲜艳的梦幻般的世界里表演的这个MTV被评为MTV时代最差的25个音乐电视之一。 642 | 墨菲还与沙巴·兰克斯录制了一首二重唱“我是王”,但也同样受到批评。 643 | 1992年,墨菲还与魔术师约翰逊和伊曼·阿卜杜勒马吉德出现在迈克尔·杰克逊的“Remember the Time”视频中。 644 | 在1994年的时候,墨菲曾经尝试与英国“艺术家与曲目”(A&R)顾问西蒙-科威尔作一笔交易。 645 | 后者当时已经和辛妮塔,战队和Zig and Zag等签约,但是后来逐渐都退出了。 646 | 虽然没有留名,但墨菲也为“周末夜现场”的剧组同伴乔·皮斯柯波的畅销喜剧单曲“The Honeymooners Rap”配音。 647 | 皮斯柯波在歌曲中模仿杰克·格里森,而墨菲则是扮演阿特·卡尼。 648 | 墨菲的歌唱技巧被很好的用在《史瑞克》系列电影中。 649 | 在第一集里,他演唱了电影终场的“我是一个信徒”。 650 | 在《史瑞克2》中,他与安东尼奥·班德拉斯一道演唱了瑞奇·马丁的畅销曲“Livin' La Vida Loca”。 651 | 从1989年直到1990年代中期,墨菲出演的电影票房下跌,特别是《比佛利山警探3》、《布鲁克林的吸血鬼》,以及《滑头绅士闯通关》,即使他的电影《偷心情圣》还有《另一个48小时》还算成功。 652 | 他执导的第一部电影《哈林夜总会》,被舆论还有电影中的部分演员批评。 653 | 理查德·普莱尔对于这部电影以及演员没有什么好评,这让墨菲很诧异,因为他曾经视普莱尔为偶像。 654 | 墨菲也因为没有利用他的演艺事业高度为黑人演员打入电影圈而被电影制片人斯派克·李责难。 655 | 大卫·斯派德在他的“周末夜现场”的节目片断“好莱坞时间”里,拿墨菲的事业低谷逗乐。 656 | 当墨菲的照片在屏幕上出现时,斯派德说:“看吧,孩子们,一颗坠落的流星,快,许个愿吧!”。 657 | 虽然墨菲算是“周末夜现场”有史以来培养出的最有影响的电影明星,但他从没有参加过任何剧组的聚会,周年庆典,甚至连《来自纽约现场》的回忆录撰写也没有参加。 658 | 一些人相信墨菲觉得“周末夜现场”因为斯派德的言论而背叛了他(虽然他在斯派德发言之前的第15次周年庆典就已经缺席了)。 659 | 另外一些人相信是墨菲对于洛恩·迈克尔斯不够忠诚。 660 | 因为当墨菲在“周末夜现场”的时候是迪克·艾伯索尔在做执行制片人,而不是迈克尔斯。 661 | 墨菲的票房成绩在1996年开始回复,由《肥佬教授》开始。 662 | 之后他又陆续参与了一系列适合全家的电影,例如《花木兰》、《怪医杜立德》和其续集,《史瑞克》和其续集,《老爸托儿所》,以及《幽灵鬼屋》,还有《肥佬教授2》。 663 | 墨菲的大部分针对成人的电影都不是畅销之作,例如《摇钱树》、《轰天妙探》、《玩转月球》、《我是间谍》以及《做秀时刻》。 664 | 1997年5月2日,洛杉矶县州长的代表正在圣塔莫尼卡大道的“卖淫治理区域”巡视的时候,截停了墨菲的轿车,并且在车内乘客席找到一个名叫沙立玛(Atisone Seiuli)的跨性别妓女。 665 | 墨菲声称他只是刚刚驾车路过,而这个妓女叫他载上一程。 666 | 这个事件之后被蒂姆·梅多斯在“周末夜现场”节目中所讽刺。 667 | 这个特别的小品,连同大卫·斯派德之前的评论,让“周末夜现场”和墨菲之前产生了更大的间隙。 668 | 赛于莉之后写下并将这个所谓的于墨菲的性接触故事卖给一家小报。 669 | 赛于莉在1998年由于从15楼高的窗口堕下而去世。 670 | 墨菲在1988年美国有色人种进步国家协会图片奖的演出时遇到尼可·米切尔之后与她开始了一段长期的风流关系。 671 | 他们同居了1年半之后于1993年3月18日在纽约的广场大酒店的豪华舞厅举行婚礼。 672 | 他们一共有5个孩子,但是在2005年八月米切尔申请离婚,理由是“不可协调的差异”。 673 | 离婚最终在2006年4月17日生效。 674 | 675 | 焕镛箣竹是香港独有的百合植物纲鸭跖草亚纲莎草目禾本科箣竹属植物。 676 | 于1980年在元朗锦田高埔村发现,经过贾良智、冯学琳鉴定后确定为新品种,并以香港植物学家陈焕镛教授而命名。 677 | 根状茎粗短型。 678 | 乔木状、秆丛生,高约11米下部略呈之字形曲折。 679 | 节间有厚壁,幼时疏被上小刺毛,基部微微肿胀。 680 | 681 | 和平车站位于台湾台北市万华区,曾为台湾铁路管理局新店线(现已废止)的铁路车站。 682 | 站名取自所在地临近的和平西路。 683 | 站址约位于汀州路以及南海路口一带。 684 | 日治时期原本专供单节汽油客车停靠;废止前则为招呼站。 685 | 686 | 小簕竹(学名:)为禾本科簕竹属下的一个种。 687 | 688 | 黄永玉,民国十三年七月初九(公历8月9日)生于湘西凤凰县,土家族,中国画家,现为中央美术学院教授,曾任版画系主任 。 689 | 黄永玉在版画、中国画、雕塑、文学、建筑、邮票设计等方面都有创作作品。 690 | 绘画有浓厚西方画元素,但又不失中国画气韵著称。 691 | 黄永玉亦是一位非常出色的诗人,其诗作中的民间化、口语化风格与质朴的诗风,十分别致动人。 692 | 他的著名作品包括生肖邮票猴票、文学作品包括《永玉六记》、《吴世茫论坛》、《老婆呀,不要哭》、《这些忧郁的碎屑》、《沿著塞纳河到翡冷翠》、《太阳下的风景》、《无愁河的浪荡汉子》等书。 693 | 他的建筑设计作品有凤凰县的《玉氏山房》、《夺翠楼》,香港的《山之半居》,北京通州的《万荷堂》和意大利佛罗伦萨的《无数山庄》。 694 | 黄永玉的作品,在2007年7月曾经于香港时代广场展出。 695 | 服装品牌佐丹奴中文标识由黄永玉题写。 696 | 697 | 大叶草(Axonopus Compressus, 有「地毯草」之称)是香港常见地禾本科毯草属植物,多年生匍匐草本。 698 | 原产热带美洲,主要分布海南、广东,常见于香港各地公园及草地。 699 | 700 | 白云山(白云山风景名胜区)号称“羊城第一秀”,位于广州市区北部,因为主峰摩星岭常为白云所掩,所以得名。 701 | 白云山是广州-{著名}-的风景区,与广州市内穿过的珠江并称“云山珠水”。 702 | 由30多座山峰组成,呈长方形,南北长9.7公里,东西宽4.5公里,总面积约28平方公里,为东北—西南走向。 703 | 主峰摩星岭位于中部,海拔382米。 704 | 正门入口位于广园中路,邻近麓湖公园及云台花园。 705 | 白云山风景秀丽,古迹众多,经历年开发,现有八个游览区:明珠楼、摩星岭、三台岭、鸣春谷、飞鹅岭、云台花园、麓湖公园、天南第一峰,包含能仁寺、碑林、九龙泉、蒲谷、柯子岭、廉泉等景点。 706 | 在正门附近更建有白云索道缆车站,游客可以乘坐缆车直达山顶广场,成人来回票价40元,其中上山25元,下山15元。 707 | 据统计,现白云山共有15个门,顺时针分别为:南大门,柯子岭门,云溪生态公园门,西大门,水库门,广外校内门,元下田门,永泰乒乓体育公园门,握山村门,蟹山门,荷树林门,梅花园门,梅花园小门,伍仙桥门,濂泉门。 708 | 其中可供车辆行驶的有3个:南大门,西大门,水库门。 709 | 24小时开放,车辆晚上8点后可进山。 710 | 石阶登山正规门7个:柯子岭门,元下田门,蟹山门,荷树林门,梅花园门,伍仙桥门,濂泉门。 711 | 晚上6点关门。 712 | 半正规小门3个:云溪生态公园门,广外校内门,握山村门。 713 | 门口不正规,下午3点前有人把守门口收费。 714 | 野门2个:永泰乒乓体育公园门,位于公园内最东端水池旁,入口为打开一个口子的铁丝网,但挑山水的人很多;梅花园小门。 715 | 摩星岭位于白云山中部,是白云山的最高峰,海拔372.8米。 716 | 山顶平坦但山坡陡峻,周围岩石出露,岩性坚硬。 717 | 宋、明以前称最高顶、第一天或天南第一峰。 718 | 清朝康熙年间始称摩星岭,寓意山峰高耸可摩星辰。 719 | 1986年建有测量标志“广州市二等三角点摩星岭”。 720 | 目前摩星岭已做为白云山风景区的一个二级旅游点,登摩星岭须另行购票,超过1.5米者非符合半票和免票条件者均为人民币5元。 721 | 山上另有许愿树、祈福亭和摘斗亭,其中祈福亭设有一钟供游人免费敲钟。 722 | 而摩星岭本身则有一中国移动信号发射塔,中间有供游人留下刻字锁的地方,附近还有小型的风力发电装置,同时还有一雕塑指向北京并标出距离已经此处的海拔高度。 723 | 从摩星岭顶端可以看到广州市区的状况。 724 | 725 | 坭竹(学名:)为禾本科簕竹属下的一个种。 726 | 产地为香港太平山、新界、大屿山等地。 727 | 主要分布于广东、广西、福建三个省份。 728 | 729 | 1936年的世界大赛是由纽约洋基与纽约巨人交手,洋基队以4胜2负赢得队史第5座奖杯。 730 | 投手虽然率领巨人赢得第一战,但却每况愈下。 731 | 洋基队第二战在巨人主场Polo Grounds以18比4大胜,这是一项纪录。 732 | 第九局的三个出局数都是由所贡献,他在接杀最后一球高飞球后甚至还一路冲上球场的台阶。 733 | 总教练:(洋基)、(巨人)。 734 | 裁判:(国联)、(美联)、(国联)、(美联)。 735 | 美联 纽约洋基(4)- 国联 纽约巨人(2) 736 | 737 | 1937年的世界大赛是由卫冕队纽约洋基再度与纽约巨人交手,洋基队以4胜2负的战绩完成二连霸,以及15年来第6座奖杯。 738 | 在这次的系列战中,击出了首发世界大赛的全垒打;而则是击出了最后一发于世界大赛中击出的全垒打。 739 | 总教练:(洋基)、(巨人)。 740 | 裁判:(美联)、(国联)、(美联)、(国联)。 741 | 美联 纽约洋基(4)- 国联 纽约巨人(1) 742 | 743 | 1938年的世界大赛是由两度卫冕的纽约洋基与芝加哥小熊交手,洋基四战全胜横扫小熊完成三连霸,这也是队史第7座奖杯。 744 | 投手酸痛的手臂虽然支撑了小熊队一整年,不过在系列战中气力用尽,惨遭洋基队再度痛击。 745 | 小熊队直到2003年开始的跨联盟赛制,才在正式比赛里击败洋基队。 746 | 总教练:(洋基)、与(小熊)。 747 | 裁判:(国联)、(美联)、(国联)、(美联)。 748 | 美联 纽约洋基(4)- 国联 芝加哥小熊(0) 749 | 750 | 1939年的世界大赛是由三度卫冕的纽约洋基与辛辛那提红人交手。 751 | 后者自从赢得被丑闻污染的1919年世界大赛后,首度打进世界大赛。 752 | 洋基再度以四战全胜横扫对手完成四连霸,这也是17年来第8座奖杯。 753 | 这是洋基教头个人的第五座奖杯,追平了先前由所保持的纪录。 754 | 这个系列战最著名的一刻发生在最后一战的第九局。 755 | 洋基队冲回本垒时与捕手发生冲撞,球被撞掉,得分算。 756 | 当在地上打滚,试图把球捡起来的时候,也冲回来得分。 757 | 的鼠蹊部被狠狠地撞了一下,不过各大报却写得好像他在本垒板「打瞌睡」一样。 758 | 这是连续四年每一场比赛都由来自纽约的球队获胜。 759 | 总教练:(洋基)、(红人)。 760 | 裁判:(美联)、(国联)、(美联)、(国联)。 761 | 美联 纽约洋基(4)- 国联 辛辛那提红人(0) 762 | 763 | 宁陕县是中国陕西省安康市所辖的一个县,位于陕西南部,秦岭南坡。 764 | 总面积为3678平方公里,2002年人口为7万人。 765 | 下辖11个镇: 766 | 767 | 石泉县是中国陕西省安康市所辖的一个县,位于陕西南部,汉江沿岸。 768 | 总面积为1525平方公里,辖15个乡镇,219个行政村,2002年人口为18.2万人。 769 | 明初该县曾属四川省夔州府大宁州。 770 | 著名的“鬼谷子”曾经修道的地方,便在该县北部的云雾山顶的天台观,被称为鬼谷岭。 771 | 此处现已申报为省级森林公园。 772 | 并辟为县级重要旅游景点,有三条公路直通其上。 773 | 2004年曾在该县举行一次先秦文化及鬼谷子专题研讨会。 774 | 下辖11个镇: 775 | 776 | 平利县位于中国陕西省东南部,是安康市所辖的一个县。 777 | 面积为2627平方公里,2002年人口为23万人。 778 | 西晋太康元年(280年)以上廉水为名设置上廉县(地域包括今平利、镇坪县及岚皋县部分乡镇),系平利县前身,为置县之始,属房州上庸郡。 779 | 南朝宋初分上廉增设吉阳,不久撤上廉县,辖地并入吉阳,名吉阳县。 780 | 西魏废帝元年(552年),改为吉安县,属安康郡。 781 | 唐武德元年(618年),划金川县东南(今平利县、镇坪县及岚皋县花里镇),在上廉故城,以平利川名置平利县,属金州。 782 | 明初属四川省夔州府大宁州。 783 | 下辖11个镇: 784 | 785 | 镇坪县是中国陕西省安康市所辖的一个县,位于陕西南部,大巴山北坡,邻接湖北和重庆。 786 | 总面积为1504平方公里,2002年人口为6万人。 787 | 1920年由平利县析置。 788 | 下辖7个镇: 789 | 790 | 旬阳县(旧作“洵阳”,因“洵”字生僻难认,1964年经国务院批准改为“-{旬}-阳”)是中国陕西省安康市所辖的一个县。 791 | 总面积为3554平方公里,2002年人口为45万人。 792 | 因为流经县城的旬河的特殊形状,县城部分地区与中国道教传统的太极图相似的。 793 | 故亦被官方或民间称为“太极城”。 794 | 前4000年左右,就有先民在旬阳地区生息。 795 | 战国中期,旬阳是秦楚战争的战略要地。 796 | 秦统一中国后,曾于此设立洵关,汉代设为洵阳县,是在陕南建县最早的县份之一。 797 | 据《洵阳县志》记载,隋、唐、北周、明、清各朝代均有较大规模的县城设施建设。 798 | 旬阳因旬水得名。 799 | 秦时设旬关,汉以旬关置旬阳县,西魏改旬阳为洵阳,因“洵”字生僻难认,1964年经国务院批准,复改为旬阳县。 800 | 下辖21个镇: 801 | 本县地处陕西南部汉水与旬河的交汇处,水运较为便利. 包茂高速公路西康段、十天高速公路途径本县区域.并有316国道过境,乡村通路工程也已完工. 铁路方面,西康线与襄渝线在本县交汇,分别设有旬阳北站与旬阳站. 802 | 旬阳的矿产资源丰富,铅锌矿储量位居全国前列,汞的探明储藏量全国第二,仅次于贵州铜仁。 803 | 因此被称为“中国汞都”。 804 | 其余的各种矿产种类在旬阳都可以找到分布。 805 | 旬阳水利资源较为丰富。 806 | 已建成的旬河达铭湖坝是旬河下游重要的水利工程设施,已完工汉江蜀河电站与在建汉江旬阳电站都是大中型水电站。 807 | 但也有人认为汉江的过度梯度开发将会加剧周边生态环境的变化,引起环境问题。 808 | 809 | 小叶海金沙是香港常见海金沙科海金沙属的攀援草本蕨类植物。 810 | 分部地区包括台湾、福建、广东、海南、广西及云南、印度、缅甸和马来西亚。 811 | 小叶海金沙树皮富含树脂,可以从伤口分泌乳状树液,蔓生攀援可高达7米。 812 | 茎细而弱,叶薄革质,不育叶矩圆形羽状,可育叶小羽片卵状三角形。 813 | 美国佛罗里达大沼泽公园面临生态浩劫,迅速侵略与破坏当地生态系统,如野火燎原一般难以收拾。 814 | 815 | 岚皋县是中国陕西省安康市所辖的一个县。 816 | 总面积为1851平方公里,2015年人口为16.7万人。 817 | 下辖12个镇: 818 | 819 | 紫阳县是中国陕西省安康市所辖的一个县,位于陕西南部,大巴山之北,邻接四川省和重庆市。 820 | 总面积为2204平方公里,2002年人口为34万人。 821 | 紫阳县因曾在该地修行悟道的道教南派创始人张伯端而得名,是中国唯一以道教名号命名的县。 822 | 下辖17个镇: 823 | 824 | 汉滨区是中国陕西省安康市所辖的唯一市辖区,为市政府驻地。 825 | 总面积为3652平方公里,2002年人口为94万人。 826 | 下辖4个街道办事处、24个镇: 827 | 828 | 山阳县是中国陕西省商洛市所辖的一个县。 829 | 总面积为3515平方公里,2002年人口为42万人。 830 | 西晋泰始三年(267年)分商县置,因县境有丰阳川而得名。 831 | 属上洛郡辖。 832 | 太康年间(280-289)以前已废县。 833 | (《太平寰宇记》卷141〈山南西道〉商州条)北魏太安二年(456年)复设丰阳县,属上庸郡。 834 | 隋朝时属商州。 835 | 唐朝二度改属京兆府。 836 | 北宋时属永与军路,咸平元年(998年)改名山阳县,因县城地处商山之南而得名。 837 | 金朝时废县为镇。 838 | 元朝时复设丰阳县。 839 | 明朝属西安府商州,成化十二年(1476)改名山阳县。 840 | 清朝属商州直隶州。 841 | 民国初属汉中道,后属陕西省第四行政督察区。 842 | 解放后属商雒专区,1969年改属商洛地区,2001年12月属地级商洛市。 843 | 下辖2个街道办事处、16个镇: 844 | 845 | 商州区是中国陕西省商洛市所辖的唯一市辖区,为市政府驻地。 846 | 位于陕西省东南部,区政府驻地为南街,面积为2672平方公里,人口55万人。 847 | 西汉地属弘农郡,辖区相当于今河南省黄河以南、伊河以西及淅川、丹江流域。 848 | 治所在弘农(今河南灵宝貌略镇北),领11县,在陕西境内的上洛、商县两县,大体相当于现今的商洛地域。 849 | 上洛这一地名,因居洛河上游故名,战国时即有。 850 | 汉朝忌水,将上洛改为上雒,包括今商州区、洛南县。 851 | 北周宣政元年(578年)改洛州为商州;治所设上格(今商州城)领上洛郡(含今商州、山阳、丹凤、商南4县及镇柞部分地域);拒阳郡(今洛南地)。 852 | 元代商州先属安西路,后属奉元路管辖。 853 | 民国二年(1913年)废州改设商县(今商州区、丹凤县西南部)、洛南县(今洛南及丹凤县东北部)、柞水县(今柞水县西部及镇安县东北部),属关中道。 854 | 1988年6月改商县为商州市。 855 | 2002年1月改商州市为商州区。 856 | 下辖4个街道办事处、14个镇: 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 | 第四回 薄命女偏逢薄命郎 葫芦僧乱判葫芦案 887 | 遭薛蟠杀害之人名叫冯渊。 888 | 冯渊年十九,本好男风,遇被拐后长大之英莲,愿结良缘,遂于拐贩处把英莲买下,拐贩却又重卖于薛蟠。 889 | 冯渊与薛蟠相夺英莲,豪强者胜,冯渊遇害。 890 | 家人告了一年的状,竟无人作主。 891 | 由贾政举荐,新任应天府尹的贾雨村恰巧受理此案。 892 | 最初贾雨村本想明断,却被府中门子(昔日葫芦寺沙弥)劝阻,门子把薛蟠及本省贾、史、王、薛四大家族之间的利害关系相告,点名这是为官不败的护管符。 893 | 贾雨村徇私枉法,依从门子之计不究薛蟠,草草断案为薛家代为掩饰,并在事后致信贾政和王子腾。 894 | 薛夫人带同其子薛蟠、其女薛宝钗进都中,暂居贾府梨香院。 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 | 尔后凤姐和宝玉去看望秦可卿,宝玉落泪。 939 | 宝玉走后,凤姐又和可卿深谈良久方告辞。 940 | 凤姐在回园途中,遇见贾瑞。 941 | 贾瑞言辞挑逗凤姐,凤姐表面迎合,内心却十分恼怒。 942 | 秦可卿病情渐重,凤姐不敢将实情告诉贾母。 943 | 凤姐回家后听平儿说贾瑞要来请安,知其用意,准备用计处置。 944 | 第十二回 王熙凤毒设相思局 贾天祥正照风月鉴 945 | 贾瑞向凤姐“请安”,凤姐假意迎合,约其起更之后在荣府西边穿堂幽会。 946 | 贾瑞喜不自胜,如期赴约,空等一宿,回家后被代儒责罚。 947 | 贾瑞仍不死心,过了几天又去找凤姐,凤姐见他仍不悔改,又约他当晚在房后空屋相见。 948 | 贾瑞不知是计,再度赴约,被贾蔷、贾蓉扣住,各勒索五十两,又被粪尿泼身。 949 | 贾瑞回家后即发重病,久治不愈。 950 | 跛足道人赠“风月宝鉴”让贾瑞只照反面,贾瑞偏照正面,一命呜呼。 951 | 冬底,林如海病重,贾母让贾琏送黛玉回扬州。 952 | 第十三回 秦可卿死封龙禁尉 王熙凤协理宁国府(脂砚斋称原题为“秦可卿淫丧天香楼”,有的版本是“上”) 953 | 秦可卿病故,托梦给凤姐,叮嘱“盛筵必散”,居安思危,并告知近日贾府将有大喜事。 954 | 宝玉听说可卿身故,急火攻心而吐血,连夜赶去吊唁。 955 | 宁府来了许多人,尤氏姊妹也都来了。 956 | 贾珍极其悲痛,愿为秦可卿的丧礼尽其所有,用了薛蟠送来的原为义忠亲王老千岁准备的棺木。 957 | 秦可卿的丫鬟瑞珠也触柱而死,贾珍以孙女之礼葬之。 958 | 贾珍又为贾蓉捐了个龙禁尉的官职。 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 | 贾府上下为元妃省亲一事奔忙,从姑苏采买12女孩住梨香院学戏(薛姨妈家另搬到园子东北角),贾蔷总管,又请得妙玉入主园内的道观。 1000 | 元宵节至,元妃归省,元妃点戏《豪宴》、《乞巧》、《仙缘》、《离魂》四出,赏赐龄官,又命宝玉及众姐妹以景为题作诗。 1001 | -------------------------------------------------------------------------------- /src/models/RNN.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | 5 | class RNNConfig: 6 | hidden_size = 256 7 | vocab_size = 50000 8 | requires_grad = True 9 | pretrain_embeddings = False 10 | pretrain_embeddings_path = None 11 | drop_out = 0.3 12 | num_class = 10 13 | num_layers = 2 14 | bidirectional = True 15 | embed_size = 300 16 | 17 | 18 | class RNNClassifier(nn.Module): 19 | def __init__(self): 20 | super(RNNClassifier, self).__init__() 21 | if RNNConfig.pretrain_embeddings: 22 | self.embeddings = nn.Embedding.from_pretrained(RNNConfig.pretrain_embeddings_path) 23 | else: 24 | self.embeddings = nn.Embedding(RNNConfig.vocab_size, RNNConfig.embed_size) 25 | # self.lstm = nn.LSTM(input_size=RNNConfig.embed_size, 26 | # hidden_size=RNNConfig.hidden_size, 27 | # batch_first=True, 28 | # num_layers=RNNConfig.num_layers, 29 | # bidirectional=RNNConfig.bidirectional) 30 | self.gru = nn.GRU(input_size=RNNConfig.embed_size, 31 | hidden_size=RNNConfig.hidden_size, 32 | batch_first=True, 33 | num_layers=RNNConfig.num_layers, 34 | bidirectional=RNNConfig.bidirectional) 35 | # self.rnn = nn.RNN(input_size=RNNConfig.embed_size, 36 | # hidden_size=RNNConfig.hidden_size, 37 | # batch_first=True, 38 | # num_layers=RNNConfig.num_layers, 39 | # bidirectional=RNNConfig.bidirectional) 40 | self.drop_out = nn.Dropout(RNNConfig.drop_out) 41 | self.fc = nn.Linear(2*RNNConfig.hidden_size, RNNConfig.num_class) 42 | 43 | def forward(self, x): 44 | embedding = self.embeddings(x) # [batch_size,seq_len,embed_size] 45 | # hidden, _ = self.lstm(embedding) # [batch_size,seq_len,hidden_size] 46 | hidden, _ = self.gru(embedding) # [batch_size,seq_len,2*hidden_size] 47 | 48 | hidden = self.drop_out(hidden) 49 | hidden = hidden[:, -1, :] # 获取最后一层的输出 50 | prob = self.fc(hidden) 51 | return prob 52 | -------------------------------------------------------------------------------- /src/models/TextCNN.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | 7 | class Config: 8 | learning_rate = 1e-3 # 学习率 9 | filter_sizes = (2, 3, 4) # 卷积核尺寸 10 | num_filters = 256 11 | n_vocab = 50000 12 | embedding_pretrained = False 13 | embed = 300 14 | num_classes = 14 15 | 16 | 17 | 18 | class Model(nn.Module): 19 | def __init__(self, config): 20 | super(Model, self).__init__() 21 | if config.embedding_pretrained is not None: 22 | self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False) 23 | else: 24 | self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=0) 25 | # self.embedding.weight.requires_grad=True 26 | self.convs = nn.ModuleList( 27 | [nn.Conv2d(1, config.num_filters, (k, config.embed)) for k in config.filter_sizes]) 28 | self.dropout = nn.Dropout(config.dropout) 29 | self.fc = nn.Linear(config.num_filters * len(config.filter_sizes), config.num_classes) 30 | 31 | def conv_and_pool(self, x, conv): 32 | x = F.relu(conv(x)).squeeze(3) # [128,256,32] 33 | x = F.max_pool1d(x, x.size(2)).squeeze(2) # [128,256] 34 | return x # [128,256] 35 | 36 | def forward(self, x): 37 | out = self.embedding(x) # # [128,32,300] 38 | out = out.unsqueeze(1) # [128,1,32,300] 39 | out = torch.cat([self.conv_and_pool(out, conv) for conv in self.convs], 1) # [128,1,32,300] 40 | out = self.dropout(out) # [128,768] 41 | out = self.fc(out) # [128,10] 42 | return out 43 | -------------------------------------------------------------------------------- /src/models/TextDGCNN.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | 7 | class TextDGCNN(nn.Module): 8 | def __init__(self, config): 9 | super(TextDGCNN, self).__init__() 10 | if config.embedding_pretrained is not None: 11 | self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False) 12 | else: 13 | self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=0) 14 | # self.embedding.weight.requires_grad=True 15 | self.dropout = nn.Dropout(config.dropout) 16 | self.dgcnn = nn.ModuleList( 17 | DGCNNLayer(config.embed, config.embed, k_size=param[0], dilation_rate=param[1]) for param in 18 | config.dgccn_params) 19 | self.fc = nn.Linear(config.embed, config.num_classes) 20 | self.position_embedding = nn.Embedding(512, config.embed) 21 | 22 | def forward(self, x): 23 | word_emb = self.embedding(x) # # [64,256,300] 24 | # pos_emb = self.position_embedding(x) 25 | mask = (x != 0) # [64,256] 26 | out = word_emb 27 | for dgcnn in self.dgcnn: 28 | out = dgcnn(out, mask) 29 | out = torch.max(out, dim=1)[0] 30 | out = self.fc(out) # [128,10] 31 | return out 32 | 33 | 34 | class DGCNNLayer(nn.Module): 35 | 36 | def __init__(self, in_channels, out_channels, k_size=3, dilation_rate=1, dropout=0.1): 37 | super(DGCNNLayer, self).__init__() 38 | self.k_size = k_size 39 | self.dilation_rate = dilation_rate 40 | self.hid_dim = out_channels 41 | self.pad_size = int(self.dilation_rate * (self.k_size - 1) / 2) 42 | self.dropout_layer = nn.Dropout(dropout) 43 | # self.liner_layer = nn.Linear(int(out_channels / 2), out_channels) 44 | self.glu_layer = nn.GLU() 45 | self.conv_layer = nn.Conv1d(in_channels, out_channels * 2, kernel_size=k_size, dilation=dilation_rate, 46 | padding=(self.pad_size,)) 47 | self.layer_normal = nn.LayerNorm(in_channels) 48 | self.sigmoid = nn.Sigmoid() 49 | 50 | def forward(self, x, mask=None): 51 | ''' 52 | 53 | :param x: shape: [batch_size, seq_length, channels(embeddings)] 54 | :return: 55 | ''' 56 | x_r = x 57 | x = x.permute(0, 2, 1) # [batch_size, 2*hidden_size, seq_length] 58 | x = self.conv_layer(x) # [batch_size, 2*hidden_size, seq_length] 59 | x = x.permute(0, 2, 1) # [batch_size, seq_length, 2*hidden_size] 60 | x = self.glu_layer(x) # [batch_size, seq_length, hidden_size] 61 | x = self.dropout_layer(x) # 62 | mask = mask.unsqueeze(2).repeat(1, 1, self.hid_dim).float() 63 | x = x * mask 64 | return self.layer_normal(x + x_r) 65 | 66 | 67 | class SelfAttentionLayer(nn.Module): 68 | def __init__(self, hid_dim, n_heads, dropout, device): 69 | super(SelfAttentionLayer, self).__init__() 70 | self.hid_dim = hid_dim 71 | self.n_heads = n_heads 72 | assert self.hid_dim % n_heads == 0 73 | 74 | self.w_q = nn.Linear(hid_dim, hid_dim) 75 | self.w_k = nn.Linear(hid_dim, hid_dim) 76 | self.w_v = nn.Linear(hid_dim, hid_dim) 77 | 78 | self.fc = nn.Linear(hid_dim, hid_dim) 79 | 80 | self.dropout = nn.Dropout(dropout) 81 | self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads])).to(device) 82 | 83 | def forward(self, q, k, v, mask=None): 84 | ''' 85 | 86 | :param q: shape [batch_size, seq_length, hid_dim] 87 | :param k: shape [batch_size, seq_length, hid_dim] 88 | :param v: shape [batch_size, seq_length, hid_dim] 89 | :param mask: 90 | :return: 91 | ''' 92 | batch_size = q.shape[0] 93 | 94 | Q = self.w_q(q) 95 | K = self.w_k(k) 96 | V = self.w_v(v) 97 | 98 | # Q,K,V shape [batch_size, n_heads, seq_length, hid_dim // n_heads] 99 | 100 | Q = Q.contiguous().view(batch_size, -1, self.n_heads, self.hid_dim // self.n_heads).permute(0, 2, 1, 3) 101 | K = K.contiguous().view(batch_size, -1, self.n_heads, self.hid_dim // self.n_heads).permute(0, 2, 1, 3) 102 | V = V.contiguous().view(batch_size, -1, self.n_heads, self.hid_dim // self.n_heads).permute(0, 2, 1, 3) 103 | 104 | # energy [batch_size, n_heads, seq_length, seq_length] 105 | energy = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale 106 | 107 | if mask is not None: 108 | energy = energy.masked_fill(mask == 0, -1e10) 109 | # attention [batch_size, n_heads, seq_length, seq_length] 110 | attention = self.dropout(torch.softmax(energy, dim=-1)) 111 | # x [batch_size, n_heads, seq_length, hid_dim // n_heads] 112 | x = torch.matmul(attention, V) 113 | 114 | x = x.contiguous().permute(0, 2, 1, 3) 115 | # x [batch_size, seq_length, hid_dim] 116 | x = x.contiguous().view(batch_size, -1, self.n_heads * (self.hid_dim // self.n_heads)) 117 | 118 | x = self.fc(x) 119 | 120 | if mask is not None: 121 | mask = mask.squeeze(1).squeeze(1) 122 | mask = mask.unsqueeze(2).repeat(1, 1, self.hid_dim).float() 123 | x = x * mask 124 | # [batch_size, seq_length, hid_dim] 125 | return x -------------------------------------------------------------------------------- /src/models/bert.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | from transformers import BertModel, BertConfig 3 | 4 | 5 | class Model(nn.Module): 6 | def __init__(self, config): 7 | super(Model, self).__init__() 8 | model_config = BertConfig.from_pretrained( 9 | config.bert_path, num_labels=config.num_classes) 10 | self.bert = BertModel.from_pretrained(config.bert_path, 11 | config=model_config) 12 | for param in self.bert.parameters(): 13 | param.requires_grad = True 14 | self.fc = nn.Linear(config.hidden_size, config.num_classes) 15 | 16 | def forward(self, x): 17 | context = x[0] # 输入的句子 18 | mask = x[1] # 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0] 19 | token_type_ids = x[2] 20 | _, pooled = self.bert(context, 21 | attention_mask=mask, 22 | token_type_ids=token_type_ids) 23 | out = self.fc(pooled) 24 | return out -------------------------------------------------------------------------------- /src/prepare_dataset.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from tqdm import tqdm 3 | from sklearn.model_selection import train_test_split 4 | import pandas as pd 5 | from collections import defaultdict 6 | import re 7 | import os 8 | import json 9 | 10 | 11 | def count_text(category_path): 12 | """ 13 | 统计总体语料的分布情况 14 | :param category_path: 语料路径 15 | :return: 不同种类的语料字典 16 | """ 17 | if os.path.exists(category_path): 18 | # 语料的路径 19 | category_path = category_path + '/*' # 匹配所有的目录 20 | total = {} # 语料总数 21 | for dir in glob(category_path): 22 | total[dir] = len(glob(dir + '/*.txt')) # 每个类别下文件的数量 23 | print(total) 24 | print("文件共{}类,总的文件的数量:{}".format(len(total), sum(total.values()))) 25 | else: 26 | raise FileExistsError('{}文件的路径不存在'.format(category_path)) 27 | return total 28 | 29 | 30 | def cut_corpus(path): 31 | """ 32 | 切分语料集 训练集 验证集 测试集,比例0.7:0.15:0.15 33 | :param path: 语料路径list 34 | :return: 切分后的数据集和标签 35 | """ 36 | label = re.findall(r"[\u4e00-\u9fa5]+", path) # 匹配汉字 37 | files = glob(path + '/*.txt') # 匹配txt文件的绝对路径 38 | # 切分数据集 39 | train, test = train_test_split(files, test_size=0.3, shuffle=True, random_state=2020) 40 | valid, test = train_test_split(test, test_size=0.5, shuffle=True, random_state=2021) 41 | print("train:{} test:{} valid:{}".format(len(train), len(test), len(valid))) 42 | return train, test, valid, label 43 | 44 | 45 | def read_data(path, label=None, debug=False, frac=1): 46 | """ 47 | 读取文件中的数据title content 48 | :param path: 每条语料的路径信息list 49 | :param debug: 采样模式 50 | :param frac: 采样的比例 51 | :return: 52 | """ 53 | titles = [] 54 | contents = [] 55 | for file in tqdm(path): 56 | with open(file, 'r', encoding='utf-8') as obj: 57 | data = obj.readlines() 58 | title = data[0].strip() 59 | content = [sen.strip() for sen in data[1:]] 60 | titles.append(title) 61 | contents.append(''.join(content)) 62 | 63 | title_content = defaultdict(list) 64 | 65 | if len(titles) == len(contents): 66 | title_content['title'] = titles 67 | title_content['content'] = contents 68 | title_content['label'] = [label] * len(titles) 69 | else: 70 | raise ValueError('数据titles和contents数量不一致') 71 | df = pd.DataFrame(title_content, columns=['title', 'content', 'label']) 72 | if debug: 73 | # 采样 74 | df = df.sample(frac=frac, random_state=2020).reset_index(drop=True) 75 | print('采样的样本数量{}'.format(df.shape[0])) 76 | return df 77 | 78 | 79 | def writ_to_csv(dictionary, filename='train'): 80 | """ 81 | 将数据写入csv文件 82 | :param dictionary: 字典格式 83 | :return: 84 | """ 85 | df = pd.DataFrame(dictionary, columns=['title', 'content', 'label']) 86 | df.to_csv('{}.csv'.format(filename), sep='\t', index=False) 87 | print() 88 | print('writing succesfully') 89 | 90 | 91 | def process(path, filename='train', frac=1): 92 | """ 93 | 读取数据文件将数据写入csv文件 title content label 94 | :param path: 数据文件的路径dict 95 | :param filename: 保存文件命名 96 | :return: None 97 | """ 98 | print('loading {}'.format(filename)) 99 | sample = [] 100 | for label, data in path.items(): 101 | under_sample = read_data(data, label, debug=True, frac=frac) 102 | sample.append(under_sample) 103 | df = pd.concat(sample, axis=0) 104 | print("{}文件的数据量为:{}".format(filename, df.shape[0])) 105 | # 保存文件的路径 106 | base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 107 | save_path = base_path + '/data/' + filename + '.csv' 108 | df.to_csv(save_path, sep='\t', index=False) 109 | print('{} writing succesfully'.format(save_path)) 110 | 111 | 112 | def write_label_id(train_path,label_path): 113 | """标签映射为id""" 114 | data = pd.read_csv(train_path).dropna() 115 | label = data['label'].unique() 116 | print('标签:{}'.format(label)) 117 | label2id = dict(zip(label, range(len(label)))) 118 | json.dump(label2id, open(label_path, 'w', encoding='utf-8')) 119 | 120 | 121 | if __name__ == '__main__': 122 | root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 123 | category_path = root_path + '/data/THUCNews/' 124 | # 语料的路径 125 | dir_dict = count_text(category_path) 126 | train_path = defaultdict(list) 127 | test_path = defaultdict(list) 128 | valid_path = defaultdict(list) 129 | for path in dir_dict.keys(): 130 | # 切分数据集 131 | train, test, valid, label = cut_corpus(path) 132 | # 保存数据到字典 133 | train_path[label[0]] = train 134 | test_path[label[0]] = test 135 | valid_path[label[0]] = valid 136 | 137 | process(train_path, filename='train', frac=0.6) 138 | process(test_path, filename='test', frac=0.5) 139 | process(valid_path, filename='valid', frac=0.5) 140 | 141 | 142 | train_path = root_path + '/data/train.csv' 143 | label_path = root_path + '/data/label2id.json' 144 | # 生成标签到id的json文件 145 | write_label_id(train_path, label_path) -------------------------------------------------------------------------------- /src/prepare_distill_dataset.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from tqdm import tqdm 3 | from sklearn.model_selection import train_test_split 4 | import pandas as pd 5 | from collections import defaultdict 6 | import re 7 | import os 8 | import json 9 | 10 | 11 | def count_text(category_path): 12 | """ 13 | 统计总体语料的分布情况 14 | :param category_path: 语料路径 15 | :return: 不同种类的语料字典 16 | """ 17 | if os.path.exists(category_path): 18 | # 语料的路径 19 | category_path = category_path + '/*' # 匹配所有的目录 20 | total = {} # 语料总数 21 | for dir in glob(category_path): 22 | total[dir] = len(glob(dir + '/*.txt')) # 每个类别下文件的数量 23 | print(total) 24 | print("文件共{}类,总的文件的数量:{}".format(len(total), sum(total.values()))) 25 | else: 26 | raise FileExistsError('{}文件的路径不存在'.format(category_path)) 27 | return total 28 | 29 | 30 | def cut_corpus(path): 31 | """ 32 | 切分语料集 训练集 验证集 测试集,比例0.7:0.15:0.15 33 | :param path: 语料路径list 34 | :return: 切分后的数据集和标签 35 | """ 36 | label = re.findall(r"[\u4e00-\u9fa5]+", path) # 匹配汉字 37 | files = glob(path + '/*.txt') # 匹配txt文件的绝对路径 38 | # 切分数据集 39 | train, test = train_test_split(files, test_size=0.3, shuffle=True, random_state=2020) 40 | valid, test = train_test_split(test, test_size=0.5, shuffle=True, random_state=2021) 41 | print("train:{} test:{} valid:{}".format(len(train), len(test), len(valid))) 42 | return train, test, valid, label 43 | 44 | 45 | def read_data(path, label=None, debug=False, frac=1): 46 | """ 47 | 读取文件中的数据title content 48 | :param path: 每条语料的路径信息list 49 | :param debug: 采样模式 50 | :param frac: 采样的比例 51 | :return: 52 | """ 53 | titles = [] 54 | contents = [] 55 | for file in tqdm(path): 56 | with open(file, 'r', encoding='utf-8') as obj: 57 | data = obj.readlines() 58 | title = data[0].strip() 59 | content = [sen.strip() for sen in data[1:]] 60 | titles.append(title) 61 | contents.append(''.join(content)) 62 | 63 | title_content = defaultdict(list) 64 | 65 | if len(titles) == len(contents): 66 | title_content['content'] = contents 67 | title_content['label'] = [label] * len(titles) 68 | else: 69 | raise ValueError('数据titles和contents数量不一致') 70 | df = pd.DataFrame(title_content, columns=['title', 'content', 'label']) 71 | if debug: 72 | # 采样 73 | df = df.sample(frac=frac, random_state=2020).reset_index(drop=True) 74 | print('采样的样本数量{}'.format(df.shape[0])) 75 | return df 76 | 77 | 78 | def writ_to_csv(dictionary, filename='train'): 79 | """ 80 | 将数据写入csv文件 81 | :param dictionary: 字典格式 82 | :return: 83 | """ 84 | df = pd.DataFrame(dictionary, columns=['title', 'content', 'label']) 85 | df.to_csv('{}.csv'.format(filename), sep='\t', index=False) 86 | print() 87 | print('writing succesfully') 88 | 89 | 90 | def process(path, filename='train', frac=1): 91 | """ 92 | 读取数据文件将数据写入csv文件 title content label 93 | :param path: 数据文件的路径dict 94 | :param filename: 保存文件命名 95 | :return: None 96 | """ 97 | print('loading {}'.format(filename)) 98 | sample = [] 99 | for label, data in path.items(): 100 | under_sample = read_data(data, label, debug=True, frac=frac) 101 | sample.append(under_sample) 102 | df = pd.concat(sample, axis=0) 103 | print("{}文件的数据量为:{}".format(filename, df.shape[0])) 104 | # 保存文件的路径 105 | base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 106 | save_path = base_path + '/data/' + filename + '.csv' 107 | df.to_csv(save_path, sep='\t', index=False) 108 | print('{} writing succesfully'.format(save_path)) 109 | 110 | 111 | def write_label_id(train_path, label_path): 112 | """标签映射为id""" 113 | data = pd.read_csv(train_path).dropna() 114 | label = data['label'].unique() 115 | print('标签:{}'.format(label)) 116 | label2id = dict(zip(label, range(len(label)))) 117 | json.dump(label2id, open(label_path, 'w', encoding='utf-8')) 118 | 119 | 120 | def convert_distll_data(data, label_path, save_path): 121 | """ 122 | 数据转换为蒸馏需要的数据 123 | :param data: 原始data 124 | :param label_path: 标签到id的映射文件 125 | :param save_path: 文件保存路径 126 | :return: 127 | """ 128 | # 读取label->id的映射 129 | label2id = json.load(open(label_path, 'r', encoding='utf-8')) 130 | print(label2id) 131 | data['label'] = data['label'].map(label2id) 132 | print('data', data.head(5)) 133 | data.to_csv(save_path, sep='\t',index=False) 134 | print('文件:{}保存成功'.format(save_path)) 135 | 136 | 137 | if __name__ == '__main__': 138 | root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 139 | category_path = root_path + '/data/THUCNews/' 140 | # # 语料的路径 141 | # dir_dict = count_text(category_path) 142 | # train_path = defaultdict(list) 143 | # test_path = defaultdict(list) 144 | # valid_path = defaultdict(list) 145 | # for path in dir_dict.keys(): 146 | # # 切分数据集 147 | # train, test, valid, label = cut_corpus(path) 148 | # # 保存数据到字典 149 | # train_path[label[0]] = train 150 | # test_path[label[0]] = test 151 | # valid_path[label[0]] = valid 152 | # 153 | # process(train_path, filename='train', frac=0.6) 154 | # process(test_path, filename='test', frac=0.5) 155 | # process(valid_path, filename='valid', frac=0.5) 156 | 157 | train_path = root_path + '/data/train.csv' 158 | label_path = root_path + '/data/label2id.json' 159 | # 生成标签到id的json文件 160 | # write_label_id(train_path, label_path) 161 | train_data = pd.read_csv(root_path + '/data/train.csv', sep='\t') 162 | test_data = pd.read_csv(root_path + '/data/test.csv', sep='\t') 163 | dev_data = pd.read_csv(root_path + '/data/valid.csv', sep='\t') 164 | train_distill_path = root_path + '/data/train_distill.tsv' 165 | test_distill_path = root_path + '/data/test_distill.tsv' 166 | dev_distill_path = root_path + '/data/dev_distill.tsv' 167 | convert_distll_data(train_data, label_path, train_distill_path) 168 | convert_distll_data(test_data, label_path, test_distill_path) 169 | convert_distll_data(dev_data, label_path, dev_distill_path) -------------------------------------------------------------------------------- /src/rnn_dataset.py: -------------------------------------------------------------------------------- 1 | from torch.utils.data import Dataset 2 | import pandas as pd 3 | from src.utils import config 4 | from src.utils.tools import clean_symbols, query_cut, multiprocess_data 5 | from tqdm import tqdm 6 | import torch 7 | 8 | tqdm.pandas() 9 | 10 | 11 | def precess_data(data, word): 12 | # data["sentence"] = data['title'] 13 | # data["sentence"] = data['title'] + data['content'] 14 | # data["sentence"] = data['content'] 15 | data["sentence"] = data['content'] 16 | data['clean_sentence'] = data['sentence'].progress_apply(clean_symbols) 17 | # char粒度 18 | if not word: 19 | data['cut_sentence'] = data['clean_sentence'] 20 | data["cut_sentence"] = data['clean_sentence'].progress_apply(query_cut) 21 | # 标签映射到id 22 | data['category_id'] = data['label'].progress_apply(lambda x: x.strip()).map(config.label2id) 23 | return data 24 | 25 | 26 | class NewsDataset(Dataset): 27 | """数据集的创建""" 28 | 29 | def __init__(self, path, dictionary=None, tokenizer=None, word=False, debug=True): 30 | super(NewsDataset, self).__init__() 31 | self.word = word 32 | self.data = pd.read_csv(path, sep='\t').dropna() 33 | if debug: 34 | self.data = self.data.sample(n=1000, replace=True) 35 | self.data = multiprocess_data(self.data, precess_data, worker=15, word=self.word) 36 | self.dictionary = dictionary 37 | 38 | def __getitem__(self, i): 39 | data = self.data.iloc[i] 40 | text = data['cut_sentence'] # text数据 41 | labels = int(data['category_id']) 42 | if not self.word: 43 | text = text.split(' ') 44 | input_ids = [self.dictionary.indexer(t) for t in text] # 获取每个token的id 45 | output = { 46 | "token_ids": input_ids, 47 | "labels": labels 48 | } 49 | return output 50 | 51 | def __len__(self): 52 | return self.data.shape[0] 53 | 54 | 55 | def collate_fn(batch): 56 | """ 57 | 动态padding,返回Tensor 58 | :param batch: 59 | :return: 每个batch id和label 60 | """ 61 | 62 | def padding(indice, max_length, pad_idx=0): 63 | """ 64 | 填充每个batch的句子长度 65 | """ 66 | # pad_indice = [item + [pad_idx] * max(0, max_length - len(item)) for item in indice] 67 | pad_indice = [item + [pad_idx] * (max_length - len(item)) if len(item) < max_length else item[:max_length] for item in indice] 68 | return torch.tensor(pad_indice) 69 | 70 | token_ids = [data["token_ids"] for data in batch] 71 | # max_length = max([len(t) for t in token_ids]) # batch中样本的最大的长度 72 | max_length = 256 # batch中样本的最大的长度 73 | labels = torch.tensor([data["labels"] for data in batch]) 74 | 75 | token_ids_padded = padding(token_ids, max_length) # 填充每个batch的sample 76 | return token_ids_padded, labels 77 | -------------------------------------------------------------------------------- /src/train.py: -------------------------------------------------------------------------------- 1 | import time 2 | from src.utils import config 3 | from src.utils.tools import create_logger 4 | from importlib import import_module 5 | import argparse 6 | import numpy as np 7 | import torch 8 | from sklearn import metrics 9 | import torch.nn.functional as F 10 | from tqdm import tqdm 11 | from src.bert_dataset import BertDataset, collate_fn 12 | from torch.utils.data import DataLoader 13 | from transformers import (WEIGHTS_NAME, AdamW, BertConfig, 14 | BertForSequenceClassification, BertTokenizer, 15 | RobertaConfig, RobertaForSequenceClassification, 16 | RobertaTokenizer, 17 | XLNetConfig, XLNetForSequenceClassification, 18 | XLNetTokenizer, get_linear_schedule_with_warmup) 19 | from tensorboardX import SummaryWriter 20 | 21 | parse = argparse.ArgumentParser(description='文本分类') 22 | parse.add_argument('--model', type=str, default='BERT', help='选择模型: CNN, RNN, RCNN, RNN_Att, DPCNN, Transformer') 23 | parse.add_argument('--word', default=True, type=bool, help='词或者字符') 24 | parse.add_argument('--dictionary', default=config.dict_path, type=str, help='字典的路径') 25 | args = parse.parse_args() 26 | 27 | 28 | def train(config, model, train_iter, dev_iter, test_iter, model_name): 29 | start_time = time.time() 30 | model.train() 31 | print('User AdamW...') 32 | print(config.device) 33 | param_optimizer = list(model.named_parameters()) 34 | no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight'] 35 | optimizer_grouped_parameters = [{ 36 | 'params': [ 37 | p for n, p in param_optimizer 38 | if not any(nd in n for nd in no_decay) 39 | ], 40 | 'weight_decay': 41 | 0.01 42 | }, { 43 | 'params': 44 | [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 45 | 'weight_decay': 46 | 0.0 47 | }] 48 | # optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate) 49 | optimizer = AdamW(optimizer_grouped_parameters, 50 | lr=config.learning_rate, 51 | eps=config.eps) 52 | total_batch = 0 # 记录进行到多少batch 53 | dev_best_loss = float('inf') 54 | last_improve = 0 # 记录上次验证集loss下降的batch数 55 | flag = False # 记录是否很久没有效果提升 56 | writer = SummaryWriter(log_dir=config.log_path + '/' + time.strftime('%m-%d_%H.%M', time.localtime())) 57 | for epoch in range(config.num_epochs): 58 | print('Epoch [{}/{}]'.format(epoch + 1, config.num_epochs)) 59 | # scheduler.step() # 学习率衰减 60 | for i, (trains, mask, tokens, labels) in tqdm(enumerate(train_iter)): 61 | trains = trains.to(config.device) 62 | labels = labels.to(config.device) 63 | mask = mask.to(config.device) 64 | tokens = tokens.to(config.device) 65 | outputs = model((trains, mask, tokens)) 66 | model.zero_grad() 67 | loss = F.cross_entropy(outputs, labels) 68 | loss.backward() 69 | optimizer.step() 70 | # scheduler.step() 71 | if total_batch % 1000 == 0 and total_batch != 0: 72 | # 每多少轮输出在训练集和验证集上的效果 73 | true = labels.data.cpu() 74 | predic = torch.max(outputs.data, 1)[1].cpu() 75 | train_acc = metrics.accuracy_score(true, predic) 76 | dev_acc, dev_loss = evaluate(config, model, dev_iter) 77 | if dev_loss < dev_best_loss: 78 | dev_best_loss = dev_loss 79 | torch.save(model.state_dict(), config.save_path) 80 | improve = '*' 81 | last_improve = total_batch 82 | else: 83 | improve = '' 84 | time_dif = round(time.time() - start_time, 4) 85 | msg = 'Iter: {0:>6}, Train Loss: {1:>5.2}, Train Acc: {2:>6.2%}, Val Loss: {3:>5.2}, Val Acc: {4:>6.2%}, Time: {5} {6}' 86 | print( 87 | msg.format(total_batch, loss.item(), train_acc, dev_loss, 88 | dev_acc, time_dif, improve)) 89 | writer.add_scalar("loss/train", loss.item(), total_batch) 90 | writer.add_scalar("loss/dev", dev_loss, total_batch) 91 | writer.add_scalar("acc/train", train_acc, total_batch) 92 | writer.add_scalar("acc/dev", dev_acc, total_batch) 93 | model.train() 94 | total_batch += 1 95 | if total_batch - last_improve > config.require_improvement: 96 | # 验证集loss超过1000batch没下降,结束训练 97 | print("No optimization for a long time, auto-stopping...") 98 | flag = True 99 | break 100 | if flag: 101 | break 102 | writer.close() 103 | test(config, model, test_iter) 104 | 105 | 106 | def test(config, model, test_iter): 107 | # test 108 | model.load_state_dict(torch.load(config.save_path)) 109 | model.eval() 110 | start_time = time.time() 111 | test_acc, test_loss, test_report, test_confusion = evaluate(config, 112 | model, 113 | test_iter, 114 | test=True) 115 | msg = 'Test Loss: {0:>5.2}, Test Acc: {1:>6.2%}' 116 | print(msg.format(test_loss, test_acc)) 117 | print("Precision, Recall and F1-Score...") 118 | print(test_report) 119 | print("Confusion Matrix...") 120 | print(test_confusion) 121 | time_dif = round(time.time() - start_time, 4) 122 | print("Time usage:", time_dif) 123 | 124 | 125 | def evaluate(config, model, data_iter, test=False): 126 | model.eval() 127 | loss_total = 0 128 | predict_all = np.array([], dtype=int) 129 | labels_all = np.array([], dtype=int) 130 | with torch.no_grad(): 131 | for texts, mask, tokens, labels in tqdm(data_iter): 132 | texts = texts.to(config.device) 133 | labels = labels.to(config.device) 134 | mask = mask.to(config.device) 135 | tokens = tokens.to(config.device) 136 | outputs = model((texts, mask, tokens)) 137 | loss = F.cross_entropy(outputs, labels) 138 | loss_total += loss 139 | labels = labels.data.cpu().numpy() 140 | predic = torch.max(outputs.data, 1)[1].cpu().numpy() 141 | labels_all = np.append(labels_all, labels) 142 | predict_all = np.append(predict_all, predic) 143 | 144 | acc = metrics.accuracy_score(labels_all, predict_all) 145 | if test: 146 | report = metrics.classification_report(labels_all, 147 | predict_all, 148 | target_names=config.label_list, 149 | digits=4) 150 | confusion = metrics.confusion_matrix(labels_all, predict_all) 151 | return acc, loss_total / len(data_iter), report, confusion 152 | return acc, loss_total / len(data_iter) 153 | 154 | 155 | if __name__ == '__main__': 156 | model_name = args.model 157 | x = import_module('models.' + model_name) 158 | np.random.seed(1) 159 | torch.manual_seed(1) 160 | torch.cuda.manual_seed_all(1) 161 | torch.backends.cudnn.deterministic = True # 保证每次结果一样 162 | logger = create_logger(config.root_path + '/logs/train.log') 163 | 164 | logger.info('Building tokenizer') 165 | tokenizer = BertTokenizer.from_pretrained(config.bert_path) 166 | 167 | logger.info('Loading dataset') 168 | # 数据集的定义 169 | train_dataset = BertDataset(config.train_path, tokenizer=tokenizer, word=args.word) 170 | train_dataloader = DataLoader(train_dataset, 171 | batch_size=config.batch_size, 172 | collate_fn=collate_fn, 173 | shuffle=True) 174 | dev_dataset = BertDataset(config.valid_path, tokenizer=tokenizer, word=args.word) 175 | dev_dataloader = DataLoader(dev_dataset, 176 | batch_size=config.batch_size, 177 | collate_fn=collate_fn, 178 | shuffle=True) 179 | test_dataset = BertDataset(config.test_path, tokenizer=tokenizer, word=args.word) 180 | test_dataloader = DataLoader(test_dataset, 181 | batch_size=config.batch_size, 182 | collate_fn=collate_fn) 183 | logger.info('load network') 184 | model = x.Model(config).to(config.device) 185 | # 初始化参数 186 | 187 | logger.info('training model') 188 | train(config, model, train_dataloader, dev_dataloader, test_dataloader, model_name) 189 | # test(config, model, test_dataloader) # 只测试模型的效果 190 | -------------------------------------------------------------------------------- /src/train_fasttext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | import pandas as pd 4 | from tqdm import tqdm 5 | from fasttext import train_supervised 6 | import fasttext 7 | import os 8 | from __init__ import * 9 | from src.utils import config 10 | from src.utils.config import root_path 11 | from src.utils.tools import create_logger, clean_symbols, query_cut, rm_stop_word 12 | 13 | logger = create_logger(root_path + '/logs/Fasttext.log') 14 | tqdm.pandas() 15 | 16 | 17 | class Fasttext(object): 18 | """ 19 | 使用fasttext 训练文本分类的模型 20 | """ 21 | 22 | def __init__(self, 23 | train_raw_path=config.train_path, 24 | test_raw_path=config.test_path, 25 | valid_raw_path=config.valid_path, 26 | model_train_file=root_path + '/data/fast_train.txt', 27 | model_test_file=root_path + '/data/fast_test.txt', 28 | model_valid_file=root_path + '/data/fast_valid.txt', 29 | model_path=None): 30 | """ 31 | 初始化参数 32 | :param train_raw_path: 原始训练文件路径 33 | :param test_raw_path: 原始测试文件路径 34 | :param valid_raw_path: 原始验证文件路径 35 | :param model_train_file: 训练集文件路径 36 | :param model_test_file: 测试集文件路径 37 | :param model_valid_file: 验证集文件路径 38 | :param model_path: 模型路径 39 | """ 40 | if model_path is None or not os.path.exists(model_path): 41 | self.Train_raw_data = pd.read_csv(train_raw_path, sep='\t').dropna() 42 | self.Test_raw_data = pd.read_csv(test_raw_path, sep='\t').dropna() 43 | self.Valid_raw_data = pd.read_csv(valid_raw_path, sep='\t').dropna() 44 | if not os.path.exists(model_train_file): 45 | self.data_process(self.Train_raw_data, model_train_file) 46 | if not os.path.exists(model_test_file): 47 | self.data_process(self.Test_raw_data, model_test_file) 48 | if not os.path.exists(model_valid_file): 49 | self.data_process(self.Valid_raw_data, model_valid_file) 50 | self.train(model_train_file, model_test_file, model_valid_file) # 训练模型 51 | else: 52 | self.classifier = fasttext.load_model(model_path) # 导入模型 53 | 54 | def data_process(self, raw_data, model_data_file): 55 | """ 56 | 数据的预处理 57 | :param raw_data: 原始文本dataframe 58 | :param model_data_file: 保存的数据文件路径 59 | :return: None 60 | """ 61 | logger.info('Geneating data.') 62 | raw_data["sentence"] = raw_data['title'] + raw_data['content'] 63 | raw_data['clean_sentence'] = raw_data['sentence'].progress_apply(clean_symbols) 64 | # 去除标点符号 65 | raw_data["cut_sentence"] = raw_data['clean_sentence'].progress_apply(query_cut) 66 | # 去除停用词 67 | raw_data['stop_sentence'] = raw_data["cut_sentence"].progress_apply(rm_stop_word) 68 | raw_data['stop_sentence'] = raw_data['stop_sentence'].progress_apply(lambda x: ' '.join(x)) 69 | with open(model_data_file, 'w', encoding="utf-8") as f: 70 | for index, row in tqdm(raw_data.iterrows(), total=raw_data.shape[0]): 71 | outline = row['stop_sentence'] + "\t__label__" + row['label'] + "\n" # 生成的训练数据 72 | f.write(outline) 73 | 74 | def train(self, model_train_file, model_test_file, model_valid_file): 75 | """ 76 | 训练集、验证集和测试集的文件路径 77 | :param model_train_file: 78 | :param model_test_file: 79 | :param model_valid_file: 80 | :return: None 81 | """ 82 | # self.classifier = train_supervised(model_train_file, 83 | # label="__label__", 84 | # dim=300, 85 | # epoch=50, 86 | # lr=0.1, 87 | # wordNgrams=2, 88 | # loss='softmax', 89 | # minCount=5, 90 | # verbose=True) 91 | # 使用验证集自动调参的方式,没有验证集可以手动设置参数 92 | logger.info('Training model.') 93 | self.classifier = train_supervised(model_train_file, verbose=2, 94 | autotuneValidationFile=model_valid_file, 95 | autotuneModelSize="2M", 96 | autotuneDuration=600) 97 | logger.info('Saving model.') 98 | self.classifier.save_model(config.root_path + '/model/news.ftz') # 保存模型 99 | # 测试模型 100 | self.test(model_train_file, model_test_file) 101 | 102 | def test(self, model_train_file, model_test_file): 103 | """ 104 | 训练模型的测试 105 | :param model_train_file: 训练集路径 106 | :param model_test_file: 测试集路径 107 | :return: 108 | """ 109 | logger.info('Testing.') 110 | 111 | def score(result): 112 | """precision recall f1 score""" 113 | f1 = (result[1] * result[2] * 2) / (result[2] + result[1]) 114 | precision = result[1] 115 | recall = result[2] 116 | return precision, recall, f1 117 | 118 | test_result = self.classifier.test(model_test_file) # 测试集结果 119 | train_result = self.classifier.test(model_train_file) # 训练集结果 120 | 121 | # 返回精确率和召回率、F1-score 122 | train_score = score(train_result) 123 | test_score = score(test_result) 124 | print('训练集的precision:{:.4f} recall:{:.4f} f1-score:{:.4f}'.format(*train_score)) 125 | print('测试集的precision:{:.4f} recall:{:.4f} f1-score:{:.4f}'.format(*test_score)) 126 | 127 | def predict(self, text): 128 | """ 129 | 处理输入的文本数据 130 | :return: label,score 131 | """ 132 | logger.info('Predicting.') 133 | clean_text = clean_symbols(text) 134 | cut_text = query_cut(clean_text) # 分词后的句子 135 | label, score = self.classifier.predict(' '.join(cut_text)) 136 | res = {'label': label[0].split('__label__')[1], 137 | 'score': score[0]} 138 | return res 139 | 140 | 141 | if __name__ == '__main__': 142 | ft = Fasttext(model_path=config.root_path + '/model/news.ftz') 143 | res = ft.predict('9月3日,美国媒体报道,英国演员罗伯特·帕丁森的新冠病毒检测结果呈阳性,导致电影《蝙蝠侠》的拍摄停工。') 144 | print(res) 145 | -------------------------------------------------------------------------------- /src/utils/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import torch 4 | 5 | current_path = os.path.abspath(os.path.dirname(__file__)) 6 | root_path = os.path.split(os.path.split(current_path)[0])[0] 7 | 8 | data_path = root_path + '/data/THUCNews/' 9 | 10 | train_path = root_path + '/data/train.csv' 11 | test_path = root_path + '/data/test.csv' 12 | valid_path = root_path + '/data/valid.csv' 13 | 14 | label_path = root_path + '/data/label2id.json' 15 | # bert_path = root_path + '/model/wobert_torch/' 16 | # bert_path = root_path + '/model/goole_bert/' 17 | # bert_path = root_path + '/model/roberta/' 18 | bert_path = root_path + '/model/wonezha_L-12_H-768_A-12/' 19 | # bert_path = root_path + 'model/bert-chinese-wwm-ext' 20 | 21 | is_cuda = True 22 | device = torch.device('cuda') if is_cuda else torch.device('cpu') 23 | 24 | with open(root_path + '/data/stopwords.txt', "r", encoding='utf-8') as f: 25 | stopWords = [word.strip() for word in f.readlines()] 26 | 27 | with open(label_path, 'r', encoding='utf-8') as f: 28 | label2id = json.load(f) 29 | 30 | label_list = label2id.keys() 31 | model_name = 'Bert' 32 | save_path = root_path + '/model/saved_dict/bert_wo_cls.pt' 33 | # bert 34 | eps = 1e-8 35 | learning_rate = 2e-5 # 学习率 36 | embedding_pretrained = None 37 | batch_size = 64 38 | hidden_size = 768 39 | num_epochs = 100 40 | dropout = 0.3 # 随机失活 41 | require_improvement = 1000 # 若超过1000batch效果还没提升,则提前结束训练 42 | num_classes = len(label2id) # 类别数 43 | n_vocab = 50000 # 词表大小,在运行时赋值 44 | embed = 300 45 | 46 | # cnn 47 | # learning_rate = 1e-3 # 学习率 48 | # filter_sizes = (2, 3, 4) # 卷积核尺寸 49 | # num_filters = 256 50 | 51 | # lstm 52 | # hidden_size = 256 53 | # num_layers = 2 54 | # epochs = 64 55 | # pad_size = 128 56 | 57 | fast_path = root_path + '/model/fast.bin' 58 | w2v_path = root_path + '/model/w2v.bin' 59 | 60 | dict_path = root_path + '/data/vocab.bin' 61 | log_path = root_path + '/logs/' + model_name 62 | 63 | # rnn_att 64 | hidden_size2 = 256 -------------------------------------------------------------------------------- /src/utils/convert_tf_torch.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import torch 3 | from transformers import BertConfig, BertForPreTraining, load_tf_weights_in_bert, BertModel, BertTokenizer 4 | import logging 5 | import shutil 6 | 7 | logger = logging.getLogger('convert_tf_torch') 8 | 9 | 10 | def convert_tf_checkpoint_to_pytorch(tf_checkpoint_path, bert_config_file, pytorch_dump_path): 11 | # Initialise PyTorch model 12 | config = BertConfig.from_json_file(bert_config_file) 13 | logger.info("Building PyTorch model from configuration: {}".format(str(config))) 14 | model = BertForPreTraining(config) 15 | 16 | # Load weights from tf checkpoint 17 | load_tf_weights_in_bert(model, config, tf_checkpoint_path) 18 | 19 | # Save pytorch-model 20 | logger.info("Save PyTorch model to {}".format(pytorch_dump_path)) 21 | torch.save(model.state_dict(), pytorch_dump_path) 22 | 23 | 24 | 25 | 26 | 27 | 28 | if __name__ == "__main__": 29 | tf_checkpoint_path = './chinese_wonezha_L-12_H-768_A-12/bert_model.ckpt' # tf模型文件 30 | bert_config_file = './chinese_wonezha_L-12_H-768_A-12/bert_config.json' # tf模型配置 31 | pytorch_dump_path = './wonezha_bert/pytorch_model.bin' # 转换后的模型文件,配置文件和词典和tf的一致 32 | convert_tf_checkpoint_to_pytorch(tf_checkpoint_path, bert_config_file, pytorch_dump_path) 33 | # 复制配置文件和词典 34 | shutil.copy('./chinese_wonezha_L-12_H-768_A-12/bert_config.json','./wonezha_bert/config.json') 35 | shutil.copy('./chinese_wonezha_L-12_H-768_A-12/vocab.txt','./wonezha_bert/vocab.txt') 36 | # 测试转换后的模型 37 | model = BertModel.from_pretrained('./wonezha_bert/') 38 | logger.info('loading bert model') 39 | print(model) 40 | tokenizer = BertTokenizer.from_pretrained('./wonezha_bert/') 41 | text_dict = tokenizer.encode_plus('自然语言处理',return_tensors='pt') 42 | input_ids = text_dict['input_ids'] 43 | token_type_ids = text_dict['token_type_ids'] 44 | attention_mask= text_dict['attention_mask'] 45 | res = model(input_ids=input_ids, 46 | token_type_ids=token_type_ids, 47 | attention_mask=attention_mask) 48 | print(res) -------------------------------------------------------------------------------- /src/utils/tools.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import handlers 3 | import re 4 | from src.utils import config 5 | import jieba 6 | import json 7 | from tqdm import tqdm 8 | # jieba.enable_parallel(4) 9 | import pandas as pd 10 | import time 11 | from functools import partial, wraps 12 | from datetime import timedelta 13 | 14 | tqdm.pandas() 15 | 16 | 17 | def timethis(func=None, log=logging.getLogger(), loglevel=logging.INFO): 18 | ''' 19 | 计算程序运行时间的装饰器。 20 | ''' 21 | if func is None: 22 | return partial(timethis, log=log, loglevel=loglevel) 23 | log.addHandler(logging.StreamHandler()) 24 | log.setLevel(loglevel) 25 | 26 | @wraps(func) 27 | def wrapper(*args, **kwargs): 28 | start_time = time.time() 29 | log.info(f'running {func.__name__}') 30 | res = func(*args, **kwargs) 31 | end_time = time.time() 32 | log.info( 33 | f"Comparing trainings costs {end_time - start_time:.6} seconds.") 34 | return res 35 | 36 | return wrapper 37 | 38 | 39 | def get_time_dif(start_time): 40 | """获取已使用时间""" 41 | end_time = time.time() 42 | time_dif = end_time - start_time 43 | return timedelta(seconds=int(round(time_dif))) 44 | 45 | 46 | def create_logger(log_path): 47 | """ 48 | 日志的创建 49 | :param log_path: 50 | :return: 51 | """ 52 | level_relations = { 53 | 'debug': logging.DEBUG, 54 | 'info': logging.INFO, 55 | 'warning': logging.WARNING, 56 | 'error': logging.ERROR, 57 | 'crit': logging.CRITICAL 58 | } # 日志级别关系映射 59 | 60 | logger = logging.getLogger(log_path) 61 | fmt = '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s' 62 | format_str = logging.Formatter(fmt) # 设置日志格式 63 | logger.setLevel(level_relations.get('info')) # 设置日志级别 64 | sh = logging.StreamHandler() # 往屏幕上输出 65 | sh.setFormatter(format_str) # 设置屏幕上显示的格式 66 | th = handlers.TimedRotatingFileHandler( 67 | filename=log_path, when='D', backupCount=3, 68 | encoding='utf-8') # 往文件里写入#指定间隔时间自动生成文件的处理器 69 | th.setFormatter(format_str) # 设置文件里写入的格式 70 | logger.addHandler(sh) # 把对象加到logger里 71 | logger.addHandler(th) 72 | 73 | return logger 74 | 75 | 76 | 77 | 78 | def rm_stop_word(wordList): 79 | ''' 80 | @description: delete stop_word 81 | @param {type} wordList: input data 82 | @return: 83 | list of cut word 84 | ''' 85 | wordlist = [w for w in wordList if w not in config.stopWords] 86 | return wordlist 87 | 88 | 89 | def query_cut(query): 90 | ''' 91 | @description: word segment 92 | @param {type} query: input data 93 | @return: 94 | list of cut word 95 | ''' 96 | return jieba.lcut(query) 97 | 98 | 99 | def clean_symbols(text): 100 | """ 101 | 对特殊符号做一些处理 102 | """ 103 | text = re.sub('[0-9]+', " NUM ", str(text)) 104 | text = re.sub('[!!]+', "!", text) 105 | # text = re.sub('!', '', text) 106 | text = re.sub('[??]+', "?", text) 107 | text = re.sub("[a-zA-Z#$%&\'()*+,-./:;:<=>@,。★、…【】《》“”‘’'!'[\\]^_`{|}~]+", " OOV ", text) 108 | return re.sub("\s+", " ", text) 109 | 110 | 111 | def processes_data(data,word): 112 | """预处理数据""" 113 | # self.data["sentence"] = self.data['title'] 114 | self.data["sentence"] = self.data['title'] + self.data['content'] 115 | # data["sentence"] = data['content'] 116 | data['clean_sentence'] = data['sentence'].progress_apply(clean_symbols) 117 | data["cut_sentence"] = data['clean_sentence'].progress_apply(' '.join(query_cut)) 118 | 119 | 120 | # 标签映射到id 121 | data['category_id'] = data['label'].progress_apply(lambda x: x.strip()).map(config.label2id) 122 | # char粒度 123 | if not word: 124 | data['raw_words'] = data["cut_sentence"].apply(lambda x: " ".join(list("".join(x.split(' '))))) 125 | 126 | return data 127 | 128 | 129 | def multiprocess_data(data, func, worker=None, word=None): 130 | """多进程处理数据""" 131 | cpu_count = multiprocessing.cpu_count() // 2 # cpu核心数 132 | if worker == -1 or worker > cpu_count: 133 | worker = cpu_count 134 | print('used cpu:' + format(worker)) 135 | length = len(data) 136 | chunk_size = length // worker # 切分数据 137 | start = 0 138 | end = 0 139 | pool = multiprocessing.Pool(processes=worker) 140 | result = [] 141 | start_time = time.time() 142 | while end < length: 143 | end = start + chunk_size 144 | if end > length: 145 | end = length 146 | res = pool.apply_async(func, (data[start:end], word)) 147 | start = end 148 | result.append(res) 149 | pool.close() # 关闭进程池 150 | pool.join() # 主进程等待进程结束 151 | end_time = time.time() 152 | print('multiprocess time: %.4f seconds.' % (end_time - start_time)) 153 | 154 | # result = np.concatenate([i.get() for i in result], axis=0) 155 | total_data = pd.concat([i.get() for i in result], axis=0) # 合并数据 156 | return total_data 157 | 158 | 159 | 160 | if __name__ == '__main__': 161 | # str1 = '我的世界充3满奸诈,dfs ,的 各种,111, 222,放手' 162 | # st = clean_str(str1) 163 | # print(st) 164 | --------------------------------------------------------------------------------