starter_code.ipynb 28.7 KB
Newer Older
202205008011 committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 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 225 226 227 228 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 254 255 256 257 258 259 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 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 广告点击率预测\n",
    "这个项目的主要的目的是通过给定的广告信息和用户信息来预测一个广告被点击与否。 如果广告有很大概率被点击就展示广告,如果概率低,就不展示。 因为如果广告没有被点击,对双方(广告主、平台)来讲都没有好处。所以预测这个概率非常重要,也是此项目的目标。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在这个项目中,你需要完成以下的任务:\n",
    "- ``数据的读取和理解``: 把给定的.csv文件读入到内存,并通过pandas做数据方面的统计以及可视化来更深入地理解数据。\n",
    "- ``特征构造``: 从原始特征中衍生出一些新的特征,这部分在机器学习领域也是很重要的工作。\n",
    "- ``特征的转化``: 特征一般分为连续型(continuous)和类别型(categorical), 需要分别做不同的处理。\n",
    "- ``特征选择``: 从已有的特征中选择合适的特征,这部分也是很多项目中必不可少的部分。 \n",
    "- ``模型训练与评估``: 通过交叉验证方式来训练模型,这里需要涉及到网格搜索等技术。\n",
    "\n",
    "\n",
    "你需要完成标记为`TODO`的部分。 \n",
    "\n",
    "另外,提交作业时候的注意点:\n",
    "> 1. 不要试图去创建另外一个.ipynb文件,所有的程序需要在`starter_code.ipynb`里面实现。很多的模块已经帮你写好,不要试图去修改已经定义好的函数以及名字。 当然,自己可以按需求来创建新的函数。但一定要按照给定的框架来写程序,不然判作业的时候会出现很多问题。 \n",
    "> 2. 作业可以讨论,但请自己完成。让我们一起遵守贪心学院的`honor code`。\n",
    "\n",
    "好了,我们开始吧!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. 数据读取和理解\n",
    "对于``.CSV``的文件,我们一般使用``pandas``工具来读取,读取之后的数据会存放在``dataframe``中。在此项目中,我们使用的是``kaggle``的一个竞赛数据,具体官网地址为:https://www.kaggle.com/c/avazu-ctr-prediction 。 训练和测试数据分别为``train.csv``和``test.csv``。 官网提供的数据比较大,压缩之后的已经达到1G以上。 在此项目中,我们特意去采样了一部分数据。 采样的规则为:从``train.csv``文件中读取头``400000``个样本,并重命名为``train_subset.csv``。 之后在这个数据的基础上我们会进一步分为训练集和测试集。所以解压完.zip文件后会发现只有一个``train_subset.csv``文件。 \n",
    "\n",
    "项目中请``务必要使用``我们提供的样本(当然,你感兴趣也可以在原始样本里尝试,但提交作业时代码一定要基于我们给定的)。 这里给一个小提示: 如果你想在某一个``.csv``文件中只提取头``1000``个样本,在``linux``或者``os``下可以使用如下命令:``head -n1000 train.csv > train_subset.csv``, 意思就是从``train.csv``中提取头``1000``个样本,并存放在``train_subset.csv``中。 为了以后更好的做数据方面的处理,这些基本的命令建议大家都掌握一下。\n",
    "\n",
    "#### 学习资料推荐 ####\n",
    "- 如果对``pandas``比较生疏,或者之前没有使用过,请耐心看这份文档,帮助你马上上手:https://pandas.pydata.org/pandas-docs/stable/user_guide/10min.html#min  \n",
    "- 如果对``numpy``比较生疏,或者之前没有接触过,请耐心看这两份文档,很实用: http://jalammar.github.io/visual-numpy/         http://cs231n.github.io/python-numpy-tutorial/ \n",
    "\n",
    "这些文档足够让你上手``numpy``和``pandas``"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 数据的读取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入基本的库,每个项目的必备\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 设置matplotlib的模式\n",
    "%matplotlib inline\n",
    "\n",
    "# 设置matplot的样式\n",
    "import matplotlib\n",
    "matplotlib.style.use('ggplot')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 通过pandas读取.csv文件,并展示头几个样本。\n",
    "data_df = pd.read_csv('train_subset.csv')\n",
    "data_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题1(TODO):`` 在上面的数据中有一个特征叫作``hour``, 是时间的特征,但这个值有些看不懂... 这部分需要通过pandas来做处理。把这个数转换成具体时间的格式。请把这个特征格式化成``%y%m%d%H``形式。格式化完之后请覆盖掉原来的特征。提示: 使用 ``pd.to_datetime``函数,然后指定需要格式化的特征和格式化的样式。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO 把hour特征格式化成 '%y%m%d%H'形式。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重新打印一下是否有改变\n",
    "data_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 由于特征个数比较多,有些特征被隐藏掉了(表示成了...), 为了把所有特征全部看到,可以分两次来展示:\n",
    "data_df.iloc[:, :12].head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df.iloc[:, 12:].head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从上述数据中,你会发现大量的特征为类别型特征,而且很多特征已经被编码成看不懂的字符串(这些都是为了不公开用户数据),但即便如此,我们也可以把它们直接看成是类别型特征(categorical featuer)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 查看一下每一个特征的类型以及是否存在null\n",
    "data_df.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 数据的理解"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``id``特征 ####\n",
    "我们是否需要这个特征? 应该如何判断? 如果发现不需要此特征就可以从data_df中删掉了。\n",
    "> ``问题2(TODO)`` 1. 请判断这个特征有没有价值  2. 如果没有价值就把它从data_df中删掉。 提示: 如果对于每一个样本,``id``都是不一样的,那显然是无用的特征。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 请实现上述的逻辑\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``click``标签(label) ####\n",
    "对标签分布的理解是必不可少的,因为这直接跟样本不平衡相关。\n",
    "> ``问题3(TODO)``: 对于``data_df``, 请输出正样本和负样本各自的比例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 输出正样本的负样本各自的比例\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过上述的数据,可以很容易看出被点击的次数要远小于没有被点击的次数。所以这个数据是不平衡的数据。但这个不平衡还没有那么严重。其实不平衡严重时,负样本和正样本比例有可能1000:1, 甚至更悬殊。 由于样本的不平衡,使用准确率是不明智的,所以评估指标我们选用``F1-score``. 如果对这部分不熟,请看第7章的视频。 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``hour``特征 ####\n",
    "时间特征有可能对我们帮助,比如是否凌晨点击率要低于早上的,是否早上的要低于下午的? 从直观上理解其实是有帮助的。 但由于在这个项目中,我们只提取了前40万个样本,有可能时间上的差别不大(我们要知道一个大的平台,仅仅1分钟就可以收集到数十万到几百万以上的样本)。 但是,不管怎样,打印一下``hour``特征相关的信息看看:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df.hour.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其实从上述的结果中可以看到,时间的区间为10-21的00点到10-21的02点,也就是2个小时的间隔。所以在使用这个特征的时候,可以把小时的特征提取出来,因为日期都是一样的(这部分没价值)。 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``banner_pos``特征 ####\n",
    "这是广告投放的位置,从直观上来看对广告点击的结果影响比较大,所以做一下可视化的分析并更好地理解这个特征。首先来看一下``banner_pos``的取值范围。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df['banner_pos'].unique()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从这个结果里可以看出,它的范围是0-7, 但中间不包含3和6, 有可能是我们的训练数据不全。 对于这些数据请不要理所当然地理解为它表示的是具体的位置信息,比如1代表最前面的位置... 因为我们也不知道它的编码规则是怎么样的。但不管怎样,我们可以通过可视化方式来大概了解一下每一个位置对点击率的影响。 \n",
    "\n",
    "> ``问题4(TODO)``: 通过可视化方式来展示每一个位置上的样本总数以及其中被点击和没有被点击的样本个数。需要得到如下的这样的一幅图:\n",
    "<img src=\"sample_img1.png\" alt=\"drawing\" width=\"300\"/>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 输出类似于上述这幅图\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题5(TODO)``: 生成完上面的图之后能感觉到这个特征还是蛮重要的,而且由于``banner_pos=2,4,5,7``的样本比较少,在图里不那么直观。所以我们就尝试打印一下一个表格。表格里的每一行针对于的是banner_pos具体的值,另外表格有两列,分别是false和true,  分别代表在某一个banner_pos的样本,有百分之多少的概率不被点击和被点击。 生成出来的表格如下图所示:\n",
    "<img src=\"sample_img2.png\" alt=\"drawing\" width=\"200\"/>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 生成如上面的表格\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``site``相关特征 ####"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "site_features = ['site_id', 'site_domain', 'site_category']\n",
    "data_df[site_features].describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app_features = ['app_id', 'app_domain', 'app_category']\n",
    "data_df[app_features].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题6(TODO)``:这里重点研究一下,``app_category``特征,看是否跟标签有比较强的关系。 为了理解这一点,对于每一种类型的``app_category``值,请画出``histogram``,展示每一种取值条件下样本被点击或者没有被点击的概率。效果图如下:\n",
    "<img src=\"sample_img3.png\" alt=\"drawing\" width=\"300\"/>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 画出如上这幅图。\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "####  ``device``相关的特征 ####\n",
    "查看跟device相关的特征信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "device_features = ['device_id', 'device_ip', 'device_model', 'device_type', 'device_conn_type']\n",
    "data_df[device_features].astype('object').describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题7(TODO)``: 对于不同的``device_conn_type``, 画一个histogram,并表示在不同type的情况下被点击和没有被点击的概率。效果图跟上面的问题(问题6)类似。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 画出device_conn_type和CTR相关的histogram, 参考问题6的效果图。\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ``C1, C14-C21`` 特征####\n",
    "这些特征没有具体被标记到底是什么意思,有可能是涉及到公司的隐私。 当然,理解一个特征的含义其实挺重要的,但对于这个问题没办法,毕竟他们没有提供描述。但无论如何,也可以通过可视化分析去理解这些特征是否影响点击率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c_features = ['C1', 'C14', 'C15', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21']\n",
    "data_df[c_features].astype('object').describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题8(TODO)``: 请画出C1和点击率之间的关系。请参考问题6的样例图。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 画如上所要求的图\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. 特征的构造\n",
    "特征构造对于一个机器学习建模非常重要。它的意思就是基于原有给定的特征基础上构造一些新的特征。构造特征的方法有很多: 1. 在原有的特征基础上做一些转换从而提取特征 2. 不同特征之间利用常规的运算来构造更复杂的特征(比如有特征f1, f2, 则可以通过f1 * f2操作生成新的特征)。\n",
    "对于特征的构造在之后的项目中会有更详细的介绍,在这里,我们就简单的体验一下。 \n",
    "\n",
    "首先,我们来看一下数据集里的``hour``字段."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df['hour']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于我们的数据只是做了部分采样,所以对于所有的样本日期是一致的。唯一不一样的是具体的时间。时间从10-21 00点到 10-21 02点,总共3个不同的时间段来记录。\n",
    "\n",
    "> ``问题9(TODO)``: 把``hour``这个字段转换成离散型变量,分别是0,1,2。 也就是2014-10-21 00点对应到0, 2014-10-21 01点对应到1, 2014-10-21 02点对应到2. 并把原来的``hour``字段替换一下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO:  实现问题9\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. 特征转化\n",
    "在上述数据中,存在着大量的类别型特征(categorical feature), 这部分的特征我们需要转换成独热编码的形式(one-hot encoding),如果对这部分不熟悉,请参考之前的视频课程。 比如”男”,“女”这个特征分别转换成(0, 1), (1, 0)这种形式。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 由于这两个特征的稀疏性,从特征库中去掉。 但如果计算资源允许,可以加入进来。 \n",
    "data_df.drop('device_id', axis=1, inplace=True)\n",
    "data_df.drop('device_ip', axis=1, inplace=True)\n",
    "data_df.drop('device_model', axis=1, inplace=True)\n",
    "data_df.drop('site_id', axis=1, inplace=True)\n",
    "data_df.drop('site_domain', axis=1, inplace=True)\n",
    "data_df.drop('app_id', axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 输出数据的描述\n",
    "data_df.astype('object').describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题10(TODO)`` 数据中的每一个特征,其实都可以看作是类别型特征(离散型), 所以我们接下来要对所有的特征做独热编码的转换。这个时候总特征的维度就 变成每一个特征独热编码长度之和。 (也就是表格里的unique的之和)。务必要删除原始特征,因为已经把它们转换成了新的独热编码的形式。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 实现对每一个特征的独热编码转换, 并删除原始特征\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 构造训练数据和测试数据\n",
    "feature_names = np.array(data_df.columns[data_df.columns != 'click'].tolist())\n",
    "\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    data_df[feature_names].values, \n",
    "    data_df['click'].values,\n",
    "    test_size=0.2,\n",
    "    random_state=42\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print (X_train.shape, X_test.shape, y_train.shape, y_test.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. 特征选择\n",
    "由于转换成了独热编码的形式,你会发现数据的维度一下子变多了,从几十维编程了上千维。接下来,我们来做个特征选择。我们可以回顾一下课程中提到的特征选择的方法, 也思考一下哪一种可能不太适合这个场景。 很显然生成所有的可能性方法和贪心方法是不太适合的,因为特征维度很高,计算量就变得特别大。在这里,我们使用基于L1+逻辑回归的方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> ``问题11(TODO)``: 使用基于L1的方法,请参考https://scikit-learn.org/stable/modules/feature_selection.html (SelectFromModel部分)。 我们使用的模型是逻辑回归 + L1的正则。 我们都知道L1正则会产生稀疏解,相当于帮我们选出特征。具体的方法是: 对于每一种可能的C值(代表正则的强弱)做交叉验证,从中选择效果最好的C值, 而且对于这个C值,我们有对应的选出来的特征。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import GridSearchCV\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.feature_selection import SelectFromModel\n",
    "from sklearn.metrics import f1_score\n",
    "from sklearn.model_selection import KFold\n",
    "\n",
    "params_c = np.logspace(-4, 1, 11)\n",
    "# TODO: 循环每一个C值,计算交叉验证后的F1-SCORE, 最终选择最好的C值c_best, 然后选出针对这个c_best对应的特征。 务必要使用L1正则。\n",
    "#       对于实现,有很多方法,自行选择合理的方法就可以了。 关键是包括以下模块:1. 逻辑回归   2. 交叉验证  3. L1正则  4. SelectFromModel\n",
    "\n",
    "\n",
    "# 求出c_best\n",
    "c_best = "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 通过c_best值,重新在整个X_train里做训练,并选出特征。\n",
    "lr_clf = LogisticRegression(penalty='l1', C=c_best)\n",
    "lr_clf.fit(X_train, y_train) # 在整个训练数据重新训练\n",
    "\n",
    "select_model = SelectFromModel(lr_clf, prefit=True)\n",
    "selected_features = select_model.get_support()  # 被选出来的特征\n",
    "\n",
    "# 重新构造feature_names\n",
    "feature_names = feature_names[selected_features]\n",
    "\n",
    "# 重新构造训练数据和测试数据\n",
    "X_train = X_train[:, selected_features]\n",
    "X_test = X_test[:, selected_features]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. 模型训练与评估\n",
    "选择完特征之后,我们来构建模型并做训练。这部分的内容跟第一个项目没什么太大区别,无非就是选择模型之后,通过交叉验证来学习最好的超参数。在这里我们使用两种类型的模型,分别是逻辑回归+L2正则,以及决策树。 第二种模型还没有讲解,但没关系,我们只是在这里调用一下而已,在之后的章节里会详细地涉及到。 \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1 使用逻辑回归模型\n",
    "> ``问题12(TODO)``在我们选择特征的时候其实也用了逻辑回归,但要记住,选特征的时候用的是L1的正则。但是在真正来训练最终版本模型的时候我们通常都是使用L2正则。所以这里就按照这个逻辑来训练一个逻辑回归模型。需要注意的一点是:评价标准使用F1-SCORE, 包括在交叉验证阶段。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import classification_report  #  这个用来打印最终的结果,包括F1-SCORE\n",
    "\n",
    "params_c = np.logspace(-5,2,15) # 也可以自行定义一个范围\n",
    "\n",
    "# TODO: 实现逻辑回归 + L2正则, 利用GrisSearchCV\n",
    "\n",
    "model = \n",
    "\n",
    "# 输出最好的参数 \n",
    "print(model.best_params_)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 在测试数据上预测,并打印在测试集上的结果\n",
    "\n",
    "\n",
    "print(classification_report(y_test, predictions))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 使用决策树模型\n",
    "> ``问题13(TODO):``在这里,我们使用决策树算法做分类。这部分内容还没有讲到,但没关系,在这里只是用来调用。在PART3里会涉及到决策树的细节。 决策树本身有很多超参数需要调节,所以调节决策树的复杂度要远高于逻辑回归模型。决策树的使用请参考: https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.tree import DecisionTreeClassifier\n",
    "\n",
    "params_min_sampes_split = np.linspace(5, 20, 4)\n",
    "params_min_samples_leaf = np.linspace(2, 10, 5)\n",
    "params_max_depth = np.linspace(4, 10, 4)\n",
    "\n",
    "# TODO: 构造决策树,并做交叉验证。 除了上面三个参数,其他参数用默认的。 \n",
    "\n",
    "model = \n",
    "\n",
    "# 输出最好的参数 \n",
    "print(model.best_params_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 在测试数据上预测,并打印在测试集上的结果\n",
    "\n",
    "\n",
    "print(classification_report(y_test, predictions))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 利用启发式算法来调节参数(选做部分, 不计为分数)\n",
    "在课程中,我们讲过启发式算法的大致思路。 在这里,我们使用贝叶斯优化方法去选择超参数,具体的使用方法请参考: https://github.com/fmfn/BayesianOptimization  也需要提前安装好这个库,请按照此链接中的方法来安装。 \n",
    "\n",
    "同时,我们在使用决策树的过程中也发现参数数量多,花费的时间也很长。这种现象在参数越多的时候越明显。所以,可以适当采用启发式算法比如贝叶斯优化。贝叶斯优化整体的思路是构建在贝叶斯模型之上的,内核包括高斯过程。具体细节可以参考Adam Ryans(princeton)教授的相关文章。 \n",
    "\n",
    "请认真读一下上述github的内容,应该可以大致理解它的主要作用。然后按照里面写的方法来实现决策树上的参数优化。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bayes_opt import BayesianOptimization\n",
    "\n",
    "params_min_sampes_split = np.linspace(5, 20, 4)\n",
    "params_min_samples_leaf = np.linspace(2, 10, 5)\n",
    "params_max_depth = np.linspace(4, 10, 4)\n",
    "\n",
    "# TODO: 使用贝叶斯优化去选择超参数\n",
    "\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 在测试数据上预测,并打印在测试集上的结果\n",
    "\n",
    "\n",
    "print(classification_report(y_test, predictions))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4 使用XGBoost做分类(选做部分, 不计为分数)\n",
    "还有一类算法叫做XGBoost, 这是目前工业界和各类比赛最常用的算法之一。 我们会在PART3里做详细的讲解,但感兴趣的朋友可以尝试一下。 它是一种集成式的方法,相当于多位专家共同去决策,所以模型既稳定效果也不错。这个模型也需要单独安装,具体安装请见: https://pypi.org/project/xgboost/  在linux和mac os上安装起来比较简单,但windows上可能会有各种问题出现,如在windows上使用,请参考: https://xgboost.readthedocs.io/en/latest/build.html\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from xgboost import XGBClassifier\n",
    "\n",
    "# TODO: 训练XGBoost模型  提示: 使用XGBClassifier。 至于超参数,可以试着去看一下官方文档,然后多尝试尝试。 \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: 在测试数据上预测,并打印在测试集上的结果\n",
    "\n",
    "\n",
    "print(classification_report(y_test, predictions))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "恭喜你,完成第一次作业。 希望通过完成此作业,对建模有更深入的理解。 特别是对于数据的理解、特征选择以及超参数的选择等技术点。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}